# layoutz **Simple, beautiful CLI output for Haskell ๐Ÿชถ** Build declarative and composable sections, trees, tables, dashboards for your Haskell applications. ## Features - Zero dependencies, use `Layoutz.hs` like a header file - Rich text formatting: alignment, underlines, padding, margins - Lists, trees, tables, charts, banners... ## Installation Add to your `package.yaml` or `.cabal` file: ```yaml dependencies: - layoutz ``` Or install directly: ```bash cabal install layoutz ``` All you need: ```haskell import Layoutz ``` ## Quickstart Beautiful, compositional text layouts: ```haskell import Layoutz demo = layout [ center $ row [text "Layoutz", underline' "ห†" $ text "DEMO"] , br , row [ statusCard "Users" "1.2K" , statusCard' DoubleBorder "API" "UP" , statusCard' ThickBorder "CPU" "23%" , table' RoundBorder ["Name", "Role", "Status"] [ [text "Alice", text "Engineer", text "Online"] , [text "Eve", text "QA", text "Away"] ] , section "Pugilists" [kv [("Kazushi", "Sakuraba"), ("Jet", "Li")]] ] ] putStrLn $ render demo ``` ``` Layoutz DEMO ห†ห†ห†ห† โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ•”โ•โ•โ•โ•โ•โ•โ•โ•— โ”โ”โ”โ”โ”โ”โ”โ”โ”“ โ•ญโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ === Pugilists === โ”‚ Users โ”‚ โ•‘ API โ•‘ โ”ƒ CPU โ”ƒ โ”‚ Name โ”‚ Role โ”‚ Status โ”‚ Kazushi: Sakuraba โ”‚ 1.2K โ”‚ โ•‘ UP โ•‘ โ”ƒ 23% โ”ƒ โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค Jet: Li โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ•šโ•โ•โ•โ•โ•โ•โ•โ• โ”—โ”โ”โ”โ”โ”โ”โ”โ”› โ”‚ Alice โ”‚ Engineer โ”‚ Online โ”‚ โ”‚ Eve โ”‚ QA โ”‚ Away โ”‚ โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ ``` ## Core concepts - Every piece of content is an `Element` - Elements are **immutable** and **composable** - build complex layouts by combining simple elements - A `layout` arranges elements **vertically**: ```haskell layout [elem1, elem2, elem3] -- Joins with "\n" ``` Call `render` on any element to get a string The power comes from **uniform composition** - since everything has the `Element` typeclass, everything can be combined. ## Elements ### Text ```haskell text "Simple text" ``` ``` Simple text ``` ### Line Break Add line breaks with `br`: ```haskell layout [text "Line 1", br, text "Line 2"] ``` ``` Line 1 Line 2 ``` ### Section: `section` ```haskell section "Config" [kv [("env", "prod")]] section' "-" "Status" [kv [("health", "ok")]] section'' "#" "Report" 5 [kv [("items", "42")]] ``` ``` === Config === env: prod --- Status --- health: ok ##### Report ##### items: 42 ``` ### Layout (vertical): `layout` ```haskell layout [text "First", text "Second", text "Third"] ``` ``` First Second Third ``` ### Row (horizontal): `row` ```haskell row [text "Left", text "Middle", text "Right"] ``` ``` Left Middle Right ``` ### Horizontal rule: `hr` ```haskell hr hr' "~" hr'' "-" 10 ``` ``` โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ---------- ``` ### Key-value pairs: `kv` ```haskell kv [("name", "Alice"), ("role", "admin")] ``` ``` name: Alice role: admin ``` ### Table: `table` Tables automatically handle alignment and borders: ```haskell table ["Name", "Age", "City"] [ [text "Alice", text "30", text "New York"] , [text "Bob", text "25", text ""] -- Missing values handled , [text "Charlie", text "35", text "London"] ] ``` ``` โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ Name โ”‚ Age โ”‚ City โ”‚ โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค โ”‚ Alice โ”‚ 30 โ”‚ New Yorkโ”‚ โ”‚ Bob โ”‚ 25 โ”‚ โ”‚ โ”‚ Charlie โ”‚ 35 โ”‚ London โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ ``` ### Unordered Lists: `ul` Clean unordered lists with automatic nesting: ```haskell ul [text "Feature A", text "Feature B", text "Feature C"] ``` ``` โ€ข Feature A โ€ข Feature B โ€ข Feature C ``` Nested lists with auto-styling: ```haskell ul [ text "Backend" , ul [text "API", text "Database"] , text "Frontend" , ul [text "Components", ul [text "Header", ul [text "Footer"]]] ] ``` ``` โ€ข Backend โ—ฆ API โ—ฆ Database โ€ข Frontend โ—ฆ Components โ–ช Header โ€ข Footer ``` ### Underline: `underline` Add underlines to any element: ```haskell underline $ text "Important Title" underline' "=" $ text "Custom" ``` ``` Important Title โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ Custom โ•โ•โ•โ•โ•โ• ``` ### Box: `box` With title: ```haskell box "Summary" [kv [("total", "42")]] ``` ``` โ”Œโ”€โ”€Summaryโ”€โ”€โ”€โ” โ”‚ total: 42 โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ ``` Without title: ```haskell box "" [kv [("total", "42")]] ``` ``` โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ total: 42 โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ ``` ### Status card: `statusCard` ```haskell statusCard "CPU" "45%" ``` ``` โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ CPU โ”‚ โ”‚ 45% โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ ``` ### Progress bar: `inlineBar` ```haskell inlineBar "Download" 0.75 ``` ``` Download [โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ”€โ”€โ”€โ”€โ”€] 75% ``` ### Tree: `tree` ```haskell tree "Project" [ branch "src" [ leaf "main.hs" , leaf "test.hs" ] , branch "docs" [ leaf "README.md" ] ] ``` ``` Project โ”œโ”€โ”€ src โ”‚ โ”œโ”€โ”€ main.hs โ”‚ โ””โ”€โ”€ test.hs โ””โ”€โ”€ docs โ””โ”€โ”€ README.md ``` ### Chart: `chart` ```haskell chart [("Web", 10), ("Mobile", 20), ("API", 15)] ``` ``` Web โ”‚โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆ 10 Mobile โ”‚โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆ 20 API โ”‚โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆ 15 ``` ### Padding: `pad` Add uniform padding around any element: ```haskell pad 2 $ text "content" ``` ``` content ``` ### Centering: `center` Smart auto-centering and manual width: ```haskell center $ text "Auto-centered" -- Uses layout context center' 20 $ text "Manual width" -- Fixed width ``` ``` Auto-centered Manual width ``` ### Margin: `margin` Use `margin` for colorful "compiler-style" prefixes: ```haskell layout [ marginError [text "Type error: expected Int, got String"] , marginWarn [text "Unused variable 'temp'"] , marginSuccess [text "Build completed successfully"] , marginInfo [text "Pro tip: Use layoutz for beautiful output"] ] ``` ``` [error] Type error: expected Int, got String [warn] Unused variable 'temp' [success] Build completed successfully [info] Pro tip: Use layoutz for beautiful output ``` ## Border Styles Elements like `box`, `table`, and `statusCard` support different border styles: **NormalBorder** (default): ```haskell box "Title" [text "content"] ``` ``` โ”Œโ”€โ”€Titleโ”€โ”€โ” โ”‚ content โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ ``` **DoubleBorder**: ```haskell statusCard' DoubleBorder "API" "UP" ``` ``` โ•”โ•โ•โ•โ•โ•โ•โ•โ•— โ•‘ API โ•‘ โ•‘ UP โ•‘ โ•šโ•โ•โ•โ•โ•โ•โ•โ• ``` **ThickBorder**: ```haskell table' ThickBorder ["Name"] [[text "Alice"]] ``` ``` โ”โ”โ”โ”โ”โ”โ”โ”โ”“ โ”ƒ Name โ”ƒ โ”ฃโ”โ”โ”โ”โ”โ”โ”โ”ซ โ”ƒ Alice โ”ƒ โ”—โ”โ”โ”โ”โ”โ”โ”โ”› ``` **RoundBorder**: ```haskell box' RoundBorder "Info" [text "content"] ``` ``` โ•ญโ”€โ”€Infoโ”€โ”€โ”€โ•ฎ โ”‚ content โ”‚ โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ ``` ## REPL Usage Drop into GHCi to experiment: ```bash cabal repl ``` ```haskell ฮป> import Layoutz ฮป> putStrLn $ render $ center $ box "Hello" [text "World!"] โ”Œโ”€โ”€Helloโ”€โ”€โ” โ”‚ World! โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ ฮป> putStrLn $ render $ table ["A", "B"] [[text "1", text "2"]] โ”Œโ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ” โ”‚ A โ”‚ B โ”‚ โ”œโ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”ค โ”‚ 1 โ”‚ 2 โ”‚ โ””โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”˜ ``` ## Inspiration - Original Scala [layoutz](https://github.com/mattlianje/layoutz)