packages feed

haskell-awk (empty) → 1.0

raw patch · 15 files changed

+1881/−0 lines, 15 filesdep +HUnitdep +MonadCatchIO-mtldep +basesetup-changed

Dependencies added: HUnit, MonadCatchIO-mtl, base, bytestring, containers, directory, doctest, easy-file, filepath, haskell-src-exts, hint, hspec, network, process, stringsearch, temporary, test-framework, test-framework-hunit, time

Files

+ LICENSE view
@@ -0,0 +1,202 @@++                                 Apache License+                           Version 2.0, January 2004+                        http://www.apache.org/licenses/++   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION++   1. Definitions.++      "License" shall mean the terms and conditions for use, reproduction,+      and distribution as defined by Sections 1 through 9 of this document.++      "Licensor" shall mean the copyright owner or entity authorized by+      the copyright owner that is granting the License.++      "Legal Entity" shall mean the union of the acting entity and all+      other entities that control, are controlled by, or are under common+      control with that entity. For the purposes of this definition,+      "control" means (i) the power, direct or indirect, to cause the+      direction or management of such entity, whether by contract or+      otherwise, or (ii) ownership of fifty percent (50%) or more of the+      outstanding shares, or (iii) beneficial ownership of such entity.++      "You" (or "Your") shall mean an individual or Legal Entity+      exercising permissions granted by this License.++      "Source" form shall mean the preferred form for making modifications,+      including but not limited to software source code, documentation+      source, and configuration files.++      "Object" form shall mean any form resulting from mechanical+      transformation or translation of a Source form, including but+      not limited to compiled object code, generated documentation,+      and conversions to other media types.++      "Work" shall mean the work of authorship, whether in Source or+      Object form, made available under the License, as indicated by a+      copyright notice that is included in or attached to the work+      (an example is provided in the Appendix below).++      "Derivative Works" shall mean any work, whether in Source or Object+      form, that is based on (or derived from) the Work and for which the+      editorial revisions, annotations, elaborations, or other modifications+      represent, as a whole, an original work of authorship. For the purposes+      of this License, Derivative Works shall not include works that remain+      separable from, or merely link (or bind by name) to the interfaces of,+      the Work and Derivative Works thereof.++      "Contribution" shall mean any work of authorship, including+      the original version of the Work and any modifications or additions+      to that Work or Derivative Works thereof, that is intentionally+      submitted to Licensor for inclusion in the Work by the copyright owner+      or by an individual or Legal Entity authorized to submit on behalf of+      the copyright owner. For the purposes of this definition, "submitted"+      means any form of electronic, verbal, or written communication sent+      to the Licensor or its representatives, including but not limited to+      communication on electronic mailing lists, source code control systems,+      and issue tracking systems that are managed by, or on behalf of, the+      Licensor for the purpose of discussing and improving the Work, but+      excluding communication that is conspicuously marked or otherwise+      designated in writing by the copyright owner as "Not a Contribution."++      "Contributor" shall mean Licensor and any individual or Legal Entity+      on behalf of whom a Contribution has been received by Licensor and+      subsequently incorporated within the Work.++   2. Grant of Copyright License. Subject to the terms and conditions of+      this License, each Contributor hereby grants to You a perpetual,+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable+      copyright license to reproduce, prepare Derivative Works of,+      publicly display, publicly perform, sublicense, and distribute the+      Work and such Derivative Works in Source or Object form.++   3. Grant of Patent License. Subject to the terms and conditions of+      this License, each Contributor hereby grants to You a perpetual,+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable+      (except as stated in this section) patent license to make, have made,+      use, offer to sell, sell, import, and otherwise transfer the Work,+      where such license applies only to those patent claims licensable+      by such Contributor that are necessarily infringed by their+      Contribution(s) alone or by combination of their Contribution(s)+      with the Work to which such Contribution(s) was submitted. If You+      institute patent litigation against any entity (including a+      cross-claim or counterclaim in a lawsuit) alleging that the Work+      or a Contribution incorporated within the Work constitutes direct+      or contributory patent infringement, then any patent licenses+      granted to You under this License for that Work shall terminate+      as of the date such litigation is filed.++   4. Redistribution. You may reproduce and distribute copies of the+      Work or Derivative Works thereof in any medium, with or without+      modifications, and in Source or Object form, provided that You+      meet the following conditions:++      (a) You must give any other recipients of the Work or+          Derivative Works a copy of this License; and++      (b) You must cause any modified files to carry prominent notices+          stating that You changed the files; and++      (c) You must retain, in the Source form of any Derivative Works+          that You distribute, all copyright, patent, trademark, and+          attribution notices from the Source form of the Work,+          excluding those notices that do not pertain to any part of+          the Derivative Works; and++      (d) If the Work includes a "NOTICE" text file as part of its+          distribution, then any Derivative Works that You distribute must+          include a readable copy of the attribution notices contained+          within such NOTICE file, excluding those notices that do not+          pertain to any part of the Derivative Works, in at least one+          of the following places: within a NOTICE text file distributed+          as part of the Derivative Works; within the Source form or+          documentation, if provided along with the Derivative Works; or,+          within a display generated by the Derivative Works, if and+          wherever such third-party notices normally appear. The contents+          of the NOTICE file are for informational purposes only and+          do not modify the License. You may add Your own attribution+          notices within Derivative Works that You distribute, alongside+          or as an addendum to the NOTICE text from the Work, provided+          that such additional attribution notices cannot be construed+          as modifying the License.++      You may add Your own copyright statement to Your modifications and+      may provide additional or different license terms and conditions+      for use, reproduction, or distribution of Your modifications, or+      for any such Derivative Works as a whole, provided Your use,+      reproduction, and distribution of the Work otherwise complies with+      the conditions stated in this License.++   5. Submission of Contributions. Unless You explicitly state otherwise,+      any Contribution intentionally submitted for inclusion in the Work+      by You to the Licensor shall be under the terms and conditions of+      this License, without any additional terms or conditions.+      Notwithstanding the above, nothing herein shall supersede or modify+      the terms of any separate license agreement you may have executed+      with Licensor regarding such Contributions.++   6. Trademarks. This License does not grant permission to use the trade+      names, trademarks, service marks, or product names of the Licensor,+      except as required for reasonable and customary use in describing the+      origin of the Work and reproducing the content of the NOTICE file.++   7. Disclaimer of Warranty. Unless required by applicable law or+      agreed to in writing, Licensor provides the Work (and each+      Contributor provides its Contributions) on an "AS IS" BASIS,+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or+      implied, including, without limitation, any warranties or conditions+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A+      PARTICULAR PURPOSE. You are solely responsible for determining the+      appropriateness of using or redistributing the Work and assume any+      risks associated with Your exercise of permissions under this License.++   8. Limitation of Liability. In no event and under no legal theory,+      whether in tort (including negligence), contract, or otherwise,+      unless required by applicable law (such as deliberate and grossly+      negligent acts) or agreed to in writing, shall any Contributor be+      liable to You for damages, including any direct, indirect, special,+      incidental, or consequential damages of any character arising as a+      result of this License or out of the use or inability to use the+      Work (including but not limited to damages for loss of goodwill,+      work stoppage, computer failure or malfunction, or any and all+      other commercial damages or losses), even if such Contributor+      has been advised of the possibility of such damages.++   9. Accepting Warranty or Additional Liability. While redistributing+      the Work or Derivative Works thereof, You may choose to offer,+      and charge a fee for, acceptance of support, warranty, indemnity,+      or other liability obligations and/or rights consistent with this+      License. However, in accepting such obligations, You may act only+      on Your own behalf and on Your sole responsibility, not on behalf+      of any other Contributor, and only if You agree to indemnify,+      defend, and hold each Contributor harmless for any liability+      incurred by, or claims asserted against, such Contributor by reason+      of your accepting any such warranty or additional liability.++   END OF TERMS AND CONDITIONS++   APPENDIX: How to apply the Apache License to your work.++      To apply the Apache License to your work, attach the following+      boilerplate notice, with the fields enclosed by brackets "[]"+      replaced with your own identifying information. (Don't include+      the brackets!)  The text should be enclosed in the appropriate+      comment syntax for the file format. We also recommend that a+      file or class name and description of purpose be included on the+      same "printed page" as the copyright notice for easier+      identification within third-party archives.++   Copyright [yyyy] [name of copyright owner]++   Licensed under the Apache License, Version 2.0 (the "License");+   you may not use this file except in compliance with the License.+   You may obtain a copy of the License at++       http://www.apache.org/licenses/LICENSE-2.0++   Unless required by applicable law or agreed to in writing, software+   distributed under the License is distributed on an "AS IS" BASIS,+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.+   See the License for the specific language governing permissions and+   limitations under the License.
+ README.md view
@@ -0,0 +1,70 @@+# Hawk++Transform text from the command-line using Haskell expressions. Similar to [awk](http://cm.bell-labs.com/cm/cs/awkbook/index.html), but using Haskell as the text-processing language.++## Examples++In [Unix](http://en.wikipedia.org/wiki/Unix) the file `/etc/passwd` is used to+keep track of every registered user in the system. Each entry in the file+contains information about a single user, using a simple colon-separated format.+For example:++```+root:x:0:0:root:/root:/bin/bash+```++The first field is the username. We can use Hawk to list all usernames as follows:++```bash+> cat /etc/passwd | hawk -d: -m 'head'+root+```++The `-d` option tells Hawk to use `:` as word delimiters, causing the first line to be interpreted as `["root", "x", "0", "0", "root", "/root", "/bin/bash"]`.+The `-m` tells Hawk to map a function over each line of the input. In this case, the function `head` extracts the first word of the line, which happens to be the username.++We could of course have achieved identical results by using awk instead of Hawk:++```bash+> cat /etc/passwd | awk -F: '{print $1}'+root+```++While Hawk and awk have similar use cases, the philosophy behind the two is very+different. Awk uses a specialized language designed to concisely express many text transformations,+while Hawk uses the general-purpose language Haskell, which is also known for being concise, among other things.+There are many standard command-line tools that can be easily approximated using+[short Haskell expressions](http://www.haskell.org/haskellwiki/Simple_Unix_tools).++Another important difference is that while awk one-liners are self-contained, Hawk encourages the use of libraries and user-defined functions. By adding function definitions, module imports and language pragmas to Hawk's user-configurable [prelude file](https://github.com/gelisam/hawk/tree/master/doc#user-prelude), those functions, libraries and language extensions become available to Hawk one-liners.+For instance, we could add a `takeLast` function extracting the last `n` elements from a list, and use it to (inefficiently) approximate `tail`:++```bash+> echo 'takeLast n = reverse . take n . reverse' >> ~/.hawk/prelude.hs+> seq 0 100 | hawk -a 'takeLast 3'+98+99+100+```+++For more details, see the [documentation](doc/README.md).++## Installation++To install the stable version, simply use `cabal install haskell-hawk` and+add `~/.cabal/bin` (or your sandbox's `bin` folder) to your PATH. You should+be ready to use Hawk:++```bash+> hawk '[1..3]'+1+2+3+```++To install the development version, clone this repository and use `cabal+install` or `cabal-dev install` to compile Hawk and its dependencies. Cabal+installs the binary to `~/.cabal/bin/hawk`, while cabal-dev installs it to+`./cabal-dev/bin/hawk`. The first run will create a default configuration into+`~/.hawk/prelude.hs` if it doesn't exist.
+ Setup.hs view
@@ -0,0 +1,16 @@+--   Copyright 2013 Mario Pastorelli (pastorelli.mario@gmail.com) Samuel Gélineau (gelisam@gmail.com)+--+--   Licensed under the Apache License, Version 2.0 (the "License");+--   you may not use this file except in compliance with the License.+--   You may obtain a copy of the License at+--+--       http://www.apache.org/licenses/LICENSE-2.0+--+--   Unless required by applicable law or agreed to in writing, software+--   distributed under the License is distributed on an "AS IS" BASIS,+--   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.+--   See the License for the specific language governing permissions and+--   limitations under the License.++import Distribution.Simple+main = defaultMain
+ haskell-awk.cabal view
@@ -0,0 +1,78 @@+Name:           haskell-awk+Version:        1.0+Author:         Mario Pastorelli <pastorelli.mario@gmail.com>,  Samuel Gélineau <gelisam@gmail.com>+Maintainer:     Mario Pastorelli <pastorelli.mario@gmail.com>,  Samuel Gélineau <gelisam@gmail.com>+Synopsis:       Transform text from the command-line using Haskell expressions.+Description:    Hawk is a command line utility to process streams of text+                using Haskell code. It is intended to be used in a UNIX+                pipeline. It offers a configuration system to personalize+                imported modules and a way to represent values on the console.+Category:       Console+License:        Apache-2.0+License-File:   LICENSE+Build-Type:     Simple+Cabal-version:  >=1.10+Extra-Source-Files: src/System/Console/Hawk.hs+                  , src/System/Console/Hawk/*.hs+                  , README.md+                  , tests/System/Console/Hawk/Representable/Test.hs++Source-Repository head+    type: git+    location: https://github.com/gelisam/hawk++Executable hawk+    Main-is:        Main.hs+    Default-Language: Haskell98+    ghc-options:    -Wall+    build-depends:  base >=4.6.0.1 && <5+                  , bytestring >=0.10.0.2+                  , containers -any+                  , directory >=1.2.0.1+                  , easy-file >=0.1.1+                  , filepath >=1.3.0.1+                  , haskell-src-exts >=1.14.0+                  , hint >=0.3.3.5+                  , MonadCatchIO-mtl >=0.3.0.0+                  , network >=2.3.1.0+                  , stringsearch >=0.3.6.4+                  , process >=1.1.0.2+                  , time -any+    hs-source-dirs: src++Library+    exposed-modules: System.Console.Hawk.Representable+                    ,System.Console.Hawk.Runtime+                    ,System.Console.Hawk.IO+    ghc-options:    -Wall +    hs-source-dirs: src+    build-depends: base >=4.6.0.1+                 , bytestring >=0.10.0.2+                 , containers -any+                 , stringsearch >=0.3.6.4+    Default-Language: Haskell98++Test-suite reference+  Hs-Source-Dirs:       src,tests+  Main-Is:              RunTests.hs+  Type:                 exitcode-stdio-1.0+  Ghc-Options:          -Wall+  Build-Depends:        base>=4.6.0.1 && <5+                      , bytestring>=0.10.0.2+                      , containers -any+                      , directory >=1.2.0.1+                      , doctest >= 0.8+                      , test-framework+                      , test-framework-hunit+                      , temporary+                      , hspec+                      , HUnit+                      , easy-file >=0.1.1+                      , haskell-src-exts >= 1.14.0+                      , hint >=0.3.3.5+                      , filepath >=1.3.0.1+                      , network >=2.3.1.0+                      , process >=1.1.0.2+                      , stringsearch >=0.3.6.4+                      , time -any+  Default-Language: Haskell98
+ src/Main.hs view
@@ -0,0 +1,20 @@+--   Copyright 2013 Mario Pastorelli (pastorelli.mario@gmail.com) Samuel Gélineau (gelisam@gmail.com)+--+--   Licensed under the Apache License, Version 2.0 (the "License");+--   you may not use this file except in compliance with the License.+--   You may obtain a copy of the License at+--+--       http://www.apache.org/licenses/LICENSE-2.0+--+--   Unless required by applicable law or agreed to in writing, software+--   distributed under the License is distributed on an "AS IS" BASIS,+--   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.+--   See the License for the specific language governing permissions and+--   limitations under the License.++module Main where++import qualified System.Console.Hawk as Hawk++main :: IO ()+main = Hawk.main
+ src/System/Console/Hawk.hs view
@@ -0,0 +1,236 @@+--   Copyright 2013 Mario Pastorelli (pastorelli.mario@gmail.com) Samuel Gélineau (gelisam@gmail.com)+--+--   Licensed under the Apache License, Version 2.0 (the "License");+--   you may not use this file except in compliance with the License.+--   You may obtain a copy of the License at+--+--       http://www.apache.org/licenses/LICENSE-2.0+--+--   Unless required by applicable law or agreed to in writing, software+--   distributed under the License is distributed on an "AS IS" BASIS,+--   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.+--   See the License for the specific language governing permissions and+--   limitations under the License.++{-# LANGUAGE NoImplicitPrelude+           , OverloadedStrings+           , ScopedTypeVariables+           , TupleSections #-}++module System.Console.Hawk (++    hawk+  , main++) where+++import Control.Applicative ((<$>))+import Control.Monad+import qualified Data.List as L+import Data.List ((++),(!!))+import Data.Either+import Data.Function+import Data.Ord+import Data.Maybe+import Data.String+import qualified Data.Typeable.Internal as Typeable+import Data.Typeable.Internal+  (TypeRep(..)+  ,tyConName)+import qualified Data.ByteString as B+import qualified Data.ByteString.Lazy as LB+import Data.Version (versionBranch)+import Language.Haskell.Interpreter+import qualified Prelude as P+import System.Console.GetOpt (usageInfo)+import System.Environment (getArgs,getProgName)+import System.Exit (exitFailure,exitSuccess)+import qualified System.IO as IO+import System.IO (IO)+import Text.Printf (printf)++import System.Console.Hawk.Sandbox+import System.Console.Hawk.Config+import System.Console.Hawk.Lock+import System.Console.Hawk.IO+import System.Console.Hawk.Options++-- magic self-referential module created by cabal+import Paths_haskell_awk (version)+++initInterpreter :: (String, String) -- ^ config file and module name+                -> [(String,Maybe String)] -- ^ the modules maybe qualified+                -> [Extension]+                -> InterpreterT IO ()+initInterpreter (preludeFile,preludeModule) userModules extensions = do+        +        set [languageExtensions := extensions]++        -- load the config file+        loadModules [preludeFile]++        -- load the config module plus representable+        setImportsQ $ (preludeModule,Nothing):defaultModules+                                           ++ userModules++printErrors :: InterpreterError -> IO ()+printErrors e = case e of+                  WontCompile es' -> do+                    IO.hPutStrLn IO.stderr "\nWon't compile:"+                    forM_ es' $ \e' ->+                      case e' of+                        GhcError e'' -> IO.hPutStrLn IO.stderr $ '\t':e'' ++ "\n"+                  _ -> IO.print e++runHawk :: Options+        ->  (String,String)+        -> [String]+        -> IO ()+runHawk os prelude nos = do+  let file = if L.length nos > 1 then Just (nos !! 1) else Nothing+  extensions <- P.read <$> (getExtensionsFile >>= IO.readFile)+  modules <- maybe (return []) (\f -> P.read <$> IO.readFile f) (optModuleFile os)++  maybe_f <- hawk os prelude modules extensions (L.head nos)+  case maybe_f of+    Left ie -> printErrors ie+    Right f -> getInput file >>= printOutput . f++runLockedHawkInterpreter :: forall a . InterpreterT IO a+                            -> IO (Either InterpreterError a)+runLockedHawkInterpreter i = do+    withLock $ runHawkInterpreter i++data StreamFormat = StreamFormat | LinesFormat | WordsFormat+    deriving (P.Eq,P.Show,P.Read)++streamFormat :: B.ByteString+             -> B.ByteString+             -> StreamFormat+streamFormat ld wd = if B.null ld+                        then StreamFormat+                        else if B.null wd+                                then LinesFormat+                                else WordsFormat++-- | 'ByteString' wrapper used to override the 'ByteString' @typeOf@ into+-- a qualified version+-- @typeOf (ByteString.pack "test") == "ByteString"@+-- @typeof (QualifiedByteString $ ByteString.pack "test") == "Data.ByteString.Lazy.Char8.Bytestring"@+newtype QualifiedByteString = QB { unQB :: LB.ByteString }++instance Typeable.Typeable QualifiedByteString where+  typeOf (QB bs) = let TypeRep fp tc trs = Typeable.typeOf bs+                   in TypeRep fp+                              tc{ tyConName = "Data.ByteString.Lazy.Char8."+                                          ++ tyConName tc }+                              trs++hawk :: Options                -- ^ Program options+     -> (String,String)         -- ^ The prelude file and module name+     -> [(String,Maybe String)] -- ^ The modules maybe qualified+     -> [Extension]             -- ^ The extensions to enable+     -> String                  -- ^ The user expression to evaluate+     -> IO (Either InterpreterError (LB.ByteString -> LB.ByteString))+hawk opts prelude modules extensions userExpr = do+    eitherErrorF <- runLockedHawkInterpreter $ do++        initInterpreter prelude modules extensions+        +        -- eval program based on the existence of a delimiter+        case (optMode opts,streamFormat linesDelim wordsDelim) of+            (EvalMode,_)             -> interpret' $ evalExpr      userExpr+            (ApplyMode,StreamFormat) -> interpret' $ streamExpr    userExpr+            (ApplyMode,LinesFormat)  -> interpret' $ linesExpr     userExpr+            (ApplyMode,WordsFormat)  -> interpret' $ wordsExpr     userExpr+            (MapMode,StreamFormat)   -> interpret' $ mapStreamExpr userExpr+            (MapMode,LinesFormat)    -> interpret' $ mapLinesExpr  userExpr+            (MapMode,WordsFormat)    -> interpret' $ mapWordsExpr  userExpr+    +    return ((\f -> unQB . f . QB) <$> eitherErrorF)+    where +          interpret' expr = do+            -- print the user expression+            -- lift $ IO.hPutStrLn IO.stderr expr +            interpret expr (as :: QualifiedByteString -> QualifiedByteString)+          evalExpr = printf "const (%s (%s))" showRows+          mapStreamExpr = streamExpr . listMap+          mapLinesExpr = linesExpr . listMap+          mapWordsExpr = wordsExpr . listMap+          streamExpr expr = compose [showRows, expr]+          linesExpr expr = compose [showRows, expr, parseRows]+          wordsExpr expr = compose [showRows, expr, parseWords]+          linesDelim = optLinesDelim opts+          wordsDelim = optWordsDelim opts+          outLinesDelim = case optOutLinesDelim opts of+                            Nothing -> linesDelim+                            Just delim -> delim+          outWordsDelim = case optOutWordsDelim opts of+                            Nothing -> wordsDelim+                            Just delim -> delim+          compose :: [String] -> String+          compose = L.intercalate (prel ".") . P.map (printf "(%s)")+          listMap :: String -> String+          listMap = printf (runtime "listMap (%s)")+          c8pack :: String -> String+          c8pack = printf (runtime "c8pack (%s)")+          sc8pack :: String -> String+          sc8pack = printf (runtime "sc8pack (%s)")+          showRows :: String+          showRows = printf (runtime "showRows (%s) (%s)")+                             (c8pack $ P.show outLinesDelim)+                             (c8pack $ P.show outWordsDelim)+          parseRows :: String+          parseRows = printf (runtime "parseRows (%s)")+                             (sc8pack $ P.show linesDelim)++          parseWords :: String+          parseWords = printf (runtime "parseWords (%s) (%s)")+                              (sc8pack $ P.show linesDelim)+                              (sc8pack $ P.show wordsDelim)+          +          qualify :: String -> String -> String+          qualify moduleName = printf "%s.%s" moduleName+          +          prel = qualify "Prelude"+          runtime = qualify "System.Console.Hawk.Runtime"++getUsage :: IO String+getUsage = do+    pn <- getProgName+    return $ usageInfo ("Usage: " ++ pn ++ " [<options>] <expr> [<file>]") +                       options++main :: IO ()+main = do+    moduleFile <- getModulesFile+    optsArgs <- processArgs moduleFile <$> getArgs+    either printErrorAndExit go optsArgs+    where processArgs moduleFile args =+                case compileOpts args of+                    Left err -> Left err+                    Right (opts,notOpts) -> +                        Right (opts{ optModuleFile = Just moduleFile},notOpts)+          printErrorAndExit errors = errorMessage errors >> exitFailure+          errorMessage errs = do+                usage <- getUsage+                IO.hPutStr IO.stderr $ L.intercalate "\n" (errs ++ ['\n':usage])+          go (opts,notOpts) = do+                config <- if optRecompile opts+                              then recompileConfig+                              else recompileConfigIfNeeded+                +                when (optVersion opts) $ do+                  let versionString = L.intercalate "."+                                    $ P.map P.show+                                    $ versionBranch version+                  IO.putStrLn versionString+                  exitSuccess+                +                when (optHelp opts) $ do+                  getUsage >>= IO.putStr+                  exitSuccess+                +                runHawk opts config notOpts
+ src/System/Console/Hawk/Config.hs view
@@ -0,0 +1,160 @@+--   Copyright 2013 Mario Pastorelli (pastorelli.mario@gmail.com) Samuel Gélineau (gelisam@gmail.com)+--+--   Licensed under the Apache License, Version 2.0 (the "License");+--   you may not use this file except in compliance with the License.+--   You may obtain a copy of the License at+--+--       http://www.apache.org/licenses/LICENSE-2.0+--+--   Unless required by applicable law or agreed to in writing, software+--   distributed under the License is distributed on an "AS IS" BASIS,+--   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.+--   See the License for the specific language governing permissions and+--   limitations under the License.++{-# LANGUAGE OverloadedStrings #-}+module System.Console.Hawk.Config (+      defaultModules+    , defaultPrelude+    , recompileConfigIfNeeded+    , getExtensionsFile+    , getModulesFile+    , parseModules+    , recompileConfig+    , recompileConfig'+) where++import Control.Applicative ((<$>))+import Control.Monad (when, unless)++import Data.Time+import System.EasyFile++import System.Console.Hawk.Config.Base+import System.Console.Hawk.Config.Cache+import System.Console.Hawk.Config.Compile+import System.Console.Hawk.Config.Extend+import System.Console.Hawk.Config.Parse+import System.Console.Hawk.Lock+++defaultModules :: [QualifiedModule]+defaultModules = map fully_qualified [ +                                       "Prelude"+                                     , "System.Console.Hawk.IO"+                                     , "System.Console.Hawk.Representable"+                                     , "System.Console.Hawk.Runtime"+                                     , "System.IO.Unsafe"+                                     , "Data.ByteString.Lazy.Char8"+                                     ]+  where+    fully_qualified x = (x, Just x)++defaultPrelude :: String+defaultPrelude = unlines+               [ "{-# LANGUAGE ExtendedDefaultRules, OverloadedStrings #-}"+               , "import Prelude"+               , "import qualified Data.ByteString.Lazy.Char8 as B"+               , "import qualified Data.List as L"+               ]++-- --+-- From now the code is heavy, it needs a refactoring (renaming)++-- maybe (file name, module name)+-- TODO: error handling++recompileConfigIfNeeded :: IO (String,String) -- ^ Maybe (FileName,ModuleName)+recompileConfigIfNeeded = withLock $ do+    dir <- getConfigDir+    dirExists <- doesDirectoryExist dir+    unless dirExists $+        createDirectoryIfMissing True dir+    configFile <- getConfigFile+    configFileExists <- doesFileExist configFile+    unless configFileExists $+        writeFile configFile defaultPrelude+    configInfosFile <- getConfigInfosFile+    configInfosExists <- doesFileExist configInfosFile+    if configInfosExists+      then do+          configInfos <- lines <$> readFile configInfosFile+          if length configInfos /= 3 -- error+            then recompileConfig+            else do+                let [fileName,moduleName,rawLastModTime] = configInfos+                let withoutExt = dropExtension fileName+                let hiFile = withoutExt ++ ".hi"+                hiFileDoesntExist <- not <$> doesFileExist hiFile+                let objFile = withoutExt ++ ".o"+                objFileDoesntExist <- not <$> doesFileExist objFile+                let lastModTime = (read rawLastModTime :: UTCTime)+                currModTime <- getModificationTime configFile+                if hiFileDoesntExist || objFileDoesntExist +                                     || currModTime > lastModTime+                 then recompileConfig+                 else return (fileName,moduleName)+      else recompileConfig+++-- TODO: error handling+recompileConfig :: IO (String,String)+recompileConfig = do+  configFile <- getConfigFile+  cacheDir <- getCacheDir+  sourceFile <- getSourceFile+  extensionFile <- getExtensionsFile+  modulesFile <- getModulesFile+  compiledFile <- getCompiledFile+  configInfosFile <- getConfigInfosFile+  recompileConfig' configFile+                   cacheDir+                   sourceFile+                   extensionFile+                   modulesFile+                   compiledFile+                   configInfosFile++recompileConfig' :: FilePath -- ^ config file+                 -> FilePath -- ^ cache dir+                 -> FilePath -- ^ source file+                 -> FilePath -- ^ output extensions cache file+                 -> FilePath -- ^ output modules cache file+                 -> FilePath -- ^ output compiled file+                 -> FilePath -- ^ output config info path+                 -> IO (String,String)+recompileConfig' configFile+                 cacheDir+                 sourceFile+                 extensionsFile+                 modulesFile+                 compiledFile+                 configInfosFile = do+    clean+    createDirectoryIfMissing True cacheDir+    +    extensions <- parseExtensions configFile+    orig_modules <- parseModules configFile extensions+    orig_source <- parseSource configFile+    +    let modules = extendModules extensions orig_modules+    let source = extendSource configFile extensions orig_modules orig_source+    +    cacheExtensions extensionsFile extensions+    cacheModules modulesFile modules+    cacheSource sourceFile source+    +    compile sourceFile compiledFile cacheDir+    +    let moduleName = getModuleName source+    lastModTime <- getModificationTime configFile+    writeFile configInfosFile $ unlines [sourceFile+                                         ,moduleName+                                         ,show lastModTime]+    +    return (sourceFile, moduleName)+  where+    clean :: IO ()+    clean = do+        dirExists <- doesDirectoryExist cacheDir+        when (dirExists) (removeDirectoryRecursive cacheDir)
+ src/System/Console/Hawk/IO.hs view
@@ -0,0 +1,45 @@+--   Copyright 2013 Mario Pastorelli (pastorelli.mario@gmail.com) Samuel Gélineau (gelisam@gmail.com)+--+--   Licensed under the Apache License, Version 2.0 (the "License");+--   you may not use this file except in compliance with the License.+--   You may obtain a copy of the License at+--+--       http://www.apache.org/licenses/LICENSE-2.0+--+--   Unless required by applicable law or agreed to in writing, software+--   distributed under the License is distributed on an "AS IS" BASIS,+--   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.+--   See the License for the specific language governing permissions and+--   limitations under the License.++{-# LANGUAGE NoImplicitPrelude #-}+-- | Used by Hawk's runtime to write to stdout.+--   The API may change at any time.+module System.Console.Hawk.IO +  (getInput+  ,printOutput)+where++import Control.Exception+  (handle)+import Data.ByteString.Lazy.Char8+import GHC.IO.Exception+  (IOErrorType(ResourceVanished)+  ,IOException(ioe_type))+import System.IO+  (hFlush+  ,hPrint+  ,stderr+  ,stdout)+import Prelude hiding (getContents,putStrLn,readFile)++getInput :: Maybe FilePath+         -> IO ByteString+getInput = maybe getContents readFile++printOutput :: ByteString+            -> IO ()+printOutput s = handle ioHandler (putStrLn s >> hFlush stdout)+  where ioHandler e = case ioe_type e of+                        ResourceVanished -> return ()+                        _ -> hPrint stderr e
+ src/System/Console/Hawk/Lock.hs view
@@ -0,0 +1,122 @@+--   Copyright 2013 Mario Pastorelli (pastorelli.mario@gmail.com) Samuel Gélineau (gelisam@gmail.com)+--+--   Licensed under the Apache License, Version 2.0 (the "License");+--   you may not use this file except in compliance with the License.+--   You may obtain a copy of the License at+--+--       http://www.apache.org/licenses/LICENSE-2.0+--+--   Unless required by applicable law or agreed to in writing, software+--   distributed under the License is distributed on an "AS IS" BASIS,+--   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.+--   See the License for the specific language governing permissions and+--   limitations under the License.++module System.Console.Hawk.Lock+    ( withLock+    , withTestLock+    ) where++import Control.Concurrent ( threadDelay )+import Control.Exception+import Control.Monad ( when, guard )+import Data.List ( elemIndex )+import GHC.IO.Exception+import GHC.IO.Handle ( Handle, hGetContents, hClose )+import Network.BSD ( getProtocolNumber ) -- still cross-platform, don't let the name fool you+import Network ( PortID (..), connectTo )+import Network.Socket+import Text.Printf+++-- use a socket number as a lock indicator.+withSocketLock :: Bool -> IO a -> IO a+withSocketLock testing = withSocketsDo . bracket (lock testing) unlock . const++-- make sure GHC inlines withSocketLock in the following two functions+-- and optimizes the testing and non-testing versions separately.+{-# INLINE withSocketLock #-}++withLock :: IO a -> IO a+withLock = withSocketLock False++withTestLock :: IO a -> IO a+withTestLock = withSocketLock True+++type Lock = Socket++lock :: Bool -> IO Lock+lock testing = catchJust isADDRINUSE openSocket $ \() -> do+    -- open failed, the lock must be in use.+    +    when testing $ putStrLn "** LOCKED **"+    +    -- used to test an interleaving in which the socket is closed here,+    -- between openSocket and waitForException.+    when testing $ threadDelay 20000+    +    -- wait for the other instance to signal that it is done with the lock.+    catchJust isDisconnected waitForException $ \reason -> do+      -- we were disconnected, the server must have released the lock!+      +      when testing $ printf "** UNLOCKED (%s) **\n" reason+      +      -- try again.+      lock testing++unlock :: Lock -> IO ()+unlock = closeSocket+++waitForException :: IO a+waitForException = bracket openHandle closeHandle $ \h -> do+  s <- hGetContents h+  length s `seq` return ()  -- blocks until EOF, which never comes+                            -- because the server never accepted the connection+  error $ printf "port %s in use by a program other than hawk" $ show portNumber+++isADDRINUSE :: IOError -> Maybe ()+isADDRINUSE = guard . (== "bind") . ioe_location++isDisconnected :: IOError -> Maybe String+isDisconnected = fmap (xs !!) . indexOf . ioe_location+  where+    xs = ["connect", "hGetContents"]+    indexOf x = elemIndex x xs+++openHandle :: IO Handle+openHandle = connectTo "localhost" $ PortNumber portNumber++closeHandle :: Handle -> IO ()+closeHandle = hClose+++openSocket :: IO Socket+openSocket = listenOn portNumber++closeSocket :: Socket -> IO ()+closeSocket = sClose+++-- the first few [0-9] characters of the sha1 of "hawk"+portNumber :: PortNumber+portNumber = 62243++-- from the source of Network.listenTo+listenOn :: PortNumber -> IO Socket+listenOn port = do+    proto <- getProtocolNumber "tcp"+    bracketOnError+        (socket AF_INET Stream proto)+        (sClose)+        (\sock -> do+            -- unlike the original listenOn, we do NOT set ReuseAddr.+            -- this way the call will fail if another instance holds the lock.+            --setSocketOption sock ReuseAddr 1+            bindSocket sock (SockAddrInet port iNADDR_ANY)+            listen sock maxListenQueue+            return sock+        )
+ src/System/Console/Hawk/Options.hs view
@@ -0,0 +1,113 @@+--   Copyright 2013 Mario Pastorelli (pastorelli.mario@gmail.com) Samuel Gélineau (gelisam@gmail.com)+--+--   Licensed under the Apache License, Version 2.0 (the "License");+--   you may not use this file except in compliance with the License.+--   You may obtain a copy of the License at+--+--       http://www.apache.org/licenses/LICENSE-2.0+--+--   Unless required by applicable law or agreed to in writing, software+--   distributed under the License is distributed on an "AS IS" BASIS,+--   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.+--   See the License for the specific language governing permissions and+--   limitations under the License.++module System.Console.Hawk.Options where++import Data.ByteString (ByteString)++import qualified Data.ByteString.Char8 as C8+import qualified Data.List as L+import qualified System.FilePath as FP++import System.Console.GetOpt+++data Modes = EvalMode | ApplyMode | MapMode+    deriving (Eq,Enum,Read,Show)++data Options = Options { optMode :: Modes +                       , optLinesDelim :: ByteString+                       , optWordsDelim :: ByteString+                       , optOutLinesDelim :: Maybe ByteString+                       , optOutWordsDelim :: Maybe ByteString+                       , optRecompile :: Bool+                       , optVersion :: Bool+                       , optHelp :: Bool+                       , optIgnoreErrors :: Bool+                       , optModuleFile :: Maybe FP.FilePath}+    deriving Show++defaultOptions :: Options+defaultOptions = Options { optMode = EvalMode+                         , optLinesDelim = C8.singleton '\n'+                         , optWordsDelim = C8.singleton ' '+                         , optOutLinesDelim = Nothing +                         , optOutWordsDelim =  Nothing+                         , optRecompile = False+                         , optVersion = False+                         , optHelp = False+                         , optIgnoreErrors = False+                         , optModuleFile = Nothing }++delimiter :: ByteString -> ByteString+delimiter = C8.concat . (\ls -> L.head ls:L.map subFirst (L.tail ls))+                     . C8.splitWith (== '\\')+    where subFirst s = case C8.head s of+                        'n' -> C8.cons '\n' $ C8.tail s+                        't' -> C8.cons '\t' $ C8.tail s+                        _ -> s++options :: [OptDescr (Options -> Options)]+options = + -- delimiters+ [ Option ['D'] ["lines-delimiter"] (OptArg delimiterAction "<delim>") delimiterHelp+ , Option ['d'] ["words-delimiter"] (OptArg wordsDelimAction "<delim>") wordsDelimHelp+ , Option ['O'] ["output-lines-delim"] (OptArg outDelimAction "<delim>") outDelimHelp+ , Option ['o'] ["output-words-delim"] (OptArg outWordsDelimAction "<delim>") outWordsDelimHelp++ -- modes+ , Option ['a'] ["apply"] (NoArg $ setMode ApplyMode) applyHelp+ , Option ['m'] ["map"] (NoArg $ setMode MapMode) mapHelp++ -- other options+ , Option ['r'] ["recompile"] (NoArg setRecompile) recompileHelp+ , Option ['v'] ["version"] (NoArg $ \o -> o{ optVersion = True }) versionHelp+ , Option ['h'] ["help"] (NoArg $ \o -> o{ optHelp = True }) helpHelp+-- , Option ['k'] ["keep-going"] (NoArg keepGoingAction) keepGoingHelp + ]+    where outDelimAction d o = o{ optOutLinesDelim = fmap (delimiter . C8.pack) d }+          outDelimHelp = "output lines delimiter, default " +++                         "is equal to the input lines delimiter (-D)"+          outWordsDelimAction d o = o{ optOutWordsDelim = fmap (delimiter . C8.pack) d }+          outWordsDelimHelp = "output words delimiter, default " +++                              "is equal to the input words delimiter (-d)"+          delimiterAction ms o = let d = case ms of+                                            Nothing -> C8.pack ""+                                            Just "" -> C8.pack ""+                                            Just s -> delimiter $ C8.pack s+                                 in o{ optLinesDelim = d } +          delimiterHelp = "lines delimiter, default '\\n'"+          wordsDelimAction ms o = let d = case ms of+                                            Nothing -> C8.pack ""+                                            Just "" -> C8.pack ""+                                            Just s -> delimiter $ C8.pack s+                                  in o{ optWordsDelim = d}+          wordsDelimHelp = "words delimiter, default ' '"+          setRecompile o = o{ optRecompile = True}+          recompileHelp = "recompile ~/.hawk/prelude.hs\neven if it did not change"+          +          applyHelp = "apply <expr> to the stream"+          mapHelp = "map <expr> to the stream"++          versionHelp = "print the version number and exit"+          helpHelp = "print this help message and exit"+          --keepGoingAction o = o{ optIgnoreErrors = True}+          --keepGoingHelp = "keep going when one line fails"+          setMode m o = o{ optMode = m }++compileOpts :: [String] -> Either [String] (Options,[String])+compileOpts argv =+   case getOpt Permute options argv of+      (os,nos,[]) -> Right (L.foldl (.) id (L.reverse os) defaultOptions, nos)+      (_,_,errs) -> Left errs
+ src/System/Console/Hawk/Representable.hs view
@@ -0,0 +1,365 @@+--   Copyright 2013 Mario Pastorelli (pastorelli.mario@gmail.com) Samuel Gélineau (gelisam@gmail.com)+--+--   Licensed under the Apache License, Version 2.0 (the "License");+--   you may not use this file except in compliance with the License.+--   You may obtain a copy of the License at+--+--       http://www.apache.org/licenses/LICENSE-2.0+--+--   Unless required by applicable law or agreed to in writing, software+--   distributed under the License is distributed on an "AS IS" BASIS,+--   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.+--   See the License for the specific language governing permissions and+--   limitations under the License.++-- | Used by Hawk's runtime to format the output of a Hawk expression.+--    You can use this from your user prelude if you want Hawk to print+--    your custom datatypes in a console-friendly format.+module System.Console.Hawk.Representable (++    ListAsRow (listRepr')+  , ListAsRows (listRepr)+  , Row  (repr')+  , Rows (repr)++) where++import Prelude+import Data.ByteString.Lazy.Char8 (ByteString)+import qualified Data.ByteString.Lazy.Char8 as C8 hiding (hPutStrLn)+import qualified Data.List as L+import Data.Set (Set)+import qualified Data.Set as S+import Data.Map (Map)+import qualified Data.Map as M+++-- | A type that instantiate ListAsRow is a type that has a representation+-- when is embedded inside a list+--+-- For example:+--+-- >>> mapM_ Data.ByteString.Lazy.Char8.putStrLn $ repr Data.ByteString.Lazy.Char8.empty "test"+-- test+class (Show a) => ListAsRow a where+    listRepr' :: ByteString -> [a] -> ByteString+    listRepr' d = C8.intercalate d . L.map (C8.pack . show)++instance ListAsRow Bool+instance ListAsRow Float+instance ListAsRow Double+instance ListAsRow Int+instance ListAsRow Integer+instance ListAsRow ()++instance (ListAsRow a) => ListAsRow [a] where+    -- todo check the first delimiter if it should be d+    listRepr' d = C8.intercalate d . L.map (listRepr' d)++instance (Row a) => ListAsRow (Maybe a) where+    listRepr' d = C8.intercalate d . L.map (repr' d)++instance (ListAsRow a) => ListAsRow (Set a) where+    listRepr' d = listRepr' d . L.map (listRepr' d . S.toList)++instance ListAsRow Char where+    listRepr' _ = C8.pack++instance ListAsRow ByteString where+    listRepr' d = C8.intercalate d++instance (Row a, Row b) => ListAsRow (Map a b) where+    listRepr' d = listRepr' d . L.map (listRepr' d . M.toList)++instance (Row a,Row b) => ListAsRow (a,b) where+    listRepr' d = C8.intercalate d . L.map (\(x,y) -> C8.unwords+                  [repr' d x,repr' d y])++instance (Row a,Row b,Row c) => ListAsRow (a,b,c) where+    listRepr' d = C8.intercalate d . L.map (\(x,y,z) -> C8.unwords+                  [repr' d x,repr' d y,repr' d z])++instance (Row a,Row b,Row c,Row d) => ListAsRow (a,b,c,d) where+    listRepr' d = C8.intercalate d . L.map (\(a,b,c,e) -> C8.unwords+                  [repr' d a,repr' d b,repr' d c,repr' d e])++instance (Row a,Row b,Row c,Row d,Row e) => ListAsRow (a,b,c,d,e) where+    listRepr' d = C8.intercalate d . L.map (\(a,b,c,e,f) -> C8.unwords+                  [repr' d a,repr' d b,repr' d c,repr' d e,repr' d f])++instance (Row a,Row b,Row c,Row d,Row e,Row f) => ListAsRow (a,b,c,d,e,f) where+    listRepr' d = C8.intercalate d . L.map (\(a,b,c,e,f,g) -> C8.unwords+                  [repr' d a,repr' d b,repr' d c,repr' d e,repr' d f+                  ,repr' d g])++instance (Row a,Row b,Row c,Row d,Row e,Row f,Row g)+  => ListAsRow (a,b,c,d,e,f,g) where+    listRepr' d = C8.intercalate d . L.map (\(a,b,c,e,f,g,h) -> C8.unwords+                  [repr' d a,repr' d b,repr' d c,repr' d e,repr' d f+                  ,repr' d g,repr' d h])++instance (Row a,Row b,Row c,Row d,Row e,Row f,Row g,Row h)+  => ListAsRow (a,b,c,d,e,f,g,h) where+    listRepr' d = C8.intercalate d . L.map (\(a,b,c,e,f,g,h,i) -> C8.unwords+                  [repr' d a,repr' d b,repr' d c,repr' d e,repr' d f+                  ,repr' d g,repr' d h,repr' d i])++instance (Row a,Row b,Row c,Row d,Row e,Row f,Row g,Row h,Row i)+  => ListAsRow (a,b,c,d,e,f,g,h,i) where+    listRepr' d = C8.intercalate d . L.map (\(a,b,c,e,f,g,h,i,l) -> C8.unwords+                  [repr' d a,repr' d b,repr' d c,repr' d e,repr' d f+                  ,repr' d g,repr' d h,repr' d i,repr' d l])++instance (Row a,Row b,Row c,Row d,Row e,Row f,Row g,Row h,Row i,Row l)+  => ListAsRow (a,b,c,d,e,f,g,h,i,l) where+    listRepr' d = C8.intercalate d . L.map (\(a,b,c,e,f,g,h,i,l,m) -> C8.unwords+                  [repr' d a,repr' d b,repr' d c,repr' d e,repr' d f+                  ,repr' d g,repr' d h,repr' d i,repr' d l,repr' d m])++++-- | A Row is something that can be expressed as a line. +-- The output of repr' should be formatted such that+-- it can be read and processed from the command line.+--+-- For example:+--+-- >>> IO.putStrLn $ show [1,2,3,4]+-- [1,2,3,4]+--+-- >>> Data.ByteString.Lazy.Char8.putStrLn $ repr' (Data.ByteString.Lazy.Char8.pack " ") [1,2,3,4]+-- 1 2 3 4+class (Show a) => Row a where+    repr' :: ByteString -- ^ columns delimiter+          -> a           -- ^ value to represent+          -> ByteString+    repr' _ = C8.pack . show++instance Row Bool+instance Row Float+instance Row Double+instance Row Int+instance Row Integer+instance Row ()++instance Row Char where+    repr' _ = C8.singleton++instance (ListAsRow a) => Row [a] where+    repr' = listRepr'++instance (ListAsRow a) => Row (Set a) where+    repr' d = listRepr' d . S.toList++instance (Row a,Row b) => Row (Map a b) where+    repr' d = listRepr' d . M.toList++instance Row ByteString where+    repr' _ = id++instance (Row a) => Row (Maybe a) where+    repr' _ Nothing = C8.empty+    repr' d (Just x) = repr' d x -- check if d is correct here++instance (Row a,Row b) => Row (a,b) where+    repr' d (a,b) = repr' d a `C8.append` (d `C8.append` repr' d b)+    --repr' d (a,b) = repr' d [repr' d a,repr' d b] ++instance (Row a,Row b,Row c) => Row (a,b,c) where+    repr' d (a,b,c) =  repr' d a `C8.append` (d `C8.append`+                      (repr' d b `C8.append` (d `C8.append` repr' d c)))++instance (Row a,Row b,Row c,Row d) => Row (a,b,c,d) where+    repr' d (a,b,c,e) = repr' d a `C8.append` (d `C8.append`+                        (repr' d b `C8.append` (d `C8.append`+                        (repr' d c `C8.append` (d `C8.append` repr' d e)))))++instance (Row a,Row b,Row c,Row d,Row e) => Row (a,b,c,d,e) where+    repr' d (a,b,c,e,f) = repr' d a `C8.append` (d `C8.append`+                        (repr' d b `C8.append` (d `C8.append`+                        (repr' d c `C8.append` (d `C8.append`+                        (repr' d e `C8.append` (d `C8.append` repr' d f)))))))++instance (Row a,Row b,Row c,Row d,Row e,Row f) => Row (a,b,c,d,e,f) where+    repr' d (a,b,c,e,f,g) = repr' d a `C8.append` (d `C8.append`+                            (repr' d b `C8.append` (d `C8.append`+                            (repr' d c `C8.append` (d `C8.append`+                            (repr' d e `C8.append` (d `C8.append`+                            (repr' d f `C8.append` (d `C8.append` repr' d g)))))))))++instance (Row a,Row b,Row c,Row d,Row e,Row f,Row g) => Row (a,b,c,d,e,f,g) where+    repr' d (a,b,c,e,f,g,h) = repr' d a `C8.append` (d `C8.append`+                              (repr' d b `C8.append` (d `C8.append`+                              (repr' d c `C8.append` (d `C8.append`+                              (repr' d e `C8.append` (d `C8.append`+                              (repr' d f `C8.append` (d `C8.append`+                              (repr' d g `C8.append` (d `C8.append` repr' d h)))))))))))++instance (Row a,Row b,Row c,Row d,Row e,Row f,Row g,Row h)+        => Row (a,b,c,d,e,f,g,h) where+    repr' d (a,b,c,e,f,g,h,i) =+        repr' d a `C8.append` (d `C8.append`+       (repr' d b `C8.append` (d `C8.append`+       (repr' d c `C8.append` (d `C8.append`+       (repr' d e `C8.append` (d `C8.append`+       (repr' d f `C8.append` (d `C8.append`+       (repr' d g `C8.append` (d `C8.append`+       (repr' d h `C8.append` (d `C8.append` repr' d i)))))))))))))++instance (Row a,Row b,Row c,Row d,Row e,Row f,Row g,Row h,Row i)+        => Row (a,b,c,d,e,f,g,h,i) where+    repr' d (a,b,c,e,f,g,h,i,l) =+        repr' d a `C8.append` (d `C8.append`+       (repr' d b `C8.append` (d `C8.append`+       (repr' d c `C8.append` (d `C8.append`+       (repr' d e `C8.append` (d `C8.append`+       (repr' d f `C8.append` (d `C8.append`+       (repr' d g `C8.append` (d `C8.append`+       (repr' d h `C8.append` (d `C8.append`+       (repr' d i `C8.append` (d `C8.append` repr' d l)))))))))))))))++instance (Row a,Row b,Row c,Row d,Row e,Row f,Row g,Row h,Row i,Row l)+        => Row (a,b,c,d,e,f,g,h,i,l) where+    repr' d (a,b,c,e,f,g,h,i,l,m) =+        repr' d a `C8.append` (d `C8.append`+       (repr' d b `C8.append` (d `C8.append`+       (repr' d c `C8.append` (d `C8.append`+       (repr' d e `C8.append` (d `C8.append`+       (repr' d f `C8.append` (d `C8.append`+       (repr' d g `C8.append` (d `C8.append`+       (repr' d h `C8.append` (d `C8.append`+       (repr' d i `C8.append` (d `C8.append`+       (repr' d l `C8.append` (d `C8.append` repr' d m)))))))))))))))))+++-- | A type that instantiate ListAsRows is a type that has a representation+-- when is embedded inside a list+--+-- Note: we use this class for representing a list of chars as String+-- instead of the standard list representation. Without this repr "test" would+-- yield ['t','e','s','r'] instead of "test".+--+-- For example:+--+-- >>> mapM_ Data.ByteString.Lazy.Char8.putStrLn $ repr Data.ByteString.Lazy.Char8.empty "test"+-- test+class (Row a) => ListAsRows a where+    listRepr :: ByteString -- ^ column delimiter+               -> [a]         -- ^ list of values to represent+               -> [ByteString]+    listRepr d = L.map (repr' d)++instance ListAsRows ByteString+instance ListAsRows Bool+instance ListAsRows Double+instance ListAsRows Float+instance ListAsRows Int+instance ListAsRows Integer+instance (Row a) => ListAsRows (Maybe a)+instance ListAsRows ()+instance (ListAsRow a,ListAsRows a) => ListAsRows [a]+instance (Row a,Row b) => ListAsRows (a,b)+instance (Row a,Row b,Row c) => ListAsRows (a,b,c)+instance (Row a,Row b,Row c,Row d) => ListAsRows (a,b,c,d)+instance (Row a,Row b,Row c,Row d,Row e) => ListAsRows (a,b,c,d,e)+instance (Row a,Row b,Row c,Row d,Row e,Row f) => ListAsRows (a,b,c,d,e,f)+instance (Row a,Row b,Row c,Row d,Row e,Row f,Row g) => ListAsRows (a,b,c,d,e,f,g)+instance (Row a,Row b,Row c,Row d,Row e,Row f,Row g,Row h)+  => ListAsRows (a,b,c,d,e,f,g,h)+instance (Row a,Row b,Row c,Row d,Row e,Row f,Row g,Row h,Row i)+  => ListAsRows (a,b,c,d,e,f,g,h,i)+instance (Row a,Row b,Row c,Row d,Row e,Row f,Row g,Row h,Row i,Row l)+  => ListAsRows (a,b,c,d,e,f,g,h,i,l)++instance ListAsRows Char where+    listRepr _ = (:[]) . C8.pack++instance (ListAsRow a,ListAsRows a) => ListAsRows (Set a) where+    listRepr d = listRepr d . L.map S.toList++instance (Row a,Row b) => ListAsRows (Map a b) where+    listRepr d = listRepr d . L.map M.toList++instance (ListAsRows a) => Rows [a] where+    repr = listRepr+++-- | A type that instantiate Rows is a type that can be represented as+-- a list of rows, where typically a row is a line.+--+-- For example:+--+-- >>> mapM_ Data.ByteString.Lazy.Char8.putStrLn $ repr (Data.ByteString.Lazy.Char8.singleton '\n') [1,2,3,4]+-- 1+-- 2+-- 3+-- 4+class (Show a) => Rows a where+    -- | Return a representation of the given value as list of strings.+    repr :: ByteString -- ^ rows delimiter+         -> a           -- ^ value to represent+         -> [C8.ByteString]+    repr _ = (:[]) . C8.pack . show+++instance Rows Bool+instance Rows Double+instance Rows Float+instance Rows Int+instance Rows Integer++instance Rows () where+    repr _ = const [C8.empty]++instance Rows Char where+    repr _ = (:[]) . C8.singleton++instance Rows ByteString where+    repr _ = (:[])++instance (Rows a) => Rows (Maybe a) where+    repr d = maybe [C8.empty] (repr d)++instance (Row a, Row b) => Rows (Map a b) where+    repr d = listRepr d . M.toList++instance (ListAsRows a) => Rows (Set a) where+    repr d = listRepr d . S.toList++instance (Row a, Row b) => Rows (a,b) where+    repr d (x,y) = [repr' d x,repr' d y]++instance (Row a, Row b, Row c) => Rows (a,b,c) where+    repr d (a,b,c) = [repr' d a, repr' d b, repr' d c]++instance (Row a, Row b, Row c, Row d) => Rows (a,b,c,d) where+    repr d (a,b,c,e) = [repr' d a, repr' d b, repr' d c, repr' d e]++instance (Row a, Row b, Row c, Row d, Row e) => Rows (a,b,c,d,e) where+    repr d (a,b,c,e,f) = [repr' d a, repr' d b, repr' d c, repr' d e, repr' d f]++instance (Row a, Row b, Row c, Row d, Row e, Row f) => Rows (a,b,c,d,e,f) where+    repr d (a,b,c,e,f,g) = [repr' d a, repr' d b, repr' d c,repr' d e+                           ,repr' d f, repr' d g]++instance (Row a, Row b, Row c, Row d, Row e, Row f, Row g)+       => Rows (a,b,c,d,e,f,g) where+    repr d (a,b,c,e,f,g,h) = [repr' d a, repr' d b, repr' d c,repr' d e+                             ,repr' d f, repr' d g, repr' d h]++instance (Row a, Row b, Row c, Row d, Row e, Row f, Row g, Row h)+       => Rows (a,b,c,d,e,f,g,h) where+    repr d (a,b,c,e,f,g,h,i) = [repr' d a, repr' d b, repr' d c, repr' d e+                               ,repr' d f, repr' d g, repr' d h, repr' d i]++instance (Row a, Row b, Row c, Row d, Row e, Row f, Row g, Row h, Row i)+       => Rows (a,b,c,d,e,f,g,h,i) where+    repr d (a,b,c,e,f,g,h,i,l) = [repr' d a, repr' d b, repr' d c, repr' d e+                                 ,repr' d f, repr' d g, repr' d h, repr' d i+                                 , repr' d l]++instance (Row a, Row b, Row c, Row d, Row e, Row f, Row g, Row h, Row i, Row l)+       => Rows (a,b,c,d,e,f,g,h,i,l) where+    repr d (a,b,c,e,f,g,h,i,l,m) = [repr' d a, repr' d b, repr' d c, repr' d e+                                   ,repr' d f, repr' d g, repr' d h, repr' d i+                                   ,repr' d l, repr' d m]
+ src/System/Console/Hawk/Runtime.hs view
@@ -0,0 +1,165 @@+--   Copyright 2013 Mario Pastorelli (pastorelli.mario@gmail.com) Samuel Gélineau (gelisam@gmail.com)+--+--   Licensed under the Apache License, Version 2.0 (the "License");+--   you may not use this file except in compliance with the License.+--   You may obtain a copy of the License at+--+--       http://www.apache.org/licenses/LICENSE-2.0+--+--   Unless required by applicable law or agreed to in writing, software+--   distributed under the License is distributed on an "AS IS" BASIS,+--   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.+--   See the License for the specific language governing permissions and+--   limitations under the License.++{-# LANGUAGE ExistentialQuantification+           , ExtendedDefaultRules+           , OverloadedStrings+           , ScopedTypeVariables #-}++-- | This is Hawk's runtime, it needs to be installed in order to evaluate Hawk+--   expressions. The API may change at any time.+module System.Console.Hawk.Runtime (+    c8pack+  , sc8pack+  , listMap+  , listMapWords+  , printRows+  , printRow+  , parseRows+  , parseWords+  , showRows+  , runExpr+--  , runExprs+) where++import Prelude+import Control.Exception (SomeException,handle)+import qualified Data.ByteString.Char8 as SC8+import Data.ByteString.Lazy.Char8 (ByteString)+import qualified Data.ByteString.Lazy.Char8 as C8 hiding (hPutStrLn)+import qualified Data.List as L+import qualified Data.ByteString.Lazy.Search as BS+import qualified System.IO as IO++import System.Console.Hawk.Representable+import qualified System.Console.Hawk.IO as HawkIO++handleErrors :: IO () -> IO ()+handleErrors = handle (\(e :: SomeException) -> IO.hPrint IO.stderr e)++dropLastIfEmpty :: [C8.ByteString] -> [C8.ByteString]+dropLastIfEmpty [] = []+dropLastIfEmpty (x:[]) = if C8.null x then [] else [x]+dropLastIfEmpty (x:xs) = x:dropLastIfEmpty xs++listMap :: (a -> b) -> [a] -> [b]+listMap = L.map++listMapWords :: ([a] -> b) -> [[a]] -> [b]+listMapWords = L.map++c8pack :: String+       -> C8.ByteString+c8pack = C8.pack++sc8pack :: String+        -> SC8.ByteString+sc8pack = SC8.pack++dropTrailingNewline :: C8.ByteString -> C8.ByteString+dropTrailingNewline "" = ""+dropTrailingNewline s = if last_char == '\r' then s' else s+    where last_char = C8.last s+          n = C8.length s+          s' = C8.take (n - 1) s++-- if delim is "\n", Windows-style "\r\n" delimiters are also accepted.+parseRows :: SC8.ByteString -> C8.ByteString -> [C8.ByteString]+parseRows delim str = dropLastIfEmpty+                    $ maybeDropTrailingNewline+                    $ BS.split delim str+    where maybeDropTrailingNewline = if delim == "\n"+                                       then map dropTrailingNewline+                                       else id++---- special case for space+parseWords :: SC8.ByteString -> SC8.ByteString -> C8.ByteString -> [[C8.ByteString]]+parseWords rowsDelim columnsDelim str = let rows = parseRows rowsDelim str+                                        in L.map f rows+    where f = if columnsDelim == SC8.singleton ' '+                then L.filter  (not . C8.null) . BS.split columnsDelim+                else BS.split columnsDelim+         +--parseWords :: SC8.ByteString -> [C8.ByteString] -> [[C8.ByteString]]+--parseWords delim strs = L.map f strs+--    where f = if delim == SC8.singleton ' '+--                then L.filter  (not . C8.null) . BS.split delim+--                else BS.split delim++--runOnInput :: Maybe FilePath -- ^ the input file or stdout when Nothing+--            -> (C8.ByteString -> IO ()) -- ^ the action to run on the input+--            -> IO ()+--runOnInput fp f = do+--    input <- maybe C8.getContents C8.readFile fp+--    f input+--    IO.hFlush IO.stdout+    -- TODO: we need also hFlush stderr?++runExpr :: Maybe FilePath -- ^ if the input is a file+        -> (Maybe FilePath -> IO C8.ByteString) -- ^ input reader+        -> (C8.ByteString -> C8.ByteString)+        -> (C8.ByteString -> IO ())+        -> IO ()+runExpr m i f o = i m >>= o . f+--runExpr = runOnInput+--+--runExprs :: Maybe FilePath -> (C8.ByteString -> [IO ()]) -> IO ()+--runExprs fp f = runOnInput fp (sequence_ . f)+--+showRows :: forall a . (Rows a)+         => C8.ByteString -- ^ rows delimiter+         -> C8.ByteString -- ^ columns delimiter+         -> a -- ^ value to print+         -> C8.ByteString+showRows rd cd = C8.intercalate rd . repr cd++printRows :: forall a . (Rows a) +          => Bool -- ^ if printRows will continue after errors+          -> C8.ByteString -- ^ rows delimiter+          -> C8.ByteString -- ^ columns delimiter+          -> a -- ^ the value to print as rows+          -> IO ()+printRows _ rd cd = HawkIO.printOutput . showRows rd cd+--printRows _ rd cd v = handle handler printRows_ +--  where handler e = case ioe_type e of+--                      ResourceVanished -> return ()+--                      _ -> IO.hPrint IO.stderr e+--        printRows_ = C8.putStrLn (showRows rd cd v) >> IO.hFlush IO.stdout+--printRows b rowDelimiter columnDelimiter = printFirstRow_ . repr columnDelimiter+--    where printRows_ [] = return ()+--          printRows_ (x:xs) = do+--            putStrAndDelim x+--            handle ioExceptionsHandler (continue xs)+--          printFirstRow_ [] = return ()+--          printFirstRow_ (x:xs) = do+--            putStrOnly x+--            handle ioExceptionsHandler (continue xs)+--          putStrAndDelim = if b then handleErrors . putDelimAndStr_+--                                else putDelimAndStr_+--          putStrOnly = if b then handleErrors . C8.putStr else C8.putStr+--          putDelimAndStr_ :: C8.ByteString -> IO ()+--          putDelimAndStr_ c = C8.putStr rowDelimiter >> C8.putStr c+--          continue xs = IO.hFlush IO.stdout >> printRows_ xs+--          ioExceptionsHandler e = case ioe_type e of+--                                    ResourceVanished -> return ()+--                                    _ -> IO.hPrint IO.stderr e++printRow :: forall a . (Row a)+         => Bool -- ^ if printRow should continue after errors+         -> ByteString -- ^ the column delimiter+         -> a -- ^ the value to print+         -> IO ()+printRow b d = if b then handleErrors . f else f+  where f = C8.putStrLn . repr' d+
+ src/System/Console/Hawk/Sandbox.hs view
@@ -0,0 +1,147 @@+--   Copyright 2013 Mario Pastorelli (pastorelli.mario@gmail.com) Samuel Gélineau (gelisam@gmail.com)+--+--   Licensed under the Apache License, Version 2.0 (the "License");+--   you may not use this file except in compliance with the License.+--   You may obtain a copy of the License at+--+--       http://www.apache.org/licenses/LICENSE-2.0+--+--   Unless required by applicable law or agreed to in writing, software+--   distributed under the License is distributed on an "AS IS" BASIS,+--   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.+--   See the License for the specific language governing permissions and+--   limitations under the License.++-- Extra steps to be performed if hawk was installed from a sandbox.+-- +-- Extra steps are needed because the hawk binary needs runtime access+-- to the hawk library, but the hint library only knows about the globally-+-- installed libraries. If hawk has been installed with a sandbox, its+-- binary and its library will be installed in a local folder instead of+-- in the global location.+module System.Console.Hawk.Sandbox+    ( extraGhcArgs+    , runHawkInterpreter+    ) where++import Control.Applicative+import Control.Monad+import Data.List+import Language.Haskell.Interpreter (InterpreterT, InterpreterError)+import Language.Haskell.Interpreter.Unsafe (unsafeRunInterpreterWithArgs)+import System.Directory (getDirectoryContents, getHomeDirectory, doesFileExist)+import System.Environment (getExecutablePath)+import System.FilePath (pathSeparator, splitFileName)+import Text.Printf (printf)+++data Sandbox = Sandbox+  { folder :: FilePath+  , packageFilePrefix :: String+  , packageFileSuffix :: String+  }++cabalDev, cabalSandbox :: Sandbox+cabalDev = Sandbox "cabal-dev" "packages-" ".conf"+cabalSandbox = Sandbox ".cabal-sandbox" "" "-packages.conf.d"++-- all the sandbox systems we support.+sandboxes :: [Sandbox]+sandboxes = [cabalDev, cabalSandbox]+++-- a version of isSuffixOf which returns the string stripped of its suffix.+isSuffixOf' :: String -> String -> Maybe String+isSuffixOf' suffix s = if suffix `isSuffixOf` s+                         then Just (take (n - m) s)+                         else Nothing+  where+    n = length s+    m = length suffix++-- a version of doesFileExist which returns the file path if it exists.+doesFileExist' :: FilePath -> IO (Maybe FilePath)+doesFileExist' f = do+    r <- doesFileExist f+    if r+      then return (Just f)+      else return Nothing++firstWhichExists :: [FilePath] -> IO (Maybe FilePath)+firstWhichExists fs = do+    fs' <- mapM doesFileExist' fs+    return $ msum fs'+++-- convert slashes to backslashes if needed+path :: String -> String+path = map replaceSeparator where+  replaceSeparator '/' = pathSeparator+  replaceSeparator x = x++-- a version of getExecutablePath which returns the path to the installed hawk+-- executable even the current executable is actually hawk's test suite.+getHawkPath :: IO FilePath+getHawkPath = do+    executablePath <- getExecutablePath+    case path "/dist/build/reference/reference" `isSuffixOf'` executablePath of+      Nothing -> return executablePath+      Just basePath -> do+        -- We are running the test suite. Is hawk installed?+        +        home <- getHomeDirectory+        let userPath = home ++ "/.cabal/bin/hawk"+        +        let folders = map folder sandboxes+        let sandboxPaths = map (printf "%s/%s/bin/hawk" basePath) folders+        +        firstMatch <- firstWhichExists (userPath:sandboxPaths)+        case firstMatch of+          Nothing -> fail "please run 'cabal install' before 'cabal test'."+          Just hawkPath -> return hawkPath+++-- if hawk has been compiled by a sandboxing tool,+-- its binary has been placed in a special folder.+-- +-- return something like (Just "/.../cabal-dev")+getSandboxDir :: Sandbox -> IO (Maybe String)+getSandboxDir sandbox = do+    (dir, _) <- splitFileName <$> getHawkPath+    let sandboxFolder = folder sandbox+    let suffix = path (sandboxFolder ++ "/bin/")+    let basePath = suffix `isSuffixOf'` dir+    let sandboxPath = fmap (++ sandboxFolder) basePath+    return sandboxPath++-- something like "packages-7.6.3.conf"+isPackageFile :: Sandbox -> FilePath -> Bool+isPackageFile sandbox f = packageFilePrefix sandbox `isPrefixOf` f+                       && packageFileSuffix sandbox `isSuffixOf` f++-- something like "/.../cabal-dev/package-7.6.3.conf"+getPackageFile :: Sandbox -> String -> IO String+getPackageFile sandbox dir = do+    files <- getDirectoryContents dir+    let [file] = filter (isPackageFile sandbox) files+    return $ printf (path "%s/%s") dir file++sandboxSpecificGhcArgs :: Sandbox -> IO [String]+sandboxSpecificGhcArgs sandbox = do+    sandboxDir <- getSandboxDir sandbox+    case sandboxDir of+      Nothing -> return []+      Just dir -> do packageFile <- getPackageFile sandbox dir+                     let arg = printf "-package-db %s" packageFile+                     return [arg]+++extraGhcArgs :: IO [String]+extraGhcArgs = concat <$> mapM sandboxSpecificGhcArgs sandboxes++-- a version of runInterpreter which can load libraries+-- installed along hawk's sandbox folder, if applicable.+runHawkInterpreter :: InterpreterT IO a -> IO (Either InterpreterError a)+runHawkInterpreter mx = do+    args <- extraGhcArgs+    unsafeRunInterpreterWithArgs args mx
+ tests/RunTests.hs view
@@ -0,0 +1,29 @@+--   Copyright 2013 Mario Pastorelli (pastorelli.mario@gmail.com) Samuel Gélineau (gelisam@gmail.com)+--+--   Licensed under the Apache License, Version 2.0 (the "License");+--   you may not use this file except in compliance with the License.+--   You may obtain a copy of the License at+--+--       http://www.apache.org/licenses/LICENSE-2.0+--+--   Unless required by applicable law or agreed to in writing, software+--   distributed under the License is distributed on an "AS IS" BASIS,+--   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.+--   See the License for the specific language governing permissions and+--   limitations under the License.++import qualified System.Console.Hawk.Representable.Test as ReprTest+import qualified System.Console.Hawk.Config.Test as ConfigTest+import qualified System.Console.Hawk.Test as HawkTest++import Test.DocTest (doctest)+import Test.Hspec (hspec)++main :: IO ()+main = do+    doctest ["-isrc", "tests/System/Console/Hawk/Lock/Test.hs"]+    hspec $ do+        ReprTest.reprSpec'+        ReprTest.reprSpec+        ConfigTest.spec+    HawkTest.run
+ tests/System/Console/Hawk/Representable/Test.hs view
@@ -0,0 +1,113 @@+--   Copyright 2013 Mario Pastorelli (pastorelli.mario@gmail.com) Samuel Gélineau (gelisam@gmail.com)+--+--   Licensed under the Apache License, Version 2.0 (the "License");+--   you may not use this file except in compliance with the License.+--   You may obtain a copy of the License at+--+--       http://www.apache.org/licenses/LICENSE-2.0+--+--   Unless required by applicable law or agreed to in writing, software+--   distributed under the License is distributed on an "AS IS" BASIS,+--   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.+--   See the License for the specific language governing permissions and+--   limitations under the License.++{-# LANGUAGE ExtendedDefaultRules,OverloadedStrings #-}+module System.Console.Hawk.Representable.Test where++import Data.Map (Map)+import qualified Data.Map as M+import Data.Set (Set)+import qualified Data.Set as S++import System.Console.Hawk.Representable++import Test.Hspec +++-- explicitly type all values to avoid warnings.++_1,_2,_3,_4,_5,_6,_7,_8,_9,_10 :: Int+(_1,_2,_3,_4,_5,_6,_7,_8,_9,_10) = (1,2,3,4,5,6,7,8,9,10)++emptyString,word,word_word,w_w :: String+(emptyString,word,word_word,w_w) = ("","word","word word","w w")+++reprSpec' :: Spec+reprSpec' = describe "repr'" $ do+    it "can convert tuple values" $ do+      example $ repr' " " (_1,True) `shouldBe` "1 True"+      example $ repr' " " (_1,True,_2) `shouldBe` "1 True 2"+      example $ repr' " " (_1,_2,_3,_4) `shouldBe` "1 2 3 4"+      example $ repr' " " (_1,_2,_3,_4,_5) `shouldBe` "1 2 3 4 5"+      example $ repr' " " (_1,_2,_3,_4,_5,_6) `shouldBe` "1 2 3 4 5 6"+      example $ repr' " " (_1,_2,_3,_4,_5,_6,_7) `shouldBe` "1 2 3 4 5 6 7"+      example $ repr' " " (_1,_2,_3,_4,_5,_6,_7,_8) `shouldBe` "1 2 3 4 5 6 7 8"+      example $ repr' " " (_1,_2,_3,_4,_5,_6,_7,_8,_9) `shouldBe` "1 2 3 4 5 6 7 8 9"+      example $ repr' " " (_1,_2,_3,_4,_5,_6,_7,_8,_9,_10) `shouldBe` "1 2 3 4 5 6 7 8 9 10"++reprSpec :: Spec+reprSpec = describe "repr" $ do+    it "can convert boolean values" $ do+        repr "\t" True `shouldBe` ["True"]+        repr "\t" False `shouldBe` ["False"]++    it "can convert char values" $ example $+        repr "\t" 'c' `shouldBe` ["c"]++    it "can convert double values" $ example $+        repr "\t" (1.1::Double) `shouldBe` ["1.1"]++    it "can convert float values" $ example $+        repr "\t" (1.1::Float) `shouldBe` ["1.1"]++    it "can convert int values" $ example $+        repr "\t" (1::Int) `shouldBe` ["1"]++    it "can convert integer values" $ example $+        repr "\t" (1::Integer) `shouldBe` ["1"]++    it "can convert maybe values" $ do +        example $ repr "\t" (Nothing::Maybe ()) `shouldBe` [""]+        example $ repr "\t" (Just 1::Maybe Int) `shouldBe` ["1"]+        example $ repr "\t" (Just (Just True)) `shouldBe` ["True"]++    it "can convert unit value" $+        repr "\t" () `shouldBe` [""]++    it "can convert string values" $ do+        example $ repr "\t" emptyString `shouldBe` [""]+        example $ repr "\t" word `shouldBe` ["word"]+        example $ repr "\t" word_word `shouldBe` ["word word"]++    it "can convert tuple values" $ do+        example $ repr "\t" (_1,True) `shouldBe` ["1","True"]+        example $ repr "\t" ((_1,_2),False) `shouldBe` ["1\t2","False"]+        example $ repr "\t" (_1,_2,_3) `shouldBe` ["1","2","3"]+        example $ repr "\t" (_1,_2,_3,_4) `shouldBe` ["1","2","3","4"]+        example $ repr "\t" (_1,_2,_3,_4,_5) `shouldBe` ["1","2","3","4","5"]+        example $ repr "\t" (_1,_2,_3,_4,_5,_6) `shouldBe` ["1","2","3","4","5","6"]+        example $ repr "\t" (_1,_2,_3,_4,_5,_6,_7) `shouldBe` ["1","2","3","4","5","6","7"]+        example $ repr "\t" (_1,_2,_3,_4,_5,_6,_7,_8) `shouldBe` ["1","2","3","4","5","6","7","8"]+        example $ repr "\t" (_1,_2,_3,_4,_5,_6,_7,_8,_9) `shouldBe` ["1","2","3","4","5","6","7","8","9"]+        example $ repr "\t" (_1,_2,_3,_4,_5,_6,_7,_8,_9,_10) `shouldBe` ["1","2","3","4","5","6","7","8","9","10"]++    it "can convert list values" $ do+        repr "\t" ([]::[()]) `shouldBe` []+        example $ repr "\t" [True] `shouldBe` ["True"]+        example $ repr "\t" [True,False] `shouldBe` ["True","False"]+        example $ repr "\t" [Just _1,Nothing] `shouldBe` ["1",""]+        example $ repr "\t" [[_1,_2,_3],[_4,_5,_6]] `shouldBe` ["1\t2\t3","4\t5\t6"]+        example $ repr "\t" [w_w,w_w] `shouldBe` ["w w","w w"]+        example $ repr "\t" [[w_w],[w_w]] `shouldBe` ["w w","w w"]++    it "can convert map values" $ do+        repr "\t" (M.empty::Map Bool Bool) `shouldBe` []+        example $ repr "\t" (M.fromList [(_1,_2),(_3,_4)]) `shouldBe` ["1\t2","3\t4"]+        example $ repr "\t" ([M.fromList [(_1,_2),(_3,_4)]]) `shouldBe` ["1 2\t3 4"]++    it "can convert set values" $ do+        repr "\t" (S.empty::Set Bool) `shouldBe` []+        example $ repr "\t" (S.fromList [_1,_2,_3,_4]) `shouldBe` ["1","2","3","4"]+        example $ repr "\t" ([S.fromList [_1,_2]]) `shouldBe` ["1\t2"]