packages feed

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 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&#39;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