AddHandler.hs 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206
  1. {-# LANGUAGE PatternGuards #-}
  2. module AddHandler (addHandler) where
  3. import Prelude hiding (readFile)
  4. import System.IO (hFlush, stdout)
  5. import Data.Char (isLower, toLower, isSpace)
  6. import Data.List (isPrefixOf, isSuffixOf, stripPrefix)
  7. import Data.Maybe (fromMaybe)
  8. import qualified Data.Text as T
  9. import qualified Data.Text.IO as TIO
  10. import System.Directory (getDirectoryContents, doesFileExist)
  11. import Control.Monad (unless)
  12. data RouteError = EmptyRoute
  13. | RouteCaseError
  14. | RouteExists FilePath
  15. deriving Eq
  16. instance Show RouteError where
  17. show EmptyRoute = "No name entered. Quitting ..."
  18. show RouteCaseError = "Name must start with an upper case letter"
  19. show (RouteExists file) = "File already exists: " ++ file
  20. -- strict readFile
  21. readFile :: FilePath -> IO String
  22. readFile = fmap T.unpack . TIO.readFile
  23. cmdLineArgsError :: String
  24. cmdLineArgsError = "You have to specify a route name if you want to add handler with command line arguments."
  25. addHandler :: Maybe String -> Maybe String -> [String] -> IO ()
  26. addHandler (Just route) pat met = do
  27. cabal <- getCabal
  28. checked <- checkRoute route
  29. let routePair = case checked of
  30. Left err@EmptyRoute -> (error . show) err
  31. Left err@RouteCaseError -> (error . show) err
  32. Left err@(RouteExists _) -> (error . show) err
  33. Right p -> p
  34. addHandlerFiles cabal routePair pattern methods
  35. where
  36. pattern = fromMaybe "" pat -- pattern defaults to ""
  37. methods = unwords met -- methods default to none
  38. addHandler Nothing (Just _) _ = error cmdLineArgsError
  39. addHandler Nothing _ (_:_) = error cmdLineArgsError
  40. addHandler _ _ _ = addHandlerInteractive
  41. addHandlerInteractive :: IO ()
  42. addHandlerInteractive = do
  43. cabal <- getCabal
  44. let routeInput = do
  45. putStr "Name of route (without trailing R): "
  46. hFlush stdout
  47. name <- getLine
  48. checked <- checkRoute name
  49. case checked of
  50. Left err@EmptyRoute -> (error . show) err
  51. Left err@RouteCaseError -> print err >> routeInput
  52. Left err@(RouteExists _) -> do
  53. print err
  54. putStrLn "Try another name or leave blank to exit"
  55. routeInput
  56. Right p -> return p
  57. routePair <- routeInput
  58. putStr "Enter route pattern (ex: /entry/#EntryId): "
  59. hFlush stdout
  60. pattern <- getLine
  61. putStr "Enter space-separated list of methods (ex: GET POST): "
  62. hFlush stdout
  63. methods <- getLine
  64. addHandlerFiles cabal routePair pattern methods
  65. addHandlerFiles :: FilePath -> (String, FilePath) -> String -> String -> IO ()
  66. addHandlerFiles cabal (name, handlerFile) pattern methods = do
  67. modify "Application.hs" $ fixApp name
  68. modify cabal $ fixCabal name
  69. modify "config/routes" $ fixRoutes name pattern methods
  70. writeFile handlerFile $ mkHandler name pattern methods
  71. specExists <- doesFileExist specFile
  72. unless specExists $
  73. writeFile specFile $ mkSpec name pattern methods
  74. where
  75. specFile = "test/Handler/" ++ name ++ "Spec.hs"
  76. modify fp f = readFile fp >>= writeFile fp . f
  77. getCabal :: IO FilePath
  78. getCabal = do
  79. allFiles <- getDirectoryContents "."
  80. case filter (".cabal" `isSuffixOf`) allFiles of
  81. [x] -> return x
  82. [] -> error "No cabal file found"
  83. _ -> error "Too many cabal files found"
  84. checkRoute :: String -> IO (Either RouteError (String, FilePath))
  85. checkRoute name =
  86. case name of
  87. [] -> return $ Left EmptyRoute
  88. c:_
  89. | isLower c -> return $ Left RouteCaseError
  90. | otherwise -> do
  91. -- Check that the handler file doesn't already exist
  92. let handlerFile = concat ["Handler/", name, ".hs"]
  93. exists <- doesFileExist handlerFile
  94. if exists
  95. then (return . Left . RouteExists) handlerFile
  96. else return $ Right (name, handlerFile)
  97. fixApp :: String -> String -> String
  98. fixApp name =
  99. unlines . reverse . go . reverse . lines
  100. where
  101. l spaces = "import " ++ spaces ++ "Handler." ++ name
  102. go [] = [l ""]
  103. go (x:xs)
  104. | Just y <- stripPrefix "import " x, "Handler." `isPrefixOf` dropWhile (== ' ') y = l (takeWhile (== ' ') y) : x : xs
  105. | otherwise = x : go xs
  106. fixCabal :: String -> String -> String
  107. fixCabal name =
  108. unlines . reverse . go . reverse . lines
  109. where
  110. l = " Handler." ++ name
  111. go [] = [l]
  112. go (x:xs)
  113. | "Handler." `isPrefixOf` x' = (spaces ++ "Handler." ++ name) : x : xs
  114. | otherwise = x : go xs
  115. where
  116. (spaces, x') = span isSpace x
  117. fixRoutes :: String -> String -> String -> String -> String
  118. fixRoutes name pattern methods fileContents =
  119. fileContents ++ l
  120. where
  121. l = concat
  122. [ startingCharacter
  123. , pattern
  124. , " "
  125. , name
  126. , "R "
  127. , methods
  128. , "\n"
  129. ]
  130. startingCharacter = if "\n" `isSuffixOf` fileContents then "" else "\n"
  131. mkSpec :: String -> String -> String -> String
  132. mkSpec name _ methods = unlines
  133. $ ("module Handler." ++ name ++ "Spec (spec) where")
  134. : ""
  135. : "import TestImport"
  136. : ""
  137. : "spec :: Spec"
  138. : "spec = withApp $ do"
  139. : concatMap go (words methods)
  140. where
  141. go method =
  142. [ ""
  143. , " describe \"" ++ func ++ "\" $ do"
  144. , " error \"Spec not implemented: " ++ func ++ "\""
  145. , ""]
  146. where
  147. func = concat [map toLower method, name, "R"]
  148. mkHandler :: String -> String -> String -> String
  149. mkHandler name pattern methods = unlines
  150. $ ("module Handler." ++ name ++ " where")
  151. : ""
  152. : "import Import"
  153. : concatMap go (words methods)
  154. where
  155. go method =
  156. [ ""
  157. , concat $ func : " :: " : map toArrow types ++ ["Handler Html"]
  158. , concat
  159. [ func
  160. , " "
  161. , concatMap toArgument types
  162. , "= error \"Not yet implemented: "
  163. , func
  164. , "\""
  165. ]
  166. ]
  167. where
  168. func = concat [map toLower method, name, "R"]
  169. types = getTypes pattern
  170. toArrow t = concat [t, " -> "]
  171. toArgument t = concat [uncapitalize t, " "]
  172. getTypes "" = []
  173. getTypes ('/':rest) = getTypes rest
  174. getTypes (c:rest) | c `elem` "#*" =
  175. typ : getTypes rest'
  176. where
  177. (typ, rest') = break (== '/') rest
  178. getTypes rest = getTypes $ dropWhile (/= '/') rest
  179. uncapitalize :: String -> String
  180. uncapitalize (x:xs) = toLower x : xs
  181. uncapitalize "" = ""