# 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... - Easily create new primitives (no component-library limitations). ## Installation **Add Layoutz on [Hackage](https://hackage.haskell.org/package/layoutz) to your project's `.cabal` file:** ```haskell build-depends: layoutz ``` All you need: ```haskell import Layoutz ``` ## Quickstart Beautiful, compositional text layouts: ```haskell import Layoutz demo = layout [ center $ row ["Layoutz", underline' "ห†" $ text "DEMO"] , br , row [ statusCard "Users" "1.2K" , withBorder BorderDouble $ statusCard "API" "UP" , withBorder BorderThick $ statusCard "CPU" "23%" , withBorder BorderRound $ table ["Name", "Role", "Status"] [ ["Alice", "Engineer", "Online"] , ["Eve", "QA", "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. ### String Literals With `OverloadedStrings` enabled, you can use string literals directly: ```haskell layout ["Hello", "World"] -- Instead of layout [text "Hello", text "World"] ``` **Note:** When passing to functions that take polymorphic `Element a` parameters (like `underline'`, `center'`, `pad`), use `text` explicitly: ```haskell underline' "=" $ text "Title" -- Correct underline' "=" "Title" -- Ambiguous type error ``` ## Elements ### Text ```haskell text "Simple text" -- Or with OverloadedStrings: "Simple text" ``` ``` Simple text ``` ### Line Break Add line breaks with `br`: ```haskell layout ["Line 1", br, "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 ["First", "Second", "Third"] ``` ``` First Second Third ``` ### Row (horizontal): `row` Arrange elements side-by-side horizontally: ```haskell row ["Left", "Middle", "Right"] ``` ``` Left Middle Right ``` Multi-line elements are aligned at the top: ```haskell row [ layout ["Left", "Column"] , layout ["Middle", "Column"] , layout ["Right", "Column"] ] ``` ### Tight Row: `tightRow` Like `row`, but with no spacing between elements (useful for gradients and progress bars): ```haskell tightRow [withColor ColorRed $ text "โ–ˆ", withColor ColorGreen $ text "โ–ˆ", withColor ColorBlue $ text "โ–ˆ"] ``` ``` โ–ˆโ–ˆโ–ˆ ``` ### Text alignment: `alignLeft`, `alignRight`, `alignCenter`, `justify` Align text within a specified width: ```haskell layout [ alignLeft 40 "Left aligned" , alignCenter 40 "Centered" , alignRight 40 "Right aligned" , justify 40 "This text is justified evenly" ] ``` ``` Left aligned Centered Right aligned This text is justified evenly ``` ### Horizontal rule: `hr` ```haskell hr hr' "~" hr'' "-" 10 ``` ``` โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ---------- ``` ### Vertical rule: `vr` ```haskell row [vr, vr' "โ•‘", vr'' "x" 5] ``` ``` โ”‚ โ•‘ x โ”‚ โ•‘ x โ”‚ โ•‘ x โ”‚ โ•‘ x โ”‚ โ•‘ x โ”‚ โ•‘ โ”‚ โ•‘ โ”‚ โ•‘ โ”‚ โ•‘ โ”‚ โ•‘ ``` ### 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"] [ ["Alice", "30", "New York"] , ["Bob", "25", ""] , ["Charlie", "35", "London"] ] ``` ``` โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ Name โ”‚ Age โ”‚ City โ”‚ โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค โ”‚ Alice โ”‚ 30 โ”‚ New Yorkโ”‚ โ”‚ Bob โ”‚ 25 โ”‚ โ”‚ โ”‚ Charlie โ”‚ 35 โ”‚ London โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ ``` ### Unordered Lists: `ul` Clean unordered lists with automatic nesting: ```haskell ul ["Feature A", "Feature B", "Feature C"] ``` ``` โ€ข Feature A โ€ข Feature B โ€ข Feature C ``` Nested lists with auto-styling: ```haskell ul [ "Backend" , ul ["API", "Database"] , "Frontend" , ul ["Components", ul ["Header", ul ["Footer"]]] ] ``` ``` โ€ข Backend โ—ฆ API โ—ฆ Database โ€ข Frontend โ—ฆ Components โ–ช Header โ€ข Footer ``` ### Ordered Lists: `ol` Numbered lists with automatic nesting: ```haskell ol ["First step", "Second step", "Third step"] ``` ``` 1. First step 2. Second step 3. Third step ``` Nested ordered lists with automatic style cycling (numbers โ†’ letters โ†’ roman numerals): ```haskell ol [ "Setup" , ol ["Install dependencies", "Configure", ol ["Check version"]] , "Build" , "Deploy" ] ``` ``` 1. Setup a. Install dependencies b. Configure i. Check version 2. Build 3. Deploy ``` ### Underline: `underline` Add underlines to any element: ```haskell underline "Important Title" underline' "=" $ text "Custom" -- Use text for custom underline char ``` ``` 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 "Auto-centered" -- Uses layout context center' 20 "Manual width" -- Fixed width ``` ``` Auto-centered Manual width ``` ### Margin: `margin` Add prefix margins to elements for compiler-style error messages: ```haskell margin "[error]" [ text "Ooops" , text "" , row [ text "result :: Int = " , underline' "^" $ text "getString" ] , text "Expected Int, found String" ] ``` ``` [error] Ooops [error] [error] result :: Int = getString [error] ^^^^^^^^^ [error] Expected Int, found String ``` ## Border Styles Elements like `box`, `table`, and `statusCard` support different border styles: **BorderNormal** (default): ```haskell box "Title" ["content"] ``` ``` โ”Œโ”€โ”€Titleโ”€โ”€โ” โ”‚ content โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ ``` **BorderDouble**: ```haskell withBorder BorderDouble $ statusCard "API" "UP" ``` ``` โ•”โ•โ•โ•โ•โ•โ•โ•โ•— โ•‘ API โ•‘ โ•‘ UP โ•‘ โ•šโ•โ•โ•โ•โ•โ•โ•โ• ``` **BorderThick**: ```haskell withBorder BorderThick $ table ["Name"] [["Alice"]] ``` ``` โ”โ”โ”โ”โ”โ”โ”โ”โ”“ โ”ƒ Name โ”ƒ โ”ฃโ”โ”โ”โ”โ”โ”โ”โ”ซ โ”ƒ Alice โ”ƒ โ”—โ”โ”โ”โ”โ”โ”โ”โ”› ``` **BorderRound**: ```haskell withBorder BorderRound $ box "Info" ["content"] ``` ``` โ•ญโ”€โ”€Infoโ”€โ”€โ”€โ•ฎ โ”‚ content โ”‚ โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ ``` **BorderNone** (invisible borders): ```haskell withBorder BorderNone $ box "Info" ["content"] ``` ``` Info content ``` ## Colors (ANSI Support) Add ANSI colors to any element: ```haskell layout[ withColor ColorRed $ text "The quick brown fox...", withColor ColorBrightCyan $ text "The quick brown fox...", underlineColored "~" ColorRed $ text "The quick brown fox...", margin "[INFO]" [withColor ColorCyan $ text "The quick brown fox..."] ] ```

