rob (empty) → 0.0.1
raw patch · 18 files changed
+885/−0 lines, 18 filesdep +Globdep +ansi-terminaldep +basesetup-changed
Dependencies added: Glob, ansi-terminal, base, bytestring, cmdargs, directory, ede, filepath, fortytwo, pathwalk, rob, text, time, unordered-containers, vector, yaml
Files
- LICENSE +30/−0
- README.md +34/−0
- Setup.hs +2/−0
- app/Main.hs +29/−0
- rob.cabal +69/−0
- src/Rob/Actions/Add.hs +26/−0
- src/Rob/Actions/List.hs +22/−0
- src/Rob/Actions/New.hs +45/−0
- src/Rob/Actions/Remove.hs +32/−0
- src/Rob/Config.hs +68/−0
- src/Rob/Logger.hs +48/−0
- src/Rob/Package.hs +22/−0
- src/Rob/Project.hs +181/−0
- src/Rob/Questionnaire.hs +60/−0
- src/Rob/Tasks.hs +32/−0
- src/Rob/Types.hs +84/−0
- src/Rob/UserMessages.hs +99/−0
- test/Spec.hs +2/−0
+ LICENSE view
@@ -0,0 +1,30 @@+Copyright Gianluca Guarini (c) 2017++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++ * Redistributions of source code must retain the above copyright+ notice, this list of conditions and the following disclaimer.++ * Redistributions in binary form must reproduce the above+ copyright notice, this list of conditions and the following+ disclaimer in the documentation and/or other materials provided+ with the distribution.++ * Neither the name of Gianluca Guarini nor the names of other+ contributors may be used to endorse or promote products derived+ from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ README.md view
@@ -0,0 +1,34 @@+<p align="center">+ <img src="rob-logo.svg" alt="rob logo"/>+</p>++<p align="center">+Simple projects generator written in Haskell+</p>++---+> Call on me in the day of trouble, and I will deliver, and thou shalt glorify me.</p>+###### Daniel Defoe, Robinson Crusoe++# Installation++```shell+$ cabal install rob+```++# Usage++## TODO++### Available commands++```shell+$ rob --help+```++# Todo++- [ ] Unit test!+- [ ] Improve performance+- [ ] Write Documentation+- [ ] Refactor the code and cleanup
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ app/Main.hs view
@@ -0,0 +1,29 @@+module Main where++import System.Environment+import System.Exit+import System.Console.CmdArgs (cmdArgsRun)+import System.Console.CmdArgs.Explicit(helpText, HelpFormat(..))+import Rob.Types (Task(..))+import Rob.Tasks (mode)+import qualified Rob.Actions.Add+import qualified Rob.Actions.New+import qualified Rob.Actions.List+import qualified Rob.Actions.Remove++main :: IO()+main = do+ args <- getArgs+ if null args then exitWithHelp+ else parse =<< cmdArgsRun mode++parse :: Task -> IO ()+parse Add {name = n, path = p} = Rob.Actions.Add.main n p+parse New = Rob.Actions.New.main+parse List = Rob.Actions.List.main+parse Remove = Rob.Actions.Remove.main++exitWithHelp :: IO a+exitWithHelp = do+ putStr $ show $ helpText [] HelpFormatOne mode+ exitSuccess
+ rob.cabal view
@@ -0,0 +1,69 @@+name: rob+version:0.0.1+synopsis: Simple projects generator+description: See README at <https://github.com/GianlucaGuarini/rob/blob/develop/README.md>+homepage: https://github.com/gianlucaguarini/rob#readme+license: MIT+license-file: LICENSE+author: Gianluca Guarini+maintainer: gianluca.guarini@gmail.com+copyright: Gianlua Guarini+category: CLI+build-type: Simple+extra-source-files: README.md+cabal-version: >=1.10++library+ hs-source-dirs: src+ exposed-modules: Rob.Config,+ Rob.Types,+ Rob.Project,+ Rob.Tasks,+ Rob.UserMessages,+ Rob.Questionnaire,+ Rob.Logger,+ Rob.Package,+ Rob.Actions.New,+ Rob.Actions.Remove,+ Rob.Actions.List,+ Rob.Actions.Add+ build-depends: base >= 4.7 && < 5,+ fortytwo <= 1.0.2,+ directory <= 1.3.0.0,+ filepath <= 1.4.1.2,+ pathwalk <= 0.3.1.2,+ Glob <= 0.9.1,+ yaml <= 0.8.23.3,+ vector <= 0.12.0.1,+ ede == 0.2.8.7,+ text <= 1.2.2.2,+ unordered-containers <= 0.2.8.0,+ cmdargs >= 0.10.17 && <= 0.10.18,+ ansi-terminal <= 0.6.3.1,+ bytestring <= 0.10.8.1,+ time <= 1.6.0.1+ other-modules: Paths_rob+ default-language: Haskell2010++executable rob+ hs-source-dirs: app+ main-is: Main.hs+ build-depends: base >= 4.7 && < 5,+ cmdargs >= 0.10.17 && <= 0.10.18+ build-depends: base,+ rob+ default-language: Haskell2010++test-suite rob-test+ type: exitcode-stdio-1.0+ hs-source-dirs: test+ main-is: Spec.hs+ build-depends: base,+ directory,+ rob+ ghc-options: -threaded -rtsopts -with-rtsopts=-N+ default-language: Haskell2010++source-repository head+ type: git+ location: https://github.com/gianlucaguarini/rob
+ src/Rob/Actions/Add.hs view
@@ -0,0 +1,26 @@+module Rob.Actions.Add (main) where++import Rob.Logger (err, success)+import Rob.Config (get, addTemplate)+import Rob.Project (hasPathQuestionnaire)+import Rob.UserMessages (projectPathDoesNotExist, projectQuestionnaireMissing, projectAdded)++import System.Exit (exitFailure, exitSuccess)+import System.Directory (doesPathExist)++main :: String -> FilePath -> IO()+main name path = do+ hasProjectPath <- doesPathExist path+ if hasProjectPath then do+ hasQuestionnaire <- hasPathQuestionnaire path+ if hasQuestionnaire then do+ config <- get+ _ <- addTemplate config name path+ success $ projectAdded name+ exitSuccess+ else do+ err $ projectQuestionnaireMissing path+ exitFailure+ else do+ err $ projectPathDoesNotExist path+ exitFailure
+ src/Rob/Actions/List.hs view
@@ -0,0 +1,22 @@+module Rob.Actions.List (main) where++import Rob.Logger (info)+import Rob.UserMessages (availableTemplates, emptyString)+import Rob.Config (get, errorNoTemplatesAvailable)+import Rob.Types(Config(..))++import System.Exit (exitSuccess)++main :: IO()+main = do+ config <- get+ putStrLn emptyString+ info availableTemplates+ putStrLn emptyString+ listTemplates config+ exitSuccess++-- | List all the templates as string+listTemplates :: Config -> IO ()+listTemplates (Config []) = errorNoTemplatesAvailable;+listTemplates (Config templates) = putStrLn $ unlines $ map show templates
+ src/Rob/Actions/New.hs view
@@ -0,0 +1,45 @@+module Rob.Actions.New (main) where++import Rob.Logger (err, success)+import Rob.Config (get, errorNoTemplatesAvailable)+import Rob.Types (Config(..))+import Rob.Questionnaire (run)+import Rob.Project (getTemplatePathByName, getTemplateName, createFilesFromTemplate)+import Rob.UserMessages (+ choseATemplate,+ noTemplateSelected,+ projectSuccessfullyCreated,+ projectPathDoesNotExist,+ emptyString+ )++import System.Exit (exitFailure, exitSuccess)+import System.Directory (doesPathExist)+import FortyTwo (select)++main :: IO ()+main = do+ config <- get+ createNewProject config++-- | Create a new project using one of the templates available+createNewProject :: Config -> IO ()+createNewProject (Config []) = errorNoTemplatesAvailable+createNewProject (Config templates) = do+ templateName <- select choseATemplate $ map getTemplateName templates+ putStrLn emptyString+ if not . null $ templateName then do+ let path = getTemplatePathByName templates templateName+ hasProjectPath <- doesPathExist path+ if hasProjectPath then do+ responses <- run path+ putStrLn emptyString+ createFilesFromTemplate path responses+ success projectSuccessfullyCreated+ exitSuccess+ else do+ err $ projectPathDoesNotExist path+ exitFailure+ else do+ err noTemplateSelected+ exitFailure
+ src/Rob/Actions/Remove.hs view
@@ -0,0 +1,32 @@+module Rob.Actions.Remove (main) where++import Rob.Logger (err)+import Rob.Config (get, errorNoTemplatesAvailable, deleteTemplate)+import Rob.Types (Config(..))+import Rob.Project (getTemplateName)+import Rob.UserMessages (+ choseATemplateToDelete,+ noTemplateSelected,+ emptyString+ )++import System.Exit (exitFailure, exitSuccess)+import FortyTwo (select)++main :: IO ()+main = do+ config <- get+ nukeTemplate config++-- Remove a project template from the available ones+nukeTemplate :: Config -> IO ()+nukeTemplate (Config []) = errorNoTemplatesAvailable+nukeTemplate (Config templates) = do+ templateName <- select choseATemplateToDelete $ map getTemplateName templates+ putStrLn emptyString+ if (not . null) templateName then do+ _ <- deleteTemplate (Config templates) templateName+ exitSuccess+ else do+ err noTemplateSelected+ exitFailure
+ src/Rob/Config.hs view
@@ -0,0 +1,68 @@+module Rob.Config where++import qualified Rob.Package as Package+import Rob.Logger (err, warning, success, info, flatten)+import Rob.UserMessages (configFileFound, noConfigFileFound, configFileCreated, noTemplatesAvailable, tryAddingATemplate)+import Rob.Types (Config(..), Template(..))++import Data.Maybe+import System.Exit+import System.FilePath (joinPath)+import System.Directory (getHomeDirectory, doesFileExist)+import Data.Yaml (encodeFile, decodeFile)++-- | Get the config file name+configFileName :: String+configFileName = "." ++ Package.name++-- | Get the whole path to the config file+configFilePath :: IO FilePath+configFilePath = do+ home <- getHomeDirectory+ return $ joinPath [home, configFileName]++-- | Write the config file and return it+write :: Config -> IO Config+write config = do+ configFilePath >>= \path -> encodeFile path config+ return config++-- | Get the current Config file Data+-- | If it doesn't exist it will create a new one+get :: IO Config+get = do+ path <- configFilePath+ hasConfigPath <- doesFileExist path+ if hasConfigPath+ then do+ success $ configFileFound path+ config <- decodeFile path+ return $ fromJust config+ else do+ warning $ noConfigFileFound configFileName+ flatten info $ configFileCreated path+ -- return an empty Config object and write it in the home directory+ write $ Config []++-- | Dispatch the no templates available error+errorNoTemplatesAvailable :: IO ()+errorNoTemplatesAvailable = do+ err noTemplatesAvailable+ warning tryAddingATemplate+ exitFailure++-- | Add a new template to the config object and write it+addTemplate :: Config -> String -> String -> IO Config+addTemplate (Config templates) name path = write $ Config newTemplates+ where+ newTemplate = Template name path+ newTemplates = if newTemplate `elem` templates then+ map (\t -> if t == newTemplate then newTemplate else t) templates+ else+ templates ++ [newTemplate]++-- | Delete a template from the list of templates+deleteTemplate :: Config -> String -> IO Config+deleteTemplate (Config templates) nameToRemove = write $ Config newTemplates+ where+ newTemplates = (\(Template name _) -> name /= nameToRemove) `filter` templates
+ src/Rob/Logger.hs view
@@ -0,0 +1,48 @@+module Rob.Logger where++import System.Console.ANSI+import Data.Time.Clock (getCurrentTime, UTCTime)+import Data.Time.Format (formatTime, defaultTimeLocale)++-- | Print a message highlighting it+log' :: Color -> ColorIntensity -> String -> IO()+log' color intensity message = do+ setSGR [SetColor Foreground Dull White]+ time <- getCurrentTime+ print' $ formatTime' time+ setSGR [Reset]+ setSGR [SetColor Foreground intensity color]+ print' $ message ++ "\n"+ setSGR [Reset]++-- | Format the time, showing it into the log message+formatTime' :: UTCTime -> String+formatTime' = formatTime defaultTimeLocale "[%T] "++-- | Print a message in the console+print' :: String -> IO()+print' = putStr++-- | Log success messages+success :: String -> IO()+success = log' Green Dull++-- | Log error messages+err :: String -> IO()+err = log' Red Vivid++-- | Log info messages+info :: String -> IO()+info = log' Cyan Dull++-- | Log warning messages+warning :: String -> IO()+warning = log' Yellow Dull++-- | Log a raw message without using colors+raw :: String -> IO()+raw = print'++-- | Execute log on a list of messages+flatten :: Monad m => (a -> m b) -> [a] -> m ()+flatten = mapM_
+ src/Rob/Package.hs view
@@ -0,0 +1,22 @@+module Rob.Package where++-- TODO: get these props from the .cabal file++import qualified Paths_rob+import Data.Version (showVersion)++-- | Return the Package version (stored in the .cabal file)+version :: String+version = showVersion Paths_rob.version++-- | Return the package name+name :: String+name = "rob"++-- | Return the package author+author :: String+author = "Author: Gianluca Guarini"++-- | Return the package description+description :: String+description = "Projects generator"
+ src/Rob/Project.hs view
@@ -0,0 +1,181 @@+{-# LANGUAGE OverloadedStrings #-}++module Rob.Project where++import Rob.Types (Template(..))+import Rob.Logger (warning, success)+import Rob.UserMessages (parserError, fileCreated)++import Data.Yaml (Value)+import Control.Monad (forM_, unless)+import Data.Text (Text)+import qualified Data.ByteString.Lazy as BS+import Data.Text.Lazy.Encoding (encodeUtf8)+import System.FilePath.Posix (makeRelative)+import Data.List (any, nub, intercalate)+import System.FilePath.Glob (match, simplify, compile, Pattern)+import Text.EDE (eitherRender, eitherParseFile, fromPairs)+import System.Directory.PathWalk (pathWalkInterruptible, WalkStatus(..))+import System.Directory (doesFileExist, createDirectoryIfMissing)+import System.FilePath (joinPath, takeDirectory, normalise, isDrive, isValid, pathSeparator)++-- | Get only the template name+getTemplateName :: Template -> String+getTemplateName (Template name _) = name++-- | Get only the template path+getTemplatePath :: Template -> FilePath+getTemplatePath (Template _ path) = path++-- | Get the template name by its path+getTemplatePathByName :: [Template] -> String -> FilePath+getTemplatePathByName [] [] = ""+getTemplatePathByName [] _ = ""+getTemplatePathByName (x:xs) name =+ if templateName == name then+ templatePath+ else+ getTemplatePathByName xs name+ where+ templateName = getTemplateName x+ templatePath = getTemplatePath x++-- | File where users can store all the project creation questions+projectDataFile :: String+projectDataFile = "project.yml"++ignoreFiles :: [FilePath]+ignoreFiles = [".gitignore", "svnignore.txt"]++knownIgnoredFiles :: [Pattern]+knownIgnoredFiles = globbifyList [".git", ".svn", projectDataFile]++-- | Check if the path contains the questionnaire file+hasPathQuestionnaire :: FilePath -> IO Bool+hasPathQuestionnaire = doesFileExist . questionnaireFileByPath++-- | Get the questionnaire file by project path+questionnaireFileByPath :: FilePath -> FilePath+questionnaireFileByPath path = joinPath [path, projectDataFile]++-- | Start the template creation+createFilesFromTemplate :: FilePath -> [(Text, Value)] -> IO ()+createFilesFromTemplate root = walk knownIgnoredFiles root ""++-- | Walk recursively a folder copying its files using+walk :: [Pattern] -> FilePath -> FilePath -> [(Text, Value)] -> IO ()+walk currentBlacklist templateRoot currentPath responses =+ pathWalkInterruptible absolutePath $ \_ dirs files -> do+ blacklist <- getBlacklist+ render templateRoot relativePath files blacklist responses+ mapM_ (\f ->+ walk blacklist templateRoot (joinPath [relativePath, f]) responses+ )+ (whitelist dirs blacklist)+ return StopRecursing+ where+ absolutePath = joinPath [templateRoot, currentPath]+ relativePath = makeRelative templateRoot currentPath+ getBlacklist = do+ newBlacklist <- populateBlacklist absolutePath+ return $ currentBlacklist ++ newBlacklist+ whitelist dirs blacklist = [+ f | f <- dirs,+ not $ isInBlacklist f blacklist+ ]++-- | Add eventually new ignored files to the blacklist map+populateBlacklist :: FilePath -> IO [Pattern]+populateBlacklist root = getIgnoredPatterns (map (\f -> joinPath [root, f]) ignoreFiles)++-- | Parse a directory copying the files found into the current one+-- | where rob was called, it will also render eventually the answers to the questionnaire+-- | if template token will be found in any of the files+render :: FilePath -> FilePath -> [FilePath] -> [Pattern] -> [(Text, Value)] -> IO()+render templateRoot path files blacklist responses = do+ createDirectoryIfMissing True path+ forM_ files $ \file ->+ unless (isInBlacklist file blacklist) $ do+ let fileAbsolutePath = joinPath [templateRoot, path, file]+ fileRelativePath = joinPath [path, file]+ success $ fileCreated fileRelativePath+ template <- eitherParseFile fileAbsolutePath+ case template of+ Right t ->+ case eitherRender t templateData of+ Right res -> BS.writeFile fileRelativePath (encodeUtf8 res)+ Left e -> fallback e fileAbsolutePath fileRelativePath+ Left e -> fallback e fileAbsolutePath fileRelativePath+ where+ templateData = fromPairs responses+ fallback e inPath outPath = do+ warning parserError+ putStrLn e+ file <- BS.readFile inPath+ BS.writeFile outPath file++-- | Check whether a path is blacklisted looking it up in the blacklist map+-- | here basically we try to emulate the gitignore behavior recursively+isInBlacklist :: FilePath -> [Pattern] -> Bool+isInBlacklist path = any (`match` path)++-- | Get all the files to ignore uniquelly from a list of known .ignore files+getIgnoredPatterns :: [FilePath] -> IO [Pattern]+getIgnoredPatterns files = do+ res <- mapM findIgnoredFilesList files+ return $ nub $ intercalate [] res++-- | Figure out which files must be ignored reading them from the .gitignore+findIgnoredFilesList :: FilePath -> IO [Pattern]+findIgnoredFilesList f = do+ hasFile <- doesFileExist f+ if hasFile then do+ file <- readFile f+ return $ (+ globbifyList .+ extendIgnoredFiles .+ removeSeparatorPrefix .+ cleanList+ ) $ lines file+ else return []+ where+ cleanList list = (\l -> (not . null) l && head l /= '#') `filter` list++-- | Remove the initial separator prefix+removeSeparatorPrefix :: [FilePath] -> [FilePath]+removeSeparatorPrefix = map $ \p -> if head p == pathSeparator then tail p else p+++-- | Extend the ignored files in order to enhance the patterns matching+-- | for example with the "/node_modules/*" pattern we will add also "/node_modules"+-- | to the excluded folders+extendIgnoredFiles :: [FilePath] -> [FilePath]+extendIgnoredFiles (x:xs) =+ if (not . null) extension then+ x : extension : extendIgnoredFiles xs+ else x : extendIgnoredFiles xs+ where+ extension = if all (\t -> t dirName) tests then dirName else []+ dirName = (takeDirectory . normalise) x+ tests = [+ isValid,+ not . isDrive,+ not . isDot,+ not . isWildCard,+ not . isDoubleWildCard+ ]+extendIgnoredFiles [] = []++-- | Helpers to enhance the ignored files+isDot :: FilePath -> Bool+isDot = (==) "."++isWildCard :: FilePath -> Bool+isWildCard = (==) "*"++isDoubleWildCard :: FilePath -> Bool+isDoubleWildCard = (==) "**"++-- | Map a list of file paths to glob patterns+globbifyList :: [FilePath] -> [Pattern]+globbifyList = map (simplify . compile)
+ src/Rob/Questionnaire.hs view
@@ -0,0 +1,60 @@+{-# LANGUAGE NamedFieldPuns #-}+module Rob.Questionnaire where+import Rob.Types+import Rob.Project (questionnaireFileByPath, hasPathQuestionnaire)+import Rob.UserMessages (unableToParseQuestionnaire, projectQuestionnaireMissing)+import qualified Rob.Logger as Logger++import System.Exit (exitFailure)+import FortyTwo+import Data.Yaml+import Data.Text (Text, pack)+import Data.HashMap.Strict (toList)+import qualified Data.Vector as V++-- | Get only the questions out of a questionnaier data struct as list+getQuestions :: Questionnaire -> [(Text, Question)]+getQuestions Questionnaire { questions } = toList questions++-- | Run the questionnaire+run :: FilePath -> IO [(Text, Value)]+run path = do+ hasQuestionnaireFile <- hasPathQuestionnaire path+ if hasQuestionnaireFile then do+ questionnaire <- decodeFileEither questionnaireFile+ case questionnaire of+ Right q -> mapM mapQuestion (getQuestions q)+ Left e -> do+ Logger.err unableToParseQuestionnaire+ Logger.warning $ show e+ exitFailure+ else do+ Logger.err $ projectQuestionnaireMissing path+ exitFailure++ where+ questionnaireFile = questionnaireFileByPath path++-- Map all the questions to answers+mapQuestion :: (Text, Question) -> IO (Text, Value)+mapQuestion (key, q) = do+ answer <- ask q+ return (key, answer)++-- Ask a Question to get a response from the user+ask :: Question -> IO Value+ask (PasswordQuestion question) = do+ res <- password question+ return (String $ pack res)+ask (SimpleQuestion question defaultValue) = do+ res <- inputWithDefault question defaultValue+ return (String $ pack res)+ask (SelectQuestion question answers defaultValue) = do+ res <- selectWithDefault question answers defaultValue+ return (String $ pack res)+ask (ConfirmQuestion question defaultValue) = do+ res <- confirmWithDefault question defaultValue+ return (Bool res)+ask (MultiselectQuestion question answers defaultValues) = do+ res <- multiselectWithDefault question answers defaultValues+ return (Array $ V.fromList $ map (String . pack) res)
+ src/Rob/Tasks.hs view
@@ -0,0 +1,32 @@+module Rob.Tasks where++import qualified Rob.Package as Package+import Rob.UserMessages (newTaskHelp, addTaskHelp, listTaskHelp, removeTaskHelp)+import qualified Rob.Types as Types (Task(..))++import System.Console.CmdArgs++list :: Types.Task+list = Types.List &= help listTaskHelp++-- | New task factory function+new :: Types.Task+new = Types.New &= help newTaskHelp++-- | New task factory function+remove :: Types.Task+remove = Types.Remove &= help removeTaskHelp++-- | Add task factory function+add :: Types.Task+add = Types.Add {+ Types.name = def &= typ "template-name" &= argPos 0,+ Types.path = def &= typ "path/to/the/template/folder" &= argPos 1+ } &= help addTaskHelp++-- | Export all the command line modes+mode :: Mode (CmdArgs Types.Task)+mode = cmdArgsMode $ modes [new, add, remove, list]+ &= help Package.description+ &= program Package.name+ &= summary (unwords [Package.name, "- v", Package.version, Package.author])
+ src/Rob/Types.hs view
@@ -0,0 +1,84 @@+{-# LANGUAGE DeriveGeneric, DuplicateRecordFields, DeriveDataTypeable, OverloadedStrings #-}++module Rob.Types where++import GHC.Generics (Generic(..))+import Data.HashMap.Strict (HashMap)+import System.FilePath.Glob (Pattern)+import Data.Text (Text)+import Data.Yaml+import System.Console.CmdArgs++-- | Task struct listing all the available actions+data Task+ = Add {+ name :: String,+ path :: FilePath+ }+ | List+ | Remove+ | New deriving (Data, Typeable, Show)++-- | Template name + path+data Template = Template String FilePath deriving (Generic)++instance Show Template where+ show (Template name path) = unlines [+ "- Name: " ++ name,+ " Path: " ++ path+ ]++instance Eq Template where+ (Template nameA _) == (Template nameB _) = nameA == nameB++instance FromJSON Template+instance ToJSON Template++-- | Config file struct+newtype Config = Config {+ templates :: [Template]+} deriving (Generic, Show, Eq)++instance FromJSON Config+instance ToJSON Config++-- | Question struct+data Question =+ SimpleQuestion String String+ | PasswordQuestion String+ | ConfirmQuestion String Bool+ | SelectQuestion String [String] String+ | MultiselectQuestion String [String] [String]+ deriving (Show, Eq)++instance FromJSON Question where+ parseJSON (Object v) = do+ questionType <- v .:? "type" .!= "simple"+ parseJSON (Object v) >>= parseQuestion questionType++parseQuestion :: String -> Value -> Parser Question+parseQuestion questionType =+ case questionType of+ "confirm" -> withObject "ConfirmQuestion"+ (\o -> ConfirmQuestion <$> o .: questionKey <*> o .:? defaultKey .!= False)+ "select" -> withObject "SelectQuestion"+ (\o -> SelectQuestion <$> o .: questionKey <*> o .:? answersKey .!= [] <*> o .:? defaultKey .!= "")+ "multiselect" -> withObject "MultiselectQuestion"+ (\o -> MultiselectQuestion <$> o .: questionKey <*> o .:? answersKey .!= [] <*> o .:? defaultKey .!= [])+ "password" -> withObject "Password"+ (\o -> PasswordQuestion <$> o .: questionKey)+ _ -> withObject "SimpleQuestion"+ (\o -> SimpleQuestion <$> o .: questionKey <*> o .:? defaultKey .!= "")+ where+ questionKey = "question"+ answersKey = "answers"+ defaultKey = "default"++-- | Questionnaiere struct+newtype Questionnaire = Questionnaire {+ questions :: HashMap Text Question+} deriving (Generic, Show, Eq)++instance FromJSON Questionnaire++type Blacklist = [(FilePath, [Pattern])]
+ src/Rob/UserMessages.hs view
@@ -0,0 +1,99 @@+module Rob.UserMessages where++import qualified Rob.Package as Package++-- Questions messages++choseATemplate :: String+choseATemplate = "Which template do you want to use?"++choseATemplateToDelete :: String+choseATemplateToDelete = "Which template do you want to delete?"++-- Config messages++configFileFound :: FilePath -> String+configFileFound path = unwords [path, "was found!"]++noConfigFileFound :: String -> String+noConfigFileFound configFileName = unwords [+ "No",+ configFileName,+ "file was found in your $HOME path"+ ]++configFileCreated :: FilePath -> [String]+configFileCreated path = [+ "Creating a new config file in:",+ path+ ]++-- Tasks help++newTaskHelp :: String+newTaskHelp = "Create a new project in the current folder"++addTaskHelp :: String+addTaskHelp = "Add a new project template"++listTaskHelp :: String+listTaskHelp = "List all the available projects templates"++removeTaskHelp :: String+removeTaskHelp = "Remove a template from the list of the ones available"++-- Errors++noTemplatesAvailable :: String+noTemplatesAvailable = "It was not possible to find any project template. Have you ever tried adding one?"++noTemplateSelected :: String+noTemplateSelected = "No template selected. Please select one the list"++tryAddingATemplate :: String+tryAddingATemplate = "Try: " ++ Package.name ++ " " ++ "new template-name path/to/the/template/folder"++unableToParseQuestionnaire :: String+unableToParseQuestionnaire = "It was not able to parse the project questionnaire"+++projectPathDoesNotExist :: FilePath -> String+projectPathDoesNotExist path = unwords [+ "The path to",+ "\"" ++ path ++ "\"",+ "does not exist!"+ ]++projectQuestionnaireMissing :: FilePath -> String+projectQuestionnaireMissing path = unwords [+ "It was not possible to find any project.yml in",+ "\"" ++ path ++ "\"",+ "Please make sure to have a questionnaire project.yml in your template!"+ ]++projectAdded :: String -> String+projectAdded name = unwords [+ name,+ "was added to your projects templates!"+ ]++parserError :: String+parserError = "There was a parser error, this file will only be copied"++-- General info++availableTemplates :: String+availableTemplates = "Available Templates:"++-- Success messages++projectSuccessfullyCreated :: String+projectSuccessfullyCreated = "Your project was successfully created"++fileCreated :: String -> String+fileCreated file = unwords ["Create:", file]++-- Helpers++emptyString :: String+emptyString = ""
+ test/Spec.hs view
@@ -0,0 +1,2 @@+main :: IO ()+main = putStrLn "Test suite not yet implemented"