An open API service indexing awesome lists of open source software.

https://github.com/mattlianje/layoutz

Simple, beautiful CLI output
https://github.com/mattlianje/layoutz

cli dsl functional-programming scala tui

Last synced: 5 months ago
JSON representation

Simple, beautiful CLI output

Awesome Lists containing this project

README

          



# layoutz
**Simple, beautiful CLI output πŸͺΆ**

Build declarative and composable sections, trees, tables and dashboards for your consoles. Part of [d4](https://github.com/mattlianje/d4)

## Features
- Zero dependencies, use **Layoutz.scala** like a header-file
- Effortless composition of elements
- Thread-safe, purely functional rendering

## Installation
**layoutz** is on MavenCentral and cross-built for Scala, 2.12, 2.13, 3.x
```scala
"xyz.matthieucourt" %% "layoutz" % "0.1.0"
```
Or try in REPL:
```bash
scala-cli repl --scala 3 --dep xyz.matthieucourt:layoutz_3:0.1.0
```

All you need:
```scala
import layoutz._
```

## Quickstart
```scala
import layoutz._

val dashboard = layout(
section("System Status")(
row(
statusCard("CPU", "45%"),
statusCard("Memory", "78%"),
statusCard("Disk", "23%")
)
),
box("Recent Activity")(
bullets(
"User alice logged in",
"Database backup completed",
"3 new deployments"
)
)
)

println(dashboard.render)
```
yields:
```
=== System Status ===
β”Œβ”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ CPU β”‚ β”‚ Memory β”‚ β”‚ Disk β”‚
β”‚ 45% β”‚ β”‚ 78% β”‚ β”‚ 23% β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”˜

β”Œβ”€β”€β”€β”€β”€β”€β”€Recent Activity───────┐
β”‚ β€’ User alice logged in β”‚
β”‚ β€’ Database backup completed β”‚
β”‚ β€’ 3 new deployments β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
```

## Motivation
- We have `s"..."`, and full-blown TUI libraries - but there is a gap in-between.
- With LLM's, boilerplate code that formats & "pretty-prints" is **_cheaper than ever_**...
- Thus, **_more than ever_**, "string formatting code" is spawning, and polluting domain logic
- Utlimately, **layoutz** is just a tiny, declarative DSL to combat this

## Core concepts
- Every piece of content is an `Element`
- Elements are **immutable** and **composable** - you build complex layouts by combining simple elements.
- A `layout` is just a special element that arranges other elements **vertically** with consistent spacing:
```scala
layout(elem1, elem2, elem3) /* Joins with "\n\n" */
```
Call `.render` on an element to get a String

The power comes from **uniform composition**, since everything is an `Element`, everything can be combined with everything else.

## Elements
All components implementing the Element interface you can use in your layouts...

### Text: `Text`
**layoutz** implicitly converts Strings to `Text` element
```scala
"Simple text" // <- valid Element
Text("Simple text") // <- you don't need to do this
```
this lets you splice strings into layouts as you build them with var-arg shorthand

### Line Break: `br`
Add extra line-break "\n" with `br`:
```scala
layout("Line 1", br, "Line 2")
```

### Section: `section`
```scala
section("Config")(kv("env" -> "prod"))
```
```
=== Config ===
env : prod
```

### Layout (vertical): `layout`
```scala
layout("First", "Second", "Third")
```
```
First

Second

Third
```

### Row (horizontal): `row`
```scala
row("Left", "Middle", "Right")
```
```
Left Middle Right
```

### Horizontal rule: `hr`
```scala
hr
hr("~", 10)
```
```
──────────────────────────────────────────────────
~~~~~~~~~
```

### Key-value pairs: `kv`
```scala
kv("name" -> "Alice", "role" -> "admin")
```
```
name : Alice
role : admin
```

### Table: `table`
```scala
table(
headers = Seq("Name", "Status"),
rows = Seq(Seq("Alice", "Online"), Seq("Bob", "Away"))
)
```
```
β”Œβ”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Name β”‚ Status β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ Alice β”‚ Online β”‚
β”‚ Bob β”‚ Away β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”˜
```

### Bullets: `bullets`/`bullet`
Simple bullet list
```scala
bullets("Task 1", "Task 2", "Task 3")
```
```
β€’ Task 1
β€’ Task 2
β€’ Task 3
```
Single bullet with nested children
```scala
bullet("Backend",
bullet("API"),
bullet("Database")
)
```
```
β€’ Backend
β€’ API
β€’ Database
```
Complex nesting
```scala
bullets(
bullet("Frontend",
bullet("Components",
bullet("Header"),
bullet("Footer")
),
bullet("Styles")
),
bullet("Backend",
bullet("API"),
bullet("Database")
)
)
```
```
β€’ Frontend
β€’ Components
β€’ Header
β€’ Footer
β€’ Styles
β€’ Backend
β€’ API
β€’ Database
```
Mix bullets with other elements
```scala
bullet("Status",
"System online",
inlineBar("Health", 0.95),
"All services running"
)
```
```
β€’ Status
β€’ System online
β€’ Health [β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ”€] 95%
β€’ All services running
```

### Box: `box`
```scala
box("Summary")(kv("total" -> "42"))
```
```
β”Œβ”€β”€Summary───┐
β”‚ total : 42 β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
```

### Status card: `statusCard`
```scala
statusCard("CPU", "45%")
```
```
β”Œβ”€β”€β”€β”€β”€β”€β”€β”
β”‚ CPU β”‚
β”‚ 45% β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”˜
```

### Progress bar: `inlineBar`
```scala
inlineBar("Download", 0.75)
```
```
Download [β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ”€β”€β”€β”€β”€] 75%
```

### Diff block: `diffBlock`
```scala
diffBlock(
added = Seq("new feature"),
removed = Seq("old code")
)
```
```
Changes:
- old code
+ new feature
```

### Tree: `tree`/`branch`/`leaf`
```scala
tree("Project")(
branch("src",
branch("main", leaf("App.scala")),
branch("test", leaf("AppSpec.scala"))
)
)
```
```
Project
└── src/
β”œβ”€β”€ main/
β”‚ └── App.scala
└── test/
└── AppSpec.scala
```

## Working with collections
The full power of Scala functional collections is at your fingertips to render your strings with **layoutz**
```scala
case class User(name: String, role: String)
val users = Seq(User("Alice", "Admin"), User("Bob", "User"), User("Tom", "User"))

val usersByRole = users.groupBy(_.role)
section("Users by Role")(
layout(
usersByRole.map { case (role, roleUsers) =>
box(role)(
bullets(roleUsers.map(_.name): _*)
)
}.toSeq: _*
)
)
```
```
=== Users by Role ===
β”Œβ”€β”€Admin──┐
β”‚ β€’ Alice β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

β”Œβ”€β”€User──┐
β”‚ β€’ Bob β”‚
β”‚ β€’ Tom β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”˜
```

## Inspiration
- [ScalaTags](https://github.com/com-lihaoyi/scalatags) by Li Haoyi
- Countless templating libraries via osmosis ...