packages feed

table (empty) → 0.1.0.0

raw patch · 6 files changed

+320/−0 lines, 6 filesdep +basedep +csvdep +optparse-applicativesetup-changed

Dependencies added: base, csv, optparse-applicative, process, split

Files

+ LICENSE view
@@ -0,0 +1,20 @@+Copyright (c) 2014 Daniel Choi++Permission is hereby granted, free of charge, to any person obtaining+a copy of this software and associated documentation files (the+"Software"), to deal in the Software without restriction, including+without limitation the rights to use, copy, modify, merge, publish,+distribute, sublicense, and/or sell copies of the Software, and to+permit persons to whom the Software is furnished to do so, subject to+the following conditions:++The above copyright notice and this permission notice shall be included+in all copies or substantial portions of the Software.++THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ Main.hs view
@@ -0,0 +1,177 @@+{-# LANGUAGE RecordWildCards, ScopedTypeVariables #-} +module Main where+import Data.List +import Data.Monoid+import Text.Printf+import Options.Applicative+import Data.List.Split (splitOn)+import Control.Applicative+import Data.Char (isDigit, isSpace)+import Control.Monad (when)+import System.Process (readProcess)+import qualified Text.CSV as CSV+++data Options = Options {+    splitter :: String -> [String]   -- delimiter splitting function+  , csvInput :: Bool+  , suppressRowDividers :: Bool +  , printHeaderDivider :: Bool +  , maxWidth :: Int+  } ++parseOptions :: Parser Options+parseOptions = Options+  <$> (setDelimiter <|> whiteSpaceDelimiter <|> pure (splitOn "\t"))+  <*> switch (short 'c' <> help "Parse input as CSV")+  <*> switch (short 'R' <> help "Don't print row dividers")+  <*> switch (short 'H' <> help "Print header row divider")+  <*> parseMaxWidth++setDelimiter = +   splitOn <$> strOption (metavar "DELIM" <> short 'd' <> help "Input field delimiter. Default is TAB (\\t).")++whiteSpaceDelimiter = flag' +    words+    (short 's' <> help "Use any run of whitespace as input field delimiter")++parseMaxWidth = read <$> strOption (+      value "0" <> short 'w' <> metavar "WIDTH" +      <> help "Max table width. Defaults to value of `tput cols` command.")++opts = info (helper <*> parseOptions)+            (fullDesc +              <> progDesc "Pretty format TSV input into table with aligned and wrapped cells"+              <> header "table"+              <> footer "https://github.com/danchoi/table")++main = do +  Options {..} <- execParser opts+  s <- getContents +  let rawRows = if csvInput +                then case CSV.parseCSV "" s of+                      Left err -> error $ "Error: " ++ show err+                      Right xs' -> stripBlankRows xs'+                else map splitter . lines $ s +  let initialWidths = getCellWidths rawRows+  -- Adjust the max width subtract padding for gutters on side and ' | ' between cells+  maxWidth' <- do +      if maxWidth == 0+      then (read <$> readProcess "tput" ["cols"] [])+      else (return maxWidth)+  let adjMaxWidth = maxWidth' - 2 - ((length initialWidths - 1) * 3)+  let adjustedWidths = adjustWidths adjMaxWidth initialWidths+  let rows = mkCells adjustedWidths rawRows+  mapM_ (\(n, row) -> do+      when (not suppressRowDividers || (printHeaderDivider && n == 1)) $ +         putStrLn $ printDivider 1 $ map width row+      putStrLn . printRow 1 $ row +    ) $ zip [0..] rows+  when (not suppressRowDividers) $ +    putStrLn $ printDivider 1 $ map width (head rows)+++stripBlankRows :: [[String]] -> [[String]]+stripBlankRows xs = [x | x <- xs, all (> 0) $ map length x ]++adjustWidths :: Int -> [Int] -> [Int]+adjustWidths maxWidth xs | sum xs <= maxWidth = xs+                    | otherwise     = adjustWidths maxWidth $ reduceWidest xs++reduceWidest :: [Int] -> [Int]+reduceWidest xs = let m = maximum xs+                  in [ if x == m then x - 1 else x | x <- xs ]++data Cell = Cell {+    content :: [String]+  , width :: Int+  , height :: Int +  , isNumeric :: Bool+  } deriving (Show)++-- | get initial column widths+getCellWidths :: [[String]] -> [Int]+getCellWidths rows = map (maximum . map length) . transpose $ rows++{- Each row is represented as Cell, which contains dimension information. The+first value is the text content; the second is the normalized of the column+width for that cell.  -}++mkCells :: [Int] -> [[String]] -> [[Cell]]+mkCells columnWidths rows = +    let cols     = transpose rows+        colCells = map (\(width, cell) -> addCellDimensions width cell) $ zip columnWidths cols +    in transpose colCells++{- Input is a column of strings. Wraps data in a Cell, which adds width and+height to each cell in a column, and also a flag if the column looks numeric,+which determines the alignment.  Also wraps stings to max cell width -} ++addCellDimensions :: Int -> [String] -> [Cell]+addCellDimensions maxWidth xs = +  let w = min (maximum . map length $ xs) maxWidth+      xs' = map (wrapString w) xs       -- wrapped string content+      numeric = all (all isDigit) (if length xs > 1 then (tail xs) else xs)+  in map (\lines -> Cell lines w (length lines) numeric) xs'++wrapString :: Int -> String -> [String]+wrapString maxWidth x = map trim . wrapLine maxWidth $ x++-- | prints a row of cells with dividers+--   gutter is the width of the blank space at left and right of table+printRow :: Int -> [Cell] -> String+printRow gutter xs = +    let rowHeight = maximum $ map height xs+        subcells :: [[String]]+        subcells = map content xs+        lines = map (\n -> +                       let ss :: [String] +                           ss = map (cellLine n) xs+                       in formatRow ss)+                    [0..(rowHeight - 1)] +    in mconcat $ intersperse "\n" $ lines+  where formatRow :: [String] -> String+        formatRow ss = +            mconcat [margin gutter ' ' , (intercalate " | " ss) , margin gutter ' '] ++-- prints the nth line of a cell +cellLine :: Int -> Cell -> String+cellLine n Cell {..} = +      if n < length content +      then printf fmt (content !! n)  +      else printf fmt ""+    where fmt | isNumeric = "%" ++ show width ++ "s"+              | otherwise = "%-" ++ show width ++ "s"++margin :: Int -> Char -> String+margin w c = take w $ repeat c++printDivider :: Int -> [Int] -> String+printDivider gutter widths = +    mconcat [margin gutter '-'+      , (intercalate "-+-" +        $ map (\w -> take w $ repeat '-') widths)+      , margin gutter '-']+++------------------------------------------------------------------------+-- Word wrapping+-- taken from http://moreindirection.blogspot.com/2010/08/blog-post.html+++trim :: String -> String+trim = trimAndReverse . trimAndReverse+  where trimAndReverse = reverse . dropWhile isSpace++reverseBreak :: (a -> Bool) -> [a] -> ([a], [a])+reverseBreak f xs = (reverse before, reverse after)+  where (after, before) = break f $ reverse xs++wrapLine :: Int -> String -> [String]+wrapLine maxLen line +  | length line <= maxLen  = [line]+  | any isSpace beforeMax  = beforeSpace : (wrapLine maxLen $ afterSpace ++ afterMax)+  | otherwise              = beforeMax : wrapLine maxLen afterMax+    where (beforeMax, afterMax) = splitAt maxLen line+          (beforeSpace, afterSpace) = reverseBreak isSpace beforeMax+
+ README.md view
@@ -0,0 +1,88 @@+# table++Formats lines of TSV, CSV, or DSV (delimiter-separated values) into a pretty+plain text table, wrappings cells with long content to try to fit the table in+the screen.++## Usage ++```+table++Usage: table ([-d DELIM] | [-s]) [-c] [-R] [-H] [-w WIDTH]+  Pretty format TSV input into table with aligned and wrapped cells++Available options:+  -h,--help                Show this help text+  -d DELIM                 Input field delimiter. Default is TAB (\t).+  -s                       Use any run of whitespace as input field delimiter+  -c                       Parse input as CSV+  -R                       Don't print row dividers+  -H                       Print header row divider+  -w WIDTH                 Max table width. Defaults to value of `tput cols`+                           command.++https://github.com/danchoi/table+```++## Example++Format Rails issues from the GitHub API into a table. This example uses+[jq](http://stedolan.github.io/jq/) and+[jsontsv](https://github.com/danchoi/jsontsv) to transform the GitHub API JSON+into tab separated values before piping that data into `table`:++```bash+curl -s "https://api.github.com/repos/rails/rails/issues" | +jq -M '.[]' | +jsontsv -H 'number title user.login:user state' |+table+```++Outputs this when the terminal width is 72 characters: ++```+--------+--------------------------------------+----------------+-------+ number | title                                | user           | state +--------+--------------------------------------+----------------+-------+  18290 | Updating guides for Rails 5.0        | lucascaton     | open  +--------+--------------------------------------+----------------+-------+  18288 | Add --skip-action-mailer (or -M) to  | claudiob       | open  +        | rails generate                       |                |       +--------+--------------------------------------+----------------+-------+  18287 | Rails 4.2.0: Scaffold generator      | dldinternet    | open  +        | with --helper=false produced an      |                |       +        | error                                |                |       +--------+--------------------------------------+----------------+-------+  18285 | undefined method 'clear' for         | nPn-           | open  +        | nil:NilClass in                      |                |       +        | actionpack-4.2.0/lib/action_controll |                |       +        | er/test_case.rb                      |                |       +--------+--------------------------------------+----------------+-------+  18283 | Prefer `array?` rather than `array`  | kamipo         | open  +--------+--------------------------------------+----------------+-------+  18279 | Refactoring of add_constraints in    | eileencodes    | open  +        | AssociationScope                     |                |       +--------+--------------------------------------+----------------+-------+  18273 | Rails 3.2.21 (4.x too?):             | jensb          | open  +        | attribute_will_change! with symbol   |                |       +        | parameter causes "multiple           |                |       +        | assignment" error in Postgres        |                |       +--------+--------------------------------------+----------------+-------+  18257 | ActionView::Helpers::TranslationHelp | jcoyne         | open  +        | er.translate makes unsafe values     |                |       +        | html_safe                            |                |       +...+```++You can use `table` to pretty-format TSV output for `mysql`, `psql`, or+`sqlite3` as well, especially when you need row cell content to wrap to fit+everything nicely on the screen.++## Author++* Daniel Choi <https://github.com/danchoi>++## License++MIT License
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ input view
@@ -0,0 +1,4 @@+id fruit+1 apple+2 banana+3 yogurt
+ table.cabal view
@@ -0,0 +1,29 @@+-- Initial table.cabal generated by cabal init.  For further documentation,+--  see http://haskell.org/cabal/users-guide/++name:                table+version:             0.1.0.0+synopsis:            Simple tool to generate tables from DSV input+-- description:         +homepage:            https://github.com/danchoi/table+license:             MIT+license-file:        LICENSE+author:              Daniel Choi+maintainer:          dhchoi@gmail.com+-- copyright:           +category:            Text+build-type:          Simple+-- extra-source-files:  +cabal-version:       >=1.10++executable table+  main-is:             Main.hs+  -- other-modules:       +  -- other-extensions:    +  build-depends:       base >=4.6 && <4.8+                     , optparse-applicative+                     , split >=0.2+                     , process+                     , csv >= 0.1.0+  -- hs-source-dirs:      +  default-language:    Haskell2010