diff --git a/app/Main.hs b/app/Main.hs index 5768f80..40aacaa 100644 --- a/app/Main.hs +++ b/app/Main.hs @@ -1,9 +1,12 @@ -{-# LANGUAGE DataKinds #-} -{-# LANGUAGE MultiWayIf #-} -{-# LANGUAGE OverloadedLabels #-} -{-# LANGUAGE OverloadedStrings #-} -{-# LANGUAGE PolyKinds #-} -{-# LANGUAGE TypeOperators #-} +{-# LANGUAGE DataKinds #-} +{-# LANGUAGE FlexibleInstances #-} +{-# LANGUAGE LambdaCase #-} +{-# LANGUAGE MultiParamTypeClasses #-} +{-# LANGUAGE OverloadedLabels #-} +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE PolyKinds #-} +{-# LANGUAGE TypeOperators #-} +{-# OPTIONS_GHC -fno-warn-orphans #-} module Main where @@ -23,44 +26,29 @@ import qualified Git import Mix import Mix.Plugin.Logger as MixLogger import qualified Mix.Plugin.Shell as MixShell +import Options.Magic import qualified ScrapBook import System.Cron (addJob, execSchedule) import qualified Version main :: IO () -main = withGetOpt' "[options] [input-file]" opts $ \r args usage -> - if | r ^. #help -> hPutBuilder stdout (fromString usage) - | r ^. #version -> hPutBuilder stdout (Version.build version) - | r ^. #daily -> runCmd r (listToMaybe args) `withCron` "0 8 * * *" - | r ^. #hourly -> runCmd r (listToMaybe args) `withCron` "0 * * * *" - | r ^. #minutely -> runCmd r (listToMaybe args) `withCron` "* * * * *" - | otherwise -> runCmd r (listToMaybe args) +main = withGetOpt' "[options] [input-file]" opts $ \r args -> + let cmdEnchantments = shrink r :: Record CmdEnchantments + defaultCmd = Cmd $ \_ -> runCmd r (listToMaybe args) + in exec (peel $ Continue defaultCmd `withEnchantment` cmdEnchantments) where opts = #help @= helpOpt <: #version @= versionOpt - <: #verbose @= verboseOpt <: #daily @= dailyOpt <: #hourly @= hourlyOpt <: #minutely @= minutelyOpt + <: #verbose @= verboseOpt <: #skip @= skipOpt <: #withCopy @= withCopyOpt <: #withCommit @= withCommitOpt <: #withPush @= withPushOpt <: nil -type Options = Record - '[ "help" >: Bool - , "version" >: Bool - , "verbose" >: Bool - , "daily" >: Bool - , "hourly" >: Bool - , "minutely" >: Bool - , "skip" >: Bool - , "withCopy" >: Bool - , "withCommit" >: Bool - , "withPush" >: Bool - ] - helpOpt :: OptDescr' Bool helpOpt = optFlag ['h'] ["help"] "Show this help text" @@ -91,6 +79,72 @@ withCommitOpt = optFlag [] ["with-commit"] "Create commit after generate HTML" withPushOpt :: OptDescr' Bool withPushOpt = optFlag [] ["with-push"] "Push commit after create commit" +type Options = + Record (CmdEnchantments ++ LogOptEnchantemnts ++ ActionEnchantments) + +type CmdEnchantments = + '[ "help" >: Bool + , "version" >: Bool + , "daily" >: Bool + , "hourly" >: Bool + , "minutely" >: Bool + ] + +newtype Cmd = Cmd { exec :: String -> IO () } + +instance Magic (Enchantment (Breakable Cmd)) ("help" >: Bool) where + magic _ = enchantmentIfTrue $ onlyOnce $ \_ -> + Cmd $ \usage -> hPutBuilder stdout (fromString usage) + +instance Magic (Enchantment (Breakable Cmd)) ("version" >: Bool) where + magic _ = enchantmentIfTrue $ onlyOnce $ \_ -> + Cmd $ \_ -> hPutBuilder stdout (Version.build version) + +instance Magic (Enchantment (Breakable Cmd)) ("daily" >: Bool) where + magic _ = enchantmentIfTrue $ onlyOnce $ \(Cmd cmd) -> + Cmd $ \x -> cmd x `withCron` "0 8 * * *" + +instance Magic (Enchantment (Breakable Cmd)) ("hourly" >: Bool) where + magic _ = enchantmentIfTrue $ onlyOnce $ \(Cmd cmd) -> + Cmd $ \x -> cmd x `withCron` "0 * * * *" + +instance Magic (Enchantment (Breakable Cmd)) ("minutely" >: Bool) where + magic _ = enchantmentIfTrue $ onlyOnce $ \(Cmd cmd) -> + Cmd $ \x -> cmd x `withCron` "* * * * *" + +type LogOptEnchantemnts = + '[ "verbose" >: Bool + ] + +instance Magic (Enchantment MixLogger.MixLoggerConfig) ("verbose" >: Bool) where + magic _ = Enchantment . set #verbose + +type ActionEnchantments = + '[ "skip" >: Bool + , "withCopy" >: Bool + , "withCommit" >: Bool + , "withPush" >: Bool + ] + +instance Magic (Enchantment (RIO Env ())) ("skip" >: Bool) where + magic _ = enchantmentIfTrue $ \_ -> pure () + +instance Magic (Enchantment (RIO Env ())) ("withCopy" >: Bool) where + magic _ = enchantmentIfTrue $ \act -> do + copyFilesByAnotherBranch + act + +instance Magic (Enchantment (RIO Env ())) ("withCommit" >: Bool) where + magic _ = enchantmentIfTrue $ \act -> do + MixShell.exec (Git.pull []) + act + commitGeneratedFiles + +instance Magic (Enchantment (RIO Env ())) ("withPush" >: Bool) where + magic _ = enchantmentIfTrue $ \act -> do + act + pushCommit + type Env = Record '[ "logger" >: LogFunc , "config" >: Config @@ -102,20 +156,18 @@ runCmd _ Nothing = error "please input config file path." runCmd opts (Just path) = do config <- readConfig path let plugin = hsequence - $ #logger <@=> MixLogger.buildPlugin logOpts + $ #logger <@=> MixLogger.buildPlugin logOpts' <: #config <@=> pure config <: #work <@=> pure "." <: nil - Mix.run plugin $ do - when (opts ^. #withCommit) $ MixShell.exec (Git.pull []) - when (opts ^. #withCopy) $ copyFilesByAnotherBranch - when (not $ opts ^. #skip) $ generate path - when (opts ^. #withCommit) $ commitGeneratedFiles - when (opts ^. #withPush) $ pushCommit + Mix.run plugin $ + generate path `withEnchantment` (shrink opts :: Record ActionEnchantments) where - logOpts = #handle @= stdout - <: #verbose @= (opts ^. #verbose) + logOpts = #handle @= stdout + <: #verbose @= False <: nil + logOpts' = logOpts `withEnchantment` (shrink opts :: Record LogOptEnchantemnts) + readConfig :: FilePath -> IO Config readConfig = either (error . show) pure <=< Y.decodeFileEither diff --git a/lib/Options/Magic.hs b/lib/Options/Magic.hs new file mode 100644 index 0000000..1311a24 --- /dev/null +++ b/lib/Options/Magic.hs @@ -0,0 +1,7 @@ +module Options.Magic + ( module X + ) where + +import Options.Magic.Breakable as X +import Options.Magic.Enchantment as X +import Options.Magic.Internal as X diff --git a/lib/Options/Magic/Breakable.hs b/lib/Options/Magic/Breakable.hs new file mode 100644 index 0000000..c82ed06 --- /dev/null +++ b/lib/Options/Magic/Breakable.hs @@ -0,0 +1,17 @@ +module Options.Magic.Breakable where + +import RIO + +data Breakable a = Break a | Continue a + +instance Functor Breakable where + fmap f (Continue a) = Continue (f a) + fmap f (Break a) = Break (f a) + +onlyOnce :: (a -> a) -> Breakable a -> Breakable a +onlyOnce f (Continue a) = Break (f a) +onlyOnce _ brk = brk + +peel :: Breakable a -> a +peel (Break a) = a +peel (Continue a) = a diff --git a/lib/Options/Magic/Enchantment.hs b/lib/Options/Magic/Enchantment.hs new file mode 100644 index 0000000..4bc972d --- /dev/null +++ b/lib/Options/Magic/Enchantment.hs @@ -0,0 +1,27 @@ +{-# LANGUAGE DataKinds #-} +{-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE MultiParamTypeClasses #-} +{-# LANGUAGE PolyKinds #-} +{-# LANGUAGE ScopedTypeVariables #-} +{-# LANGUAGE TypeApplications #-} + +module Options.Magic.Enchantment where + +import RIO + +import Data.Extensible +import Options.Magic.Internal (Magic (..)) + +newtype Enchantment a = Enchantment { enchantment :: a -> a } + +withEnchantment :: + forall a xs . Forall (Magic (Enchantment a)) xs + => a -> Record xs -> a +withEnchantment = + hfoldlWithIndexWith @ (Magic (Enchantment a)) + (\m acc x -> enchantment (magic m (runIdentity $ getField x)) acc) + +enchantmentIfTrue :: + (a -> a) -> Bool -> Enchantment a +enchantmentIfTrue _ False = Enchantment id +enchantmentIfTrue e True = Enchantment e diff --git a/lib/Options/Magic/Internal.hs b/lib/Options/Magic/Internal.hs new file mode 100644 index 0000000..ee45d6f --- /dev/null +++ b/lib/Options/Magic/Internal.hs @@ -0,0 +1,9 @@ +{-# LANGUAGE MultiParamTypeClasses #-} +{-# LANGUAGE PolyKinds #-} + +module Options.Magic.Internal where + +import Data.Extensible + +class Magic a kv where + magic :: proxy kv -> TargetOf kv -> a diff --git a/package.yaml b/package.yaml index 67fdd28..8fdb671 100644 --- a/package.yaml +++ b/package.yaml @@ -27,7 +27,9 @@ default-extensions: executables: antenna: main: Main.hs - source-dirs: app + source-dirs: + - app + - lib dependencies: - blaze-html - cron