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  +<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