-- | -- Module: NetSpider.CLI.Snapshot -- Description: CLI option parser for Query for snapshot graphs -- Maintainer: Toshio Ito -- -- This module defines CLI option parser for 'Q.Query' for snapshot -- graphs. module NetSpider.CLI.Snapshot ( parserSnapshotQuery, makeSnapshotQuery, SnapshotConfig(..), CLISnapshotQuery ) where import Control.Applicative ((<$>), (<*>), (<|>), many, optional, empty) import Data.Int (Int64) import NetSpider.Interval ( interval, parseTimeIntervalEnd, secSince, secUntil, IntervalEnd, Interval ) import qualified NetSpider.Query as Q import NetSpider.Timestamp (Timestamp) import qualified Options.Applicative as Opt import qualified Options.Applicative.Types as OptT -- | Configuration for option parser for Snapshot 'Q.Query'. -- -- @since 0.2.0.0 data SnapshotConfig n = SnapshotConfig { nodeIDReader :: Opt.ReadM n, -- ^ Parser that reads a CLI option to generate a node ID. startsFromAsArguments :: Bool -- ^ If 'True', the 'Q.startsFrom' field is read from CLI -- arguments. If 'False', arguments are not parsed. In either -- case, \"-s\" option is always parsed to generate -- 'Q.startsFrom'. } -- | Settings for Snapshot 'Q.Query' parsed from the command-line -- options. You can make 'Q.Query' by 'makeSnapshotQuery' function. -- -- @since 0.2.0.0 data CLISnapshotQuery n = CLISnapshotQuery { startsFrom :: [n], timeDurationSec :: Maybe Int64, timeFrom :: Maybe (IntervalEnd Timestamp), timeTo :: Maybe (IntervalEnd Timestamp) } deriving (Show,Eq,Ord) -- | Make a 'Q.Query' by applying 'CLISnapshotQuery' to the base -- query. The 'CLISnapshotQuery' overwrites 'Q.startsFrom' and -- 'Q.timeInterval' fields. -- -- It can fail to convert 'CLISnapshotQuery' to 'Q.Query' fields. In -- that case, the result is 'Left' with a human-readable error -- message. -- -- @since 0.2.0.0 makeSnapshotQuery :: Q.Query n na fla sla -- ^ base query -> CLISnapshotQuery n -> Either String (Q.Query n na fla sla) -- ^ Left: human-readable error message. Right: updated query makeSnapshotQuery q cliq = do ivl <- makeTimeInterval cliq return $ q { Q.startsFrom = startsFrom cliq, Q.timeInterval = ivl } makeTimeInterval :: CLISnapshotQuery n -> Either String (Interval Timestamp) makeTimeInterval c = case (timeFrom c, timeTo c, timeDurationSec c) of (ms, me, Nothing) -> Right $ interval s e where s = maybe (Q.NegInf, False) id ms e = maybe (Q.PosInf, False) id me (Just s, Nothing, Just d) -> Right $ secSince d s (Nothing, Just e, Just d) -> Right $ secUntil d e (Just _, Just _, Just _) -> Left ("Specifying all --time-to, --time-from and --duration is not allowed.") (Nothing, Nothing, Just _) -> Left ("Specifying --duration only is not allowed. Specify --time-to or --time-from, too.") -- | CLI option parser for Snapshot 'Q.Query'. Use 'makeSnapshotQuery' -- to convert 'CLISnapshotQuery' to 'Q.Query'. -- -- @since 0.2.0.0 parserSnapshotQuery :: SnapshotConfig n -> Opt.Parser (CLISnapshotQuery n) parserSnapshotQuery conf = CLISnapshotQuery <$> pStartsFromTotal <*> pDuration <*> pTimeLower <*> pTimeUpper where pStartsFromTotal = (++) <$> pStartsFrom <*> pStartsFromArgs rNodeID = nodeIDReader conf nodeID_metavar = "NODE-ID" pStartsFrom = many $ Opt.option rNodeID $ mconcat [ Opt.short 's', Opt.long "starts-from", Opt.help "ID of a node from which the Spider starts traversing the history graph. You can specify this option multiple times.", Opt.metavar nodeID_metavar ] pStartsFromArgs = if not $ startsFromAsArguments conf then pure [] else many $ Opt.argument rNodeID $ mconcat [ Opt.help $ "Same as -s option.", Opt.metavar $ nodeID_metavar ] pTimeLower = optional $ Opt.option (Opt.eitherReader parseTimeIntervalEnd) $ mconcat ( [ Opt.short 'f', Opt.long "time-from", Opt.help ( "Lower bound of query timestamp. " ++ "Local findings with timestamp newer than this value are used to create the snapshot graph. " ++ "ISO 8601 format is used for timestamps (e.g. `2019-03-22T10:20:12+09:00`). " ++ "The timezone is optional. " ++ "By default, the lower bound is inclusive. " ++ "Add prefix 'x' to make it exclusive (e.g. `x2019-03-22T10:20:12+09:00`). " ++ "Prefix of 'i' explicitly mark the lower bound is inclusive. " ++ "Special value `x-inf` indicates there is no lower bound. " ++ "Default: x-inf" ), Opt.metavar "TIMESTAMP" ] ) pTimeUpper = optional $ Opt.option (Opt.eitherReader parseTimeIntervalEnd) $ mconcat ( [ Opt.short 't', Opt.long "time-to", Opt.help ( "Upper bound of query timestamp. " ++ "Local findings with timestamp older than this value are used to create the snapshot graph. " ++ "See --time-from for format of timestamps. " ++ "Special value `x+inf` indicates there is no upper bound. " ++ "Default: x+inf" ), Opt.metavar "TIMESTAMP" ] ) pDuration = optional $ Opt.option Opt.auto $ mconcat [ Opt.short 'd', Opt.long "duration", Opt.help ( "Duration of the query time interval in seconds. " ++ " Use with either --time-to or --time-from option." ), Opt.metavar "SECONDS" ]