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 +202/−0
- README.md +70/−0
- Setup.hs +16/−0
- haskell-awk.cabal +78/−0
- src/Main.hs +20/−0
- src/System/Console/Hawk.hs +236/−0
- src/System/Console/Hawk/Config.hs +160/−0
- src/System/Console/Hawk/IO.hs +45/−0
- src/System/Console/Hawk/Lock.hs +122/−0
- src/System/Console/Hawk/Options.hs +113/−0
- src/System/Console/Hawk/Representable.hs +365/−0
- src/System/Console/Hawk/Runtime.hs +165/−0
- src/System/Console/Hawk/Sandbox.hs +147/−0
- tests/RunTests.hs +29/−0
- tests/System/Console/Hawk/Representable/Test.hs +113/−0
+ 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"]