cron 0.4.2 → 0.5.0
raw patch · 15 files changed
+729/−4 lines, 15 filesdep +data-default-classdep ~basePVP ok
version bump matches the API change (PVP)
Dependencies added: data-default-class
Dependency ranges changed: base
API changes (from Hackage documentation)
+ System.Cron.Describe: data OptionBuilder
+ System.Cron.Describe: defaultOpts :: OptionBuilder
+ System.Cron.Describe: describe :: OptionBuilder -> CronSchedule -> String
+ System.Cron.Describe: notVerbose :: OptionBuilder
+ System.Cron.Describe: twelveHourFormat :: OptionBuilder
+ System.Cron.Describe: twentyFourHourFormat :: OptionBuilder
+ System.Cron.Describe: verbose :: OptionBuilder
+ System.Cron.Internal.Describe.Descriptors: domDescriptor :: Descriptor
+ System.Cron.Internal.Describe.Descriptors: dowDescriptor :: Descriptor
+ System.Cron.Internal.Describe.Descriptors: hourDescriptor :: TimeFormat -> Descriptor
+ System.Cron.Internal.Describe.Descriptors: minuteDescriptor :: Descriptor
+ System.Cron.Internal.Describe.Descriptors: monthDescriptor :: Descriptor
+ System.Cron.Internal.Describe.Options: Builder :: (Options -> Options) -> OptionBuilder
+ System.Cron.Internal.Describe.Options: Opts :: TimeFormat -> Verbosity -> Options
+ System.Cron.Internal.Describe.Options: [timeFormat] :: Options -> TimeFormat
+ System.Cron.Internal.Describe.Options: [verbosity] :: Options -> Verbosity
+ System.Cron.Internal.Describe.Options: data OptionBuilder
+ System.Cron.Internal.Describe.Options: data Options
+ System.Cron.Internal.Describe.Options: defaultOpts :: OptionBuilder
+ System.Cron.Internal.Describe.Options: getOpts :: OptionBuilder -> Options
+ System.Cron.Internal.Describe.Options: instance Data.Default.Class.Default System.Cron.Internal.Describe.Options.Options
+ System.Cron.Internal.Describe.Options: instance Data.Semigroup.Semigroup System.Cron.Internal.Describe.Options.OptionBuilder
+ System.Cron.Internal.Describe.Options: instance GHC.Base.Monoid System.Cron.Internal.Describe.Options.OptionBuilder
+ System.Cron.Internal.Describe.Options: notVerbose :: OptionBuilder
+ System.Cron.Internal.Describe.Options: twelveHourFormat :: OptionBuilder
+ System.Cron.Internal.Describe.Options: twentyFourHourFormat :: OptionBuilder
+ System.Cron.Internal.Describe.Options: verbose :: OptionBuilder
+ System.Cron.Internal.Describe.Time: Hour :: Int -> Hour
+ System.Cron.Internal.Describe.Time: Minute :: Int -> Minute
+ System.Cron.Internal.Describe.Time: format :: TimeFormat -> Minute -> Hour -> String
+ System.Cron.Internal.Describe.Time: newtype Hour
+ System.Cron.Internal.Describe.Time: newtype Minute
+ System.Cron.Internal.Describe.Types: April :: Month
+ System.Cron.Internal.Describe.Types: August :: Month
+ System.Cron.Internal.Describe.Types: Concrete :: String -> DescribedValue
+ System.Cron.Internal.Describe.Types: ConcreteTime :: String -> Time
+ System.Cron.Internal.Describe.Types: December :: Month
+ System.Cron.Internal.Describe.Types: Desc :: Time -> Maybe DescribedValue -> Maybe DescribedValue -> Maybe DescribedValue -> Description
+ System.Cron.Internal.Describe.Types: Descriptor :: String -> String -> String -> String -> String -> (Int -> String) -> String -> String -> (Int -> Maybe String) -> String -> Maybe String -> Descriptor
+ System.Cron.Internal.Describe.Types: Every :: String -> DescribedValue
+ System.Cron.Internal.Describe.Types: February :: Month
+ System.Cron.Internal.Describe.Types: Friday :: Weekday
+ System.Cron.Internal.Describe.Types: Hour12 :: TimeFormat
+ System.Cron.Internal.Describe.Types: Hour24 :: TimeFormat
+ System.Cron.Internal.Describe.Types: January :: Month
+ System.Cron.Internal.Describe.Types: July :: Month
+ System.Cron.Internal.Describe.Types: June :: Month
+ System.Cron.Internal.Describe.Types: March :: Month
+ System.Cron.Internal.Describe.Types: May :: Month
+ System.Cron.Internal.Describe.Types: Monday :: Weekday
+ System.Cron.Internal.Describe.Types: NotVerbose :: Verbosity
+ System.Cron.Internal.Describe.Types: November :: Month
+ System.Cron.Internal.Describe.Types: October :: Month
+ System.Cron.Internal.Describe.Types: Other :: (Maybe DescribedValue) -> (Maybe DescribedValue) -> Time
+ System.Cron.Internal.Describe.Types: Saturday :: Weekday
+ System.Cron.Internal.Describe.Types: September :: Month
+ System.Cron.Internal.Describe.Types: Sunday :: Weekday
+ System.Cron.Internal.Describe.Types: Sunday2 :: Weekday
+ System.Cron.Internal.Describe.Types: Thursday :: Weekday
+ System.Cron.Internal.Describe.Types: Tuesday :: Weekday
+ System.Cron.Internal.Describe.Types: Verbose :: Verbosity
+ System.Cron.Internal.Describe.Types: Wednesday :: Weekday
+ System.Cron.Internal.Describe.Types: [_dom] :: Description -> Maybe DescribedValue
+ System.Cron.Internal.Describe.Types: [_dow] :: Description -> Maybe DescribedValue
+ System.Cron.Internal.Describe.Types: [_month] :: Description -> Maybe DescribedValue
+ System.Cron.Internal.Describe.Types: [_time] :: Description -> Time
+ System.Cron.Internal.Describe.Types: [displayItem] :: Descriptor -> Int -> String
+ System.Cron.Internal.Describe.Types: [listPrefix] :: Descriptor -> String
+ System.Cron.Internal.Describe.Types: [listSuffix] :: Descriptor -> Maybe String
+ System.Cron.Internal.Describe.Types: [pluralDesc] :: Descriptor -> String
+ System.Cron.Internal.Describe.Types: [rangeJoiner] :: Descriptor -> String
+ System.Cron.Internal.Describe.Types: [rangePrefix] :: Descriptor -> String
+ System.Cron.Internal.Describe.Types: [rangeSuffix] :: Descriptor -> String
+ System.Cron.Internal.Describe.Types: [singularDesc] :: Descriptor -> String
+ System.Cron.Internal.Describe.Types: [specificPrefix] :: Descriptor -> String
+ System.Cron.Internal.Describe.Types: [specificSuffix] :: Descriptor -> String
+ System.Cron.Internal.Describe.Types: [stepSpecificSuffix] :: Descriptor -> Int -> Maybe String
+ System.Cron.Internal.Describe.Types: data DescribedValue
+ System.Cron.Internal.Describe.Types: data Description
+ System.Cron.Internal.Describe.Types: data Descriptor
+ System.Cron.Internal.Describe.Types: data Month
+ System.Cron.Internal.Describe.Types: data Time
+ System.Cron.Internal.Describe.Types: data TimeFormat
+ System.Cron.Internal.Describe.Types: data Verbosity
+ System.Cron.Internal.Describe.Types: data Weekday
+ System.Cron.Internal.Describe.Types: instance GHC.Enum.Bounded System.Cron.Internal.Describe.Types.Month
+ System.Cron.Internal.Describe.Types: instance GHC.Enum.Bounded System.Cron.Internal.Describe.Types.Weekday
+ System.Cron.Internal.Describe.Types: instance GHC.Enum.Enum System.Cron.Internal.Describe.Types.Month
+ System.Cron.Internal.Describe.Types: instance GHC.Enum.Enum System.Cron.Internal.Describe.Types.Weekday
+ System.Cron.Internal.Describe.Types: instance GHC.Show.Show System.Cron.Internal.Describe.Types.DescribedValue
+ System.Cron.Internal.Describe.Types: instance GHC.Show.Show System.Cron.Internal.Describe.Types.Description
+ System.Cron.Internal.Describe.Types: instance GHC.Show.Show System.Cron.Internal.Describe.Types.Month
+ System.Cron.Internal.Describe.Types: instance GHC.Show.Show System.Cron.Internal.Describe.Types.Time
+ System.Cron.Internal.Describe.Types: instance GHC.Show.Show System.Cron.Internal.Describe.Types.Weekday
+ System.Cron.Internal.Describe.Types: safeIntToMonth :: Int -> Month
+ System.Cron.Internal.Describe.Types: safeIntToWeekDay :: Int -> Weekday
+ System.Cron.Internal.Describe.Utils: allWords :: [String] -> String
+ System.Cron.Internal.Describe.Utils: cap :: String -> String
+ System.Cron.Internal.Describe.Utils: joinWords :: [String] -> String
+ System.Cron.Internal.Describe.Utils: viewHour :: HourSpec -> Maybe Hour
+ System.Cron.Internal.Describe.Utils: viewHourList :: HourSpec -> Maybe (NonEmpty BaseField)
+ System.Cron.Internal.Describe.Utils: viewMinute :: MinuteSpec -> Maybe Minute
+ System.Cron.Internal.Describe.Utils: viewMinuteRange :: MinuteSpec -> Maybe (Minute, Minute)
Files
- README.md +15/−1
- bench/Main.hs +32/−1
- changelog +2/−0
- cron.cabal +10/−1
- src/System/Cron.hs +2/−0
- src/System/Cron/Describe.hs +190/−0
- src/System/Cron/Internal/Describe/Descriptors.hs +98/−0
- src/System/Cron/Internal/Describe/Options.hs +85/−0
- src/System/Cron/Internal/Describe/Time.hs +20/−0
- src/System/Cron/Internal/Describe/Types.hs +80/−0
- src/System/Cron/Internal/Describe/Utils.hs +56/−0
- src/System/Cron/Parser.hs +1/−1
- src/System/Cron/Types.hs +4/−0
- test/Main.hs +2/−0
- test/System/Test/Cron/Describe.hs +132/−0
README.md view
@@ -10,7 +10,9 @@ `scheduleMatches`, which you can use to compare a time against a `CronSchedule` to see if an action needs to be performed. System.Cron.Parser is where you will find the parsers `cronSchedule`, `crontabEntry` and `cronTab`. To parse-individual schedules up to full crontab files.+individual schedules up to full crontab files. System.Cron.Descrive is where+you will find the `describe` function for creating human-readable strings from+cron schedules, as well as any options to control how the description is created. To do anything, you'll need to install cabal-dev with cabal.@@ -51,9 +53,21 @@ job2 = putStrLn "Job 2" ``` +#### Describe+```haskell+main :: IO ()+main = do+ let Right cs1 = parseCronSchedule "*/2 * 3 * 4,5,6"+ print $ describe defaultOpts cs1++ let Right cs2 = parseCronSchedule "*/2 12 3 * 4,5,6"+ print $ describe (twentyFourHourFormat <> verbose) cs2+```+ ## Contributors * [Simon Hengel](https://github.com/sol) * [Alberto Valverde](https://github.com/albertov) * [Andrew Rademacher](https://github.com/AndrewRademacher) * [Peter Simons](https://github.com/peti)+* [Joseph Canero](https://github.com/caneroj1)
bench/Main.hs view
@@ -22,6 +22,7 @@ , scheduleMatchesBenchmarks , nextMatchBenchmarks , serializeBenchmarks+ , describeBenchmarks ] @@ -67,6 +68,21 @@ -------------------------------------------------------------------------------+describeBenchmarks :: Benchmark+describeBenchmarks = bgroup "description"+ [+ bgroup "verbose" [+ bench "simple" (whnf (describe verbose) simpleCronSchedule)+ , bench "complicated" (whnf (describe verbose) complexCronSchedule)+ ]+ , bgroup "non-verbose" [+ bench "simple" (whnf (describe notVerbose) simpleCronSchedule)+ , bench "complicated" (whnf (describe notVerbose) complexCronSchedule)+ ]+ ]+++------------------------------------------------------------------------------- parserBench :: String -> Parser a -> Text -> Benchmark parserBench n parser txt = bench n (whnf (parseOnly parser) txt) @@ -86,9 +102,24 @@ -------------------------------------------------------------------------------+mkCronSchedule :: Text -> CronSchedule+mkCronSchedule t = let (Right cs) = parseCronSchedule t in cs+++-------------------------------------------------------------------------------+simpleCronSchedule :: CronSchedule+simpleCronSchedule = mkCronSchedule "1 2 3 * *"+++-------------------------------------------------------------------------------+complexCronSchedule :: CronSchedule+complexCronSchedule = mkCronSchedule "1-10 3 1-20/2 * 3,5"+++------------------------------------------------------------------------------- exampleCrontab :: Crontab exampleCrontab = Crontab (concat (zipWith (\x y -> [x,y]) (replicate 50 cmd) (repeat envSet)))- where + where cmd = CommandEntry weekly (CronCommand "do something") envSet = EnvVariable "FOO" "BAR"
changelog view
@@ -1,3 +1,5 @@+# 0.5.0+* Add System.Cron.Describe which describes cron schedules in words (English only for now). Big thanks to Joe Canero for this feature! # 0.4.2 * Drop dependency on derive in tests. * Add some Generic and Typeable instances.
cron.cabal view
@@ -1,5 +1,5 @@ Name: cron-Version: 0.4.2+Version: 0.5.0 Description: Cron data structure and Attoparsec parser. The idea is to embed it in larger systems which want to roll their own scheduled tasks in a format that people@@ -32,6 +32,7 @@ test/Main.hs test/SpecHelper.hs test/System/Test/Cron.hs+ test/System/Test/Cron/Describe.hs test/System/Test/Cron/Parser.hs test/System/Test/Cron/Schedule.hs Homepage: http://github.com/michaelxavier/cron@@ -43,7 +44,13 @@ library Exposed-modules: System.Cron+ , System.Cron.Internal.Describe.Descriptors+ , System.Cron.Internal.Describe.Options+ , System.Cron.Internal.Describe.Time+ , System.Cron.Internal.Describe.Types+ , System.Cron.Internal.Describe.Utils , System.Cron.Internal.Check+ , System.Cron.Describe , System.Cron.Parser , System.Cron.Schedule , System.Cron.Types@@ -57,6 +64,7 @@ , mtl >= 2.0.1 , mtl-compat >= 0.2.1 , semigroups+ , data-default-class >= 0.0.1 if flag(lib-Werror) ghc-options: -Werror @@ -70,6 +78,7 @@ default-language: Haskell2010 other-modules: SpecHelper , System.Test.Cron+ , System.Test.Cron.Describe , System.Test.Cron.Parser , System.Test.Cron.Schedule Build-Depends: base
src/System/Cron.hs view
@@ -4,6 +4,7 @@ ( module System.Cron.Types , module System.Cron.Parser , module System.Cron.Schedule+ , module System.Cron.Describe , scheduleMatches , nextMatch ) where@@ -11,6 +12,7 @@ ------------------------------------------------------------------------------- import System.Cron.Internal.Check+import System.Cron.Describe import System.Cron.Parser import System.Cron.Schedule import System.Cron.Types
+ src/System/Cron/Describe.hs view
@@ -0,0 +1,190 @@+{-# LANGUAGE ViewPatterns #-}+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE CPP #-}+--------------------------------------------------------------------+-- |+-- Module : System.Cron.Describe+-- Description : Turn a cron schedule into a human-readable string+-- Copyright : (c) Joseph Canero 2016+-- License : MIT+--+-- Maintainer: Joseph Canero <jmc41493@gmail.com>+-- Portability: portable+--+--+-- > import System.Cron+-- >+-- > main :: IO ()+-- > main = do+-- > let Right cs1 = parseCronSchedule "*/2 * 3 * 4,5,6"+-- > print $ describe defaultOpts cs1+-- >+-- > let Right cs2 = parseCronSchedule "*/2 12 3 * 4,5,6"+-- > print $ describe (twentyFourHourFormat <> verbose) cs2+--------------------------------------------------------------------+module System.Cron.Describe+ (+ -- * Options handling+ defaultOpts+ , twentyFourHourFormat+ , twelveHourFormat+ , verbose+ , notVerbose+ , OptionBuilder+ -- * Describe a CronSchedule+ , describe+ ) where++-------------------------------------------------------------------------------+import Control.Monad+import Data.List.NonEmpty (NonEmpty (..), toList)+import Data.Maybe (fromJust)+#if !MIN_VERSION_base(4,8,0)+import Data.Traversable (traverse)+#endif+-------------------------------------------------------------------------------+import System.Cron.Internal.Describe.Descriptors+import System.Cron.Internal.Describe.Options+import System.Cron.Internal.Describe.Time+import System.Cron.Internal.Describe.Types+import System.Cron.Internal.Describe.Utils+import System.Cron.Types+-------------------------------------------------------------------------------+++-- | Given an 'OptionBuilder' and a 'CronSchedule' parsed with+-- 'System.Cron.Parser.parseCronSchedule', return a human-readable string+-- describing when that schedule will match.+describe :: OptionBuilder -> CronSchedule -> String+describe ob = cap .+ show .+ matchVerbosity verbosity .+ description timeFormat+ where Opts{..} = getOpts ob+++-------------------------------------------------------------------------------+-- Internals+-------------------------------------------------------------------------------+++describeRange :: RangeField -> Descriptor -> String+describeRange rf d = allWords [rangePrefix d,+ displayItem d (rfBegin rf),+ rangeJoiner d,+ displayItem d (rfEnd rf),+ rangeSuffix d]+++describeBaseField :: Descriptor -> BaseField -> DescribedValue+describeBaseField d (RangeField' rf) = Concrete $ describeRange rf d+describeBaseField d Star = Every $ "every " ++ singularDesc d+describeBaseField d (SpecificField' s) =+ Concrete $ allWords [specificPrefix d,+ displayItem d (specificField s),+ specificSuffix d]+++type StarOrDesc = Either String String+++describeListFields :: (BaseField -> String) -> NonEmpty BaseField -> StarOrDesc+describeListFields f (l :| ls) =+ fmap joinWords . foldM describeF [] $ reverse (l:ls)+ where describeF _ Star = Left $ f Star+ describeF e bf = Right $ f bf : e++++describeCronField :: Descriptor -> CronField -> DescribedValue+describeCronField d (Field f) = describeBaseField d f+++describeCronField d (StepField' sf) = Concrete $+ stepPrefix ++ maybe "" (", " ++) (stepSuffix $ sfField sf)+ where+ stepPrefix = unwords ["every", show (sfStepping sf), pluralDesc d]+ stepSuffix Star = Nothing+ stepSuffix (RangeField' rf) = Just $ describeRange rf d+ stepSuffix (SpecificField' s) = stepSpecificSuffix d $ specificField s+++describeCronField d (ListField ls) =+ case describeListFields describeBF ls of+ Left s -> Every s+ Right s -> Concrete $ unwords [listPrefix d,+ maybe s ((s ++ " ") ++) (listSuffix d)]+ where+ describeBF Star = "every " ++ singularDesc d+ describeBF (SpecificField' s) = displayItem d $ specificField s+ describeBF (RangeField' rf) = unwords [displayItem d (rfBegin rf),+ "through",+ displayItem d (rfEnd rf)]++-- There are a few special cases to handle when describing the minute and hour+-- fields that will make the cron description easier to read.+-- For the most part, these are pretty straight forward. The first three+-- pattern matches look for specific patterns in the minute and hour fields that+-- can be formatted differently. The last pattern match just defaults+-- to describing the fields using existing rules.+describeTime :: TimeFormat -> MinuteSpec -> HourSpec -> Time+describeTime tf (viewMinute -> Just m) (viewHour -> Just h) =+ ConcreteTime $ "at " ++ format tf m h+describeTime tf (viewMinuteRange -> Just (m1, m2)) (viewHour -> Just h) =+ ConcreteTime $ unwords ["every minute between",+ format tf m1 h,+ "and",+ format tf m2 h]+describeTime tf (viewMinute -> Just m) (viewHourList -> Just hs) =+ describeMultHours tf m hs+describeTime tf (minuteSpec -> m) (hourSpec -> h) =+ Other (return $ describeCronField minuteDescriptor m)+ (return $ describeCronField (hourDescriptor tf) h)++-- We want to create a description for multiple hours given a concrete minute.+-- This is rather ugly, as the ListField type allows for any BaseField, so+-- we can potentially have a '*' within the list. In that case, we don't need+-- to describe the rest of the BaseFields for hour list, since we will just be+-- firing each hour.+describeMultHours :: TimeFormat -> Minute -> NonEmpty BaseField -> Time+describeMultHours t mn@(Minute m) ls =+ maybe mkOther (formatAllFields . toList) $ traverse formatBaseField ls+ where hourCF = ListField ls+ minuteCF = Field (SpecificField' (fromJust $ mkSpecificField m))++ formatAllFields = ConcreteTime . ("at " ++) . joinWords++ formatBaseField (SpecificField' s) =+ Just $ format t mn (Hour (specificField s))+ formatBaseField Star = Nothing+ formatBaseField f@(RangeField' _) =+ Just $ unwords [show describedMinute,+ show $ describeCronField (hourDescriptor t) (Field f)]++ mkOther = Other (return describedMinute)+ (return $ describeCronField (hourDescriptor t) hourCF)+ describedMinute = describeCronField minuteDescriptor minuteCF+++description :: TimeFormat -> CronSchedule -> Description+description t c = Desc (describeTime t (minute c) (hour c))+ (return ddom)+ (return dm)+ (return ddow)+ where ddom = describeCronField domDescriptor $ dayOfMonthSpec (dayOfMonth c)+ dm = describeCronField monthDescriptor $ monthSpec (month c)+ ddow = describeCronField dowDescriptor $ dayOfWeekSpec (dayOfWeek c)+++matchVerbosity :: Verbosity -> Description -> Description+matchVerbosity v d@Desc{..} = d{ _dom = stripEvery v =<< _dom+ , _dow = stripEvery v =<< _dow+ , _time = stripTime _time+ , _month = stripEvery NotVerbose =<< _month}+ where stripTime t@(ConcreteTime _) = t+ stripTime (Other mbMin mbHour) = Other mbMin (stripEvery v =<< mbHour)+++stripEvery :: Verbosity -> DescribedValue -> Maybe DescribedValue+stripEvery NotVerbose (Every _) = Nothing+stripEvery _ c = Just c
+ src/System/Cron/Internal/Describe/Descriptors.hs view
@@ -0,0 +1,98 @@+module System.Cron.Internal.Describe.Descriptors where++import System.Cron.Internal.Describe.Time+import System.Cron.Internal.Describe.Types++minuteDescriptor :: Descriptor+minuteDescriptor = Descriptor {+ pluralDesc = "minutes"+ , singularDesc = "minute"+ , rangePrefix = "minutes"+ , rangeSuffix = "past the hour"+ , rangeJoiner = "through"+ , displayItem = show+ , specificPrefix = "at"+ , specificSuffix = "minutes past the hour"+ , stepSpecificSuffix = sss+ , listPrefix = "at"+ , listSuffix = Nothing+ }+ where+ sss n+ | n == 0 = Nothing+ | otherwise = Just $ "starting at " ++ show n ++ " minutes past the hour"+++hourDescriptor :: TimeFormat -> Descriptor+hourDescriptor tf = Descriptor {+ pluralDesc = "hours"+ , singularDesc = "hour"+ , rangePrefix = "between"+ , rangeSuffix = ""+ , rangeJoiner = "and"+ , displayItem = toHour+ , specificPrefix = "at"+ , specificSuffix = ""+ , stepSpecificSuffix = sss+ , listPrefix = "at"+ , listSuffix = Nothing+ }+ where toHour h = format tf (Minute 0) (Hour h)+ sss n+ | n == 0 = Nothing+ | otherwise = Just $ "starting at " ++ toHour n+++domDescriptor :: Descriptor+domDescriptor = Descriptor {+ pluralDesc = "days"+ , singularDesc = "day"+ , rangePrefix = "between days"+ , rangeSuffix = "of the month"+ , rangeJoiner = "and"+ , displayItem = show+ , specificPrefix = "on day"+ , specificSuffix = "of the month"+ , stepSpecificSuffix = sss+ , listPrefix = "on days"+ , listSuffix = Just "of the month"+ }+ where sss n = Just $ "starting on day " ++ show n ++ " of the month"+++monthDescriptor :: Descriptor+monthDescriptor = Descriptor {+ pluralDesc = "months"+ , singularDesc = "month"+ , rangePrefix = ""+ , rangeSuffix = ""+ , rangeJoiner = "through"+ , displayItem = toMonth+ , specificPrefix = "only in"+ , specificSuffix = ""+ , stepSpecificSuffix = sss+ , listPrefix = "only in"+ , listSuffix = Nothing+ }+ where toMonth = show . safeIntToMonth+ sss n+ | n == 1 = Nothing+ | otherwise = Just $ toMonth n ++ " through " ++ toMonth 12+++dowDescriptor :: Descriptor+dowDescriptor = Descriptor {+ pluralDesc = "days of the week"+ , singularDesc = "day of the week"+ , rangePrefix = ""+ , rangeSuffix = ""+ , rangeJoiner = "through"+ , displayItem = toWeekday+ , specificPrefix = "only on"+ , specificSuffix = ""+ , stepSpecificSuffix = sss+ , listPrefix = "only on"+ , listSuffix = Nothing+ }+ where toWeekday = show . safeIntToWeekDay+ sss n = Just $ toWeekday n ++ " through " ++ show Saturday
+ src/System/Cron/Internal/Describe/Options.hs view
@@ -0,0 +1,85 @@+--------------------------------------------------------------------+-- |+-- Module : System.Cron.Internal.Describe.Optons+-- Description : Functions for constructing the options that control how+-- cron schedules are described+-- Copyright : (c) Joseph Canero 2016+-- License : MIT+--+-- Maintainer: Joseph Canero <jmc41493@gmail.com>+-- Portability: portable+--------------------------------------------------------------------+module System.Cron.Internal.Describe.Options where++-------------------------------------------------------------------------------+import Data.Default.Class+import Data.Semigroup+-------------------------------------------------------------------------------+import System.Cron.Internal.Describe.Types+-------------------------------------------------------------------------------+++-- | Type that holds onto information for constructing options for+-- 'System.Cron.Describe.describe'.+data OptionBuilder = Builder (Options -> Options)+++-- | Return a builder that creates the default options for+-- 'System.Cron.Describe.describe'. The default options are:+-- 'System.Cron.Describe.notVerbose' and 'System.Cron.Describe.twelveHourFormat'.+defaultOpts :: OptionBuilder+defaultOpts = Builder (const def)+++-- | Return a builder that sets the options to use a 24-hour time format.+-- This changes how hours are described. Using the 24-hour format,+-- all hours are returned as their left-padded numeric value (01:00, 22:00, etc)+twentyFourHourFormat :: OptionBuilder+twentyFourHourFormat = Builder (\o -> o {timeFormat = Hour24} )+++-- | Return a builder that sets the options to use a 12-hour time format.+-- This changes how hours are described. Using the 12-hour format,+-- all hours are returned as their left-padded numeric value with their period+-- (01:00 AM, 10:00 PM, etc)+twelveHourFormat :: OptionBuilder+twelveHourFormat = Builder (\o -> o {timeFormat = Hour12} )+++-- | Return a builder that sets the options to be verbose. A verbose description+-- doesn't eliminate unnecessary information. The only caveat being that month+-- information is only ever displayed if it isn't "*".+verbose :: OptionBuilder+verbose = Builder (\o -> o {verbosity = Verbose})+++-- | Return a builder that sets the options to not be verbose. All information+-- about the described cron schedule is returned. The only caveat being that+-- month information is only ever displayed if it isn't "*".+notVerbose :: OptionBuilder+notVerbose = Builder (\o -> o {verbosity = NotVerbose})+++-------------------------------------------------------------------------------+-- Internals+-------------------------------------------------------------------------------+++getOpts :: OptionBuilder -> Options+getOpts (Builder f) = f def+++data Options = Opts {+ timeFormat :: TimeFormat+ , verbosity :: Verbosity+ }++instance Default Options where+ def = Opts {timeFormat = Hour12, verbosity = NotVerbose}++instance Semigroup OptionBuilder where+ (Builder f) <> (Builder a) = Builder (a . f)++instance Monoid OptionBuilder where+ mempty = Builder (const def)+ mappend = (<>)
+ src/System/Cron/Internal/Describe/Time.hs view
@@ -0,0 +1,20 @@+module System.Cron.Internal.Describe.Time where++import System.Cron.Internal.Describe.Types++newtype Minute = Minute Int+newtype Hour = Hour Int++format :: TimeFormat -> Minute -> Hour -> String+format t (Minute m) (Hour h) = leftPad (hour t) ++ ":" ++ leftPad m ++ suffix t+ where leftPad n+ | n < 10 = "0" ++ show n+ | otherwise = show n+ suffix Hour24 = ""+ suffix Hour12+ | h < 12 = " AM"+ | otherwise = " PM"+ hour Hour24 = h+ hour Hour12+ | h > 12 = h `mod` 12+ | otherwise = h
+ src/System/Cron/Internal/Describe/Types.hs view
@@ -0,0 +1,80 @@+{-# LANGUAGE RecordWildCards #-}++module System.Cron.Internal.Describe.Types where++import Data.List (intercalate)+import Data.Maybe (catMaybes)+++data Descriptor = Descriptor {+ pluralDesc :: String+ , singularDesc :: String+ , rangePrefix :: String+ , rangeSuffix :: String+ , rangeJoiner :: String+ , displayItem :: Int -> String+ , specificPrefix :: String+ , specificSuffix :: String+ , stepSpecificSuffix :: Int -> Maybe String+ , listPrefix :: String+ , listSuffix :: Maybe String+ }+++data Month = January | February | March | April | May | June |+ July | August | September | October | November | December+ deriving (Enum, Bounded, Show)+++safeIntToMonth :: Int -> Month+safeIntToMonth = toEnum . subtract 1 . min 12 . max 1+++data Weekday = Sunday | Monday | Tuesday | Wednesday |+ Thursday | Friday | Saturday | Sunday2+ deriving (Enum, Bounded, Show)+++safeIntToWeekDay :: Int -> Weekday+safeIntToWeekDay n+ | n == 7 = Sunday+ | otherwise = toEnum . min 6 $ max 0 n+++data Verbosity = Verbose | NotVerbose+++data TimeFormat = Hour24 | Hour12+++data DescribedValue = Concrete String+ | Every String+++instance Show DescribedValue where+ show (Concrete s) = s+ show (Every s) = s+++data Time = ConcreteTime String+ | Other (Maybe DescribedValue) (Maybe DescribedValue)+++instance Show Time where+ show (ConcreteTime s) = s+ show (Other md1 md2) = intercalate ", " .+ map show $ catMaybes [md1, md2]+++data Description = Desc {+ _time :: Time+ , _dom :: Maybe DescribedValue+ , _month :: Maybe DescribedValue+ , _dow :: Maybe DescribedValue+ }+++instance Show Description where+ show Desc{..} = intercalate ", " .+ (:) (show _time) .+ map show $ catMaybes [_dom, _dow, _month]
+ src/System/Cron/Internal/Describe/Utils.hs view
@@ -0,0 +1,56 @@+module System.Cron.Internal.Describe.Utils+(+ viewHour+, viewMinute+, viewMinuteRange+, viewHourList+, allWords+, cap+, joinWords+) where++import Data.Char (toUpper)+import Data.List.NonEmpty (NonEmpty)+import System.Cron.Types+import System.Cron.Internal.Describe.Time++viewHour :: HourSpec -> Maybe Hour+viewHour = viewSpecificTime Hour . hourSpec+++viewMinute :: MinuteSpec -> Maybe Minute+viewMinute = viewSpecificTime Minute . minuteSpec+++viewMinuteRange :: MinuteSpec -> Maybe (Minute, Minute)+viewMinuteRange = viewRange . minuteSpec+ where viewRange (Field (RangeField' rf)) = Just (Minute $ rfBegin rf,+ Minute $ rfEnd rf)+ viewRange _ = Nothing+++viewHourList :: HourSpec -> Maybe (NonEmpty BaseField)+viewHourList = viewList . hourSpec+ where viewList (ListField ne) = Just ne+ viewList _ = Nothing+++viewSpecificTime :: (Int -> a) -> CronField -> Maybe a+viewSpecificTime f (Field (SpecificField' s)) = Just . f $ specificField s+viewSpecificTime _ _ = Nothing+++allWords :: [String] -> String+allWords = unwords . filter (not . null)+++cap :: String -> String+cap [] = []+cap (x:xs) = toUpper x : xs+++joinWords :: [String] -> String+joinWords [] = []+joinWords [x] = x+joinWords [x, y] = x ++ " and " ++ y+joinWords (x:xs) = x ++ ", " ++ joinWords xs
src/System/Cron/Parser.hs view
@@ -15,7 +15,7 @@ -- > import System.Cron.Parser -- > -- > main :: IO ()--- > main = don+-- > main = do -- > print $ parseCronSchedule "*/2 * 3 * 4,5,6" -- --------------------------------------------------------------------
src/System/Cron/Types.hs view
@@ -46,6 +46,10 @@ -- * Rendering , serializeCronSchedule , serializeCrontab++ -- * Converting to human-readable string+ -- , displayCronField+ -- , Display(..) ) where
test/Main.hs view
@@ -7,6 +7,7 @@ ------------------------------------------------------------------------------- import SpecHelper import qualified System.Test.Cron+import qualified System.Test.Cron.Describe import qualified System.Test.Cron.Parser import qualified System.Test.Cron.Schedule -------------------------------------------------------------------------------@@ -15,6 +16,7 @@ main :: IO () main = defaultMain $ testGroup "cron" [ System.Test.Cron.tests+ , System.Test.Cron.Describe.tests , System.Test.Cron.Parser.tests , System.Test.Cron.Schedule.tests ]
+ test/System/Test/Cron/Describe.hs view
@@ -0,0 +1,132 @@+{-# LANGUAGE OverloadedStrings #-}++module System.Test.Cron.Describe (tests) where++import SpecHelper+-------------------------------------------------------------------------------+++tests :: TestTree+tests = testGroup "System.Cron.Describe"+ [ describeDisplayCronSchedule+ ]+++-------------------------------------------------------------------------------+describeDisplayCronSchedule :: TestTree+describeDisplayCronSchedule = testGroup "describeCronSchedule"+ [+ testGroup "describes all stars" [+ testCase "verbose" $+ "Every minute, every hour, every day, every day of the week" @=? describeV "* * * * *"++ , testCase "non-verbose" $+ "Every minute" @=? describeNV "* * * * *"+ ]+ , testGroup "describes specific values" [+ testCase "verbose" $+ "At 02:01, on day 3 of the month, every day of the week" @=? describeV "1 2 3 * *"++ , testCase "non-verbose" $+ "At 02:01, on day 3 of the month" @=? describeNV "1 2 3 * *"++ , testCase "12-hour (verbose)" $+ "At 02:01 AM, on day 3 of the month, every day of the week" @=? describe12 "1 2 3 * *"+ ]+ , testGroup "describes list values" [+ testCase "verbose" $+ "Every minute, every hour, on days 3 and 5 of the month, every day of the week" @=? describeV "* * 3,5 * *"++ , testCase "non-verbose" $+ "Every minute, on days 3 and 5 of the month" @=? describeNV "* * 3,5 * *"+ ]+ , testGroup "describes range values" [+ testCase "verbose" $+ "Every minute, every hour, between days 3 and 4 of the month, every day of the week" @=? describeV "* * 3-4 * *"++ , testCase "non-verbose" $+ "Every minute, between days 3 and 4 of the month" @=? describeNV "* * 3-4 * *"+ ]+ , testGroup "describes step values" [+ testCase "verbose" $+ "Every 2 minutes, every hour, every 4 days, between days 2 and 10 of the month, every day of the week" @=? describeV "*/2 * 2-10/4 * *"++ , testCase "non-verbose" $+ "Every 2 minutes, every 4 days, between days 2 and 10 of the month" @=? describeNV "*/2 * 2-10/4 * *"+ ]+ , testGroup "describes other values" [+ testCase "verbose" $+ "Every 2 minutes, minutes 1 through 59 past the hour, every hour, every day, Wednesday through Friday, only in February" @=? describeV "1-59/2 * * 2 3-5"++ , testCase "non-verbose" $+ "Every 2 minutes, minutes 1 through 59 past the hour, Wednesday through Friday, only in February" @=? describeNV "1-59/2 * * 2 3-5"+ ]+ , testGroup "handles Sunday weirdness" [+ testCase "range of values (Sunday at 0)" $+ "Every minute, every hour, every day, Sunday through Friday" @=? describeV "* * * * 0-5"++ , testCase "range of values (Sunday at 7)" $+ "Every minute, every hour, every day, Friday through Sunday" @=? describeV "* * * * 5-7"++ , testCase "range of values" $+ "Every minute, every hour, every day, Sunday through Sunday" @=? describeV "* * * * 0-7"+ ]+ , testGroup "describes complicated times" [+ testCase "describes specific times - twenty four hour" $+ "At 13:01, every day, every day of the week" @=? describeV "1 13 * * *"++ , testCase "describes specific times - twelve hour" $+ "At 01:01 PM, every day, every day of the week" @=? describe12 "1 13 * * *"++ , testCase "describes a range of minutes - twenty four hour" $+ "Every minute between 12:01 and 12:10, every day, every day of the week" @=? describeV "1-10 12 * * *"++ , testCase "describes a range of minutes - twelve hour" $+ "Every minute between 12:01 PM and 12:10 PM, every day, every day of the week" @=? describe12 "1-10 12 * * *"++ , testGroup "describes times for lists of hours" [+ testCase "simple list of hours - twenty four hour" $+ "At 02:01 and 03:01, every day, every day of the week" @=? describeV "1 2,3 * * *"++ , testCase "simple list of hours - twelve hour" $+ "At 02:01 AM and 03:01 AM, every day, every day of the week" @=? describe12 "1 2,3 * * *"++ , testCase "list of hours, and range - twenty four hour" $+ "At 02:01, 03:01 and at 1 minutes past the hour between 04:00 and 13:00, every day, every day of the week" @=?+ describeV "1 2,3,4-13 * * *"++ , testCase "list of hours, and range - twelve hour" $+ "At 02:01 AM, 03:01 AM and at 1 minutes past the hour between 04:00 AM and 01:00 PM, every day, every day of the week" @=?+ describe12 "1 2,3,4-13 * * *"++ , testCase "list of hours, and star" $+ "At 1 minutes past the hour, every hour, every day, every day of the week" @=? describeV "1 2,* * * *"+ ]+ , testGroup "describes other times" [+ testCase "range of minutes, range of hours" $+ "Minutes 10 through 15 past the hour, between 01:00 and 03:00, every day, every day of the week" @=?+ describeV "10-15 1-3 * * *"++ , testCase "range of minutes, every hour" $+ "Minutes 10 through 15 past the hour, every hour, every day, every day of the week" @=?+ describeV "10-15 * * * *"++ , testCase "range of minutes, interval of hours" $+ "Minutes 10 through 15 past the hour, every 3 hours, starting at 03:00, every day, every day of the week" @=?+ describeV "10-15 3/3 * * *"++ , testCase "list of minutes, at an hour" $+ "Every minute, at 03:00, every day, every day of the week" @=?+ describeV "2,* 3 * * *"++ , testCase "step minutes, step hours" $+ "Every 3 minutes, minutes 10 through 15 past the hour, every 5 hours, starting at 10:00, every day, every day of the week" @=?+ describeV "10-15/3 10/5 * * *"+ ]+ ]+ ]+ where+ mkCronSchedule t = let (Right cs) = parseCronSchedule t in cs+ describeNV = describe (twentyFourHourFormat <> notVerbose) . mkCronSchedule+ describeV = describe (twentyFourHourFormat <> verbose) . mkCronSchedule+ describe12 = describe verbose . mkCronSchedule