**Standard Colors:** - `ColorBlack` `ColorRed` `ColorGreen` `ColorYellow` `ColorBlue` `ColorMagenta` `ColorCyan` `ColorWhite` - `ColorBrightBlack` `ColorBrightRed` `ColorBrightGreen` `ColorBrightYellow` `ColorBrightBlue` `ColorBrightMagenta` `ColorBrightCyan` `ColorBrightWhite` - `ColorNoColor` *(for conditional formatting)* **Extended Colors:** - `ColorFull n` - 256-color palette (0-255) - `ColorTrue r g b` - 24-bit RGB true color ### Color Gradients Create beautiful gradients with extended colors: ```haskell let palette = tightRow $ map (\i -> withColor (ColorFull i) $ text "โ–ˆ") [16, 18..231] redToBlue = tightRow $ map (\i -> withColor (ColorTrue i 100 (255 - i)) $ text "โ–ˆ") [0, 4..255] greenFade = tightRow $ map (\i -> withColor (ColorTrue 0 (255 - i) i) $ text "โ–ˆ") [0, 4..255] rainbow = tightRow $ map colorBlock [0, 4..255] where colorBlock i = let r = if i < 128 then i * 2 else 255 g = if i < 128 then 255 else (255 - i) * 2 b = if i > 128 then (i - 128) * 2 else 0 in withColor (ColorTrue r g b) $ text "โ–ˆ" putStrLn $ render $ layout [palette, redToBlue, greenFade, rainbow] ```

## Styles (ANSI Support) Add ANSI styles to any element: ```haskell layout[ withStyle StyleBold $ text "The quick brown fox...", withColor ColorRed $ withStyle StyleBold $ text "The quick brown fox...", withStyle StyleReverse $ withStyle StyleItalic $ text "The quick brown fox..." ] ```

**Styles:** - `StyleBold` `StyleDim` `StyleItalic` `StyleUnderline` - `StyleBlink` `StyleReverse` `StyleHidden` `StyleStrikethrough` - `StyleNoStyle` *(for conditional formatting)* **Combining Styles:** Use `<>` to combine multiple styles at once: ```haskell layout[ withStyle (StyleBold <> StyleItalic <> StyleUnderline) $ text "The quick brown fox...", withStyle (StyleBold <> StyleReverse) $ text "The quick brown fox..." ] ```

You can also combine colors and styles: ```haskell withColor ColorBrightYellow $ withStyle (StyleBold <> StyleItalic) $ text "The quick brown fox..." ``` ## Custom Components Create your own components by implementing the `Element` typeclass ```haskell data Square = Square Int instance Element Square where renderElement (Square size) | size < 2 = "" | otherwise = intercalate "\n" (top : middle ++ [bottom]) where w = size * 2 - 2 top = "โ”Œ" ++ replicate w 'โ”€' ++ "โ”" middle = replicate (size - 2) ("โ”‚" ++ replicate w ' ' ++ "โ”‚") bottom = "โ””" ++ replicate w 'โ”€' ++ "โ”˜" -- Helper to avoid wrapping with L square :: Int -> L square n = L (Square n) -- Use it like any other element putStrLn $ render $ row [ square 3 , square 5 , square 7 ] ``` ``` โ”Œโ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ””โ”€โ”€โ”€โ”€โ”˜ โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ โ”‚ โ”‚ โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ ``` ## REPL Drop into GHCi to experiment: ```bash cabal repl ``` ```haskell ฮป> :set -XOverloadedStrings ฮป> import Layoutz ฮป> putStrLn $ render $ center $ box "Hello" ["World!"] โ”Œโ”€โ”€Helloโ”€โ”€โ” โ”‚ World! โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ ฮป> putStrLn $ render $ table ["A", "B"] [["1", "2"]] โ”Œโ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ” โ”‚ A โ”‚ B โ”‚ โ”œโ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”ค โ”‚ 1 โ”‚ 2 โ”‚ โ””โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”˜ ``` ## Inspiration - Original Scala [layoutz](https://github.com/mattlianje/layoutz)