diff options
Diffstat (limited to 'tprint')
-rw-r--r-- | tprint/src/Instances.hs | 25 | ||||
-rw-r--r-- | tprint/src/Main.hs | 155 | ||||
-rw-r--r-- | tprint/src/Options.hs | 115 | ||||
-rw-r--r-- | tprint/src/Options/Utils.hs | 48 | ||||
-rw-r--r-- | tprint/tprint.cabal | 6 |
5 files changed, 201 insertions, 148 deletions
diff --git a/tprint/src/Instances.hs b/tprint/src/Instances.hs new file mode 100644 index 0000000..cffb8b0 --- /dev/null +++ b/tprint/src/Instances.hs | |||
@@ -0,0 +1,25 @@ | |||
1 | module Instances where | ||
2 | |||
3 | import Data.Text (Text) | ||
4 | import qualified Data.Text as T (unpack) | ||
5 | |||
6 | import Data.Time (UTCTime, formatTime, defaultTimeLocale) | ||
7 | |||
8 | import Text.Show.Pretty (Value, PrettyVal(..), dumpStr) | ||
9 | import qualified Text.Show.Pretty as PShow (Value(..)) | ||
10 | |||
11 | import Thermoprint.Client (Scheme(..), BaseUrl(..), PrinterId(..), JobId(..), DraftId(..), Range(..)) | ||
12 | |||
13 | instance PrettyVal Scheme | ||
14 | instance PrettyVal BaseUrl | ||
15 | instance PrettyVal PrinterId | ||
16 | instance PrettyVal JobId | ||
17 | instance PrettyVal DraftId | ||
18 | instance PrettyVal a => PrettyVal (Range a) | ||
19 | |||
20 | instance PrettyVal UTCTime where | ||
21 | prettyVal = PShow.String . formatTime defaultTimeLocale "%Z %F %X" | ||
22 | |||
23 | instance PrettyVal Text where | ||
24 | prettyVal = prettyVal . T.unpack | ||
25 | |||
diff --git a/tprint/src/Main.hs b/tprint/src/Main.hs index e8d4670..cd6e68b 100644 --- a/tprint/src/Main.hs +++ b/tprint/src/Main.hs | |||
@@ -1,6 +1,4 @@ | |||
1 | {-# LANGUAGE RecordWildCards #-} | 1 | {-# LANGUAGE RecordWildCards #-} |
2 | {-# LANGUAGE StandaloneDeriving #-} | ||
3 | {-# LANGUAGE DeriveGeneric, DeriveAnyClass #-} | ||
4 | 2 | ||
5 | import Data.Map (Map) | 3 | import Data.Map (Map) |
6 | import qualified Data.Map as Map | 4 | import qualified Data.Map as Map |
@@ -8,157 +6,22 @@ import Data.Sequence (Seq) | |||
8 | import qualified Data.Sequence as Seq | 6 | import qualified Data.Sequence as Seq |
9 | import Data.Text (Text) | 7 | import Data.Text (Text) |
10 | import qualified Data.Text as T | 8 | import qualified Data.Text as T |
11 | import Data.Time | ||
12 | |||
13 | import Data.Foldable | ||
14 | import Data.Char | ||
15 | import Text.Read | ||
16 | import Data.Maybe | ||
17 | import Data.List | ||
18 | 9 | ||
19 | import GHC.Generics (Generic) | 10 | import Control.Monad |
20 | 11 | ||
21 | import Text.Show.Pretty (Value, PrettyVal(..), dumpStr) | 12 | import Text.Show.Pretty (dumpStr) |
22 | import qualified Text.Show.Pretty as PShow (Value(..)) | 13 | |
14 | import System.IO | ||
23 | 15 | ||
24 | import Thermoprint.Client | 16 | import Thermoprint.Client |
25 | import Options.Applicative | 17 | import Options |
26 | 18 | ||
27 | import Debug.Trace | 19 | import Debug.Trace |
28 | 20 | ||
29 | instance PrettyVal Scheme | ||
30 | instance PrettyVal BaseUrl | ||
31 | instance PrettyVal PrinterId | ||
32 | instance PrettyVal JobId | ||
33 | instance PrettyVal DraftId | ||
34 | instance PrettyVal a => PrettyVal (Range a) | ||
35 | |||
36 | instance PrettyVal UTCTime where | ||
37 | prettyVal = PShow.String . formatTime defaultTimeLocale "%Z %F %X" | ||
38 | |||
39 | instance PrettyVal Text where | ||
40 | prettyVal = prettyVal . T.unpack | ||
41 | |||
42 | data TPrint = TPrint | ||
43 | { baseUrl :: BaseUrl | ||
44 | , dryRun :: Bool | ||
45 | , output :: Output | ||
46 | , operation :: Operation | ||
47 | } | ||
48 | deriving (Show, Generic, PrettyVal) | ||
49 | |||
50 | data Operation | ||
51 | = Printers | ||
52 | | Jobs | ||
53 | { printer :: Maybe PrinterId | ||
54 | , jobRange :: Maybe (Range JobId) | ||
55 | , timeRange :: Maybe (Range UTCTime) | ||
56 | } | ||
57 | | JobCreate | ||
58 | { printer :: Maybe PrinterId | ||
59 | , input :: Input | ||
60 | } | ||
61 | | Job { jobId :: JobId } | ||
62 | | JobStatus { jobId :: JobId } | ||
63 | | JobDelete { jobId :: JobId } | ||
64 | | Drafts | ||
65 | | DraftCreate | ||
66 | { draftTitle :: Maybe DraftTitle | ||
67 | , input :: Input | ||
68 | } | ||
69 | | DraftReplace | ||
70 | { draftId :: DraftId | ||
71 | , draftTitle :: Maybe DraftTitle | ||
72 | , input :: Input | ||
73 | } | ||
74 | | Draft { draftId :: DraftId } | ||
75 | | DraftDelete { draftId :: DraftId } | ||
76 | | DraftPrint | ||
77 | { draftId :: DraftId | ||
78 | , printer :: Maybe PrinterId | ||
79 | } | ||
80 | deriving (Show, Generic, PrettyVal) | ||
81 | |||
82 | type Input = (Format, Source) | ||
83 | type Output = (Format, Sink ) | ||
84 | |||
85 | data Format = Human | BBCode | Internal | JSON | ||
86 | deriving (Enum, Bounded, Show, Read, Generic, PrettyVal) | ||
87 | |||
88 | data Source = Stdin | ReadFile FilePath | ||
89 | deriving (Show, Generic, PrettyVal) | ||
90 | |||
91 | data Sink = Stdout | WriteFile FilePath | ||
92 | deriving (Show, Generic, PrettyVal) | ||
93 | |||
94 | cmdPrinters :: ParserInfo Operation | ||
95 | cmdPrinters = info cmdPrinters' $ mconcat [ header "List all available printers" | ||
96 | ] | ||
97 | where cmdPrinters' = pure Printers | ||
98 | |||
99 | cmdJobs :: ParserInfo Operation | ||
100 | cmdJobs = info cmdJobs' $ mconcat [ header "List printjobs" | ||
101 | ] | ||
102 | where | ||
103 | cmdJobs' = Jobs | ||
104 | <$> optional (option (PrinterId <$> auto) $ metavar "PRINTER" <> long "printer" <> short 'p' <> help "List only jobs associated with printer #PRINTER") | ||
105 | <*> pRange (JobId <$> auto) (metavar "JOB") (long "min-job" <> short 'j' <> help "List only jobs with id greater than or equal to JOB") (long "max-job" <> short 'J' <> help "List only jobs with id less than or equal to JOB") | ||
106 | <*> pRange rTime (metavar "TIME") (long "min-time" <> short 't' <> help "List only jobs created after or at TIME") (long "max-time" <> short 'T' <> help "List only jobs created before or at TIME") | ||
107 | |||
108 | pOperation :: Parser Operation | ||
109 | pOperation = hsubparser $ mconcat [ command "printers" cmdPrinters | ||
110 | , command "jobs" cmdJobs | ||
111 | ] | ||
112 | |||
113 | pTPrint :: Parser TPrint | ||
114 | pTPrint = TPrint <$> option (eitherReader parseBaseUrl) (metavar "URL" <> long "baseurl" <> short 'u' <> help "Server to interact with" <> value (BaseUrl Http "localhost" 3000) <> showDefaultWith showBaseUrl) | ||
115 | <*> switch (long "dry-run" <> short 'n' <> help "Don't send any requests that would be expected to change the servers state" <> showDefault) | ||
116 | <*> pOutput | ||
117 | <*> pOperation | ||
118 | |||
119 | pOutput :: Parser Output | ||
120 | pOutput = (,) <$> pOutputFormat <*> pSink | ||
121 | where | ||
122 | pOutputFormat = option ciAuto $ metavar "FORMAT" <> long "output-format" <> short 'O' <> help ("Format to use for all output (possible values: " ++ show ([minBound..maxBound] :: [Format]) ++ ")") <> value Human <> showDefault | ||
123 | pSink = option rSink $ metavar "PATH" <> long "output" <> short 'o' <> help "Send output to PATH (`-' means stdout)" <> value Stdout <> showDefault | ||
124 | rSink = rSink' <$> str | ||
125 | where | ||
126 | rSink' "-" = Stdout | ||
127 | rSink' x = WriteFile x | ||
128 | |||
129 | ciAuto :: (Read a, Show a) => ReadM a | ||
130 | ciAuto = eitherReader rRep' | ||
131 | where | ||
132 | rRep' str = case mapMaybe readMaybe $ cases str of | ||
133 | [] -> Left $ "Could not parse `" ++ str ++ "'" | ||
134 | [x] -> Right x | ||
135 | xs -> Left $ "Ambiguous parse for `" ++ str ++ "': " ++ show xs | ||
136 | cases [] = [] | ||
137 | cases (c:cs) = [(c':cs') | c' <- [toLower c, toUpper c], cs' <- cases cs] | ||
138 | |||
139 | rTime :: ReadM UTCTime | ||
140 | rTime = eitherReader rTime' | ||
141 | where | ||
142 | rTime' str = maybe (Left $ "Could not parse `" ++ str ++ "' as a specification of time -- try the output of `date -u +'%Y-%m-%d %H:%M:%S' \"" ++ str ++ "\"`") Right | ||
143 | . foldr (<|>) Nothing | ||
144 | $ map (flip (parseTimeM True defaultTimeLocale) str) ( "%c" : "%s" : perms' [ [time, date, timezone] | time <- ["%T", "%X", "%I:%M:%S", "%r", "%X%Q", "%I:%M:%S%Q %p"], date <- ["%x", "%F"], timezone <- ["%z", "%Z"]] ) | ||
145 | perms' :: [[String]] -> [String] | ||
146 | perms' = map unwords . concatMap subsequences . concatMap permutations | ||
147 | |||
148 | pRange :: ReadM a | ||
149 | -> Mod OptionFields a -- ^ Modifiers applied to both min & max | ||
150 | -> Mod OptionFields a -- ^ Modifiers for minimum | ||
151 | -> Mod OptionFields a -- ^ Modifiers for maximum | ||
152 | -> Parser (Maybe (Range a)) | ||
153 | pRange r both min max = toRange <$> optional (option r (both <> min)) <*> optional (option r (both <> max)) | ||
154 | where | ||
155 | toRange Nothing Nothing = Nothing | ||
156 | toRange (Just min) Nothing = Just $ Min min | ||
157 | toRange Nothing (Just max) = Just $ Max max | ||
158 | toRange (Just min) (Just max) = Just $ min `Through` max | ||
159 | |||
160 | main :: IO () | 21 | main :: IO () |
161 | main = customExecParser (prefs $ showHelpOnError) (info pTPrint $ header "tprint - A cli interface to Thermoprint.Client") >>= tprint | 22 | main = withArgs (tprint <=< dumpOpts) |
23 | where | ||
24 | dumpOpts c@(TPrint{..}) = c <$ when (dumpOptions) (hPutStrLn stderr $ dumpStr c) | ||
162 | 25 | ||
163 | tprint :: TPrint -> IO () | 26 | tprint :: TPrint -> IO () |
164 | tprint = putStrLn . dumpStr -- *DEBUG* | 27 | tprint = undefined |
diff --git a/tprint/src/Options.hs b/tprint/src/Options.hs new file mode 100644 index 0000000..96bbde6 --- /dev/null +++ b/tprint/src/Options.hs | |||
@@ -0,0 +1,115 @@ | |||
1 | {-# LANGUAGE StandaloneDeriving #-} | ||
2 | {-# LANGUAGE DeriveGeneric, DeriveAnyClass #-} | ||
3 | |||
4 | module Options | ||
5 | ( TPrint(..) | ||
6 | , Operation(..) | ||
7 | , Input, Output | ||
8 | , Format(..), Source(..), Sink(..) | ||
9 | , withArgs | ||
10 | , module Options.Utils | ||
11 | ) where | ||
12 | |||
13 | import Data.Time | ||
14 | |||
15 | import GHC.Generics (Generic) | ||
16 | |||
17 | import Text.Show.Pretty (PrettyVal) | ||
18 | |||
19 | import Thermoprint.Client | ||
20 | import Options.Applicative | ||
21 | |||
22 | import Options.Utils | ||
23 | import Instances () | ||
24 | |||
25 | data TPrint = TPrint | ||
26 | { baseUrl :: BaseUrl | ||
27 | , dryRun :: Bool | ||
28 | , output :: Output | ||
29 | , operation :: Operation | ||
30 | , dumpOptions :: Bool | ||
31 | } | ||
32 | deriving (Show, Generic, PrettyVal) | ||
33 | |||
34 | data Operation | ||
35 | = Printers | ||
36 | | Jobs | ||
37 | { printer :: Maybe PrinterId | ||
38 | , jobRange :: Maybe (Range JobId) | ||
39 | , timeRange :: Maybe (Range UTCTime) | ||
40 | } | ||
41 | | JobCreate | ||
42 | { printer :: Maybe PrinterId | ||
43 | , input :: Input | ||
44 | } | ||
45 | | Job { jobId :: JobId } | ||
46 | | JobStatus { jobId :: JobId } | ||
47 | | JobDelete { jobId :: JobId } | ||
48 | | Drafts | ||
49 | | DraftCreate | ||
50 | { draftTitle :: Maybe DraftTitle | ||
51 | , input :: Input | ||
52 | } | ||
53 | | DraftReplace | ||
54 | { draftId :: DraftId | ||
55 | , draftTitle :: Maybe DraftTitle | ||
56 | , input :: Input | ||
57 | } | ||
58 | | Draft { draftId :: DraftId } | ||
59 | | DraftDelete { draftId :: DraftId } | ||
60 | | DraftPrint | ||
61 | { draftId :: DraftId | ||
62 | , printer :: Maybe PrinterId | ||
63 | } | ||
64 | deriving (Show, Generic, PrettyVal) | ||
65 | |||
66 | type Input = (Format, Source) | ||
67 | type Output = (Format, Sink ) | ||
68 | |||
69 | data Format = Human | BBCode | Internal | JSON | ||
70 | deriving (Enum, Bounded, Show, Read, Generic, PrettyVal) | ||
71 | |||
72 | data Source = Stdin | ReadFile FilePath | ||
73 | deriving (Show, Generic, PrettyVal) | ||
74 | |||
75 | data Sink = Stdout | WriteFile FilePath | ||
76 | deriving (Show, Generic, PrettyVal) | ||
77 | |||
78 | cmdPrinters :: ParserInfo Operation | ||
79 | cmdPrinters = info cmdPrinters' $ mconcat [ header "List all available printers" | ||
80 | ] | ||
81 | where cmdPrinters' = pure Printers | ||
82 | |||
83 | cmdJobs :: ParserInfo Operation | ||
84 | cmdJobs = info cmdJobs' $ mconcat [ header "List printjobs" | ||
85 | ] | ||
86 | where | ||
87 | cmdJobs' = Jobs | ||
88 | <$> optional (option (PrinterId <$> auto) $ metavar "PRINTER" <> long "printer" <> short 'p' <> help "List only jobs associated with printer #PRINTER") | ||
89 | <*> pRange (JobId <$> auto) (metavar "JOB") (long "min-job" <> short 'j' <> help "List only jobs with id greater than or equal to JOB") (long "max-job" <> short 'J' <> help "List only jobs with id less than or equal to JOB") | ||
90 | <*> pRange rTime (metavar "TIME") (long "min-time" <> short 't' <> help "List only jobs created after or at TIME") (long "max-time" <> short 'T' <> help "List only jobs created before or at TIME") | ||
91 | |||
92 | pOperation :: Parser Operation | ||
93 | pOperation = hsubparser $ mconcat [ command "printers" cmdPrinters | ||
94 | , command "jobs" cmdJobs | ||
95 | ] | ||
96 | |||
97 | pTPrint :: Parser TPrint | ||
98 | pTPrint = TPrint <$> option (eitherReader parseBaseUrl) (metavar "URL" <> long "baseurl" <> short 'u' <> help "Server to interact with" <> value (BaseUrl Http "localhost" 3000) <> showDefaultWith showBaseUrl) | ||
99 | <*> switch (long "dry-run" <> short 'n' <> help "Don't send any requests that would be expected to change the servers state" <> showDefault) | ||
100 | <*> pOutput | ||
101 | <*> pOperation | ||
102 | <*> switch (long "dump-options" <> internal) | ||
103 | |||
104 | pOutput :: Parser Output | ||
105 | pOutput = (,) <$> pOutputFormat <*> pSink | ||
106 | where | ||
107 | pOutputFormat = option rCI $ metavar "FORMAT" <> long "output-format" <> short 'O' <> help ("Format to use for all output (possible values: " ++ show ([minBound..maxBound] :: [Format]) ++ ")") <> value Human <> showDefault | ||
108 | pSink = option rSink $ metavar "PATH" <> long "output" <> short 'o' <> help "Send output to PATH (`-' means stdout)" <> value Stdout <> showDefault | ||
109 | rSink = rSink' <$> str | ||
110 | where | ||
111 | rSink' "-" = Stdout | ||
112 | rSink' x = WriteFile x | ||
113 | |||
114 | withArgs :: (TPrint -> IO a) -> IO a | ||
115 | withArgs a = customExecParser (prefs $ showHelpOnError) (info pTPrint $ header "tprint - A cli interface to Thermoprint.Client") >>= a | ||
diff --git a/tprint/src/Options/Utils.hs b/tprint/src/Options/Utils.hs new file mode 100644 index 0000000..80b4a7e --- /dev/null +++ b/tprint/src/Options/Utils.hs | |||
@@ -0,0 +1,48 @@ | |||
1 | module Options.Utils | ||
2 | ( rCI | ||
3 | , rTime | ||
4 | , pRange | ||
5 | ) where | ||
6 | |||
7 | import Options.Applicative | ||
8 | |||
9 | import Data.Char | ||
10 | import Data.Maybe | ||
11 | import Data.List | ||
12 | import Text.Read | ||
13 | |||
14 | import Data.Time | ||
15 | |||
16 | import Thermoprint.Client (Range(..)) | ||
17 | |||
18 | rCI :: (Read a, Show a) => ReadM a | ||
19 | rCI = eitherReader rRep' | ||
20 | where | ||
21 | rRep' str = case mapMaybe readMaybe $ cases str of | ||
22 | [] -> Left $ "Could not parse `" ++ str ++ "'" | ||
23 | [x] -> Right x | ||
24 | xs -> Left $ "Ambiguous parse for `" ++ str ++ "': " ++ show xs | ||
25 | cases [] = [] | ||
26 | cases (c:cs) = [(c':cs') | c' <- [toLower c, toUpper c], cs' <- cases cs] | ||
27 | |||
28 | rTime :: ReadM UTCTime | ||
29 | rTime = eitherReader rTime' | ||
30 | where | ||
31 | rTime' str = maybe (Left $ "Could not parse `" ++ str ++ "' as a specification of time -- try the output of `date -u +'%Y-%m-%d %H:%M:%S' \"" ++ str ++ "\"`") Right | ||
32 | . foldr (<|>) Nothing | ||
33 | $ map (flip (parseTimeM True defaultTimeLocale) str) ( "%c" : "%s" : perms' [ [time, date, timezone] | time <- ["%T", "%X", "%I:%M:%S", "%r", "%X%Q", "%I:%M:%S%Q %p"], date <- ["%x", "%F"], timezone <- ["%z", "%Z"]] ) | ||
34 | perms' :: [[String]] -> [String] | ||
35 | perms' = map unwords . concatMap subsequences . concatMap permutations | ||
36 | |||
37 | pRange :: ReadM a | ||
38 | -> Mod OptionFields a -- ^ Modifiers applied to both min & max | ||
39 | -> Mod OptionFields a -- ^ Modifiers for minimum | ||
40 | -> Mod OptionFields a -- ^ Modifiers for maximum | ||
41 | -> Parser (Maybe (Range a)) | ||
42 | pRange r both min max = toRange <$> optional (option r (both <> min)) <*> optional (option r (both <> max)) | ||
43 | where | ||
44 | toRange Nothing Nothing = Nothing | ||
45 | toRange (Just min) Nothing = Just $ Min min | ||
46 | toRange Nothing (Just max) = Just $ Max max | ||
47 | toRange (Just min) (Just max) = Just $ min `Through` max | ||
48 | |||
diff --git a/tprint/tprint.cabal b/tprint/tprint.cabal index d61ea01..aeb61c9 100644 --- a/tprint/tprint.cabal +++ b/tprint/tprint.cabal | |||
@@ -18,7 +18,9 @@ cabal-version: >=1.10 | |||
18 | 18 | ||
19 | executable tprint | 19 | executable tprint |
20 | main-is: Main.hs | 20 | main-is: Main.hs |
21 | -- other-modules: | 21 | other-modules: Options |
22 | , Options.Utils | ||
23 | , Instances | ||
22 | -- other-extensions: | 24 | -- other-extensions: |
23 | build-depends: base >=4.8 && <4.9 | 25 | build-depends: base >=4.8 && <4.9 |
24 | , thermoprint-bbcode >=1.0.0 && <2 | 26 | , thermoprint-bbcode >=1.0.0 && <2 |
@@ -27,7 +29,7 @@ executable tprint | |||
27 | , containers >=0.5.6 && <1 | 29 | , containers >=0.5.6 && <1 |
28 | , time >=1.5.0 && <2 | 30 | , time >=1.5.0 && <2 |
29 | , pretty-show >=1.6.9 && <2 | 31 | , pretty-show >=1.6.9 && <2 |
30 | , text -any | 32 | , text >=1.2.2 && <2 |
31 | hs-source-dirs: src | 33 | hs-source-dirs: src |
32 | default-language: Haskell2010 | 34 | default-language: Haskell2010 |
33 | ghc-options: -Wall \ No newline at end of file | 35 | ghc-options: -Wall \ No newline at end of file |