{"id":23508721,"url":"https://github.com/lovasko/tabl","last_synced_at":"2025-04-16T19:20:56.870Z","repository":{"id":43616823,"uuid":"63190717","full_name":"lovasko/tabl","owner":"lovasko","description":"Table Layout","archived":false,"fork":false,"pushed_at":"2020-02-26T17:47:50.000Z","size":75,"stargazers_count":6,"open_issues_count":11,"forks_count":1,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-03-29T05:34:49.809Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Haskell","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"bsd-2-clause","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/lovasko.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2016-07-12T20:36:50.000Z","updated_at":"2020-02-26T17:47:53.000Z","dependencies_parsed_at":"2022-07-13T11:20:33.477Z","dependency_job_id":null,"html_url":"https://github.com/lovasko/tabl","commit_stats":null,"previous_names":[],"tags_count":4,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lovasko%2Ftabl","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lovasko%2Ftabl/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lovasko%2Ftabl/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lovasko%2Ftabl/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/lovasko","download_url":"https://codeload.github.com/lovasko/tabl/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":249268444,"owners_count":21240927,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-12-25T11:31:44.611Z","updated_at":"2025-04-16T19:20:56.848Z","avatar_url":"https://github.com/lovasko.png","language":"Haskell","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Text.Tabl\n\n[![Build Status](https://travis-ci.org/lovasko/tabl.svg?branch=master)](https://travis-ci.org/lovasko/tabl)\n\n`Text.Tabl` is a Haskell module that arranges multiple `Data.Text.Text`\ninstances into a single table layout, while providing means of alignment\nand visual decoration. The only exported function of the module is `tabl`:\n\n```haskell\ntabl\n  :: Environment -- ^ output environment\n  -\u003e Decoration  -- ^ horizontal decorations\n  -\u003e Decoration  -- ^ vertical decorations\n  -\u003e [Alignment] -- ^ column alignments\n  -\u003e [[T.Text]]  -- ^ table cell data\n  -\u003e T.Text      -- ^ resulting table\n```\n\nAn example output of the `tabl` function within the ASCII-art environment:\n```\n$ ./Constants\n+----------------+---------+-----------+\n| Name           | SI Unit |     Value |\n+----------------+---------+-----------+\n| Speed of light | m/s     | 299792458 |\n| Atmosphere     | Pa      |    101325 |\n| Absolute zero  | C       |   -273.15 |\n+----------------+---------+-----------+\n```\n\n\n## Distribution\nThere are two ways of obtaining the `Text.Tabl` module: Hackage and source\ncode from the git repository. While the Hackage version is a stable\nrelease of the module, the GitHub version is the development branch and\nmight not always compile or function properly.\n\n### Hackage\n```sh\n$ cabal install tabl\n```\n\n### Building from source\n```sh\n$ git clone https://github.com/lovasko/tabl.git\n$ cd tabl/\n$ stack build --pedantic --haddock\n```\n\n### Dependencies\n`Text.Tabl` strives to be as lightweight as possible and depends only on\nthe following three packages:\n* `base`\n* `safe`\n* `text`\n\nIt is written in the Haskell 2010 language and uses the\n`OverloadedStrings` extension.\n\n\n## API\nThe following sections provide detailed description of the layout\nsettings, which can be separated into three categories: environment\nadaptation, column alignments, and both vertical and horizontal\ndecoration.\n\n### Environment\nThe meaning and realisation of a table layout is dependant on the context\nit appears in: a Markdown or HTML table is different from a ASCII-art one\nshowcased in the introductory section. The `Text.Tabl` module currently\nsupports two contexts represented by the `Environment` type:\n * `EnvAscii` denoting the ASCII-art approach\n * `EnvLatex` denoting the LaTeX source code\n\nThe way that the codebase is organised makes adding new environments easy:\napart from the actual layout code, the process boils down to creating a\nnew `Environment` constructor and adding the appropriate pattern matching\nrule in the main `tabl` function.\n\nWhile the introductory example is using the `EnvAscii` environment, the\noutput of equivalent table within the `EnvLatex` would look like this:\n```tex\n\\begin{tabular}{ | l | l | r | }\n\\hline\nName \u0026 SI Unit \u0026 Value \\\\\n\\hline\nSpeed of light \u0026 m/s \u0026 299792458 \\\\\nAtmosphere \u0026 Pa \u0026 101325 \\\\\nAbsolute zero \u0026 C \u0026 -273.15 \\\\\n\\hline\n\\end{tabular}\n```\n\n### Alignment\nThe library provides five alignment options on per-column basis\nrepresented by the `Alignment` data type and its constructors:\n * `AlignLeft`\n * `AlignCentre`\n * `AlignRight`\n * `AlignText T.Text`\n * `AlignIndex (T.Text -\u003e Maybe Int)`\n\nThe first three alignments provide basic self-describing alignments, while\n`AlignText` allows for centering around a first matching substring within each\ncell. `AlignIndex` provides a bit more flexibility where a function matches a\nposition within the cell content, e.g. first non-alphanumeric character or a\nfirst upper-case letter. In case any of the two latter alignments fail to\nmatch, the whole content defaults to behaviour identical to `AlignLeft`.\n\nIt is possible, much like with Haskell functions and their arguments, to\nonly partially specify the table alignments, starting from the left-most\ncolumn. The default alignment is `AlignLeft` - which means that passing\nthe empty list `[]` as the list of alignments to the `tabl` function will\nresult in a table with all columns and their respective cells aligned to\nthe left.\n\n### Decoration\nAnother added value by the `Text.Tabl` module is the process of decorating\nthe table by visually separating columns and rows. Both the decoration\nprocess and decoration interface are decomposed into two dimensions:\nhorizontal and vertical, while both at being treated equally.\n\nEach space between two rows or two columns is indexed, where the top and\nleft spaces share the index zero. Therefore, if a table is comprised of\n`n` rows, there are (inclusive) `0..n` positions that could possibly hold\ndecoration.\n\nThe description of the decoration is embodied in the `Decoration` type,\nspecifically within one (or more) of its constructors:\n * `DecorNone`\n * `DecorAll`\n * `DecorInner`\n * `DecorOuter`\n * `DecorOnly [Int]`\n * `DecorExcept [Int]`\n * `DecorUnion [Decoration]`\n * `DecorIsect [Decoration]`\n\nEssentially, the most powerful decoration constructors are `DecorOnly` and\n`DecorExcept`, which allow for a precise selection of indices that should\ncontain the decoration. Other decoration definitions, such as `DecorInner`\nor `DecorAll` are convenience constructors that help the user achieve a\nset goal without the need to specify the width nor the height of the\ntable.\n\nMoreover, the `DecorUnion` and `DecorIsect` constructors are used to\nperform set operations on top of a list of decorations - union and\nintersection respectively.\n\nIt is important to note that none of the constructors that take a list as an\nargument work with infinite lists, as they would just block indefinitely. The\nexamples listed below demonstrate various decoration options and can be used as\na further study material on this topic.\n\n## Examples\nThe following section contain various examples that use the `Text.Tabl`\nlibrary to render textual data.\n\n### Constants\nThe following code recreates the table from the introductory section:\n```haskell\n{-# LANGUAGE OverloadedStrings #-}\n\nimport Text.Tabl\nimport qualified Data.Text.IO as T\n\n-- | Table containing few physics constants.\nmain :: IO ()\nmain = T.putStrLn $ tabl EnvAscii hdecor vdecor aligns cells \n  where\n    hdecor = DecorUnion [DecorOuter, DecorOnly [1]]\n    vdecor = DecorAll\n    aligns = [AlignLeft, AlignLeft, AlignRight]\n    cells  = [ [\"Name\", \"SI Unit\", \"Value\"]\n             , [\"Speed of light\", \"m/s\", \"299792458\"]\n             , [\"Atmosphere\", \"Pa\", \"101325\"]\n             , [\"Absolute zero\", \"C\", \"-273.15\"] ]\n```\n\n### List of UNIX system users\nThe following code lists all users (their IDs, names and full\ndescriptions) on the system:\n\n```haskell\nimport System.Posix.User\nimport Text.Tabl\nimport qualified Data.Text as T\nimport qualified Data.Text.IO as T\n\n-- | Create a table row for one user entry.\ncreateRow\n  :: UserEntry -- ^ user\n  -\u003e [T.Text]  -- ^ table row\ncreateRow ue = map T.pack [show $ userID ue, userName ue, userGecos ue]\n\n-- | Table containing all system users and their respective basic\n-- information.\nmain :: IO ()\nmain = do\n  users \u003c- getAllUserEntries\n  let cells = map createRow users\n  T.putStrLn $ tabl EnvAscii hdecor vdecor aligns cells\n  where\n    hdecor = DecorNone\n    vdecor = DecorNone\n    aligns = [AlignRight]\n```\n\nAfter compiling and running the code, we get:\n```\n$ ./Users | tail -7\n   66 uucp       UUCP pseudo-user\n   68 pop        Post Office Owner\n   78 auditdistd Auditdistd unprivileged user\n   80 www        World Wide Web Owner\n  845 hast       HAST unprivileged user\n65534 nobody     Unprivileged user\n  964 git_daemon git daemon\n```\n\n### Tic-tac-toe\nThe following code creates a random (possibly invalid) state of the famous\nchild game Tic-tac-toe and renders the playing area:\n\n```haskell\n{-# LANGUAGE OverloadedStrings #-}\n\nimport Control.Monad\nimport Data.List.Split\nimport Data.Word\nimport Safe\nimport System.Random\nimport Text.Tabl\nimport qualified Data.Text.IO as T\n\n-- | Table containing the play grid of tic-tac-toe.\nmain :: IO ()\nmain = do\n  xs \u003c- replicateM 9 randomIO :: IO [Word8]\n  let cells = chunksOf 3 $ map (mark . (`mod` 3)) xs \n  T.putStrLn $ tabl EnvAscii hdecor vdecor aligns cells\n  where\n    mark x = lookupJust x [(0, \" \"), (1, \"X\"), (2, \"O\")]\n    hdecor = DecorAll\n    vdecor = DecorAll\n    aligns = []\n```\n\nAn example run of the compiled program:\n```\n$ ./TicTacToe\n+---+---+---+\n| O | X | X |\n+---+---+---+\n|   | O |   |\n+---+---+---+\n| O |   |   |\n+---+---+---+\n```\n\n### Multiplication table\nThe following code will create a simple elementary-school-level\nmultiplication table based on the provided integer `n`:\n\n```haskell\n{-# LANGUAGE OverloadedStrings #-}\n\nimport System.Environment\nimport Text.Tabl\nimport qualified Data.Text as T\nimport qualified Data.Text.IO as T\n\n-- | Create the multiplication table.\nnumbers\n  :: Int        -- ^ table side size\n  -\u003e [[T.Text]] -- ^ multiplication table\nnumbers n = header : zipWith (:) digits content\n  where\n    header  = \" \" : digits\n    digits  = map (T.pack . show) [1..n]\n    content = map (map (T.pack . show)) mults\n    mults   = map (flip map [1..n] . (*)) [1..n]\n\n-- | Table containing basic integer products.\nmain :: IO ()\nmain = do\n  [n] \u003c- getArgs\n  let cells = numbers (read n)\n  T.putStrLn $ tabl EnvAscii hdecor vdecor aligns cells\n    where\n      hdecor = DecorOnly [1]\n      vdecor = DecorOnly [1]\n      aligns = repeat AlignRight\n```\n\nWhen running the code with e.g. `n = 7`:\n```\n$ ./Multiply 7\n  | 1  2  3  4  5  6  7\n--+--------------------\n1 | 1  2  3  4  5  6  7\n2 | 2  4  6  8 10 12 14\n3 | 3  6  9 12 15 18 21\n4 | 4  8 12 16 20 24 28\n5 | 5 10 15 20 25 30 35\n6 | 6 12 18 24 30 36 42\n7 | 7 14 21 28 35 42 49\n```\n\n### Decimals\nThe following creates a table of values `100/n`, where `0 \u003c n \u003c 21`. This\nexample showcases alignment around the decimal dot character, which increases\nthe readability of floating-point numbers.\n\n```haskell\n{-# LANGUAGE OverloadedStrings #-}\n\nimport System.Environment\nimport Text.Tabl\nimport qualified Data.Text as T\nimport qualified Data.Text.IO as T\n\n-- | Create the division table.\ntable\n  :: Int    -- ^ number of rows\n  -\u003e T.Text -- ^ resulting table\ntable n = tabl EnvAscii hdecor vdecor aligns cells\n  where\n    hdecor = DecorUnion [DecorIf (\\x -\u003e mod x 5 == 0), DecorOuter]\n    vdecor = DecorAll\n    aligns = [AlignRight, AlignText \".\"]\n    cells  = zipWith (\\x y -\u003e [x, y]) xs ys\n    xs     = map (T.pack . show . round) nums\n    ys     = map (T.pack . take 5 . show . (100.0 /)) nums\n    nums   = take n $ iterate (+1.0) 1.0\n\nmain :: IO ()\nmain = getArgs \u003e\u003e= (\\[n] -\u003e T.putStrLn (table (read n)))\n```\n\nThe code above produces the following table:\n```\n$ ./Decimals 12\n+----+---------+\n|  1 | 100.0   |\n|  2 |  50.0   |\n|  3 |  33.33  |\n|  4 |  25.0   |\n|  5 |  20.0   |\n+----+---------+\n|  6 |  16.66  |\n|  7 |  14.28  |\n|  8 |  12.5   |\n|  9 |  11.11  |\n| 10 |  10.0   |\n+----+---------+\n| 11 |   9.090 |\n| 12 |   8.333 |\n+----+---------+\n```\n\n### File sizes\nThe following example lists all regular files stored in the `/var/log`\ndirectory and prints out their sizes in a human-readable form. This example\nshowcases the `AlignIndex` which aligns the column based on a predicate. In\nthis case, we want to align on the first character that is not a letter from\nthe alphabet.\n\n```haskell\nimport Control.Arrow\nimport Control.Monad.Loops\nimport Data.Char\nimport System.Posix.Directory\nimport System.Posix.Files\nimport Text.Tabl\nimport qualified Data.Text as T\nimport qualified Data.Text.IO as T\n\n-- | Compute a human-readable size of a file.\ncomputeSize\n  :: FileStatus -- ^ file handle\n  -\u003e String     -- ^ human-readable size representation\ncomputeSize status\n  | size \u003e (mega * 2) = show (round (size / mega) :: Integer) ++ \"MB\"\n  | size \u003e (kilo * 2) = show (round (size / kilo) :: Integer) ++ \"kB\"\n  | otherwise         = show (round size          :: Integer) ++ \"B\"\n  where\n    size = fromIntegral $ fileSize status :: Double\n    mega = 1024 * 1024                    :: Double\n    kilo = 1024                           :: Double\n\n-- | List all files in a directory.\nlistDirectory\n  :: FilePath                  -- ^ directory path\n  -\u003e IO [(String, FileStatus)] -- ^ directory contents (path, status)\nlistDirectory dir = do\n  stream \u003c- openDirStream dir\n  names \u003c- unfoldWhileM (not . null) (readDirStream stream)\n  closeDirStream stream\n  handles \u003c- mapM (getFileStatus . (concat [dir, \"/\"] ++)) names\n  return $ zip names handles\n\n-- | List header files in /var/log and their respective sizes.\nmain :: IO ()\nmain = do\n  files \u003c- listDirectory \"/var/log\"\n  let files'  = filter (isRegularFile . snd) files\n  let files'' = map (second computeSize) files'\n\n  let aligns = [AlignLeft, AlignIndex (T.findIndex isAlpha)]\n  let cells  = map (\\(name, size) -\u003e [T.pack name, T.pack size]) files''\n  T.putStrLn $ tabl EnvAscii DecorNone DecorAll aligns cells\n```\n\nA sample run could look like this:\n```\n$ ./FileSizes | tail -n +24 | head -n +10\n| CDIS.custom               |   12B  |\n| daily.out                 |    7MB |\n| displaypolicyd.log        |  111kB |\n| displaypolicyd.stdout.log |   50kB |\n| fsck_hfs.log              |  856kB |\n| fuse-ext2_util.log        |    4kB |\n| hdiejectd.log             |    5kB |\n| install.log               |   32MB |\n| monthly.out               |    7kB |\n| notifyd.log               |    0B  |\n```\n\n## License\nThe `tabl` module is licensed under the terms of the 2-clause BSD\nlicense.  For more information please consult the [LICENSE](LICENSE)\nfile. In case you need a different license, feel free to contact the\nauthor.\n\n## Author\nDaniel Lovasko \u003cdaniel.lovasko@gmail.com\u003e\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flovasko%2Ftabl","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Flovasko%2Ftabl","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flovasko%2Ftabl/lists"}