packages feed

hwk 0.3 → 0.4

raw patch · 6 files changed

+201/−109 lines, 6 files

Files

ChangeLog.md view
@@ -1,6 +1,13 @@ # Version history for hwk -## 0.2.1 (2020-10-11)+## 0.4 (2020-10-12)+- add --eval mode+- refactor: drop ToList and ToString classes+- import Data.List.Extra by default+- --all: remove trailing newline+- add examples/++## 0.3 (2020-10-11) - add --line mode: takes a function on a String - applied to every line   - uses new ToString class (not ToList) - add --type-check: prints the type of a given function
− Config.hs
@@ -1,56 +0,0 @@-{-# LANGUAGE FlexibleInstances #-}--module Hwk where--import Data.List (intercalate)---- | modules to be imported into hint--- these modules must be from installed packages-userModules :: [String]-userModules = [ "Prelude", "Data.List", "Data.Char", "Data.Bool"-              -- , "Data.List.Extra", "Data.Tuple.Extra"-              , "System.FilePath"-              ]---- you can put user hwk functions here:---int :: String -> Int-int str = read str :: Int--ints :: [String] -> [Int]-ints = map int-------- below here all required internal machinery ---------- | toString and toList are required by hwk--- They determine the types of functions hwk can interpret,--- allowing some simple polymorphism.---- ToString allows handling functions of type: ToString a => String -> a-class ToString a where-  toString :: a -> String-instance ToString String where-  toString x = x-instance ToString [String] where-  toString = unwords-instance ToString [[String]] where-  toString = intercalate "\t" . map unwords-instance ToString Int where-  toString x = show x-instance ToString [Int] where-  toString lst = unwords $ map show lst---- ToList allows handling functions of type: ToList a => [String] -> a-class ToList a where-  toList :: a -> [String]-instance ToList String where-  toList x = [x]-instance ToList [String] where-  toList = id-instance ToList [[String]] where-  toList = map (intercalate "\t")-instance ToList Int where-  toList x = [show x]-instance ToList [Int] where-  toList lst = map show lst
+ Hwk.hs view
@@ -0,0 +1,15 @@+module Hwk where++-- | modules to be imported in hint interpreter+-- These modules must be from installed packages+userModules :: [String]+userModules = [ "Prelude", "Data.Bool", "Data.Char", "Data.List"+              , "Data.List.Extra", "Data.Tuple.Extra", "Data.Version.Extra"+              , "System.FilePath"+              ]++int :: String -> Int+int str = read str :: Int++ints :: [String] -> [Int]+ints = map int
Main.hs view
@@ -14,7 +14,7 @@  import Paths_hwk (getDataDir, version) -data HwkMode = DefaultMode | WholeMode | LineMode | TypeMode+data HwkMode = DefaultMode | WholeMode | LineMode | TypeMode | EvalMode   deriving Eq  main :: IO ()@@ -25,7 +25,8 @@   where     modeOpt :: Parser HwkMode     modeOpt =-      flagWith' TypeMode 't' "type-check" "Print out the type of the given function" <|>+      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" @@ -38,9 +39,9 @@   datadir <- getDataDir   let versionCfg = usercfg ++ "-" ++ showVersion version   unlessM (doesFileExist versionCfg) $-    copyFile (datadir </> "Config.hs") versionCfg+    copyFile (datadir </> "Hwk.hs") versionCfg   unlessM (doesFileExist usercfg) $ do-    copyFile (datadir </> "Config.hs") usercfg+    copyFile (datadir </> "Hwk.hs") usercfg     warn $ usercfg ++ " created"   r <- runInterpreter (runHint userdir input)   case r of@@ -49,8 +50,9 @@   where     runHint :: FilePath -> String -> Interpreter ()     runHint cfgdir input = do-      set [searchPath := [cfgdir]]+      set [searchPath := [cfgdir], languageExtensions := [TypeApplications]]       loadModules ["Hwk"]+      let setHwkImports ms = setImports (L.nub (ms ++ ["Hwk"]))       setHwkImports ["Prelude"]       imports <- do         haveModules <- typeChecks "userModules"@@ -58,35 +60,19 @@           then interpret "userModules" infer           else return ["Prelude", "Data.List"]       setHwkImports imports-      if mode == TypeMode then do-#if MIN_VERSION_hint(0,8,0)-        etypchk <- typeChecksWithDetails stmt-        case etypchk of-          Left err -> liftIO $ mapM_ (putStrLn . errMsg) err-          Right typ -> liftIO $ putStrLn (cleanupType typ)-#else-        typeOf stmt >>= liftIO . putStrLn . cleanupType-#endif-        else do-        let polyList = "toList . "-            polyString = "toString . "-        case mode of-          DefaultMode -> do-            fn <- interpret (polyList ++ stmt) (as :: [String] -> [String])-            liftIO $ mapM_ putStrLn (fn (lines input))-          LineMode -> do-            fn <- interpret (polyString ++ stmt) (as :: String -> String)-            liftIO $ mapM_ (putStrLn . fn) (lines input)-          WholeMode -> do-            fn <- interpret (polyList ++ stmt) (as :: String -> [String])-            liftIO $ mapM_ putStrLn (fn input)-          TypeMode -> error "already handled earlier"+      case mode of+        DefaultMode -> mapInputList stmt (lines input)+        LineMode -> mapEachLine stmt (lines input)+        WholeMode -> applyToInput stmt (removeTrailingNewline input)+        TypeMode -> typeOfExpr stmt+        EvalMode -> evalExpr stmt       where-        cleanupType :: String -> String-        cleanupType = L.replace "[Char]" "String"--    -- FIXME use Set-    setHwkImports ms = setImports (L.nub (ms ++ ["Hwk"]))+        removeTrailingNewline :: String -> String+        removeTrailingNewline "" = ""+        removeTrailingNewline s =+          if last s == '\n'+          then init s+          else s      -- checkFileExists :: FilePath -> IO ()     -- checkFileExists file = do@@ -98,5 +84,129 @@       unlines $ "ERROR: Won't compile:" : map errMsg es     errorString e = show e +cleanupType :: String -> String+cleanupType = L.replace "FilePath" "String" . L.replace "[Char]" "String"++-- fn $ lines input+mapInputList :: String -> [String] -> InterpreterT IO ()+mapInputList stmt inputs = do+  typ <- resultTypeOfApplied stmt "[String]"+  case cleanupType typ of+    "String" -> do+      fn <- interpret stmt (as :: [String] -> String)+      liftIO $ putStrLn (fn inputs)+    "[String]" -> do+      fn <- interpret stmt (as :: [String] -> [String])+      liftIO $ mapM_ putStrLn (fn inputs)+    "[[String]]" -> do+      fn <- interpret stmt (as :: [String] -> [[String]])+      liftIO $ mapM_ (putStrLn . unwords) (fn inputs)+    "Int" -> do+      fn <- interpret stmt (as :: [String] -> Int)+      liftIO $ print (fn inputs)+    "[Int]" -> do+      fn <- interpret stmt (as :: [String] -> [Int])+      liftIO $ mapM_ print (fn inputs)+    "[[Int]]" -> do+      fn <- interpret stmt (as :: [String] -> [[Int]])+      liftIO $ mapM_ (putStrLn . unwords . map show) (fn inputs)+    _ -> do+      liftIO $ warn typ+      fn <- interpret stmt (as :: [String] -> [String])+      liftIO $ mapM_ putStrLn (fn inputs)++-- map fn $ lines input+mapEachLine :: String -> [String] -> InterpreterT IO ()+mapEachLine stmt inputs = do+  typ <- resultTypeOfApplied stmt "String"+  case cleanupType typ of+    "String" -> do+      fn <- interpret stmt (as :: String -> String)+      liftIO $ mapM_ (putStrLn . fn) inputs+    "[String]" -> do+      fn <- interpret stmt (as :: String -> [String])+      liftIO $ mapM_ (putStrLn . unwords . fn) inputs+    "[[String]]" -> do+      fn <- interpret stmt (as :: String -> [[String]])+      liftIO $ mapM_ (putStrLn . L.intercalate "\t" . map unwords . fn) inputs+    "Int" -> do+      fn <- interpret stmt (as :: String -> Int)+      liftIO $ mapM_ (print . fn) inputs+    "[Int]" -> do+      fn <- interpret stmt (as :: String -> [Int])+      liftIO $ mapM_ (putStrLn . unwords . map show . fn) inputs+    "[[Int]]" -> do+      fn <- interpret stmt (as :: String -> [[Int]])+      liftIO $ mapM_ (putStrLn . L.intercalate "\t" . map (unwords . map show) .fn) inputs+    _ -> do+      liftIO $ warn typ+      fn <- interpret stmt (as :: String -> String)+      liftIO $ mapM_ (putStrLn . fn) inputs++-- fn input+applyToInput :: String -> String -> InterpreterT IO ()+applyToInput stmt input = do+  typ <- resultTypeOfApplied stmt "String"+  case cleanupType typ of+    "String" -> do+      fn <- interpret stmt (as :: String -> String)+      liftIO $ putStrLn (fn input)+    "[String]" -> do+      fn <- interpret stmt (as :: String -> [String])+      liftIO $ mapM_ putStrLn (fn input)+    "[[String]]" -> do+      fn <- interpret stmt (as :: String -> [[String]])+      liftIO $ mapM_ (putStrLn . unwords) (fn input)+    "Int" -> do+      fn <- interpret stmt (as :: String -> Int)+      liftIO $ print (fn input)+    "[Int]" -> do+      fn <- interpret stmt (as :: String -> [Int])+      liftIO $ mapM_ print (fn input)+    "[[Int]]" -> do+      fn <- interpret stmt (as :: String -> [[Int]])+      liftIO $ mapM_ (putStrLn . unwords . map show) (fn input)+    _ -> do+      liftIO $ warn typ+      fn <- interpret stmt (as :: String -> [String])+      liftIO $ mapM_ putStrLn (fn input)++typeOfExpr :: String -> InterpreterT IO ()+typeOfExpr stmt = do+#if MIN_VERSION_hint(0,8,0)+  etypchk <- typeChecksWithDetails stmt+  case etypchk of+    Left err -> liftIO $ mapM_ (putStrLn . errMsg) err+    Right typ -> liftIO $ putStrLn (cleanupType typ)+#else+  typeOf stmt >>= liftIO . putStrLn . cleanupType+#endif++evalExpr :: String -> InterpreterT IO ()+evalExpr stmt = do+  typ <- typeOf stmt+  case cleanupType typ of+    "String" -> do+      interpret stmt "a String" >>= liftIO . putStrLn+    "[String]" -> do+      interpret stmt ["a String"] >>= liftIO . mapM_ putStrLn+    "[[String]]" -> do+      interpret stmt [["a String"]] >>= liftIO . mapM_ (putStrLn . unwords)+    "[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+        -- FIXME option to display type+        liftIO $ putStrLn $ res ++ " :: " ++ typ+ warn :: String -> IO () warn = hPutStrLn stderr++resultTypeOfApplied :: MonadInterpreter m => String -> String -> m String+resultTypeOfApplied expr typ =+  L.dropPrefix (typ ++ " -> ") <$> typeOf (expr ++ " . (id  @" ++ typ ++ ")")
README.md view
@@ -1,14 +1,23 @@ # hwk ![MIT licensed](https://img.shields.io/badge/license-MIT-blue.svg) +<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 was originally written by Lukas Martinelli in 2016-2017: see the [original README file](README.md.orig). -<img align="right" alt="hwk" src="hwk.png" />+**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** (pronounced "hawk") is a simple Haskell-based text stream manipulation tool, somewhat similar to tools like **awk** or **sed**.-`hwk` applies concisely composed pure functions to a list of strings from stdin. Because Haskell is lazy and has a powerful arsenal of functions, there is no need to invent another DSL. Hopefully this tool will also encourage more people to think functionally.+- 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`.  ## Example+Some simple use-cases are in the [examples](examples/) directory.  Change and append a string to each line: ```bash@@ -36,33 +45,34 @@  Extract data from a file: ```bash-$ cat /etc/passwd | hwk --line 'reverse . filter (/= "x") . take 3 . splitOn ":"' | head -3+$ cat /etc/passwd | hwk -l 'reverse . filter (/= "x") . take 3 . splitOn ":"' | head -3 0 root 1 bin 2 daemon ```-(a module defining `splitOn` from the extra or split library needs to be added to the Hwk.hs config file).+(uses `splitOn` from the extra library; `-l` is the short form of `--line`).  The argument passed to `hwk` must be a valid Haskell function: a function that takes a list of strings and returns a new list or a single value. -Check where input contains a certain string:+Check whether the input contains a certain string: ``` $ cat /etc/passwd | hwk --all 'bool "no" "yes" . isInfixOf "1000"' yes ```  ## Configuration-`hwk` uses a Haskell configuration file `~/.config/hwk/Hwk.hs` which provides the context for the hint evaluation of the supplied function.+`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. -The default configuration [Hwk module](data/Hwk.hs) just sets-the `Prelude`, `Data.List`, and `Data.Char` modules to be imported into the hint interpreter.+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 or define your own functions in `~/.config/hwk/Hwk.hs`. -After a hwk version update you may wish/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 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.  ## Install Either use the `install.sh` script, or install by cabal-install or stack@@ -92,23 +102,28 @@ ## How does `hwk` work?  - `hwk` use the hint library to evaluate haskell functions on standard input.-- By default it splits the input to a list of lines: `[String] -> ToList a`-- Use `-a` or `--all` to apply a function to all the input: `String -> Tolist a`+- 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`.  ## Supported return types -By default the following instances of the `ToList` class are defined:+The following return values are supported:  - `String` - `[String]` - `[[String]]` - `Int` - `[Int]`+- `[[Int]]`  ## Contribute  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 
hwk.cabal view
@@ -1,10 +1,11 @@ name:                hwk-version:             0.3-synopsis:            Simple Haskell-based awk-like tool+version:             0.4+synopsis:            Simple cli text processing with Haskell functions description:-            A simple Haskell-based alternative to awk/sed.-            It uses Hint to apply the function given on the commandline-            to standard input and outputs the result.+            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. homepage:            https://github.com/juhp/hwk license:             MIT license-file:        LICENSE@@ -14,7 +15,7 @@                      2020 Jens Petersen category:            Development build-type:          Simple-data-files:          Config.hs+data-files:          Hwk.hs extra-source-files:  install.sh extra-doc-files:     README.md                      ChangeLog.md