packages feed

rest-gen 0.12 → 0.13

raw patch · 18 files changed

+313/−122 lines, 18 files

Files

CHANGELOG.md view
@@ -1,6 +1,16 @@ # Changelog -### 0.12+## 0.13++Breaking changes:+* Un-exposes internal modules so we don't have to major bump on every change.+* `gen` Now accepts AST-like types instead of just strings to make it more obvious how to use it, see types in `Rest.Gen.Types`++Bugfixes:+* Make sure Identifiers are always imported when needed. This is a further improvement on the bugfix in rest-gen-0.11.+* rest-gen-0.12	did not always take arguments in generated methods into account, so the renamed qualification has been reverted for now.++## 0.12  * Haskell: Module rewrites such as `Data.Text.Internal` -> `Data.Text` now produces qualified imports `import qualified Data.Text as Data.Text` instead of `import qualified Data.Text as Data.Text.Lazy`. This prevents building against different versions of the same package that may have moved the internal module (as is the case with `text`) from generating different clients. 
rest-gen.cabal view
@@ -1,5 +1,5 @@ name:                rest-gen-version:             0.12+version:             0.13 description:         Documentation and client generation from rest definition. synopsis:            Documentation and client generation from rest definition. maintainer:          code@silk.co@@ -27,9 +27,17 @@ library   ghc-options:       -Wall   hs-source-dirs:    src-  other-modules:     Paths_rest_gen   exposed-modules:     Rest.Gen+    Rest.Gen.Config+    Rest.Gen.Docs.Generate+    Rest.Gen.Docs.Happstack+    Rest.Gen.Haskell.Generate+    Rest.Gen.JavaScript.Generate+    Rest.Gen.Ruby.Generate+    Rest.Gen.Types+  other-modules:+    Paths_rest_gen     Rest.Gen.Base     Rest.Gen.Base.ActionInfo     Rest.Gen.Base.ActionInfo.Ident@@ -38,12 +46,6 @@     Rest.Gen.Base.JSON.Pretty     Rest.Gen.Base.Link     Rest.Gen.Base.XML-    Rest.Gen.Config-    Rest.Gen.Docs.Generate-    Rest.Gen.Docs.Happstack-    Rest.Gen.Haskell.Generate-    Rest.Gen.JavaScript.Generate-    Rest.Gen.Ruby.Generate     Rest.Gen.Utils   build-depends:       base >= 4.5 && < 4.8
src/Rest/Gen.hs view
@@ -1,23 +1,26 @@-module Rest.Gen where+module Rest.Gen+  ( generate+  ) where  import Data.Char-import Data.Label import Data.Foldable+import Data.Label import Data.Maybe import System.Directory import System.Exit import System.Process -import Rest.Api (withVersion, Api, Some1 (..))+import Rest.Api (Api, Some1 (..), withVersion)  import Rest.Gen.Config-import Rest.Gen.Docs.Generate (writeDocs, DocsContext (DocsContext))+import Rest.Gen.Docs.Generate (DocsContext (DocsContext), writeDocs)+import Rest.Gen.Haskell.Generate (HaskellContext (HaskellContext), mkHsApi) import Rest.Gen.JavaScript.Generate (mkJsApi)-import Rest.Gen.Haskell.Generate (mkHsApi, HaskellContext (HaskellContext)) import Rest.Gen.Ruby.Generate (mkRbApi)+import Rest.Gen.Types import Rest.Gen.Utils -generate :: Config -> String -> Api m -> [String] -> [String] -> [(String, String)] -> IO ()+generate :: Config -> String -> Api m -> [ModuleName] -> [Import] -> [(ModuleName, ModuleName)] -> IO () generate config name api sources imports rewrites =   withVersion (get apiVersion config) api (putStrLn "Could not find api version" >> exitFailure) $ \ver (Some1 r) ->      case get action config of@@ -27,18 +30,18 @@             let context = DocsContext root ver (fromMaybe "./templates" (getSourceLocation config))             writeDocs context r loc             exitSuccess-       Just MakeJS          -> mkJsApi (moduleName ++ "Api") (get apiPrivate config) ver r >>= toTarget config-       Just MakeRb          -> mkRbApi (moduleName ++ "Api") (get apiPrivate config) ver r >>= toTarget config+       Just MakeJS          -> mkJsApi (overModuleName (++ "Api") moduleName) (get apiPrivate config) ver r >>= toTarget config+       Just MakeRb          -> mkRbApi (overModuleName (++ "Api") moduleName) (get apiPrivate config) ver r >>= toTarget config        Just MakeHS          ->          do loc <- getTargetDir config "./client"             setupTargetDir config loc-            let context = HaskellContext ver loc (packageName ++ "-client") (get apiPrivate config) sources imports rewrites [moduleName, "Client"]+            let context = HaskellContext ver loc (packageName ++ "-client") (get apiPrivate config) sources imports rewrites [unModuleName moduleName, "Client"]             mkHsApi context r             exitSuccess        Nothing              -> return ()   where     packageName = map toLower name-    moduleName  = upFirst packageName+    moduleName  = ModuleName $ upFirst packageName  getTargetDir :: Config -> String -> IO String getTargetDir config str =
src/Rest/Gen/Base/ActionInfo.hs view
@@ -3,7 +3,25 @@   , GADTs   , ScopedTypeVariables   #-}-module Rest.Gen.Base.ActionInfo where+module Rest.Gen.Base.ActionInfo+  ( Accessor+  , ActionInfo (..)+  , ActionType (..)+  , DataDescription (..)+  , DataType (..)+  , ResourceId+  , accessLink+  , accessors+  , chooseType+  , isAccessor+  , listGetterActionInfo+  , mkActionDescription+  , namedActionInfo+  , resourceToAccessors+  , resourceToActionInfo+  , selectActionInfo+  , singleActionInfo+  ) where  import Prelude hiding (id, (.)) @@ -15,26 +33,29 @@ import Data.Maybe import Data.Proxy import Data.Typeable+-- TODO Remove CPP #if __GLASGOW_HASKELL__ < 704 import Data.List.Split #endif-import Rest.Gen.Base.ActionInfo.Ident (Ident (Ident), description)-import Rest.Info-import qualified Data.JSON.Schema   as J-import qualified Data.Label.Total   as L-import qualified Rest.Gen.Base.JSON as J-import qualified Rest.Gen.Base.XML  as X+import qualified Data.JSON.Schema as J+import qualified Data.Label.Total as L  import Rest.Dictionary (Error (..), Input (..), Output (..), Param (..)) import Rest.Driver.Routing (mkListHandler, mkMultiHandler)-import Rest.Gen.Base.Link import Rest.Handler+import Rest.Info import Rest.Resource hiding (description) import Rest.Schema- import qualified Rest.Dictionary as Dict import qualified Rest.Resource   as Rest +import Rest.Gen.Base.ActionInfo.Ident (Ident (Ident))+import Rest.Gen.Base.Link+import Rest.Gen.Types+import qualified Rest.Gen.Base.ActionInfo.Ident as Ident+import qualified Rest.Gen.Base.JSON             as J+import qualified Rest.Gen.Base.XML              as X+ -------------------- -- * The types describing a resource's actions. @@ -89,7 +110,7 @@ -------------------- -- * Traverse a resource's Schema and Handlers to create a [ActionInfo]. -resourceToActionInfo :: Resource m s sid mid aid -> [ActionInfo]+resourceToActionInfo :: forall m s sid mid aid. Resource m s sid mid aid -> [ActionInfo] resourceToActionInfo r =   case schema r of     Schema mTopLevel step -> foldMap (topLevelActionInfo r) mTopLevel@@ -107,7 +128,7 @@   where     f ("", x) = par x     f (pth, x) = LAction pth : par x-    par = maybe [] (return . LParam . description)+    par = maybe [] (return . LParam . Ident.description)  accessors :: Step sid mid aid -> [Accessor] accessors (Named hs) = mapMaybe (uncurry accessorsNamed) hs@@ -237,7 +258,7 @@       where dirPart   = if pth /= ""                         then [LAction pth]                         else []-            identPart = maybe [] ((:[]) . LParam . description) id_+            identPart = maybe [] ((:[]) . LParam . Ident.description) id_   --------------------@@ -370,8 +391,18 @@ idIdent (Id idnt _) = actionIdent idnt  actionIdent :: forall a. Dict.Ident a -> Ident-actionIdent Dict.StringId = Ident "string" "String" []-actionIdent Dict.ReadId   = Ident (describe proxy_) (typeString proxy_) (modString proxy_)+actionIdent Dict.StringId+  = Ident+    { Ident.description    = "string"+    , Ident.haskellType    = "String"+    , Ident.haskellModules = []+    }+actionIdent Dict.ReadId+  = Ident+    { Ident.description    = describe proxy_+    , Ident.haskellType    =  typeString proxy_+    , Ident.haskellModules = map ModuleName $ modString proxy_+    }   where     proxy_ :: Proxy a     proxy_ = Proxy
src/Rest/Gen/Base/ActionInfo/Ident.hs view
@@ -1,7 +1,9 @@ module Rest.Gen.Base.ActionInfo.Ident (Ident (..)) where +import Rest.Gen.Types+ data Ident = Ident-  { description   :: String-  , haskellType   :: String-  , haskellModule :: [String]+  { description    :: String+  , haskellType    :: String+  , haskellModules :: [ModuleName]   } deriving (Show, Eq)
src/Rest/Gen/Base/ApiTree.hs view
@@ -1,5 +1,26 @@-{-# LANGUAGE GADTs #-}-module Rest.Gen.Base.ApiTree where+module Rest.Gen.Base.ApiTree+  ( ApiAction (..)+  , ApiResource (..)+  , allResourceIds+  , allSubResourceIds+  , allSubResources+  , allSubTrees+  , allTrees+  , apiResources+  , apiSubtrees+  , apiTree'+  , cleanName+  , defaultTree+  , foldTree+  , foldTreeChildren+  , hasAccessor+  , mkFuncParts+  , noPrivate+  , resIdents+  , sortTree+  , subResourceIds+  , subResourceNames+  ) where  import Data.Char import Data.Function
src/Rest/Gen/Base/JSON.hs view
@@ -1,14 +1,14 @@ {-# LANGUAGE OverloadedStrings #-}--module Rest.Gen.Base.JSON where+module Rest.Gen.Base.JSON (showExample) where  import Data.Aeson ((.=)) import Data.JSON.Schema import Data.Text (pack)-import Rest.Gen.Base.JSON.Pretty import Text.PrettyPrint.HughesPJ-import qualified Data.Aeson as A+import qualified Data.Aeson  as A import qualified Data.Vector as V++import Rest.Gen.Base.JSON.Pretty  showExample :: Schema -> String showExample = render . pp_value . showExample'
src/Rest/Gen/Base/JSON/Pretty.hs view
@@ -1,4 +1,4 @@-module Rest.Gen.Base.JSON.Pretty where+module Rest.Gen.Base.JSON.Pretty (pp_value) where  import Control.Arrow (first) import Data.Aeson.Types
src/Rest/Gen/Base/Link.hs view
@@ -1,4 +1,14 @@-module Rest.Gen.Base.Link where+module Rest.Gen.Base.Link+  ( Link+  , LinkItem (..)+  , flattenLast+  , flattenLastResource+  , flattenLink+  , getLinkIds+  , hasParam+  , itemString+  , setLinkIds+  ) where  -- | Data structure representing Api links data LinkItem =
src/Rest/Gen/Base/XML.hs view
@@ -1,4 +1,8 @@-module Rest.Gen.Base.XML (getXmlSchema, showSchema, showExample) where+module Rest.Gen.Base.XML+  ( getXmlSchema+  , showSchema+  , showExample+  ) where  import Data.List import Text.XML.HXT.Arrow.Pickle
src/Rest/Gen/Config.hs view
@@ -1,8 +1,25 @@-{-# LANGUAGE TemplateHaskell, TypeOperators #-}-module Rest.Gen.Config where+{-# LANGUAGE+    TemplateHaskell+  , TypeOperators+  #-}+module Rest.Gen.Config+  ( Action (..)+  , Location (..) -import Prelude hiding ((.), id)+  , Config+  , action+  , source+  , target+  , apiVersion+  , apiPrivate +  , defaultConfig+  , parseLocation+  , options+  ) where++import Prelude hiding (id, (.))+ import Control.Category import Data.Label import System.Console.GetOpt@@ -18,7 +35,7 @@   , _apiPrivate :: Bool   } -$(mkLabels [''Config])+mkLabels [''Config]  defaultConfig :: Config defaultConfig = Config
src/Rest/Gen/Docs/Generate.hs view
@@ -1,15 +1,26 @@-{-# LANGUAGE GADTs-           , TupleSections-           , ScopedTypeVariables-           , OverlappingInstances-           , TemplateHaskell-           , TypeFamilies-           , EmptyDataDecls-           , MultiParamTypeClasses-           #-}-module Rest.Gen.Docs.Generate where+{-# LANGUAGE+    EmptyDataDecls+  , GADTs+  , MultiParamTypeClasses+  , OverlappingInstances+  , ScopedTypeVariables+  , TemplateHaskell+  , TupleSections+  , TypeFamilies+  #-}+module Rest.Gen.Docs.Generate+  ( DocsContext (..)+  , cdiv+  , cls+  , mkAllResources+  , mkSingleResource+  , resourcesInfo+  , row+  , subResourcesInfo+  , writeDocs+  ) where -import Prelude hiding (head, id, div, span)+import Prelude hiding (div, head, id, span) import qualified Prelude as P  import Control.Monad hiding (forM_)@@ -19,16 +30,16 @@ import Data.String import System.Directory import System.FilePath+import System.Log.Logger import Text.Blaze.Html-import Text.Blaze.Html.Renderer.String import Text.Blaze.Html5 hiding (map)-import Text.Blaze.Html5.Attributes hiding (title, method, span)+import Text.Blaze.Html5.Attributes hiding (method, span, title)+import Text.Blaze.Html.Renderer.String import Text.StringTemplate -import Rest.Api (Version, Router)+import Rest.Api (Router, Version) import Rest.Gen.Base import Rest.Gen.Utils-import System.Log.Logger  -- | Information about the context in which a resource is contained data DocsContext = DocsContext
src/Rest/Gen/Docs/Happstack.hs view
@@ -1,5 +1,7 @@ {-# LANGUAGE FlexibleContexts #-}-module Rest.Gen.Docs.Happstack where+module Rest.Gen.Docs.Happstack+  ( apiDocsHandler+  ) where  import Control.Monad import Control.Monad.Trans
src/Rest/Gen/Haskell/Generate.hs view
@@ -2,21 +2,23 @@     DoAndIfThenElse   , TemplateHaskell   #-}-module Rest.Gen.Haskell.Generate where+module Rest.Gen.Haskell.Generate+  ( HaskellContext (..)+  , mkHsApi+  ) where  import Control.Applicative-import Control.Arrow (first)+import Control.Arrow (first, second) import Control.Category import Control.Monad-import Data.Label (mkLabels, modify, set)+import Data.Label (modify, set)+import Data.Label.Derive (mkLabelsNamed) import Data.List-import Data.List.Split import Data.Maybe import Prelude hiding (id, (.)) import Safe import System.Directory import System.FilePath- import qualified Distribution.ModuleName                     as Cabal import qualified Distribution.Package                        as Cabal import qualified Distribution.PackageDescription             as Cabal@@ -31,11 +33,11 @@ import Rest.Api (Router, Version)  import Rest.Gen.Base+import Rest.Gen.Types import Rest.Gen.Utils- import qualified Rest.Gen.Base.ActionInfo.Ident as Ident -$(mkLabels[''Cabal.GenericPackageDescription, ''Cabal.CondTree, ''Cabal.Library])+mkLabelsNamed ("_" ++) [''Cabal.GenericPackageDescription, ''Cabal.CondTree, ''Cabal.Library]  data HaskellContext =   HaskellContext@@ -43,9 +45,9 @@     , targetPath     :: String     , wrapperName    :: String     , includePrivate :: Bool-    , sources        :: [String]-    , imports        :: [String]-    , rewrites       :: [(String, String)]+    , sources        :: [ModuleName]+    , imports        :: [Import]+    , rewrites       :: [(ModuleName, ModuleName)]     , namespace      :: [String]     } @@ -65,7 +67,7 @@      writeCabalFile cabalFile gpkg   where     cabalFile = targetPath ctx </> wrapperName ctx ++ ".cabal"-    modules   = map Cabal.fromString (sources ctx)+    modules   = map (Cabal.fromString . unModuleName) (sources ctx)              ++ map (Cabal.fromString . qualModName . (namespace ctx ++)) (allSubResourceIds tree)  writeCabalFile :: FilePath -> Cabal.GenericPackageDescription -> IO ()@@ -73,7 +75,7 @@   where emptyField = (/= "\"\" ") . takeWhile (/= ':') . reverse  updateExposedModules :: [Cabal.ModuleName] -> Cabal.GenericPackageDescription -> Cabal.GenericPackageDescription-updateExposedModules modules = modify lCondLibrary (Just . maybe (mkCondLibrary modules) (set (lExposedModules . lCondTreeData) modules))+updateExposedModules modules = modify _condLibrary (Just . maybe (mkCondLibrary modules) (set (_exposedModules . _condTreeData) modules))  mkGenericPackageDescription :: String -> [Cabal.ModuleName] -> Cabal.GenericPackageDescription mkGenericPackageDescription name modules = Cabal.GenericPackageDescription pkg [] (Just (mkCondLibrary modules)) [] [] []@@ -101,32 +103,45 @@   showCode $       "{-# LANGUAGE OverloadedStrings #-}\n{-# OPTIONS_GHC -fno-warn-unused-imports #-}\n{- Warning!! This is automatically generated code, do not modify! -}"   <-> hsModule (qualModName $ namespace ctx ++ resId node)-       [ mkImports ctx node rewrittenMods+       [ mkImports ctx node mods        , idData node        , mkStack funcs        ]   where-    (funcs, mods) = unzip $ map (mkFunction (rewrites ctx) (apiVersion ctx) $ resName node) $ resItems node-    rewrittenMods = rewriteModules (rewrites ctx) $ nub $ concat mods+    (funcs, mods) = second (nub . concat) . unzip . map (mkFunction (apiVersion ctx) . resName $ node) $ resItems node -mkImports :: HaskellContext -> ApiResource -> [String] -> Code-mkImports ctx node datImp =-    mkStack-      [ code "import Rest.Client.Internal"-      , extraImports-      , parentImports-      , dataImports-      ]+mkImports :: HaskellContext -> ApiResource -> [ModuleName] -> Code+mkImports ctx node datImp+  = mkStack+  . map (rewriteImport $ rewrites ctx)+  $    [Import UnQualified (ModuleName "Rest.Client.Internal") Nothing Nothing]+    ++ extraImports+    ++ parentImports+    ++ dataImports+    ++ idImports   where-    extraImports  = mkStack . map ("import " <+>) $ imports ctx-    parentImports = mkStack . map mkImport . tail . inits . resParents $ node-    dataImports   = mkStack . map ("import qualified " <+>) $ datImp-    mkImport p = "import qualified" <++> qualModName (namespace ctx ++ p) <++> "as" <++> modName (last p)+    extraImports  = imports ctx+    parentImports = map mkImport . tail . inits . resParents $ node+    dataImports   = map qualImp datImp+    idImports     = concat . mapMaybe (return . map qualImp . Ident.haskellModules <=< snd) . resAccessors $ node+    -- We need the `as' name to be explicit here even though it's the same, see comment below.+    qualImp v     = Import Qualified v (Just v) Nothing+    mkImport p    = Import Qualified (ModuleName . qualModName $ namespace ctx ++ p) (Just . ModuleName . modName . last $ p) Nothing+    rewriteImport :: [(ModuleName, ModuleName)] -> Import -> Import+    rewriteImport rws i = case i of+      -- We don't rewrite the `as` part of the import so if you have a+      -- rewrite ("Data.Text.Internal.Lazy", "Data.Text.Lazy") the+      -- import will become `import qualified Data.Text as+      -- Data.Text.Internal.Lazy', this is because mkFunction produces+      -- types through strings and doesn't take rewrites into+      -- account. mkFunction should be changed to do this.+      Import q m mas l -> Import q (look m) mas l+      where+       look m = lookupJustDef m m rws -mkFunction :: [(String,String)] -> Version -> String -> ApiAction -> (Code, [String])-mkFunction rewrits ver res (ApiAction _ lnk ai) =-  let mInp     = fmap (inputInfo rewrits) $ chooseType $ inputs ai-      identMod = maybe [] Ident.haskellModule (ident ai)+mkFunction :: Version -> String -> ApiAction -> (Code, [ModuleName])+mkFunction ver res (ApiAction _ lnk ai) =+  let mInp     = fmap inputInfo $ chooseType $ inputs ai       defaultErrorConversion = if fmap dataType (chooseType (outputs ai)) == Just JSON then "fromJSON" else "fromXML"       (oMod, oType, oCType, oFunc) = maybe ([], "()", "text/plain", "(const ())") outputInfo $ chooseType $ outputs ai       (eMod, eType, eFunc) = headDef (([], "()", defaultErrorConversion))@@ -163,7 +178,7 @@               ]               $ "liftM (parseResult" <++> eFunc <++> oFunc <+> ") . doRequest $ request"         ]-     , eMod ++ oMod ++ identMod ++ maybe [] (\(m,_,_,_) -> m) mInp+     , map ModuleName $ eMod ++ oMod ++ maybe [] (\(m,_,_,_) -> m) mInp      )  linkToURL :: String -> Link -> (Code, [String])@@ -229,15 +244,6 @@       by     = if target /= [] && (isJust (ident ai) || actionType ai == UpdateMany) then ["by"] else []       get    = if isAccessor ai then [] else ["get"] --- | Rewrites multiple flattened module names-rewriteModules :: [(String, String)] -> [String] -> [String]-rewriteModules _  [] = []-rewriteModules rw (v : vs) = maybe v (\m -> m ++ " as " ++ m) (lookup v rw) : rewriteModules rw vs---- | Rewrites a single module name-rewriteModule :: [(String, String)] -> [String] -> [String]-rewriteModule rewrits m = maybe m (splitOn ".") (lookup (intercalate "." $ m) rewrits)- hsName :: [String] -> String hsName []       = "" hsName (x : xs) = clean $ downFirst x ++ concatMap upFirst xs@@ -260,12 +266,12 @@ dataName :: String -> String dataName = modName -inputInfo :: [(String,String)] -> DataDescription -> ([String], String, String, String)-inputInfo rewrits ds =+inputInfo :: DataDescription -> ([String], String, String, String)+inputInfo ds =   case dataType ds of     String -> ([], "String", "text/plain", "fromString")-    XML    -> (rewriteModule rewrits (haskellModule ds), haskellType ds, "text/xml", "toXML")-    JSON   -> (rewriteModule rewrits (haskellModule ds), haskellType ds, "text/json", "toJSON")+    XML    -> (haskellModule ds, haskellType ds, "text/xml", "toXML")+    JSON   -> (haskellModule ds, haskellType ds, "text/json", "toJSON")     File   -> ([], "ByteString", "application/octet-stream", "id")     Other  -> ([], "ByteString", "text/plain", "id") 
src/Rest/Gen/JavaScript/Generate.hs view
@@ -8,19 +8,20 @@ import Data.Maybe import Text.StringTemplate -import Rest.Api (Version, Router)+import Rest.Api (Router, Version) import Rest.Gen.Base+import Rest.Gen.Types import Rest.Gen.Utils -mkJsApi :: String -> Bool -> Version -> Router m s -> IO String+mkJsApi :: ModuleName -> Bool -> Version -> Router m s -> IO String mkJsApi ns priv ver r =   do prelude <- liftM (render . setManyAttrib attrs . newSTMP) (readContent "Javascript/base.js")      let cod = showCode $ mkStack-                [ ns ++ ".prototype.version" .=. string (show ver)-                , mkJsCode ns priv r+                [ unModuleName ns ++ ".prototype.version" .=. string (show ver)+                , mkJsCode (unModuleName ns) priv r                 ]      return $ prelude ++ cod-  where attrs = [("apinamespace", ns), ("dollar", "$")]+  where attrs = [("apinamespace", unModuleName ns), ("dollar", "$")]  mkJsCode :: String -> Bool -> Router m s -> Code mkJsCode ns priv = mkJs ns . sortTree . (if priv then id else noPrivate) . apiSubtrees
src/Rest/Gen/Ruby/Generate.hs view
@@ -1,21 +1,22 @@ {-# LANGUAGE ScopedTypeVariables #-} module Rest.Gen.Ruby.Generate (mkRbApi) where -import Code.Build-import Code.Build.Ruby- import Data.Char import Data.List import Data.Maybe -import Rest.Api (Version, Router)+import Code.Build+import Code.Build.Ruby++import Rest.Api (Router, Version) import Rest.Gen.Base+import Rest.Gen.Types import Rest.Gen.Utils -mkRbApi :: String -> Bool -> Version -> Router m s -> IO String+mkRbApi :: ModuleName -> Bool -> Version -> Router m s -> IO String mkRbApi ns priv ver r =   do prelude <- readContent "Ruby/base.rb"-     let cod = showCode . mkRb ns ver . sortTree . (if priv then id else noPrivate) . apiSubtrees $ r+     let cod = showCode . mkRb (unModuleName ns) ver . sortTree . (if priv then id else noPrivate) . apiSubtrees $ r      return $ cod ++ prelude  mkRb :: String -> Version -> ApiResource -> Code
+ src/Rest/Gen/Types.hs view
@@ -0,0 +1,60 @@+module Rest.Gen.Types+  ( ModuleName (..)+  , overModuleName+  , Import (..)+  , Qualification (..)+  , QName (..)+  , Name (..)+  ) where++import Data.List++import Code.Build++newtype ModuleName = ModuleName { unModuleName :: String }+  deriving (Eq, Show)++instance Codeable ModuleName where+  code = code . unModuleName++overModuleName :: (String -> String) -> ModuleName -> ModuleName+overModuleName f = ModuleName . f . unModuleName++newtype Name = Name { unName :: String }++instance Codeable Name where+  code = code . unName++data QName+  = Qual ModuleName Name+  | UnQual Name++instance Codeable QName where+  code (UnQual n) = code n+  code (Qual m n) = code m <+> "." <+> code n++data Qualification = Qualified | UnQualified++instance Codeable Qualification where+  code Qualified   = code "qualified"+  code UnQualified = code ""++data Import+  = Import Qualification ModuleName (Maybe ModuleName) (Maybe [QName])++instance Codeable Import where+  code i = case i of+    Import q m mas ids+      -> "import"+      <++> q+      <++> m+      <++> qualAs+      <++> maybe (code "") (\v -> "(" <+> impList v <+> ")") ids+      where+        qualAs = case mas of+          Just as+            | as == m   -> code ""+            | otherwise -> "as" <++> as+          Nothing       -> code ""+        impList :: [QName] -> Code+        impList = foldl' (<++>) (code "") . intersperse (code ",") . map code
src/Rest/Gen/Utils.hs view
@@ -1,4 +1,14 @@-module Rest.Gen.Utils where+module Rest.Gen.Utils+  ( readContent+  , copyContent+  , groupByFirst+  , fst3+  , snd3+  , thd3+  , upFirst+  , downFirst+  , mapHead+  ) where  import Data.Char import Data.List.Split