1 {-# LANGUAGE NoMonomorphismRestriction #-}
2 {-# LANGUAGE OverloadedStrings #-}
3 {-# LANGUAGE Rank2Types #-}
4 {-# LANGUAGE StrictData #-}
5 {-# LANGUAGE UndecidableInstances #-} -- for VP.Reifies instances
6 {-# OPTIONS_GHC -Wno-missing-signatures #-}
7 module Hjugement.CLI.Utils where
9 import Control.Arrow (left)
10 import Control.Applicative (Alternative(..))
11 import Control.Monad (Monad(..), forM_)
12 import Control.Monad.Trans.State.Strict (StateT(..), evalStateT)
13 import Data.Bits (setBit)
15 import Data.ByteString (ByteString)
16 import Data.Either (Either(..))
17 import Data.Foldable (Foldable)
18 import Data.Function (($), (.), id)
19 import Data.Functor ((<$>))
20 import Data.Maybe (Maybe(..), maybe)
21 import Data.Monoid (Monoid(..))
22 import Data.Semigroup (Semigroup(..))
23 import Data.Text (Text)
24 import Prelude (max, (-))
25 import Symantic.CLI as CLI
27 import Text.Show (Show(..))
28 import qualified Crypto.Hash as Crypto
29 import qualified Data.Aeson as JSON
30 import qualified Data.ByteArray as ByteArray
31 import qualified Data.ByteString as BS
32 import qualified Data.ByteString.Char8 as BS8
33 import qualified Data.ByteString.Lazy.Char8 as BSL8
34 import qualified Data.Text as Text
35 import qualified Data.Text.Encoding as T
36 import qualified Data.Text.Lazy as TL
37 import qualified Data.Text.Lazy.Builder as TLB
38 import qualified Data.Text.Lazy.Builder.Int as TLB
39 import qualified Lens.Family as Lens
40 import qualified Lens.Family.State.Strict as Lens
41 import qualified Pipes as Pip
42 import qualified Pipes.Aeson as PipJSON (DecodingError(..))
43 import qualified Pipes.Aeson.Unchecked as PipJSON
44 import qualified Pipes.ByteString as PipBS
45 import qualified Pipes.Group as Pip
46 import qualified Pipes.Parse as Pip
47 import qualified Pipes.Prelude as Pip
48 import qualified Pipes.Safe as Pip
49 import qualified Pipes.Safe.Prelude as Pip
50 import qualified Symantic.Document as Doc
51 import qualified System.Console.Terminal.Size as Console
52 import qualified System.Directory as IO
53 import qualified System.FilePath as FP
54 import qualified System.IO as IO
55 import qualified System.Posix as Posix
56 import qualified Voting.Protocol as VP
60 helps = help . Doc.justify
64 type Doc = Doc.Plain TLB.Builder
67 "Read election template from file "<>ref"FILE"<>"."
69 longOpt "template" "" (var "FILE")
71 "Take cryptographic parameters from file "<>fileRef "FILE"<>"."
73 long "crypto" (var "FILE")
74 instance CLI.IOType VP.FFC
75 instance CLI.FromSegment VP.FFC where
76 fromSegment = JSON.eitherDecodeFileStrict'
77 instance VP.Reifies c VP.FFC => CLI.FromSegment (VP.E c) where
78 fromSegment = JSON.eitherDecodeFileStrict'
80 "UUID of the election."
82 long "uuid" (var "UUID")
83 instance CLI.IOType VP.UUID
84 instance CLI.FromSegment VP.UUID where
85 fromSegment = return . left show . VP.readUUID . Text.pack
86 instance CLI.IOType VP.Credential
87 instance CLI.FromSegment VP.Credential where
88 fromSegment = return . left show . VP.readCredential . Text.pack
89 instance IOType (VP.DecryptionShare ())
90 instance Outputable (VP.DecryptionShare ()) where
91 output decShare = output $ JSON.encode decShare<>"\n"
96 api_compact <.> response @Doc <!>
97 api_full <.> response @Doc
99 (api_compact <!> api_full) <.> response @Doc
103 then help "Print an uncommented grammar tree to help using this program."
105 tagged (TagShort 'h') (just False)
108 "Print a grammar tree to help using this program,\
109 \ along with explanations."
111 tagged (TagLong "help") (just True)
113 run_help lay = route :!: route
115 route helpInh_full = do
116 width <- Just . maybe 80 (max 0 . (\x -> x - 2) . Console.width) <$> Console.size
119 runLayout helpInh_full lay
121 -- * Type 'Global_Options'
122 data Global_Options = Global_Options
123 { global_dir :: IO.FilePath
131 "Use directory "<>ref"DIR"<>" for reading and writing election files."
133 toPermDefault (FP.takeDirectory "file")
134 ( tagged (TagLong "dir") (var "DIR") `alt`
135 env "HJUGEMENT_DIR" )
142 Pip.Consumer (f Text) m r
143 writeFileLn fileMode filePath =
144 Pip.bracket open close $ \h ->
145 Pip.for Pip.cat $ \xs ->
147 forM_ xs $ BS8.hPutStr h . T.encodeUtf8
150 open = Pip.liftIO $ do
151 IO.createDirectoryIfMissing True $ FP.takeDirectory filePath
152 h <- IO.openFile filePath IO.WriteMode
154 close h = Pip.liftIO $ do
155 fd <- Posix.handleToFd h
156 Posix.setFdMode fd fileMode
165 writeJSON fileMode filePath =
166 Pip.bracket open close $ \h ->
167 Pip.for Pip.cat $ \a ->
169 BSL8.hPutStr h $ JSON.encode a
171 open = Pip.liftIO $ do
172 IO.createDirectoryIfMissing True $ FP.takeDirectory filePath
173 h <- IO.openFile filePath IO.WriteMode
175 close h = Pip.liftIO $ do
176 fd <- Posix.handleToFd h
177 Posix.setFdMode fd fileMode
185 Pip.Producer a m (Either Doc ())
187 let bytes = Pip.withFile filePath IO.ReadMode PipBS.fromHandle in
188 left handleError <$> Lens.view PipJSON.decoded bytes
190 handleError (err, _rest) =
192 PipJSON.AttoparsecError parsingErr ->
193 Doc.from filePath <> ": "<>
194 Doc.redder "parsing error"<>": "<>
195 Doc.from (show parsingErr) <> "\n"
196 PipJSON.FromJSONError decodingErr ->
197 Doc.from filePath <> ": "<>
198 Doc.redder "decoding error"<>": "<>
199 Doc.from decodingErr <> "\n"
207 Pip.Parser a m r -> m r
208 readJSON' filePath fold =
209 let bytes = Pip.withFile filePath IO.ReadMode PipBS.fromHandle in
210 evalStateT (Lens.zoom PipJSON.decoded fold) bytes
216 Pip.FreeT (Pip.Producer (Either PipJSON.DecodingError a) m) m ()
217 readJSON'' filePath =
218 let bytes = Pip.withFile filePath IO.ReadMode PipBS.fromHandle in
219 parseMany (Pip.parsed_ PipJSON.decode) bytes
224 (Pip.Producer inp m r -> Pip.Producer a m (Pip.Producer inp m r)) ->
225 Pip.Producer inp m r ->
226 Pip.FreeT (Pip.Producer a m) m r
230 Pip.Producer inp m r ->
231 Pip.FreeT (Pip.Producer a m) m r
232 go0 p = Pip.FreeT $ do
234 Left r -> return $ Pip.Pure r
235 Right (inp, p') -> return $ Pip.Free $ go1 $ Pip.yield inp >> p'
237 Pip.Producer inp m r ->
238 Pip.Producer a m (Pip.FreeT (Pip.Producer a m) m r)
244 Outputable (OnHandle d) =>
245 (a -> d) -> Pip.Pipe a a m r
247 Pip.for Pip.cat $ \s -> do
249 output $ OnHandle IO.stderr (d s)
250 output $ OnHandle IO.stderr '\n'
253 outputError :: Doc -> IO (Maybe a)
255 output $ OnHandle @Doc IO.stderr $ Doc.redder "ERROR"<>": "<>msg