hflags 0.1.2 → 0.1.3
raw patch · 8 files changed
+286/−27 lines, 8 filesPVP ok
version bump matches the API change (PVP)
API changes (from Hackage documentation)
+ HFlags: class Flag a
+ HFlags: getFlagData :: Flag a => a -> FlagData
+ HFlags: instance Show FlagData
Files
- BLOG.md +190/−0
- HFlags.hs +42/−25
- README.md +4/−0
- examples/X/B.hs +3/−0
- examples/package/Tup.hs +12/−0
- examples/package/test/main.hs +8/−0
- examples/package/tup.cabal +19/−0
- hflags.cabal +8/−2
+ BLOG.md view
@@ -0,0 +1,190 @@+Published as, http://blog.risko.hu/2012/04/ann-hflags-0.html,+replicated here for redundancy.++HFlags is library for making it easier to specify and use command line+flags in Haskell programs and libraries. It is very similar in its+concepts to Google's [gflags](http://code.google.com/p/gflags)+library.++#### TL;DR++Example:++```haskell+#!/usr/bin/env runhaskell++{-# LANGUAGE TemplateHaskell #-}++import HFlags++defineFlag "name" "Indiana Jones" "Who to greet."+defineFlag "r:repeat" (3 + 4 :: Int)+ "Number of times to repeat the message."++main = do remainingArgs <- $(initHFlags "Simple program v0.1")+ sequence_ $ replicate flags_repeat greet+ where+ greet = putStrLn $ "Hello "+ ++ flags_name+ ++ ", very nice to meet you!"+```++Code: https://github.com/errge/hflags<br>+Docs: http://hackage.haskell.org/packages/archive/hflags/latest/doc/html/HFlags.html<br>+More examples: https://github.com/errge/hflags/tree/master/examples++#### There are a tons of flags libraries already for Haskell, aren't there?++Yes, but none like [gflags](http://code.google.com/p/gflags)! All of+them are like+[getopt](http://www.gnu.org/software/libc/manual/html_node/Getopt.html).+Some has fancy Template Haskell automation, some not, but in general,+they are all the same. If you want to look into some, I recommend+[CmdArgs](http://hackage.haskell.org/package/cmdargs) and+[options](http://hackage.haskell.org/package/options).++Some properties of getopt like libraries, that I don't like:++* You can only access the flags in the `IO` monad. This seems to be+ reasonable, at least at first look. Flags originate from the+ environment, and accessing the environment is only safe through+ `IO`, right? But in my opinion, they are more similar to constants,+ they should be easy to use everywhere.+* You have to pass the flags to every function where you want to use+ them. This is also something, that is not true for simple top level+ constants, why should flags behave differently?+* Getopt makes it very hard to compose different code parts+ (e.g. libraries) that all use command line flags. Imagine, that you+ are implementing a sendmail library, which will use the default+ `/usr/bin/sendmail` executable on the system, but gives the user the+ flexibility to change the path via command line flags. This can be+ done via getopt like thinking, but it requires a lot of boilerplate+ in the program using your library (for an example, have a look on+ [option's import feature](http://hackage.haskell.org/packages/archive/options/0.1.1/doc/html/Options.html#g:6)+ and imagine doing that for all of the libs you use).+ [Gflags](http://code.google.com/p/gflags) solved this issue very+ nicely for C++/Java/Python, but there were no similar solution to+ Haskell.++HFlags tries to get rid of these properties and be as simple and easy+to use as possible.++#### gflags C++ example++As a motivation for HFlags, let's have a look on Google's C++ example:++```c+++#include <gflags/gflags.h>++DEFINE_bool(big_menu, true,+ "Include 'advanced' options in the menu listing");+DEFINE_string(languages, "english,french,german",+ "comma-separated list of languages to offer");++...++int main(int argc, char **argv) {+ google::ParseCommandLineFlags(&argc, &argv, true);+ ...+ if (FLAGS_big_menu) { ... }+ ...+}+```++Note, that once you called `google::ParseCommandLineFlags` you're+done. All of the flags in every linked in C++ file gets initialized+by that call and you can access all of the flags in every file (where+they are declared) via top level, global names.++#### Achieve the same in Haskell++It was a long journey to achieve this kind of comfort in Haskell, in a+later post I'll do a code walk around `HFlags.hs`, but it's already+well commented and should be understandable for anyone who is familiar+with type classes, instances and a little bit of Template Haskell.+Instead, let's concentrate usage for now!++If you decide to give it a try, all you have to do is to+[`cabal install hflags`](http://hackage.haskell.org/package/hflags),+then+[`import HFlags`](http://hackage.haskell.org/packages/archive/hflags/latest/doc/html/HFlags.html)+in your source files where you define flags and in your main. After+that, you can use+[`defineFlag`](http://hackage.haskell.org/packages/archive/hflags/latest/doc/html/HFlags.html#v:defineFlag)+for flags with type of `Bool`, `Double`, `Int`, `Integer` and+`String`. If you need other types, you can look into `defineQQFlag`+and `defineCustomFlag`.++The last step is to make sure that you call+[`initHFlags`](http://hackage.haskell.org/packages/archive/hflags/latest/doc/html/HFlags.html#v:initHFlags)+as the first thing in your main.++If you are not up to coding right now, have a look at the+[simple](http://github.com/errge/hflags/blob/master/examples/SimpleExample.hs)+and the+[complex](http://github.com/errge/hflags/blob/master/examples/ComplexExample.hs)+[example](http://github.com/errge/hflags/tree/master/examples). If+you can't believe that we can expose the flags in all the imported+modules automatically and you need a demonstration, look into+[ImportExample.hs](https://github.com/errge/hflags/blob/master/examples/ImportExample.hs).++#### Some criticism, we already heard++##### Fake pureness and usage of `unsafePerformIO` is bad!++The criticism goes like this: "This library is not pure, you are using+`unsafePerformIO`. This is unsafe, it's in its name. I don't know+what does that mean, but it can't be good, it's unsafe. So unsafe.+Are you sure that this is OK?"++TL;DR: yes, we are sure, kind of.++Longer version: there are two uses of `unsafePerformIO` in our code,+one is trivial and well known, the other is a bit more tricky.++The simple one is responsible for the creation of the global `IORef`,+holding the `Map` that maps flag names to values. Here, we used the+standard way to create a top level mutable variable, as discussed in+[the wiki](http://www.haskell.org/haskellwiki/Top_level_mutable_state).++The other one is when you define flag foobar, we create a top level+constant with the name `flags_foobar` containing the value. This is+not a top level mutable variable, but a constant, so the wiki page+doesn't apply. The usage of `unsafePerformIO` means that you can be+afraid of that these constants are not really constant and they change+randomly (depending on evaluation order, environment, state of the+moon) and not at all referentially transparent anymore.++To address this concern, we force evaluate all of these top level+constants at `initHFlags` time, so the thunk containing+`unsafePerformIO` gets evaluated in them and they become real+constants. We generate a `NOINLINE` pragma for them, so they won't be+duplicated (and actually that wouldn't cause any issue either).++If you are interested to read more about these issues and other real+world issues in Haskell, I strongly recommend reading the very well+written+[Tackling the Awkward Squad from Simon Peyton Jones](http://research.microsoft.com/~simonpj/papers/marktoberdorf/mark.pdf.gz).++We are aware of these dangers, but we think that a trade-off had to be+cut to make command line flags usable, easy to manage and fun to have.+If you don't agree with the necessity of these considerations and you+believe only in totally pure solutions, this library is not for you.++Also, we are not experts on this topic, so if you still think that we+made an error somewhere and you can come up with some *real* example,+where our library screws up your program, definitely leave a comment!++##### Programs using this library will be hard to test!++If you're unittesting some code where the behavior can be seriously+changed via command line flags then this library is probably not your+biggest concern. Those things should be system (functional) tested,+where specifying flags is totally normal.++BTW, have you heard of+[withArgs](http://hackage.haskell.org/packages/archive/base/latest/doc/html/System-Environment.html#v:withArgs)?++#### Comments are welcome!++[](http://www.gergely.risko.hu/blogphotos/thalwil-20120430-orig.jpg)
HFlags.hs view
@@ -19,6 +19,7 @@ {-# LANGUAGE TypeSynonymInstances #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE NamedFieldPuns #-}+{-# LANGUAGE MultiWayIf #-} -- | -- Module: HFlags@@ -39,7 +40,8 @@ -- from @main@. This means, that any Haskell package can easily -- define command line flags with @HFlags@. This feature is -- demonstrated by--- <http://github.com/errge/hflags/blob/master/examples/ImportExample.hs>.+-- <http://github.com/errge/hflags/blob/master/examples/ImportExample.hs>+-- and <http://github.com/errge/hflags/tree/master/examples/package>. -- -- A simple example (more in the -- <http://github.com/errge/hflags/tree/master/examples> directory):@@ -73,10 +75,13 @@ -- * Initialization of flags at runtime initHFlags, -- * For easy access to arguments, after initHFlags has been called- arguments+ arguments,+ -- * For debugging, shouldn't be used in production code+ Flag(..) ) where -- TODOs:+-- duplicate checking for short options: it's tricky, we need to encode info in the HFlag_... data name -- ?--no* for bools? -- --help should show the current value if it's different than the default value, so user can test command line args @@ -104,13 +109,16 @@ { fName :: String , fShort :: Maybe Char , fDefValue :: String- , fArgHelp :: String+ , fArgType :: String , fDescription :: String , fModuleName :: String , fCheck :: IO () -- ^ function to evaluate in 'initFlags' -- to force syntax check of the argument. } +instance Show FlagData where+ show fd = show (fName fd, fShort fd, fDefValue fd, fArgType fd, fDescription fd, fModuleName fd)+ -- | Every flag the program supports has to be defined through a new -- phantom datatype and the Flag instance of that datatype. --@@ -130,7 +138,7 @@ -- -- * expression quoted and type signed default value, ----- * help string for the argument,+-- * help string identifying the type of the argument (e.g. INTLIST), -- -- * read function, expression quoted, --@@ -139,22 +147,26 @@ -- * help string for the flag. defineCustomFlag :: String -> ExpQ -> String -> ExpQ -> ExpQ -> String -> Q [Dec] defineCustomFlag name' defQ argHelp readQ showQ description =- do (name, short) <- case () of- () | length name' == 0 -> fail "Flag's without names are not supported."- | length name' == 1 -> return (name', Just $ head name')- | length name' == 2 -> return (name', Nothing)- | name' !! 1 == ':' -> return (drop 2 name', Just $ head name')- | otherwise -> return (name', Nothing)+ do (name, short) <- if | length name' == 0 -> fail "Flag's without names are not supported."+ | length name' == 1 -> return (name', Just $ head name')+ | length name' == 2 -> return (name', Nothing)+ | name' !! 1 == ':' -> return (drop 2 name', Just $ head name')+ | otherwise -> return (name', Nothing) defE <- defQ flagType <- case defE of- SigE _ flagType -> return flagType+ SigE _ flagType -> return $ return flagType _ -> fail "Default value for defineCustomFlag has to be an explicitly typed expression, like (12 :: Int)" moduleName <- fmap loc_module location let accessorName = mkName $ "flags_" ++ name+ -- attention: formatting of the dataName matters here, initHFlags+ -- parses the name, so the generation here and the parsing in+ -- initHFlags has to be consistent. let dataName = mkName $ "HFlag_" ++ name- dataDec <- return $ DataD [] dataName [] [] []+ let dataConstrName = mkName $ "HFlagC_" ++ name+ -- Note: support for splicing inside [d| |] would make all this a lot nicer+ dataDec <- dataD (cxt []) dataName [] [normalC dataConstrName []] [] instanceDec <- instanceD- (return [])+ (cxt []) (appT (conT ''Flag) (conT dataName)) [funD 'getFlagData [clause [wildP] (normalB@@ -167,11 +179,9 @@ moduleName (evaluate $(varE accessorName) >> return ()) |]) []]]- flagPragmaDec <- return $ PragmaD $ InlineP accessorName NoInline FunLike AllPhases- flagSig <- return $ SigD accessorName flagType- flagDec <- funD accessorName [clause [] (normalB [| case True of- True -> $(appE readQ [| lookupFlag name moduleName |])- False -> $(defQ) |]) []]+ flagPragmaDec <- pragInlD accessorName NoInline FunLike AllPhases+ flagSig <- sigD accessorName flagType+ flagDec <- funD accessorName [clause [] (normalB $ appE readQ [| lookupFlag name moduleName |]) []] return [dataDec, instanceDec, flagPragmaDec, flagSig, flagDec] -- | This just forwards to 'defineCustomFlag' with @[| read |]@ and@@ -185,7 +195,7 @@ -- -- * expression quoted and type signed default value, ----- * help string for the argument,+-- * help string identifying the type of the argument (e.g. INTLIST), -- -- * help string for the flag. defineEQFlag :: String -> ExpQ -> String -> String -> Q [Dec]@@ -235,8 +245,11 @@ instance FlagType Double where defineFlag n v = defineEQFlag n (sigE (litE (RationalL (toRational v))) [t| Double |] ) "DOUBLE" +-- TODO(errge): hflags-instances cabal package, so the base hflags+-- doesn't depend on text, which is not in GHC. instance FlagType Data.Text.Text where defineFlag n v =+ -- defer lifting of Data.Text.Text to String lifting let s = Data.Text.unpack v in defineCustomFlag n [| Data.Text.pack s :: Data.Text.Text |] "TEXT" [| Data.Text.pack |] [| Data.Text.unpack |] @@ -297,9 +310,9 @@ ([], _, _) -> False _ -> True - flagToGetOptArgDescr FlagData { fName, fArgHelp }- | fArgHelp == "BOOL" = OptArg (\a -> (fName, maybe "True" id a)) fArgHelp- | otherwise = ReqArg (\a -> (fName, a)) fArgHelp+ flagToGetOptArgDescr FlagData { fName, fArgType }+ | fArgType == "BOOL" = OptArg (\a -> (fName, maybe "True" id a)) fArgType+ | otherwise = ReqArg (\a -> (fName, a)) fArgType -- compute GetOpt compatible [Option] structure from flags ([FlagData]) getOptFlags = flip map flags $ \flagData@(FlagData { fName, fShort, fDefValue, fDescription, fModuleName }) ->@@ -333,10 +346,14 @@ [] -> return () (dupe:_) -> fail ("Multiple definition of flag " ++ (snd $ head dupe) ++ ", modules: " ++ (show $ map fst dupe))- [| getArgs >>= initFlags progDescription $(listE $ map instanceToOptTuple instances ) |]+ [| getArgs >>= initFlags progDescription $(listE $ map instanceToFlagData instances ) |] where- instanceToOptTuple (InstanceD _ (AppT _ inst) _) = [| getFlagData (undefined :: $(return inst)) |]- instanceToOptTuple _ = error "Shouldn't happen"+ instanceToFlagData (InstanceD _ (AppT _ inst) _) = [| getFlagData (undefined :: $(return inst)) |]+ instanceToFlagData _ = error "Shouldn't happen"+ -- Duplicate checking is based on the generated `data HFlag_...'+ -- names, and not on FlagData, because we want to do the checks+ -- at compile time. It's not possible in TH, to run getFlagData+ -- on the just reified instances. instanceToModuleNamePair (InstanceD _ (AppT _ (ConT inst)) _) = let (flagrev, modrev) = span (/= '.') $ reverse $ show inst modName = reverse $ drop 1 modrev
+ README.md view
@@ -0,0 +1,4 @@+hflags+======++Command line flag parser for Haskell, conceptually very similar to Google's gflags
examples/X/B.hs view
@@ -1,3 +1,6 @@+-- to test collision detection+-- {-# LANGUAGE TemplateHaskell #-}+ module X.B (b) where import qualified X.Y_Y.A as A
+ examples/package/Tup.hs view
@@ -0,0 +1,12 @@+{-# LANGUAGE TemplateHaskell #-}++module Tup (get) where++import HFlags++defineFlag "which" (1 :: Int) "which"++get :: (a, a) -> a+get x = case flags_which of+ 1 -> fst x+ 2 -> snd x
+ examples/package/test/main.hs view
@@ -0,0 +1,8 @@+{-# LANGUAGE TemplateHaskell #-}++import HFlags+import Tup++main = do+ $(initHFlags "foobar")+ print $ get (1,2)
+ examples/package/tup.cabal view
@@ -0,0 +1,19 @@+name: tup+version: 0.0.1+license: GPL+author: Gergely Risko <gergely@risko.hu>+build-type: Simple+cabal-version: >= 1.6++synopsis: tup+description:+ tup++library+ build-depends:+ base >= 4.6 && < 5+ , template-haskell >= 2.8+ , hflags >= 0.1++ exposed-modules:+ Tup
hflags.cabal view
@@ -1,5 +1,5 @@ name: hflags-version: 0.1.2+version: 0.1.3 license: OtherLicense license-file: COPYING author: Mihaly Barasz <klao@google.com>, Gergely Risko <gergely@risko.hu>@@ -27,7 +27,8 @@ will be initialized that are transitively reachable via imports from @main@. This means, that any Haskell package can easily define command line flags with @HFlags@. This feature is demonstrated by- <http://github.com/errge/hflags/blob/master/examples/ImportExample.hs>.+ <http://github.com/errge/hflags/blob/master/examples/ImportExample.hs>+ and <http://github.com/errge/hflags/tree/master/examples/package>. . A simple example (more in the <http://github.com/errge/hflags/tree/master/examples> directory):@@ -61,6 +62,11 @@ examples/SimpleExample.hs examples/X/B.hs examples/X/Y_Y/A.hs+ examples/package/Tup.hs+ examples/package/test/main.hs+ examples/package/tup.cabal+ BLOG.md+ README.md source-repository head type: git