packages feed

hwk 0.4 → 0.5

raw patch · 4 files changed

+158/−77 lines, 4 filesdep ~hint

Dependency ranges changed: hint

Files

ChangeLog.md view
@@ -1,5 +1,11 @@ # Version history for hwk +## 0.5 (2020-10-13)+- file arguments can now provide input+- experimental --run mode to execute IO+- eval no longer prints type+- add --config-dir option+ ## 0.4 (2020-10-12) - add --eval mode - refactor: drop ToList and ToString classes
Main.hs view
@@ -1,8 +1,12 @@ {-# LANGUAGE CPP #-} +#if !MIN_VERSION_simple_cmd_args(0,1,4)+import Control.Applicative ( #if !MIN_VERSION_simple_cmd_args(0,1,3)-import Control.Applicative ((<|>))+    (<|>), #endif+    many)+#endif import Control.Monad.Extra import qualified Data.List.Extra as L import Data.Version (showVersion)@@ -14,42 +18,53 @@  import Paths_hwk (getDataDir, version) -data HwkMode = DefaultMode | WholeMode | LineMode | TypeMode | EvalMode+data HwkMode = DefaultMode | WholeMode | LineMode | TypeMode | EvalMode | RunMode   deriving Eq  main :: IO ()-main =+main = do+  userdir <- getXdgDirectory XdgConfig "hwk"   simpleCmdArgs (Just version) "A Haskell awk/sed like tool"     "Simple shell text processing with Haskell" $-  runExpr <$> modeOpt <*> strArg "FUNCTION" {-<*> many (strArg "FILE...")-}+    runExpr userdir <$> modeOpt <*> cfgdirOpt userdir <*> strArg "FUNCTION" <*> many (strArg "FILE...")   where     modeOpt :: Parser HwkMode     modeOpt =+      flagWith' LineMode 'l' "line" "Apply function to each line" <|>+      flagWith' WholeMode 'a' "all" "Apply function once to the whole input" <|>       flagWith' TypeMode 't' "typecheck" "Print out the type of the given function" <|>       flagWith' EvalMode 'e' "eval" "Evaluate a Haskell expression" <|>-      flagWith' WholeMode 'a' "all" "Apply function once to the whole input" <|>-      flagWith DefaultMode LineMode 'l' "line" "Apply function to each line"+      flagWith DefaultMode RunMode 'r' "run" "Run Haskell IO" -runExpr :: HwkMode -> String -> {-[FilePath] ->-} IO ()-runExpr mode stmt {-files-} = do-  --mapM_ checkFileExists files-  input <- getContents-  userdir <- getXdgDirectory XdgConfig "hwk"-  let usercfg = userdir </> "Hwk.hs"-  datadir <- getDataDir-  let versionCfg = usercfg ++ "-" ++ showVersion version-  unlessM (doesFileExist versionCfg) $-    copyFile (datadir </> "Hwk.hs") versionCfg-  unlessM (doesFileExist usercfg) $ do-    copyFile (datadir </> "Hwk.hs") usercfg-    warn $ usercfg ++ " created"-  r <- runInterpreter (runHint userdir input)-  case r of-    Left err -> putStrLn $ errorString err-    Right () -> return ()+    cfgdirOpt :: String -> Parser (Maybe FilePath)+    cfgdirOpt dir =+      optional (normalise <$> strOptionWith 'c' "config-dir" "DIR" ("Override the config dir [default:" ++ dir ++ "]"))++runExpr :: FilePath -> HwkMode -> Maybe FilePath -> String -> [FilePath] -> IO ()+runExpr userdir mode mcfgdir stmt files = do+  mapM_ checkFileExists files+  cfgdir <-+    case mcfgdir of+      Just dir -> do+        unlessM (doesDirectoryExist dir) $+          error' $ dir ++ ": directory not found"+        return dir+      Nothing -> do+        let usercfg = userdir </> "Hwk.hs"+        datadir <- getDataDir+        let versionCfg = usercfg ++ "-" ++ showVersion version+        unlessM (doesFileExist versionCfg) $+          copyFile (datadir </> "Hwk.hs") versionCfg+        unlessM (doesFileExist usercfg) $ do+          copyFile (datadir </> "Hwk.hs") usercfg+          warn $ usercfg ++ " created"+        return userdir+  runInterpreter (runHint cfgdir) >>=+    either (putStrLn . errorString) return   where-    runHint :: FilePath -> String -> Interpreter ()-    runHint cfgdir input = do+    runHint :: FilePath -> Interpreter ()+    runHint cfgdir = do+      -- could ignore this for eval or typecheck       set [searchPath := [cfgdir], languageExtensions := [TypeApplications]]       loadModules ["Hwk"]       let setHwkImports ms = setImports (L.nub (ms ++ ["Hwk"]))@@ -60,13 +75,26 @@           then interpret "userModules" infer           else return ["Prelude", "Data.List"]       setHwkImports imports+      let inputs = if null files then ["-"] else files       case mode of-        DefaultMode -> mapInputList stmt (lines input)-        LineMode -> mapEachLine stmt (lines input)-        WholeMode -> applyToInput stmt (removeTrailingNewline input)+        DefaultMode ->+          withInputFiles inputs (mapInputList stmt . lines)+        LineMode ->+          withInputFiles inputs (mapEachLine stmt . lines)+        WholeMode -> do+          withInputFiles inputs (applyToInput stmt . removeTrailingNewline)+        -- FIXME take or warn about args         TypeMode -> typeOfExpr stmt         EvalMode -> evalExpr stmt+        RunMode -> execExpr stmt       where+        withInputFiles :: [FilePath] -> (String -> Interpreter ())+                       -> Interpreter ()+        withInputFiles inputs interp = do+          forM_ inputs $ \ file ->+            liftIO (if file == "-" then getContents else readFile file) >>=+            interp+         removeTrailingNewline :: String -> String         removeTrailingNewline "" = ""         removeTrailingNewline s =@@ -74,10 +102,11 @@           then init s           else s -    -- checkFileExists :: FilePath -> IO ()-    -- checkFileExists file = do-    --   unlessM (doesFileExist file) $-    --     error' $ "file not found: " ++ file+    checkFileExists :: FilePath -> IO ()+    checkFileExists "-" = return ()+    checkFileExists file = do+      unlessM (doesFileExist file) $+        error' $ "file not found: " ++ file      errorString :: InterpreterError -> String     errorString (WontCompile es) =@@ -88,7 +117,7 @@ cleanupType = L.replace "FilePath" "String" . L.replace "[Char]" "String"  -- fn $ lines input-mapInputList :: String -> [String] -> InterpreterT IO ()+mapInputList :: String -> [String] -> Interpreter () mapInputList stmt inputs = do   typ <- resultTypeOfApplied stmt "[String]"   case cleanupType typ of@@ -116,7 +145,7 @@       liftIO $ mapM_ putStrLn (fn inputs)  -- map fn $ lines input-mapEachLine :: String -> [String] -> InterpreterT IO ()+mapEachLine :: String -> [String] -> Interpreter () mapEachLine stmt inputs = do   typ <- resultTypeOfApplied stmt "String"   case cleanupType typ of@@ -144,7 +173,7 @@       liftIO $ mapM_ (putStrLn . fn) inputs  -- fn input-applyToInput :: String -> String -> InterpreterT IO ()+applyToInput :: String -> String -> Interpreter () applyToInput stmt input = do   typ <- resultTypeOfApplied stmt "String"   case cleanupType typ of@@ -171,7 +200,7 @@       fn <- interpret stmt (as :: String -> [String])       liftIO $ mapM_ putStrLn (fn input) -typeOfExpr :: String -> InterpreterT IO ()+typeOfExpr :: String -> Interpreter () typeOfExpr stmt = do #if MIN_VERSION_hint(0,8,0)   etypchk <- typeChecksWithDetails stmt@@ -182,7 +211,7 @@   typeOf stmt >>= liftIO . putStrLn . cleanupType #endif -evalExpr :: String -> InterpreterT IO ()+evalExpr :: String -> Interpreter () evalExpr stmt = do   typ <- typeOf stmt   case cleanupType typ of@@ -192,21 +221,44 @@       interpret stmt ["a String"] >>= liftIO . mapM_ putStrLn     "[[String]]" -> do       interpret stmt [["a String"]] >>= liftIO . mapM_ (putStrLn . unwords)+    "Int" -> do+      interpret stmt (1 :: Int) >>= liftIO . print     "[Int]" -> do       interpret stmt [1 :: Int] >>= liftIO . mapM_ print     "[[Int]]" -> do       interpret stmt [[1 :: Int]] >>= liftIO . mapM_ (putStrLn . unwords . map show)-    _ -> do+    _ ->       if " -> " `L.isInfixOf` typ         then liftIO $ putStrLn typ-        else do-        res <- eval stmt+        else         -- FIXME option to display type-        liftIO $ putStrLn $ res ++ " :: " ++ typ+        eval stmt >>= liftIO . putStrLn -warn :: String -> IO ()-warn = hPutStrLn stderr+-- FIXME add --safe?+execExpr :: String -> Interpreter ()+execExpr stmt = do+  typ <- typeOf stmt+  case cleanupType typ of+    "IO ()" -> runStmt stmt+    "IO String" ->+      runStmt $ stmt ++ ">>= putStrLn"+    "IO [String]" ->+      runStmt $ stmt ++ ">>= mapM_ putStrLn"+    "IO [[String]]" ->+      runStmt $ stmt ++ ">>= mapM_ (putStrLn . unwords)"+    _ -> liftIO $ warn typ  resultTypeOfApplied :: MonadInterpreter m => String -> String -> m String resultTypeOfApplied expr typ =   L.dropPrefix (typ ++ " -> ") <$> typeOf (expr ++ " . (id  @" ++ typ ++ ")")++warn :: String -> IO ()+warn = hPutStrLn stderr++-- from simple-cmd+error' :: String -> a+#if (defined(MIN_VERSION_base) && MIN_VERSION_base(4,9,0))+error' = errorWithoutStackTrace+#else+error' = error+#endif
README.md view
@@ -2,21 +2,21 @@  <img align="right" alt="hwk" src="hwk.png" /> -**hwk** (pronounced "hawk") is a simple Haskell-based text processing commandline tool, somewhat similar to tools like **awk**, **grep**, **sed**.-`hwk` applies composed pure Haskell functions to a list of strings from stdin, enabling text processing without having to remember an obscure DSL or cli options. This tool can also help to encourage people to think functionally.+**hwk** (pronounced "hawk") is a simple Haskell-based commandline text processing tool, somewhat similar to tools like *awk*, *grep*, *sed*.+`hwk` applies composed pure Haskell functions to a list of lines of input, enabling text processing without having to remember an obscure DSL or awkward cli options. This tool can also help to encourage people to think functionally.  hwk was originally written by Lukas Martinelli in 2016-2017: see the [original README file](README.md.orig). -**hwk** is pretty similar to [**Hawk**](https://github.com/gelisam/hawk),+**hwk** is pretty similar to [Hawk](https://github.com/gelisam/hawk), so you may also want to try that for a different more sophisticated monadic implementation. Some of main differences are:  - hwk uses String for input for type simplicity, whereas hawk uses ByteString-- hawk has special options for controlling input and output delimiters, whereas in hwk everything is roughly just `[String] -> [String]` (more details below)-- by default hwk applies a function to the list of all the lines of stdin: `hwk -l` corresponds to `hawk -m` and `hawk -a` to `hwk`.+- hawk has special options for controlling input and output field/lines delimiters, whereas in hwk everything is roughly just `[String] -> [String]` (more details below)+- by default hwk applies a function to the list of all the lines of stdin: `hwk -l` corresponds to `hawk -m` and `hawk -a` to `hwk`. `hwk -a` applies the function to the whole stdin. -## Example+## Examples Some simple use-cases are in the [examples](examples/) directory.  Change and append a string to each line:@@ -37,7 +37,7 @@  Factorials in your shell scripts!: ```bash-seq 10 12 | hwk --line 'let {fact 0 = 1; fact n = n * fact (n - 1)} in fact . int'+$ seq 10 12 | hwk --line 'let {fact 0 = 1; fact n = n * fact (n - 1)} in fact . int' 3628800 39916800 479001600@@ -60,53 +60,76 @@ yes ``` +You can also type-check functions:+```bash+$ hwk --typecheck take+Int -> [a] -> [a]+```+or expressions:+```bash+$ hwk -t [1,2]+Num a => [a]+```++And evaluate expressions:+```bash+$ hwk -e '2 ^ 32 `div` 1024'+4194304+```++Run commands:+```bash+$ hwk --run getCurrentDirectory+/home/user/src+```+ ## Configuration-`hwk` uses a Haskell configuration file `~/.config/hwk/Hwk.hs` which provides the context for the hint evaluation of the supplied function. Hint (ghci) also checks the current directory when loading so one can also override the configuration on a directory basis.+`hwk` uses a Haskell configuration file `~/.config/hwk/Hwk.hs` which provides the context for the hint evaluation of the supplied function. Hint (ghci) checks the current directory first when loading, so one can override the configuration on a directory basis. +The first time hwk is run it sets up `~/.config/hwk/Hwk.hs`.+ The default [Hwk module](data/Hwk.hs) configuration imports `Prelude`, `Data.List`, `Data.Char`, and `System.FilePath` into the hint interpreter. -The first time hwk is run it sets up `~/.config/hwk/Hwk.hs`.+You can add other modules to import to `userModules` or+define your own functions to use in hwk expressions if you wish. -You can add other modules to import or define your own functions in-`~/.config/hwk/Hwk.hs`.+After a hwk version update you may need or wish to sync up your Hwk.hs file to take account of any new changes: a copy of the latest default Hwk.hs is also put in `~/.config/hwk/` with version suffix. -After a hwk version update you may wish or have to update up your Hwk.hs file to take account of new changes: a copy of the latest default Hwk.hs is also put in `~/.config/hwk/` with the version suffix.+One can specify `-c`/`--config-dir` to load Hwk.hs from a different location. -## Install+## Installation Either use the `install.sh` script, or install by cabal-install or stack as described below: -### Install script from source tree or git+### install.sh script from source tree or git Use `stack unpack hwk` or `git clone https://github.com/juhp/hwk`. -Then go to the source directory and run the `install.sh` script, which--- first runs `stack install`-- then moves the binary installed by `stack install` to `~/.local/lib/hwk`, and sets up a wrapper script `~/.local/bin/hwk` which runs it.+Then go to the source directory and run the `install.sh` script, which first runs `stack install`, then moves the binary installed by `stack install` to `~/.local/lib/hwk`, and sets up a wrapper script `~/.local/bin/hwk` which runs it. -You may wish to change the resolver in stack.yaml first: it is also used to determine the resolver used by the created `hwk` wrapper script.+If you wish you can change the resolver in stack.yaml first: it is also used to determine the resolver used by the created `hwk` wrapper script.  ### cabal If you are on a Linux distro with a system installed ghc and Haskell libaries, you can install with `cabal install` to make use of them.  ### stack-Installing by stack is better if you do not have a system ghc+Installing and running with stack is better if you do not have a system ghc and/or global system Haskell libraries installed. -Alternatively to install by hand: run `stack install`,+If you prefer not to use `install.sh` in the source dir,+you can install by hand: run `stack install`, and then run it with `stack exec hwk ...` using the same resolver.  ## How does `hwk` work?--- `hwk` use the hint library to evaluate haskell functions on standard input.+- `hwk` use the hint library to apply haskell functions to input. - By default it splits the input to a list of lines and applies the function to them - Use `-a` or `--all` to apply a function to all the input,   or `-l`/`--line` to map the function on each line separately.-- You can only typecheck the function or an expr with `-t`/`--typecheck`-  or evaluate an expr with `-e`/`--eval`.+- If you pass file arguments, their contents will be read and passed to the function.+- You can also typecheck the function or an expression with `-t`/`--typecheck`,+  evaluate an expr with `-e`/`--eval`, or `-r`/`--run` an IO statement.  ## Supported return types @@ -123,12 +146,12 @@  Open an issue or pull request at https://github.com/juhp/hwk to report problems or make suggestions and contributions.-Usage examples are also welcome. -## Related/alternative projects+Usage example contributions are also welcome. +## Related/alternative projects - https://github.com/gelisam/hawk - https://github.com/bawolk/hsp-- https://code.google.com/p/pyp/ - https://en.wikipedia.org/wiki/AWK - https://en.wikipedia.org/wiki/Sed+- https://code.google.com/p/pyp/
hwk.cabal view
@@ -1,11 +1,11 @@ name:                hwk-version:             0.4-synopsis:            Simple cli text processing with Haskell functions+version:             0.5+synopsis:            Commandline text processing with Haskell functions description:             A commandline tool for text processing with Haskell functions,-            which can used in addition to awk, grep, sed, etc.-            It applies the function supplied on the commandline using hint-            to lines of standard input and outputs the results.+            complementing unix-style tools like awk, grep, and sed.+            'hwk' applies the function supplied on the commandline using 'hint'+            to lines of input and outputs the results. homepage:            https://github.com/juhp/hwk license:             MIT license-file:        LICENSE@@ -33,7 +33,7 @@                        directory >= 1.2.3.0,                        extra,                        filepath,-                       hint,+                       hint >= 0.8.0,                        simple-cmd-args >= 0.1.2   default-language:    Haskell2010   ghc-options:         -Wall