+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE TemplateHaskell #-}
+{-# LANGUAGE DuplicateRecordFields #-}
+
module Main where
-import Data.List (intercalate)
+import Data.List (intercalate, zipWith)
import Data.String hiding (unlines)
-import Data.Text hiding (map, init, length, splitAt)
+import Data.Text hiding (map, init, length, splitAt, foldl, zipWith)
import Data.Text.IO (putStrLn, putStr, writeFile)
import Data.Text.Lazy (toStrict)
import Data.Text.Template
import Data.Time.Clock.POSIX
import Lib
-import Prelude hiding (putStrLn, putStr, init, unlines, writeFile)
+import Prelude hiding (putStrLn, putStr, init, unlines, writeFile, zipWith)
import Prompts
import System.Directory
import System.Directory (doesFileExist)
import System.Exit
import System.File.Tree hiding (map, mapM, mapM_)
import System.Process
-import Text.JSON
-import Text.JSON.Generic
+import Data.Aeson
+import Data.Aeson.TH
+import Data.Aeson.Text
+import Data.Maybe
import Util
import Data.Bool (bool)
+import Data.Tuple.Curry (uncurryN)
+import qualified Data.ByteString.Lazy as BL
+import Config
main :: IO ()
main = getArgs >>= parse
getGlobalProjectDir :: IO FilePath
getGlobalProjectDir = getHomeDirectory >>= return . (++ "/.xyz")
-getGlobalProjectConfig :: IO FilePath
-getGlobalProjectConfig = getGlobalProjectDir >>= return . (++ "/xyz.json")
+getGlobalProjectConfigPath :: IO FilePath
+getGlobalProjectConfigPath = getGlobalProjectDir >>= return . (++ "/xyz.json")
+
+getGlobalProjectConfig :: IO (Maybe GlobalConfig)
+getGlobalProjectConfig = getGlobalProjectConfigPath >>= BL.readFile >>= return . decode
+
+getLocalProjectConfig :: IO (Maybe LocalConfig)
+getLocalProjectConfig = BL.readFile "xyz.json" >>= return . decode
+
+resolveProjectConfig :: IO (Maybe ProjectConfig)
+resolveProjectConfig = do
+ l <- getLocalProjectConfig
+ g <- getGlobalProjectConfig
+
+ return $ case (l, g) of
+ (Just l, Just g) -> Just $ getProjectConfig g l
+ (_, _) -> Nothing
continueIfValidProject :: IO () -> IO ()
-continueIfValidProject next = doesFileExist "xyz.json" >>= bool (putStrLn "This does not appear to be a valid project directory") next
+continueIfValidProject next = doesFileExist "xyz.json" >>= bool (putStrLn "This does not appear to be a valid project directory") next
+
+continueIfInvalidProject :: IO () -> IO ()
+continueIfInvalidProject next = doesFileExist "xyz.json" >>= bool next (putStrLn "Project already initialised")
initGlobalIfNeeded :: IO () -> IO ()
-initGlobalIfNeeded next = getGlobalProjectConfig >>= doesFileExist >>= bool globalInit (return ()) >> next
+initGlobalIfNeeded next = getGlobalProjectConfigPath >>= doesFileExist >>= bool globalInit (return ()) >> next
globalInit :: IO ()
globalInit = do
home <- getHomeDirectory
putStrLn "\nWelcome to xyz! Looks like this is your first time here. Let's configure a few things...\n"
editor <- editorPrompt
- writeFile (home ++ "/.xyz/xyz.json") (pack . encode . toJSObject $ [("editor", editor)])
+ vcs <- vcsPrompt -- >>= (\x -> return $ if x then Just "git add . && git commit -m \"$title\"" else Nothing)
+ let pushCmdAndRemote = case vcs of
+ Just (True, x) -> [("pushCommand", "git add . && git commit -m \"$title\""), ("remote", x)]
+ Nothing -> []
+
+ getGlobalProjectConfigPath >>= flip writeFile (toStrict . encodeToLazyText $ GlobalConfig editor (lookup "pushCommand" pushCmdAndRemote) (lookup "remote" pushCmdAndRemote))
putStrLn "\nThanks! Now let's continue with initialising your project...\n"
init :: IO ()
init = do
- dirExists <- doesFileExist "xyz.json"
- if dirExists
- then putStrLn "Project already initialised"
- else do home <- getHomeDirectory
- themes <- allFilesIn $ home ++ "/.xyz/themes"
- projectSpec <- initPrompt (map pack themes)
- createDirectory "entries"
- writeFile "xyz.json" . pack . encode . toJSObject $ projectSpec
- putStrLn "\nInitialised empty project"
+ home <- getHomeDirectory
+ themes <- allFilesIn $ home ++ "/.xyz/themes"
+ projectSpec <- (initPrompt (map pack themes)) >>= return . fmap snd >>= (\[x,y,z] -> return (x, y, z, Nothing, Nothing, Nothing))
+ let localConfig = uncurryN LocalConfig projectSpec
+ writeFile "xyz.json" (toStrict . encodeToLazyText $ localConfig)
+ config <- resolveProjectConfig
+
+ case config of
+ Just projectConfig -> do
+ createDirectory "entries"
+
+ case (commitCommand (projectConfig :: ProjectConfig), gitRemote (projectConfig :: ProjectConfig)) of
+ (Just cmd, Just remote) -> do
+ mapM_ (callCommand . unpack . ((+++) " > /dev/unll")) ["git init", "git remote add origin " +++ remote, "git add . && git commit -m \"Initial commit\""]
+ (_, _) -> return()
+
+ putStrLn "\nInitialised empty project"
+ Nothing -> do
+ putStrLn "\nSomething went real wrong"
build :: IO ()
build = do
- fileExists <- doesFileExist "xyz.json"
- if fileExists
- then do
- files <- allFilesIn "entries"
- home <- getHomeDirectory
- config <- readFile "xyz.json"
- writeOutProject (decodeJSON config :: ProjectConfig) files >> putStrLn "Build successful"
- else putStrLn "This does not appear to be a valid project directory"
+ files <- allFilesIn "entries"
+ config <- resolveProjectConfig
+
+ case config of
+ Just c -> writeOutProject c files >> putStrLn "Build successful"
+ _ -> putStrLn "Busted"
entry :: IO ()
entry = do
+ config <- resolveProjectConfig
homeDir <- getHomeDirectory
entrySpec <- entryPrompt
entryNum <- fmap (show . (+1) . length) $ allFilesIn "entries"
True -> do callCommand . unpack $ "git add . && git commit -m \"" +++ (fst rawEntry) +++ "\" && git push"
False -> return ()
- putStrLn "Thanks for using xyz!"
+ putStrLn "\nThanks for using xyz!"
+
+config :: IO ()
+config = do
+ config <- resolveProjectConfig
+ case config of
+ Just (ProjectConfig {
+ name=n,
+ description=d,
+ theme=t,
+ editor=e,
+ commitCommand=cc
+ }) -> (putStrLn "\nProject details:\n") >> (putStr . unlines $ zipWith (+++) ["Name:\t\t", "Description:\t", "Theme:\t\t", "Editor:\t\t", "Commit command:\t"] [n, d, t, e, ccc]) where
+ ccc = case cc of
+ Just g -> g
+ Nothing -> "Not set"
+ Nothing -> putStrLn "broken"
usage :: IO ()
usage = putStr . ununlines $ [
]
]
-parse ["init"] = initGlobalIfNeeded init >> exitSuccess
+parse ["init"] = initGlobalIfNeeded (continueIfInvalidProject init) >> exitSuccess
parse ["build"] = continueIfValidProject build >> exitSuccess
parse ["entry"] = continueIfValidProject entry >> exitSuccess
+parse ["config"] = continueIfValidProject config >> exitSuccess
parse [_] = usage >> exit
parse [] = usage >> exit
--- /dev/null
+{-# LANGUAGE DeriveDataTypeable #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE DuplicateRecordFields #-}
+{-# LANGUAGE DeriveGeneric #-}
+
+module Config where
+
+import GHC.Generics
+import Data.Aeson
+import Data.Aeson.Types
+import Data.Text hiding (map, init, length, splitAt, zip)
+import Data.Text.Lazy (toStrict)
+import Data.Aeson
+import Data.Aeson.TH
+import Data.Aeson.Text
+
+data GlobalConfig = GlobalConfig {
+ editor :: Text,
+ commitCommand :: Maybe Text,
+ gitRemote :: Maybe Text
+ } deriving (Show, Generic)
+
+data LocalConfig = LocalConfig {
+ name :: Text,
+ description :: Text,
+ theme :: Text,
+ editor :: Maybe Text,
+ commitCommand :: Maybe Text,
+ gitRemote :: Maybe Text
+ } deriving (Show, Generic)
+
+data ProjectConfig = ProjectConfig {
+ name :: Text,
+ description :: Text,
+ theme :: Text,
+ editor :: Text,
+ commitCommand :: Maybe Text,
+ gitRemote :: Maybe Text
+ } deriving (Show, Generic)
+
+instance ToJSON GlobalConfig where
+ toJSON = genericToJSON defaultOptions
+ { omitNothingFields = False }
+
+instance FromJSON GlobalConfig where
+ parseJSON = genericParseJSON defaultOptions
+ { omitNothingFields = False }
+
+instance ToJSON LocalConfig where
+ toJSON = genericToJSON defaultOptions
+ { omitNothingFields = True }
+
+instance FromJSON LocalConfig where
+ parseJSON = genericParseJSON defaultOptions
+ { omitNothingFields = True }
+
+maybeTrump :: LocalConfig -> GlobalConfig -> (LocalConfig -> Maybe a) -> (GlobalConfig -> Maybe a) -> Maybe a
+maybeTrump l g lf gf = case lf l of
+ Just x -> Just x
+ Nothing -> gf g
+
+trump :: LocalConfig -> GlobalConfig -> (LocalConfig -> Maybe a) -> (GlobalConfig -> a) -> a
+trump l g lf gf = case lf l of
+ Just x -> x
+ Nothing -> gf g
+
+getProjectConfig :: GlobalConfig -> LocalConfig -> ProjectConfig
+getProjectConfig g l = ProjectConfig
+ (name (l :: LocalConfig))
+ (description (l :: LocalConfig))
+ (theme (l :: LocalConfig))
+ (trumper editor editor)
+ (maybeTrumper commitCommand commitCommand)
+ (maybeTrumper gitRemote gitRemote)
+ where
+ trumper = trump l g
+ maybeTrumper = maybeTrump l g
-{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE DuplicateRecordFields #-}
import System.FilePath.Posix (takeBaseName, takeDirectory)
import Data.Data
import Data.Typeable
+import Config
data Theme = Theme {
- name :: String, -- works better with getHomeDirectory
- templates :: [(String, Template)]
- } deriving (Show)
-
-data ProjectConfig = ProjectConfig {
- name :: Text,
- description :: Text,
- theme :: String -- as above
- } deriving (Show, Data, Typeable)
+ name :: Text, -- works better with getHomeDirectory
+ templates :: [(Text, Template)]
+ } deriving (Show)
data Project = Project {
name :: Text,
entryList <- mapM (\raw -> return $ rawTextToEntry raw) rawEntries
homeDir <- getHomeDirectory
- let themeName = theme (projectConfig :: ProjectConfig)
+ let themeName = unpack $ theme (projectConfig :: ProjectConfig)
themeTemplatePaths <- allFilesIn $ homeDir ++ "/.xyz/themes/" ++ themeName
- themeTemplates <- mapM (\x -> (readFile $ homeDir ++ "/.xyz/themes/" ++ themeName ++ "/" ++ x) >>= (\y -> return (x, (template . fromString) y))) themeTemplatePaths
+ themeTemplates <- mapM (\x -> (readFile $ homeDir ++ "/.xyz/themes/" ++ themeName ++ "/" ++ x) >>= (\y -> return (pack x, (template . fromString) y))) themeTemplatePaths
let projectName = name (projectConfig :: ProjectConfig)
let projectDescription = description (projectConfig :: ProjectConfig)
- let project = Project projectName projectDescription (Theme themeName themeTemplates) (stampsToEntryList entryList)
+ let project = Project projectName projectDescription (Theme (pack themeName) themeTemplates) (stampsToEntryList entryList)
mapM_ (createDirectoryIfMissing True . takeDirectory . unpack . fst) $ getEntryPages project
mapM_ (\x -> writeFile (unpack $ fst x) (unpack $ snd x)) $ getEntryPages project