nationstates (empty) → 0.1.0
raw patch · 9 files changed
+899/−0 lines, 9 filesdep +basedep +bytestringdep +clocksetup-changed
Dependencies added: base, bytestring, clock, containers, http-client, http-client-tls, http-types, multiset, split, transformers, xml
Files
- NationStates.hs +36/−0
- NationStates/Core.hs +222/−0
- NationStates/Nation.hs +293/−0
- NationStates/RateLimit.hs +46/−0
- NationStates/Region.hs +105/−0
- NationStates/Types.hs +122/−0
- README.md +30/−0
- Setup.hs +2/−0
- nationstates.cabal +43/−0
+ NationStates.hs view
@@ -0,0 +1,36 @@+module NationStates (++ Context(),+ withContext,++ -- * Core types+ module NationStates.Types,++ ) where+++import Network.HTTP.Client+import Network.HTTP.Client.TLS++import NationStates.Core+import NationStates.RateLimit+import NationStates.Types+++-- | Create a 'Context', and pass it to the provided function.+--+-- The 'Context' will be closed automatically when the function returns.+withContext+ :: String+ -- ^ User agent+ -> (Context -> IO a)+ -> IO a+withContext userAgent f = withManager tlsManagerSettings $ \man -> do+ limit <- newRateLimit delay+ f Context {+ contextManager = man,+ contextRateLimit = rateLimit limit,+ contextUserAgent = userAgent+ }+ where+ delay = 600 * 1000 * 1000 -- 0.6 seconds
+ NationStates/Core.hs view
@@ -0,0 +1,222 @@+{-# LANGUAGE OverloadedStrings, Rank2Types #-}++-- | Low-level tools for querying the NationStates API.+--+-- Most of the time, you should use the high-level wrappers in e.g.+-- "NationStates.Nation" instead. But if you need something not provided+-- by these wrappers, then feel free to use this module directly.++module NationStates.Core (++ -- * Requests+ NS,+ makeNS,+ makeNS',+ requestNS,+ apiVersion,++ -- * Query strings+ Query(..),++ -- * Connection manager+ Context(..),++ -- * Utilities+ splitDropBlanks,+ readMaybe,+ expect,+ expected,++ -- * Data structures+ module NationStates.Types,++ ) where+++import qualified Data.ByteString.Char8 as BC+import Data.Functor.Compose+import Data.Foldable (toList)+import Data.List+import Data.List.Split+import Data.Monoid+import Data.Map (Map)+import qualified Data.Map as Map+import Data.Maybe+import Data.Set (Set)+import qualified Data.Set as Set+import Network.HTTP.Client+import qualified Network.HTTP.Types as HTTP+import Text.Read+import Text.XML.Light++import NationStates.Types+++-- | A request to the NationStates API.+--+-- * Construct an @NS@ using 'makeNS' or 'makeNS''.+-- * Compose @NS@ values using the 'Applicative' interface.+-- * Execute an @NS@ using 'requestNS'.+--+-- This type wraps a query string, along with a function that parses the+-- response. The funky type machinery keeps these two parts in sync, as+-- long as you stick to the 'Applicative' interface.+--+-- @+-- type NS a = ('Query', Query -> 'Element' -> a)+-- @+type NS = Compose ((,) Query) (Compose ((->) Query) ((->) Element))+++-- | Construct a request for a single shard.+--+-- For example, this code requests the+-- <https://www.nationstates.net/cgi-bin/api.cgi?nation=testlandia&q=motto "motto">+-- shard:+--+-- @+-- motto :: NS String+-- motto = makeNS \"motto\" Nothing \"MOTTO\"+-- @+--+-- For more complex requests (e.g. nested elements), try 'makeNS'' instead.+makeNS+ :: String+ -- ^ Shard name+ -> String+ -- ^ XML element name+ -> NS String+makeNS shard elemName = makeNS' shard Nothing [] parse+ where+ parse _ = strContent . fromMaybe errorMissing . findChild (unqual elemName)+ errorMissing = error $ "missing <" ++ elemName ++ "> element"+++-- | Construct a request for a single shard.+makeNS'+ :: String+ -- ^ Shard name+ -> Maybe Integer+ -- ^ Shard ID+ -> [(String, String)]+ -- ^ List of options+ -> (Query -> Element -> a)+ -- ^ Function for parsing the response+ -> NS a+makeNS' name maybeId options parse = Compose+ (Query {+ queryShards = Map.singleton name (Set.singleton maybeId),+ queryOptions = Map.fromList options+ }, Compose parse)+++-- | Perform a request on the NationStates API.+requestNS+ :: Maybe (String, String)+ -- ^ Request type+ -> NS a+ -- ^ Set of shards to request+ -> Context+ -- ^ Connection manager+ -> IO a+requestNS kindAndName (Compose (q, Compose p)) c+ = parse . responseBody <$>+ (contextRateLimit c $ httpLbs req (contextManager c))+ where+ parse = p q . fromMaybe (error "invalid response") . parseXMLDoc+ req = initRequest {+ queryString+ = HTTP.renderQuery True (HTTP.toQuery $+ toList kindAndName ++ [("q", shards), ("v", show apiVersion)])+ <> BC.pack options,+ requestHeaders+ = ("User-Agent", BC.pack $ contextUserAgent c)+ : requestHeaders initRequest+ }+ (shards, options) = queryToUrl q++initRequest :: Request+Just initRequest = parseUrl "https://www.nationstates.net/cgi-bin/api.cgi"+++-- | The version of the NationStates API used by this package.+--+-- Every request to NationStates includes this number. This means that+-- if the response format changes, existing code will continue to work+-- under the old API.+--+-- This number should match the current API version, as given by+-- <https://www.nationstates.net/cgi-bin/api.cgi?a=version>. If not,+-- please file an issue.+apiVersion :: Integer+apiVersion = 7+++-- | Keeps track of rate limits and TLS connections.+--+-- You should create a single 'Context' at the start of your program,+-- then share it between multiple threads and requests.+data Context = Context {+ contextManager :: Manager,+ contextRateLimit :: forall a. IO a -> IO a,+ contextUserAgent :: String+ }+++-- | Keeps track of the set of shards to request.+data Query = Query {+ queryShards :: Map String (Set (Maybe Integer)),+ queryOptions :: Map String String+ } deriving Show++instance Monoid Query where+ mempty = Query mempty mempty+ mappend a b = Query {+ queryShards = Map.unionWith Set.union+ (queryShards a) (queryShards b),+ queryOptions = Map.unionWithKey mergeOptions+ (queryOptions a) (queryOptions b)+ }+ where+ mergeOptions key _ _+ = error $ "conflicting values for option " ++ show key+++queryToUrl :: Query -> (String, String)+queryToUrl q = (shards, options)+ where+ shards = intercalate "+" [ name ++ foldMap (\i -> "-" ++ show i) maybeId |+ (name, is) <- Map.toList $ queryShards q,+ maybeId <- Set.toList is ]+ options = concat [ ";" ++ k ++ "=" ++ v |+ (k, v) <- Map.toList $ queryOptions q ]+++-- | Split a string by a separator, dropping empty substrings.+--+-- >>> splitDropBlanks "," "the_vines,motesardo-east_adanzi,yellowapple"+-- ["the_vines", "montesardo-east_adanzi", "yellowapple"]+--+-- >>> splitDropBlanks "," ""+-- []+splitDropBlanks :: Eq a => [a] -> [a] -> [[a]]+splitDropBlanks = split . dropBlanks . dropDelims . onSublist++-- | Parse an input string using the given parser function.+--+-- If parsing fails, raise an 'error'.+--+-- >>> expect "integer" readMaybe "42" :: Integer+-- 42+--+-- >>> expect "integer" readMaybe "butts" :: Integer+-- *** Exception: invalid integer: "butts"+expect :: String -> (String -> Maybe a) -> String -> a+expect want parse = fromMaybe <$> expected want <*> parse++-- | Raise an 'error'.+--+-- >>> expected "integer" "butts"+-- *** Exception: invalid integer: "butts"+expected :: String -> String -> a+expected want s = error $ "invalid " ++ want ++ ": " ++ show s
+ NationStates/Nation.hs view
@@ -0,0 +1,293 @@+{-# LANGUAGE GeneralizedNewtypeDeriving #-}++-- | The Nation API.+--+-- This module should be imported qualified, to prevent name clashes:+--+-- @+-- import NationStates+-- import qualified NationStates.Nation as Nation+-- @+--+-- In general, this module follows the terminology used in the+-- <https://www.nationstates.net/pages/api.html#nationapi official documentation>,+-- except when it clashes with Haskell keywords. For instance, the+-- @type@ shard has been renamed to 'type_'.+--+-- Here's a short example:+--+-- @+-- import NationStates+-- import qualified NationStates.Nation as Nation+-- import Text.Printf+--+-- main = 'NationStates.withContext' "ExampleBot/2000" $ \\c -> do+-- (name, motto) <- Nation.'run' "Montesardo-East Adanzi"+-- ((,) \<$\> Nation.'name' \<*\> Nation.'motto') c+-- printf "%s has the motto: %s\\n" name motto+-- @++module NationStates.Nation (++ -- * Running queries+ Nation(..),+ run,++ -- * Shards+ name,+ fullname,+ type_,+ motto,+ category,+ wa,+ endorsements,+ gavote,+ scvote,+ freedom,+ region,+ population,+ tax,+ animal,+ animaltrait,+ currency,+ flag,+ banner,+ banners,+-- majorindustry,+-- crime,+-- sensibilities,+-- govtpriority,+-- govt,+-- govtdesc,+-- industrydesc,+-- notable,+-- admirable,+-- founded,+-- firstlogin,+-- lastlogin,+-- lastactivity,+-- influence,+-- freedomscores,+-- publicsector,+-- deaths,+-- leader,+-- capital,+-- religion,+-- customleader,+-- customcapital,+-- customreligion,+-- rcensus,+-- wcensus,+ censusscore,+ censusscore',+-- legislation,+-- happenings,+-- demonym,+-- demonym2,+-- demonym2plural,+-- factbooks,+-- factbooklist,+-- dispatches,+-- dispatchlist,++ ) where+++import Data.Maybe+import qualified Data.Map as Map+import qualified Data.MultiSet as MultiSet+import Text.XML.Light++import NationStates.Core+++-- | A request to the Nation API.+newtype Nation a = Nation { unNation :: NS a }+ deriving (Functor, Applicative)+++-- | Perform a request to the Nation API.+run+ :: String+ -- ^ Nation name+ -> Nation a+ -- ^ Requested shards+ -> Context+ -- ^ Connection manager+ -> IO a+run nation = requestNS (Just ("nation", nation)) . unNation+++-- | Short name.+--+-- > "Testlandia"+name :: Nation String+name = Nation $ makeNS "name" "NAME"++-- | Full name, including pre-title.+--+-- > "The Republic of Testlandia"+fullname :: Nation String+fullname = Nation $ makeNS "fullname" "FULLNAME"++-- | Nation type.+--+-- > "Republic"+type_ :: Nation String+type_ = Nation $ makeNS "type" "TYPE"++-- | Motto.+--+-- > "It's a feature!"+motto :: Nation String+motto = Nation $ makeNS "motto" "MOTTO"++-- | Nation category.+--+-- > InoffensiveCentristDemocracy+category :: Nation WACategory+category = Nation . fmap (expect "category" readWACategory) $+ makeNS "category" "CATEGORY"++-- | Whether the nation is in the World Assembly.+--+-- > True+wa :: Nation Bool+wa = Nation . fmap parse $ makeNS "wa" "UNSTATUS"+ where+ parse "WA Member" = True+ parse "Non-member" = False+ parse s = expected "WA status" s++-- | List of endorsements received.+--+-- > ["jlink","translenia","the_vines"]+endorsements :: Nation [String]+endorsements = Nation . fmap (splitDropBlanks ",") $+ makeNS "endorsements" "ENDORSEMENTS"++-- | General assembly vote.+--+-- > Just True+gavote :: Nation (Maybe Bool)+gavote = Nation . fmap (expect "General Assembly vote" readWAVote) $+ makeNS "gavote" "GAVOTE"++-- | Security council vote.+--+-- > Nothing+scvote :: Nation (Maybe Bool)+scvote = Nation . fmap (expect "Security Council vote" readWAVote) $+ makeNS "scvote" "SCVOTE"++-- | Description of civil rights, economy, and political freedoms.+--+-- > ("Excellent","Strong","Very Good")+freedom :: Nation (String, String, String)+freedom = Nation $ makeNS' "freedom" Nothing [] parse+ where+ parse _ root+ | Just parent <- findChild (unqual "FREEDOM") root+ , [c, e, p] <- map strContent $ elChildren parent+ = (c, e, p)+ | otherwise+ = error "could not find freedom descriptors"++-- | Resident region.+--+-- > "Testregionia"+region :: Nation String+region = Nation $ makeNS "region" "REGION"++-- | Population, in millions.+--+-- > 25764+population :: Nation Integer+population = Nation . fmap (expect "population" readMaybe) $+ makeNS "population" "POPULATION"++-- | Income tax, percent.+--+-- > 83.6+tax :: Nation Double+tax = Nation . fmap (expect "tax" readMaybe) $+ makeNS "tax" "TAX"++-- | National animal.+--+-- > "sea-snake"+animal :: Nation String+animal = Nation $ makeNS "animal" "ANIMAL"++-- | A short phrase describing the animal.+--+-- > "is also the nation's favorite main course"+animaltrait :: Nation String+animaltrait = Nation $ makeNS "animaltrait" "ANIMALTRAIT"++-- | Currency.+--+-- > "☆star☆"+currency :: Nation String+currency = Nation $ makeNS "currency" "CURRENCY"++-- | Flag URL.+--+-- > "http://www.nationstates.net/images/flags/Switzerland.png"+flag :: Nation String+flag = Nation $ makeNS "flag" "FLAG"++-- | A suitable banner for this nation.+--+-- > "v1"+banner :: Nation String+banner = Nation $ makeNS "banner" "BANNER"++-- | A list of suitable banners for this nation.+--+-- > ["v1","o4","b14","t23","m3"]+banners :: Nation [String]+banners = Nation $ makeNS' "banners" Nothing [] parse+ where+ parse _ root+ | Just parent <- findChild (unqual "BANNERS") root+ = map strContent $ elChildren parent+ | otherwise+ = error "could not find banner codes"++-- | Query today's census.+--+-- Returns the current census ID, along with its value.+--+-- > (24,6.0)+censusscore :: Nation (Integer, Double)+censusscore = Nation $ makeNS' "censusscore" Nothing [] parse+ where+ parse q root+ | Just (i, _) <- MultiSet.minView $ MultiSet.difference response request+ , Just x <- lookup i censusScores+ = (i, x)+ | otherwise+ = error "could not find census score"+ where+ censusScores = extractCensusScores root+ request = MultiSet.mapMaybe id . MultiSet.fromSet $+ queryShards q Map.! "censusscore"+ response = MultiSet.fromList $ map fst censusScores++-- | Query a census by its census ID.+--+-- > 94.0+censusscore' :: Integer -> Nation Double+censusscore' i = Nation $ makeNS' "censusscore" (Just i) [] parse+ where+ parse _ = fromMaybe (error $ "could not find census " ++ show i) .+ lookup i . extractCensusScores++extractCensusScores :: Element -> [(Integer, Double)]+extractCensusScores root = catMaybes [+ (,) <$> maybeId <*> maybeValue |+ Elem e <- elContent root,+ elName e == unqual "CENSUSSCORE",+ let maybeId = readMaybe =<< findAttr (unqual "id") e,+ let maybeValue = readMaybe $ strContent e ]
+ NationStates/RateLimit.hs view
@@ -0,0 +1,46 @@+-- | Simple rate limiting combinator.++module NationStates.RateLimit (+ RateLimit(),+ newRateLimit,+ rateLimit,+ ) where+++import Control.Concurrent+import Control.Exception+import System.Clock+++data RateLimit = RateLimit {+ rateLock :: !(MVar TimeSpec),+ rateDelay :: !TimeSpec+ }+++-- | Create a new rate limiter with the specified delay.+--+-- The rate limiter is thread-safe, and can be shared between threads.+newRateLimit :: TimeSpec -> IO RateLimit+newRateLimit delay = do+ lock <- newMVar $! negate delay+ return RateLimit {+ rateLock = lock,+ rateDelay = delay+ }+++-- | Run the given action, pausing as necessary to keep under the rate limit.+rateLimit :: RateLimit -> IO a -> IO a+rateLimit RateLimit { rateLock = lock, rateDelay = delay } action =+ mask $ \restore -> do+ prev <- takeMVar lock+ now <- getTime Monotonic+ threadDelay' $ prev + delay - now+ result <- restore action+ putMVar lock =<< getTime Monotonic+ return result+++threadDelay' :: TimeSpec -> IO ()+threadDelay' t = threadDelay . fromInteger $ timeSpecAsNanoSecs t `div` 1000
+ NationStates/Region.hs view
@@ -0,0 +1,105 @@+{-# LANGUAGE GeneralizedNewtypeDeriving #-}++-- | The Region API.+--+-- This module should be imported qualified, to prevent name clashes:+--+-- @+-- import NationStates+-- import qualified NationStates.Region as Region+-- @+--+-- In general, this module follows the terminology used in the+-- <https://www.nationstates.net/pages/api.html#nationapi official documentation>.+--+-- Here's a short example:+--+-- @+-- import NationStates+-- import qualified NationStates.Region as Region+-- import Text.Printf+--+-- main = 'NationStates.withContext' "ExampleBot/2000" $ \\c -> do+-- (name, numnations, delegate) <- Region.'run' "Pony Lands"+-- ((,,) \<$\> Region.'name' \<*\> Region.'numnations' \<*\> Region.'delegate') c+-- printf "%s has %d nations. Its delegate is %s\\n" name numnations delegate+-- @++module NationStates.Region (++ -- * Running queries+ Region(..),+ run,++ -- * Shards+ name,+ factbook,+ numnations,+ nations,+ delegate,+-- delegatevote,+-- gavote,+-- scvote,+-- founder,+-- power,+-- flag,+-- embassies,+-- tags,+-- happenings,+-- messages,+-- history,+-- poll,++ ) where+++import NationStates.Core+++-- | A request to the Region API.+newtype Region a = Region { unRegion :: NS a }+ deriving (Functor, Applicative)+++-- | Perform a request to the Region API.+run+ :: String+ -- ^ Region name+ -> Region a+ -- ^ Requested shards+ -> Context+ -- ^ Connection manager+ -> IO a+run region = requestNS (Just ("region", region)) . unRegion+++-- | Region name.+--+-- > "Pony Lands"+name :: Region String+name = Region $ makeNS "name" "NAME"++-- | Factbook, in BBCode format.+--+-- > "[b]We've got ponies, therefore your argument is invalid..."+factbook :: Region String+factbook = Region $ makeNS "factbook" "FACTBOOK"++-- | Number of nations in the region.+--+-- > 112+numnations :: Region Integer+numnations = Region . fmap (expect "nation count" readMaybe) $+ makeNS "numnations" "NUMNATIONS"++-- | List of nations in the region.+--+-- > ["urmanian","enatai","unfitting_doors","lykosia","trotterdam"]+nations :: Region [String]+nations = Region . fmap (splitDropBlanks ":") $ makeNS "nations" "NATIONS"++-- | Region delegate.+--+-- > "princess_luna"+delegate :: Region String+delegate = Region $ makeNS "delegate" "DELEGATE"
+ NationStates/Types.hs view
@@ -0,0 +1,122 @@+-- | Data structures used by NationStates.++module NationStates.Types where+++-- | Nation category.+--+-- This datum summarizes a nation's personal, economic, and political+-- freedoms.+data WACategory+ = Anarchy+ | AuthoritarianDemocracy+ | BenevolentDictatorship+ | CapitalistParadise+ | Capitalizt+ | CivilRightsLovefest+ | CompulsoryConsumeristState+ | ConservativeDemocracy+ | CorporateBordello+ | CorporatePoliceState+ | CorruptDictatorship+ | DemocraticSocialists+ | FatherKnowsBestState FatherOrMother+ -- ^ This category has two variations: \"/Father/ Knows Best+ -- State\" and \"/Mother/ Knows Best State\".+ | FreeMarketParadise+ | InoffensiveCentristDemocracy+ | IronFistConsumerists+ | IronFistSocialists+ | LeftLeaningCollegeState+ | LeftWingUtopia+ | LiberalDemocraticSocialists+ | LibertarianPoliceState+ | MoralisticDemocracy+ | NewYorkTimesDemocracy+ | PsychoticDictatorship+ | RightWingUtopia+ | ScandinavianLiberalParadise+ | TyrannyByMajority+ deriving (Eq, Ord, Read, Show)++-- | Differentiates between a /Father/ or /Mother/ Knows Best State.+data FatherOrMother = Father | Mother+ deriving (Eq, Ord, Read, Show)+++readWACategory :: String -> Maybe WACategory+readWACategory s = case s of+ "Anarchy" -> Just Anarchy+ "Authoritarian Democracy" -> Just AuthoritarianDemocracy+ "Benevolent Dictatorship" -> Just BenevolentDictatorship+ "Capitalist Paradise" -> Just CapitalistParadise+ "Capitalizt" -> Just Capitalizt+ "Civil Rights Lovefest" -> Just CivilRightsLovefest+ "Compulsory Consumerist State" -> Just CompulsoryConsumeristState+ "Conservative Democracy" -> Just ConservativeDemocracy+ "Corporate Bordello" -> Just CorporateBordello+ "Corporate Police State" -> Just CorporatePoliceState+ "Corrupt Dictatorship" -> Just CorruptDictatorship+ "Democratic Socialists" -> Just DemocraticSocialists+ "Father Knows Best State" -> Just $ FatherKnowsBestState Father+ "Free-Market Paradise" -> Just FreeMarketParadise+ "Inoffensive Centrist Democracy" -> Just InoffensiveCentristDemocracy+ "Iron Fist Consumerists" -> Just IronFistConsumerists+ "Iron Fist Socialists" -> Just IronFistSocialists+ "Left-Leaning College State" -> Just LeftLeaningCollegeState+ "Left-wing Utopia" -> Just LeftWingUtopia+ "Liberal Democratic Socialists" -> Just LiberalDemocraticSocialists+ "Libertarian Police State" -> Just LibertarianPoliceState+ "Moralistic Democracy" -> Just MoralisticDemocracy+ "Mother Knows Best State" -> Just $ FatherKnowsBestState Mother+ "New York Times Democracy" -> Just NewYorkTimesDemocracy+ "Psychotic Dictatorship" -> Just PsychoticDictatorship+ "Right-wing Utopia" -> Just RightWingUtopia+ "Scandinavian Liberal Paradise" -> Just ScandinavianLiberalParadise+ "Tyranny by Majority" -> Just TyrannyByMajority+ _ -> Nothing++showWACategory :: WACategory -> String+showWACategory c = case c of+ Anarchy -> "Anarchy"+ AuthoritarianDemocracy -> "Authoritarian Democracy"+ BenevolentDictatorship -> "Benevolent Dictatorship"+ CapitalistParadise -> "Capitalist Paradise"+ Capitalizt -> "Capitalizt"+ CivilRightsLovefest -> "Civil Rights Lovefest"+ CompulsoryConsumeristState -> "Compulsory Consumerist State"+ ConservativeDemocracy -> "Conservative Democracy"+ CorporateBordello -> "Corporate Bordello"+ CorporatePoliceState -> "Corporate Police State"+ CorruptDictatorship -> "Corrupt Dictatorship"+ DemocraticSocialists -> "Democratic Socialists"+ FatherKnowsBestState Father -> "Father Knows Best State"+ FatherKnowsBestState Mother -> "Mother Knows Best State"+ FreeMarketParadise -> "Free-Market Paradise"+ InoffensiveCentristDemocracy -> "Inoffensive Centrist Democracy"+ IronFistConsumerists -> "Iron Fist Consumerists"+ IronFistSocialists -> "Iron Fist Socialists"+ LeftLeaningCollegeState -> "Left-Leaning College State"+ LeftWingUtopia -> "Left-wing Utopia"+ LiberalDemocraticSocialists -> "Liberal Democratic Socialists"+ LibertarianPoliceState -> "Libertarian Police State"+ MoralisticDemocracy -> "Moralistic Democracy"+ NewYorkTimesDemocracy -> "New York Times Democracy"+ PsychoticDictatorship -> "Psychotic Dictatorship"+ RightWingUtopia -> "Right-wing Utopia"+ ScandinavianLiberalParadise -> "Scandinavian Liberal Paradise"+ TyrannyByMajority -> "Tyranny by Majority"+++readWAVote :: String -> Maybe (Maybe Bool)+readWAVote s = case s of+ "UNDECIDED" -> Just Nothing+ "FOR" -> Just $ Just True+ "AGAINST" -> Just $ Just False+ _ -> Nothing++showWAVote :: Maybe Bool -> String+showWAVote v = case v of+ Nothing -> "UNDECIDED"+ Just True -> "FOR"+ Just False -> "AGAINST"
+ README.md view
@@ -0,0 +1,30 @@+# NationStates for Haskell++[NationStates] is an online government simulation game, created by Max Barry. The site generates a wealth of data, some of which can be accessed through its [official API].++This library lets you query this API using the Haskell programming language.++[NationStates]: https://nationstates.net+[official API]: https://www.nationstates.net/pages/api.html+++## Dependencies++* GHC 7.10++ + GHC 7.8 support is planned – patches welcome!+++## Example++```haskell+import NationStates+import qualified NationStates.Nation as Nation+import Text.Printf++main = withContext "ExampleBot/2000" $ \c -> do+ (name, motto) <- Nation.run "Montesardo-East Adanzi" shards c+ printf "%s has the motto: %s\n" name motto+ where+ shards = (,) <$> Nation.name <*> Nation.motto+```
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ nationstates.cabal view
@@ -0,0 +1,43 @@+name: nationstates+version: 0.1.0+synopsis: NationStates API client+description:+ NationStates API client++homepage: https://github.com/lfairy/nationstates+author: Chris Wong+maintainer: lambda.fairy@gmail.com+copyright: 2015 Chris Wong+license: Apache-2.0+category: Network, Web++build-type: Simple+cabal-version: >= 1.10+extra-source-files: README.md++library+ exposed-modules:+ NationStates+ NationStates.Core+ NationStates.RateLimit+ NationStates.Nation+ NationStates.Region+ NationStates.Types+ build-depends:+ base >= 4.8 && < 5,+ bytestring,+ clock,+ containers,+ http-client,+ http-client-tls,+ http-types,+ multiset,+ split,+ transformers,+ xml+ default-language: Haskell2010+ ghc-options: -Wall++source-repository head+ type: git+ location: https://github.com/lfairy/nationstates.git