packages feed

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 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!++[![View from Thalwil train station](http://www.gergely.risko.hu/blogphotos/thalwil-20120430.jpg)](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&#39;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