packages feed

haste-gapi (empty) → 0.1.0.0

raw patch · 13 files changed

+912/−0 lines, 13 filesdep +basedep +data-defaultdep +haste-libsetup-changed

Dependencies added: base, data-default, haste-lib, transformers

Files

+ Haste/GAPI.hs view
@@ -0,0 +1,206 @@+{-# LANGUAGE OverloadedStrings #-}++{-|+Module      : Haste.GAPI+Description : Entry Module for haste-gapi+Copyright   : (c) Jonathan Skårstedt, 2016+License     : MIT+Maintainer  : jonathan.skarstedt@gmail.com+Stability   : experimental+Portability : Haste++Example of a login to the Google API. Note that @config@ is defined+elsewhere.++@++main :: IO ()+main = withGAPI config $ \token -> case token of+  OA2Success {} ->+    putStrLn $ show token++  OA2Error {errorMsg = e} ->+    putStrLn $ "Your success token is invalid (" ++ e ++ ")"++@++The login starts with the @withGAPI@, which will load the Google API+environment. If the script is compiled with @--onexec@ flag, the load will be+significally faster as we don't have to wait until the script has fully loaded+until we download the Google API hooks.++When loading is done, a @OAuth2Token@ is generated, and we may inspect it+to see wether the login is successful or not. On success, we continue with+showing the token. If not, we print an error instead. Note that the+@withGAPI@ function ought to only be invoked once.+++Example of a request:++@++greet :: IO ()+greet = runR $ do+  response <- request "plus\/v1\/people\/me" def+  Just [name, pic] \<- sequence <$\> mapM (lookupVal response) [+   "result.displayName",+   "result.image.url"]+  liftIO . putStrLn $ "Hello " ++  name ++ "! \<br /\>"+    ++ "You look like this: \<br \/\>\<img src='"++ pic ++ "' \/\>"++@++The reqeuest above will greet the user by name, fetched from the Google+ API.+A response is generated by taking a Path and some Params to the @request@+function, which will execute it and return a response.++After that we look up some fields in the result, namely /result.displayName/+and /result.image.url/. The first will be bound to name, and the second to+pic. These two elements are both strings, by Haste.Foreign magic.++After that we use @RequestM@s @liftIO@ (as it's an instance of the MonadIO+class) and present a pretty HTML string!+-}+module Haste.GAPI (+  -- | = Connecting to the Google API+  withGAPI,+  oa2success,+  getToken,+  Config(..),+  OAuth2Token(..),+  -- | = Creating Requests+  module Haste.GAPI.Request,+  -- | = Handling Results +  module Haste.GAPI.Result,+  -- | = Common types when working with Google API libraries+  module Haste.GAPI.Types++  ) where ++import Haste.Foreign hiding (get, has, hasAll)+import qualified Haste.Foreign as FFI++-- GHC 7.8 compatibility+import Data.Functor ((<$>))++import Haste.GAPI.Request +import Haste.GAPI.Result+import Haste.GAPI.Types+import Data.Default+-- import Control.Monad+import Control.Applicative++-- Datatypes -----------------------------------------------------------------++-- | Google API configuration. For an in-detail description of what each field+--    does, please see the <https://developers.google.com/api-client-library/javascript/reference/referencedocs Google API Reference>,+--    especially the methods @gapi.auth.authorize@ and @gapi.client.setApiKey@.+data Config = Config {+  -- | Client ID to generate an authentification token from. +  clientID  :: String,+  -- | The API key for your application+  apiKey    :: String,+  -- | Here you enter the availiable scopes for your application.+  scopes    :: String,+  -- | If true, the token an attempt will be made to refresh it behind the+  --    cenes+  immediate :: Bool    +  }+              +instance Show Config where+  show (Config cid key scopes' imm)+    = "\nConfig: " ++ concatMap (++ "\n\t") [cid, key, scopes', show imm]++instance ToAny Config where+  toAny cfg = toObject [("clientID",  toAny $ clientID cfg),+                        ("apiKey",    toAny $ apiKey cfg),+                        ("scopes",    toAny $ scopes cfg),+                        ("immediate", toAny $ immediate cfg)]++-- | OAuth2Token, the authentication tokens used by the Google API.+data OAuth2Token = OA2Success {+  -- | Authenticated access token +  accessToken :: String,+  -- | Expiration of the token +  expiresIn   :: String,+  -- | Google API Scopes related to this token.+  state       :: String+  }+                 | OA2Error {+                     errorMsg :: String,+                     state    :: String+                     }++instance Show OAuth2Token where+  show t = if oa2success t+           then "Success Token: '" ++ shorten (accessToken t)+                ++ "'\n\t for scopes: '" ++ state t+                ++ "'\n\t expires in: "++ expiresIn t ++ "s"+           else "Failure Token: " ++ errorMsg t+    where shorten :: String -> String+          shorten str | length str < 16 = str+                      | otherwise       = take 32 str ++ "..."+++instance FromAny OAuth2Token where+  fromAny oa2 = do+    success <- FFI.has oa2 "access_token"+    if success+      then OA2Success <$> FFI.get oa2 "access_token"+           <*> FFI.get oa2 "expires_in"+           <*> FFI.get oa2 "state"+      else OA2Error <$> FFI.get oa2 "error"+           <*> FFI.get oa2 "state"+ +-- Exported functions --------------------------------------------------------+-- | Returns true if the token represents a successful authentication+oa2success :: OAuth2Token -> Bool+oa2success OA2Success {} = True+oa2success _ = False++-- | Loads the Google API, inserts Google API headers and then executes+--    an action. +withGAPI :: Config -> (OAuth2Token -> IO ()) -> IO ()+withGAPI cfg handler = do+  loadGAPI cfg handler+  loadGAPIExternals "GAPILoader"++-- | Exports and coordinate loading of the Google API.+loadGAPI :: Config -> (OAuth2Token -> IO ()) -> IO ()+loadGAPI = loadGAPI' "GAPILoader"++-- | Loads the Google API with a custom loader name+loadGAPI' :: String -> Config -> (OAuth2Token -> IO ()) -> IO ()+loadGAPI' symbol cfg handler+  = exportLoaderSymbol symbol $ loadClient cfg $ auth cfg handler++-- | Returns the token from the current Google API state+getToken :: IO OAuth2Token+getToken = ffi "(function() {return gapi.auth.getToken();})"++-- | Loads the GAPI Client +loadClient :: Config -> IO () -> IO ()+loadClient = ffi "(function(cfg, auth){\+\gapi.client.setApiKey(cfg.apiKey); \+\window.setTimeout(auth, 1);})"++-- | Authenticates the user. Should be invoked by loadClient+auth :: Config -> (OAuth2Token -> IO ()) -> IO ()+auth = ffi "(function(cfg, ah)\+\{gapi.auth.authorize({\+ \'client_id': cfg.clientID, \+ \'scope': cfg.scopes, \+ \'immediate': cfg.immediate}, \+\ah);})"++-- | Export the loader symbol +exportLoaderSymbol :: String -> IO () -> IO ()+exportLoaderSymbol = ffi "(function(s, f) {window[s] = f;})"++-- | Loads the external GAPI scripts+loadGAPIExternals :: String -> IO ()+loadGAPIExternals = ffi "(function(sym) {\+\var s = document.createElement('script');\+\s.setAttribute('src', 'https://apis.google.com/js/client.js?onload=' + sym);\+\s.setAttribute('type', 'text/javascript');\+\document.head.appendChild(s);})"
+ Haste/GAPI/GPlus.hs view
@@ -0,0 +1,119 @@+{-# LANGUAGE OverloadedStrings #-}+{-|+Module      : Haste.GAPI.GPlus+Description : Google+ API bindings for haste-gapi +Copyright   : (c) Jonathan Skårstedt, 2016+License     : MIT+Maintainer  : jonathan.skarstedt@gmail.com+Stability   : experimental+Portability : Haste++Contains default function and result mappings for the Google+ API.+-}+module Haste.GAPI.GPlus (+  peopleGet,+  peopleSearch,+  peopleListByActivity,+  peopleList,+  -- | = Google+ datatypes:+  +  -- | == Google+ Person+  module Haste.GAPI.GPlus.Person+  +                        ) where+++import Haste.GAPI.Types+import Haste.GAPI.GPlus.Person+import Haste.GAPI.Request+import Haste.JSString as J +import Haste (JSString)++-- | Fetches a user by ID+peopleGet :: UserID -> RequestM (Result Person)+peopleGet uid = request' $ "plus/v1/people/" `append` uid++-- | Search after users given a query string+--+-- Optional parameters:+-- +--   [@language@] @language@ to search with. See Google API documentation+--                for valid language codes              +-- +--   [@maxResult@] Maximum number of people to include in the response.+--                 Acceptable values of @maxResult@ ranges between 1-50,+--                 default is 25.+--+--   [@pageToken@] Token used for pagination in large result sets. +peopleSearch :: JSString -> Params -> RequestM [Result Person]+peopleSearch query ps = do+  r <- request "plus/v1/people" $ ("query", query) `pcons` ps+  r' <- get r "items"+  childs <- children r'+  case childs of+    Just childs' -> return childs'+    Nothing -> fail "peopleSearch: Child was not found!"+  +-- | List users by activity+--+-- Valid collections are "@plusoners@" and "@resharers@".+--+-- Optional parameters:+-- +--   [@maxResult@] Maximum number of people to include in the response.+--                 Acceptable values of @maxResult@ ranges between 1-100,+--                 default is 20.+--+--   [@pageToken@] Token used for pagination in large result sets. +peopleListByActivity :: ActivityID -> Collection -> Params+                        -> RequestM [Result Person]+peopleListByActivity actId col ps = do+  r <- request (J.concat ["plus/v1/activities/", actId, "/people/", col]) ps+  r' <- get r "items"+  childs <- children r'+  case childs of+    Just childs' -> return childs'+    Nothing -> fail "peopleSearch: Child was not found!"++++-- | List people in a specific collection+--+-- Accepted collections are:+--+--   [@connected@] The list of visible people in the authenticated user's+--                 circles who also use the requesting app. This list+--                 is limited to users who made their app activities visible+--                 to the authenticated user.+--+--   [@visible@] The list of people who this user has added to one or more+--               circles, limited to the circles visible to the requesting+--               application.+-- +-- Optional parameters:+-- +--   [@maxResult@] Maximum number of people to include in the response.+--                 Acceptable values of @maxResult@ ranges between 1-100,+--                 default is 100.+--+--   [@orderBy@] The order to return people in. Valid inputs are+--               "@alphabetical@" and "@best@"+--+--   [@pageToken@] Token used for pagination in large result sets. +peopleList :: UserID -> Collection -> Params -> RequestM [Result Person]+peopleList uid c ps = do+  r  <- request (J.concat ["plus/v1/people/", uid, "/people/", c]) ps+  r' <- get r "items"+  childs <- children r'+  case childs of+    Just childs' -> return childs'+    Nothing -> fail "peopleSearch: Child was not found!"+  ++++++++  
+ Haste/GAPI/GPlus/Person.hs view
@@ -0,0 +1,17 @@+{-|+Module      : Haste.GAPI.GPlus.Person+Description : Functions for working on persons +Copyright   : (c) Jonathan Skårstedt, 2016+License     : MIT+Maintainer  : jonathan.skarstedt@gmail.com+Stability   : experimental+Portability : Haste++Functions which maps to the Google API +-}+module Haste.GAPI.GPlus.Person where++-- | Haste.GAPI.Result type parameter+data Person ++
+ Haste/GAPI/Internals/Promise.hs view
@@ -0,0 +1,46 @@+{-# LANGUAGE OverloadedStrings #-}+{-|+Module      : Haste.GAPI.Internals.Promise+Description : Mapping to Google API promises+Copyright   : (c) Jonathan Skårstedt, 2016+License     : MIT+Maintainer  : jonathan.skarstedt@gmail.com+Stability   : experimental+Portability : Haste++Wraps Promises as implemented in Google API with a haskell representation. +-}+module Haste.GAPI.Internals.Promise where++import Haste+import Haste.Foreign++-- | An argument to a fulfilled promise+type Response = JSAny+-- | An argument to a rejected promise +type Reason = JSAny++-- | A promise is either a true promise or a callback+--    (a default error handler will be provided)+data Promise = Promise (Response -> IO ()) (Reason -> IO ())+             | Callback (Response -> IO ())++-- | Transform a promise to an any (for application)+instance ToAny Promise where+  toAny (Promise thn err) = toObject [("then", toAny thn),+                                      ("error", toAny err)]+  toAny (Callback cbk) = toObject [("then", toAny cbk),+                                   ("error", toAny gapiError)]++-- | Apply a promise to a JSAny +applyPromise :: JSAny -> Promise -> IO ()+applyPromise = ffi "(function(action, p) {action.then(p.then, p.error);})"++-- | Default error handler in promises+gapiError :: (JSString -> IO ()) -> Reason -> IO ()+gapiError action reason+  = do msg <- lookupAny reason "result.error.message"+       case msg of+        Just m -> do str <- fromAny m+                     action str+        Nothing -> putStrLn "Malformed response!"
+ Haste/GAPI/Request.hs view
@@ -0,0 +1,76 @@+{-# LANGUAGE OverloadedStrings #-}+{-|+Module      : Haste.GAPI.Request+Description : Contains everything Request-related +Copyright   : (c) Jonathan Skårstedt, 2016+License     : MIT+Maintainer  : jonathan.skarstedt@gmail.com+Stability   : experimental+Portability : Haste++Contains RequestM and run functions.++-}+module Haste.GAPI.Request (+  RequestM (..),+  Request (..),+  Result(..),+  Path (..),+  runR, runRConc,+  request, request', +  has, get, hasAll, val, valOf, children,+  lookupResult, lookupVal,+  -- | Perform an IO Action inside RequestM +  liftIO,+  -- | Perform a CIO Action inside RequestM +  liftConc, +  -- | === Constructing parameters+  Params(),+  parp, merge, pcons, params+  ) where++import Haste.GAPI.Internals.Promise+import Haste.GAPI.Request.RequestM+import Haste.GAPI.Request.Types (+  Path,+  Params(..), Request(..),+  merge, pcons, params,+  rawRequest)+import Haste.GAPI.Request.Raw+import Haste.GAPI.Result+import qualified Haste.JSString as J++import Haste.Foreign hiding (has, get, hasAll)+import Haste.Concurrent ++import Data.Default +import Control.Monad+import Control.Applicative++-- | Runs the request eDSL +runR :: RequestM () -> IO ()+runR = concurrent . void . unR++-- | Runs the request eDSL from a concurrent context+runRConc :: RequestM () -> CIO ()+runRConc = void . unR++-- | Creates a request from an API path and a series of parameters.+request :: Path -> Params -> RequestM (Result a)+request p ps = customRequest (rawRequest p ps)++-- | Performs a request from an API path but without parameters+request' :: Path -> RequestM (Result a)+request' p = customRequest (rawRequest p def)++-- | Creates a request using a custom request+customRequest :: Request -> RequestM (Result a)+customRequest req = do+  v <- newEmptyMVar+  liftConc . fork . liftIO $ do+    resp <- jsCreateRequest (path req) (toAny $ rparams req)+    applyPromise resp $ Promise (concurrent . putMVar v . Right . toResult)+      (\_r -> concurrent . putMVar v . Left . J.pack $ +             "Request error: " ++ show req)+  Req $ takeMVar v+
+ Haste/GAPI/Request/Raw.hs view
@@ -0,0 +1,34 @@+{-# LANGUAGE OverloadedStrings #-}+{-|+Module      : Haste.GAPI.Request.Raw+Description : JavaScript functions needed by Request handling. +Copyright   : (c) Jonathan Skårstedt, 2016+License     : MIT+Maintainer  : jonathan.skarstedt@gmail.com+Stability   : experimental+Portability : Haste++Contains JavaScript foreign functions for use with the request handler.+-}++module Haste.GAPI.Request.Raw where++import Haste+import Haste.Foreign+import Haste.GAPI.Internals.Promise++-- | Creates a request object +jsCreateRequest :: JSString -> JSAny -> IO JSAny+jsCreateRequest = ffi "(function(p, ps) {\+\return gapi.client.request({'path': p, 'params': ps})\+\})"++-- | Executes a request and performs the given action as a continuation+jsExecuteRequestThen :: JSString -> JSAny -> (Response -> IO ()) -> IO ()+jsExecuteRequestThen = ffi "(function(p, ps, callback) {\+\ gapi.client.request({'path': p, 'params': ps}).then({\+\'then': function(resp) {callback(resp);}, \+\'error': function(err) {console.debug(err);}\+\});\+\})"+
+ Haste/GAPI/Request/RequestM.hs view
@@ -0,0 +1,59 @@+{-|+Module      : Haste.GAPI.Request.RequestM+Description : Monad for use in Requests+Copyright   : (c) Jonathan Skårstedt, 2016+License     : MIT+Maintainer  : jonathan.skarstedt@gmail.com+Stability   : experimental+Portability : Haste++Contains the Request Monad with all its instances +-}+module Haste.GAPI.Request.RequestM (+  Error, +  RequestM(..),+  liftIO+  ) where++import Haste+import Control.Monad+import Data.Functor+import Haste.Concurrent+import Control.Monad.IO.Class +import Control.Applicative+import qualified Haste.JSString as J++-- | A default error type for RequestM, will be customisable in the future. +type Error = JSString++-- | An eDSL for requests. +newtype RequestM a = Req {unR :: CIO (Either Error a)}++-- | RequestM can perform CIO actions+instance MonadConc RequestM where+  liftConc a = Req $ a >>= return . Right+  fork (Req c) = Req $ (fork $ void c) >>= return . Right++-- | RequestM can perform IO actions+instance MonadIO RequestM where+  liftIO a = Req $ liftIO a >>= return . Right++-- | RequestM is a monad+instance Monad RequestM where+  fail err = Req . return . Left $ J.pack err+  return a = Req . return $ Right a+  (Req a) >>= f = Req $ do+    a' <- a+    case a' of+      Right good -> unR $ f good+      Left  bad  -> do+        return $ Left bad++-- | Applicative is defined so that it builds under GHC 7.8 +instance Applicative RequestM where+  (<*>) = ap+  pure = return+  +-- | Functor is defined so that it builds under GHC 7.8 +instance Functor RequestM where+  fmap f m = m >>= return . f
+ Haste/GAPI/Request/Types.hs view
@@ -0,0 +1,97 @@+{-# LANGUAGE OverloadedStrings #-}+{-|+Module      : Haste.GAPI.Request.Types+Description : Types used in requests. +Copyright   : (c) Jonathan Skårstedt, 2016+License     : MIT+Maintainer  : jonathan.skarstedt@gmail.com+Stability   : experimental+Portability : Haste++Contains default types and functions to work on them. +-}+++module Haste.GAPI.Request.Types where++-- GHC 7.8 compatibility+import Data.Functor ((<$>))++import Haste+import Haste.Foreign+import Data.Default+import qualified Haste.JSString as J++-- | Returns an Anys children as a Key-value array+getKV :: JSAny -> IO [(JSString, JSString)]+getKV = ffi "(function(obj) {\+\var out = [];\+\for(i in obj) { out.push([i, obj[i] ]); }\+\return out;\+\})"++-- | Path for requests+type Path = JSString++-- | Parameters for a GAPI request+data Params = Params [(JSString, JSString)]+            deriving Show++-- | Params can be constructed from a JSAny                     +instance FromAny Params where+  fromAny a = Params <$> getKV a++-- | Params can be converted to a JSAny              +instance ToAny Params where+  toAny (Params ps) = let objField (k,v) = (k, toAny $ v)+                      in toObject $ map objField ps ++-- | Empty Parameters as a default instance+instance Default Params where+  def = Params []++-- | Merge two parameters+merge :: Params -> Params -> Params+merge (Params xs) (Params ys) = Params $ xs ++ ys ++-- | Cons a param with a base value+pcons :: (JSString, JSString) -> Params -> Params+pcons x (Params xs) = Params $ x:xs++-- | Create a set of parameters+params :: [(JSString, JSString)] -> Params+params = Params++-- | Request with parameters and everything+data Request = Request { path    :: Path,+                         method  :: JSString,+                         rparams :: Params,+                         headers :: JSString,+                         body    :: JSString}++-- | The requests can be shown as a debug feature+instance Show Request where+  show (Request p m pms _hs _body)+    = let showDict :: (String, String) -> String+          showDict (a,b) = "\n\t" ++ a ++  ": " ++ b+          showParams (Params p')+            = show $ map (\(a,b) -> (J.unpack a, J.unpack b)) p'+      in "Request: " ++  concatMap showDict [+        ("Path", J.unpack p),+        ("Method", J.unpack m),+        ("Params", showParams pms)+        ]++-- | A default request without any path given         +instance Default Request where+  def = rawRequest "" def +++-- | Creates a request by manually entering request path and parameters+rawRequest :: JSString -> Params -> Request+rawRequest p ps = Request { path    = p,+                            method  = "GET",+                            rparams = ps,+                            headers = "",+                            body    = "" }+
+ Haste/GAPI/Result.hs view
@@ -0,0 +1,132 @@+{-# LANGUAGE OverloadedStrings #-}+{-|+Module      : Haste.GAPI.Result+Description : Managing Results within the Google API+Copyright   : (c) Jonathan Skårstedt, 2016+License     : MIT+Maintainer  : jonathan.skarstedt@gmail.com+Stability   : experimental+Portability : Haste++This module contains functions to work on results from requests to the Google+API. +-}+++module Haste.GAPI.Result (+  Result(),+  Raw(..), +  get, has, hasAll, +  lookupVal, lookupResult,+  val, valOf, valMaybe,+  parp, children,+  toResult +  ) where++import Haste+import qualified Haste.Foreign as F +import qualified Haste.JSString as J++-- GHC 7.8 compatibility+import Data.Functor ((<$>))++import Haste.GAPI.Request.Types+import Haste.GAPI.Request.RequestM++-- | Result mapping. The Result takes a parameter to act as a phantom type+--    on which kind of object you're currently working on.+newtype Result a = Result {rawR :: F.JSAny}+++-- | Transforms a JSAny to a Result+toResult :: JSAny -> Result a+toResult = Result ++-- | Default result parameter.+data Raw ++-- | Fetches a data field from a GAPI Result +get :: Result a -> JSString -> RequestM (Result Raw)+get (Result r) key = liftIO $ do+  h <- F.has r key+  if h then Result <$> F.get r key+    else fail $ "Child '" ++ J.unpack key ++ "' was not found."++-- | Looks up a value by a deep index+--   Example:+-- +--   @+--     rLookup res "a.b.c"+--   @+--+--   This will look up`(a.b.c)` where `a` is a field of+--    `res`, `b` is a field of `a`, and `c` is a field of `b`.+lookupVal :: F.FromAny any => Result a -> JSString -> RequestM (Maybe any)+lookupVal res keys = liftIO $ do+  v <- F.lookupAny (rawR res) keys+  case v of+    Just a -> Just <$> F.fromAny a+    Nothing -> return Nothing ++-- | Fetches a result from a deep index+lookupResult :: Result a -> JSString -> RequestM (Maybe (Result Raw))+lookupResult res keys+  = liftIO $ fmap Result <$> F.lookupAny (rawR res) keys++-- | Checks if a Result has a certain field+has :: Result a -> JSString -> RequestM Bool+has (Result res) key = liftIO $ F.has res key++-- | Checks if a Result has several different fields. Will return true only+--    if all these fields exists. +hasAll :: Result a -> [JSString] -> RequestM Bool+hasAll res keys = and <$> mapM (has res) keys++-- | Fetches a value from a field, in contrast to a result.+--    Field must contain a value compatible with fromAny+--    Terminates with an error if the field cannot be found.+val :: F.FromAny any => Result a -> JSString -> RequestM any +val r key = liftIO $ F.get (rawR r) key >>= F.fromAny++-- | Fetches the value of the result directly.+valOf :: F.FromAny any => Result a -> RequestM any+valOf = liftIO . F.fromAny . rawR++-- | Same as `val`, but will return Nothing if there is no such field.+valMaybe :: F.FromAny any => Result a -> JSString -> RequestM (Maybe any)+valMaybe (Result res) key = liftIO $ do+  exists <- F.has res key+  if exists then Just <$> (F.get res key >>= F.fromAny)+    else return Nothing++-- | Takes a result and a list of keys and maps these values to Params.+parp :: Result a -> [JSString] -> RequestM (Maybe Params)+parp r keys = do+  exists <- hasAll r keys+  if not exists+    then return Nothing+    else do vals <- mapM (val r) keys+            return . Just . Params $ zip keys vals+++-- | Transforms a JSAny representing a list into a list of its children+children' :: JSAny -> IO [JSAny]+children' = F.fromAny++-- | Checks if a fromAny is an array +isArray :: JSAny -> IO Bool+isArray = F.ffi "(function(a) {\+\return a.prop \+\&& a.prop.constructor === Array;\+\})"++-- | Transforms a result which represents an array into a list of child+--    elements+children :: Result a -> RequestM (Maybe [Result b])+children (Result res) = do+  arr <- liftIO $ isArray res+  if arr +    then do +      childs <- liftIO $ children' res+      return . Just $ map Result childs+    else return Nothing
+ Haste/GAPI/Types.hs view
@@ -0,0 +1,43 @@+{-|+Module      : Haste.GAPI.Types+Description : Types used in different subsection APIs, like Google++Copyright   : (c) Jonathan Skårstedt, 2016+License     : MIT+Maintainer  : jonathan.skarstedt@gmail.com+Stability   : experimental+Portability : Haste++Types for use by different APIs connecting to haste-gapi. +-}+module Haste.GAPI.Types where++import Haste (JSString)++-- | Resource versioning tag. Will be updated if the resource is updated+type ETag = JSString++-- | An e-mail address+type EMail = JSString++-- | Type of e-mail address. +type EMailType = JSString++-- | An URL+type URL = JSString++-- | User id+type UserID = JSString++-- | Activity id+type ActivityID = JSString++-- | Type of Collection+type Collection = JSString++-- | Type of URL, like contributor (user is contributing to this site) or+--    website.+type URLType = JSString++-- | An image wrapped URL +data Image = Image URL+           deriving Show
+ LICENSE view
@@ -0,0 +1,20 @@+Copyright (c) 2015 Jonathan Skårstedt++Permission is hereby granted, free of charge, to any person obtaining+a copy of this software and associated documentation files (the+"Software"), to deal in the Software without restriction, including+without limitation the rights to use, copy, modify, merge, publish,+distribute, sublicense, and/or sell copies of the Software, and to+permit persons to whom the Software is furnished to do so, subject to+the following conditions:++The above copyright notice and this permission notice shall be included+in all copies or substantial portions of the Software.++THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ haste-gapi.cabal view
@@ -0,0 +1,61 @@+name:                haste-gapi+-- The package version.  See the Haskell package versioning policy (PVP) +-- for standards guiding when and how versions should be incremented.+-- http://www.haskell.org/haskellwiki/Package_versioning_policy+-- PVP summary:      +-+------- breaking API changes+--                   | | +----- non-breaking API additions+--                   | | | +--- code changes with no API change+version:             0.1.0.0+synopsis:            Google API bindings for the Haste compiler+-- A longer description of the package.+description:         This is a library to make use of the Google API Client+                     Library for JavasScript in a Haskell environment!++                     The library works by wrapping login and giving you a+                     fancy type to perform your requests in. This will ease+                     chained requests that would give large amounts of clutter+                     in JavaScript, while giving you a better ability to+                     handle errors.+                     +                     Your HTML doesn't even need to load the GAPI library+                     itself, Haste-GAPI handles that for you!++license:             MIT+license-file:        LICENSE+author:              Jonathan Skårstedt+maintainer:          jonathan.skarstedt@gmail.com+-- copyright:           +category:            Web+build-type:          Simple+cabal-version:       >=1.10++source-repository head+ type:     github+ location: https://github.com/nyson/haste-gapi+++                     +library+  -- Modules exported by the library.+  exposed-modules:     Haste.GAPI,+                       Haste.GAPI.GPlus,+                       Haste.GAPI.Types+                       +  -- Modules included in this library but not exported.+  other-modules:       Haste.GAPI.Result,+                       Haste.GAPI.Request, +                       Haste.GAPI.Request.RequestM,+                       Haste.GAPI.Request.Types,+                       Haste.GAPI.Request.Raw,+                       Haste.GAPI.GPlus.Person,+                       Haste.GAPI.Internals.Promise++  other-extensions:    OverloadedStrings+  build-depends:       base == 4.* , data-default >= 0.5, transformers >= 0.4 +--  if impl(haste)+  build-depends:     haste-lib >=0.5.1+--  else+  --  build-depends:     haste-compiler >=0.5.1+    +  default-language:    Haskell2010+