Sign in

    clitable

    Unicode tables for the command line, designed for MoonBit

    table
    terminal
    cli
    unicode
    format
    pretty-print
    Download zip
    Author
    Version
    0.2.0
    License
    MIT
    Last updated
    10 hours ago
    Downloads
    3

    #clitable

    用 MoonBit 写的终端表格渲染库。

    列定义说明「这一列怎么显示」,行数据说明「显示什么」,渲染成 Unicode 表格。

    #状态

    骨架阶段,公开接口已定,渲染主干与布局管理器都可用。

    已实现:列定义与表头、对齐、内边距、显示宽度(含全角与 ANSI 与 OSC 8 超链接)、截断、断行、六套边框预设、颜色与样式、总宽约束、固定行高、无表头表格、纵向表、跨行跨列(含空洞填补与交叉字符)。

    功能面已覆盖参考实现 cli-table3 的全部能力。

    #安装

    发布到 mooncakes 之后:

    moon add chensuiyi/clitable

    #最小示例

    let table = @clitable.Table::new([
    @clitable.Column::new("Name"),
    @clitable.Column::new("Age").align(@clitable.Right),
    ]).push_rows([["Alice", "24"], ["Bob", "30"]])

    println(table)

    输出:

    ┌───────┬─────┐ │ Name │ Age │ ├───────┼─────┤ │ Alice │ 24 │ ├───────┼─────┤ │ Bob │ 30 │ └───────┴─────┘

    Table 实现了 Show,所以 println(table) 直接出表,不必手动 render()。

    #几个用法

    边框换个样式:

    Table::new(cols).with_chars(@clitable.Chars::rounded())

    约束总宽,超出时按均摊削列宽:

    Table::new(cols).with_width(80)

    无表头表格,列数从第一行推导:

    @clitable.Table::grid([["a", "b"], ["c", "d"]])

    纵向键值表:

    @clitable.Table::kv([("Name", "Alice"), ("Age", "24")])

    跨行跨列,空格子不用手写占位符:

    Table::new(cols).push([
    @clitable.Cell::text("greetings").span(2),
    @clitable.Cell::text("sup").span(2, rows=2),
    ])

    单元格内容可以带超链接,终端支持时能点击:

    @clitable.Cell::text("文档").link("https://mooncakes.io")

    固定行高,内容放不下时末行收成省略号:

    Table::new(cols).with_row_height(2)

    整批入表的业务类型,实现 ToRow 即可:

    impl @clitable.ToRow for User with to_row(self) {
    [@clitable.Cell::text(self.name), @clitable.Cell::text(self.age.to_string())]
    }

    Table::new(cols).push_rows(users)

    #设计要点

    列属性收在一处。 表头、对齐、宽度都挂在 Column 上,不必像 cli-table3 那样把表头、列宽、列对齐分三个数组按同一下标维护。

    数量对不上就报错。 行内单元格数与列表数不一致时 raise TableError::Arity,不做静默补齐。

    默认无颜色。 颜色与边框样式全部 opt-in,避免在管道、日志、CI 里出现意外的转义序列。

    宽度按显示列数算。 全角字符计 2 列,ANSI 序列计 0 列,所以带颜色的内容也能正确对齐。

    #参考

    功能边界参考 cli-table3(MIT)。本项目是独立的 MoonBit 实现,接口与实现均为重新设计,未移植其代码。

    显示宽度依赖 moonbit-community/unicodewidth。

    #许可证

    MIT。

    ToCell

    pub trait ToCell {
    fn to_cell(Self) -> Cell
    }

    能被放进单元格的内容。为 String / Int / Double / Bool 提供实现, 业务类型可以自己实现它(例如把金额格式化成千分位)。
    impl ToCell for Bool
    impl ToCell for Int
    impl ToCell for Double
    impl ToCell for String

    ToRow

    pub trait ToRow {
    fn to_row(Self) -> Array[Cell]
    }

    能被当作一整行的数据。Array[T] 自动成为一行,业务类型实现它即可整批入表。
    impl ToRow for Array[T]

    TableError

    pub suberror TableError {
    Arity(Int, Int)
    TooNarrow(Int)
    }

    建表与渲染期间的错误。数量对不上直接报错,不做静默补齐。

    Align

    pub(all) enum Align {
    Left
    Center
    Right
    } derive(Eq,
    Debug
    )

    横向对齐。列级给默认,单元格级可覆盖。

    Align::equal

    fn Align::equal(Align, Align) -> Bool

    Align::not_equal

    fn Align::not_equal(x : Align, y : Align) -> Bool

    Align::to_repr

    Cell

    pub struct Cell {
    content : String
    col_span : Int
    row_span : Int
    h_align : Align?
    v_align : VAlign?
    color :
    Style
    ?
    truncate : String?
    link : String?
    } derive(
    Debug
    )

    一个单元格。内容一律是字符串,数字等类型经 ToCell 转换后进来。
    impl ToCell for Cell

    Cell::align

    fn Cell::align(self : Cell, align : Align) -> Cell

    Cell::bottom

    fn Cell::bottom(self : Cell) -> Cell

    Cell::color

    fn Cell::color(self : Cell, color :
    Style
    ) -> Cell

    fn Cell::link(self : Cell, url : String) -> Cell

    Cell::middle

    fn Cell::middle(self : Cell) -> Cell

    Cell::span

    fn Cell::span(self : Cell, cols : Int, rows? : Int) -> Cell

    Cell::text

    fn Cell::text(content : String) -> Cell

    Cell::to_cell

    fn Cell::to_cell(self : Cell) -> Cell

    Cell::to_repr

    Cell::truncate_with

    fn Cell::truncate_with(self : Cell, text : String) -> Cell

    Chars

    pub struct Chars {
    top : String
    top_mid : String
    top_left : String
    top_right : String
    bottom : String
    bottom_mid : String
    bottom_left : String
    bottom_right : String
    left : String
    left_mid : String
    mid : String
    mid_mid : String
    right : String
    right_mid : String
    middle : String
    } derive(
    Debug
    )

    表格边框字符集。空字符串表示不画该处字符,据此可以拼出无边框、纯 Markdown 等形态,而不必在渲染代码里到处特判。

    与 cli-table3 的 chars 字符串键 map 对应,但字段是编译期可见的: 拼错 top_mid 会在编译期报错,而不是画出一张缺角的表。

    Chars::ascii

    fn Chars::ascii() -> Chars

    ASCII 边框:+---+

    Chars::double

    fn Chars::double() -> Chars

    双线直角边框:╔═╤═╗

    Chars::markdown

    fn Chars::markdown() -> Chars

    Markdown 表格边框:| a | b | 加 |---|---| 分隔线。

    Chars::none

    fn Chars::none() -> Chars

    无边框,只用 | 分隔列:适合直接粘进 Markdown 或纯文本。

    Chars::rounded

    fn Chars::rounded() -> Chars

    单线圆角边框:╭─┬─╮

    Chars::single

    fn Chars::single() -> Chars

    单线直角边框:┌─┬─┐

    Chars::to_repr

    Column

    pub struct Column {
    title : String
    align : Align
    width : Int?
    wrap : Bool
    truncate : String?
    } derive(
    Debug
    )

    一列:表头文本与展示属性。

    列的职责只有"怎么显示":表头、对齐、宽度。取值与格式化属于数据本身, 由 ToCell / ToRow 决定。列属性收在一处,改一列不必同时改三个下标数组 ——这是与 cli-table3 的 head / colWidths / colAligns 分开传的主要区别。

    Column::align

    fn Column::align(self : Column, align : Align) -> Column

    Column::new

    fn Column::new(title : String) -> Column

    Column::to_repr

    Column::truncate_with

    fn Column::truncate_with(self : Column, text : String) -> Column

    Column::width

    fn Column::width(self : Column, width : Int) -> Column

    Column::wrap_lines

    fn Column::wrap_lines(self : Column) -> Column

    Line

    pub enum Line {
    Top
    Bottom
    At(Int)
    } derive(
    Debug
    )

    绘制位置:一条顶线、一条内容行,或一条底线。

    Line::to_repr

    Placed

    pub struct Placed {
    cell : Cell
    kind : PlacedKind
    x : Int
    y : Int
    } derive(
    Debug
    )

    布局网格里的一个位置。

    Placed::to_repr

    PlacedKind

    pub enum PlacedKind {
    Content
    ColSpan
    RowSpan(Int, Int)
    } derive(
    Debug
    )

    布局后的格子种类。占位格是 cli-table3 里 ColSpanCell / RowSpanCell 的对应物:不用继承与 instanceof,把三态写进类型,绘制时靠模式匹配分流。

    Table

    pub struct Table {
    cols : Array[Column]
    rows : Array[Array[Cell]]
    chars : Chars
    pad_l : Int
    pad_r : Int
    head_style :
    Style

    border_style :
    Style

    total_width : Int?
    row_height : Int?
    word_wrap : Bool
    wrap_on_word_boundary : Bool
    truncate : String
    } derive(
    Debug
    )

    表格。列定义与行数据分离:列说"怎么显示",行说"显示什么"。

    构建器就地累积(push 直接改内部行表并返回自身),链式调用不会复制中间 结果。字段都不带 mut:内容靠引用原地修改。
    impl Show for Table

    Table::grid

    fn Table::grid(rows : Array[Array[String]]) -> Table

    无表头表格:列数从第一行推导。

    Table::kv

    fn Table::kv(pairs : Array[(String, String)]) -> Table

    纵向表:两列无表头,每对是一行。

    Table::new

    fn Table::new(cols : Array[Column]) -> Table

    建一张表。列定义必填,其余都有默认值,用 with_* 逐个叠加。

    Table::output

    fn Table::output(self : Table, logger : &Logger) -> Unit

    让 table.to_string() 与 Show::to_string(table) 两种写法都可用。

    Table::push

    fn[T : ToCell] Table::push(self : Table, cells : Array[T]) -> Table

    追加一行。数组元素经 ToCell 转成单元格,所以 ["a", "b"] 与 [1, 2] 都行。

    跨行跨列靠单元格自己声明:Cell::text("x").span(2, rows=3)。空出来的位置 不必补占位符,布局阶段会自己填。

    Table::push_rows

    fn[R : ToRow] Table::push_rows(self : Table, rows : Array[R]) -> Table

    批量追加。元素实现 ToRow 即可:Array[T] 是一行,业务类型可以自己实现。

    Table::render

    fn Table::render(self : Table) -> String raise TableError

    渲染成字符串。

    流程与 cli-table3 一致:铺布局网格 → 算列宽 → 算行高 → 解出跨行偏移 → 逐行画顶线、内容行与底线。行内单元格数与列表数不一致时 raise TableError::Arity,不做静默补齐。

    Table::to_repr

    Table::to_string

    fn Table::to_string(self : Table) -> String

    让 table.to_string() 与 Show::to_string(table) 两种写法都可用。

    Table::with_border_style

    fn Table::with_border_style(self : Table, style :
    Style
    ) -> Table

    Table::with_chars

    fn Table::with_chars(self : Table, chars : Chars) -> Table

    换一套边框字符。

    Table::with_head_style

    fn Table::with_head_style(self : Table, style :
    Style
    ) -> Table

    表头与边框的样式。传一个 Style 值,空 Style 原样返回。

    Table::with_padding

    fn Table::with_padding(self : Table, left : Int, right : Int) -> Table

    换内边距。左右都按显示列数给。

    Table::with_row_height

    fn Table::with_row_height(self : Table, height : Int) -> Table

    固定每一行的行数。内容超出时在末行收成截断符,空出的行补空行。

    Table::with_width

    fn Table::with_width(self : Table, width : Int) -> Table

    约束表格总宽。超出时按均摊削列宽,内容窄时不会被拉伸。

    Table::with_word_boundary

    fn Table::with_word_boundary(self : Table, on : Bool) -> Table

    换行时是否优先在词边界断开。

    Table::with_word_wrap

    fn Table::with_word_wrap(self : Table) -> Table

    打开自动换行。配合列宽约束使用:窄列会把长内容折行而不是截断。

    VAlign

    pub(all) enum VAlign {
    Top
    Middle
    Bottom
    } derive(Eq,
    Debug
    )

    纵向对齐。用于单元格内容不足一格的场景(跨行、固定行高)。

    VAlign::equal

    fn VAlign::equal(VAlign, VAlign) -> Bool

    VAlign::not_equal

    fn VAlign::not_equal(x : VAlign, y : VAlign) -> Bool

    VAlign::to_repr

    compute_col_widths

    fn compute_col_widths(grid : Array[Array[Placed]], cols : Array[Column], pad_l : Int, pad_r : Int, total_width : Int?) -> Array[Int]

    每列的最终显示宽度(含左右内边距)。

    先按普通格子取各自自然宽度,显式列宽覆盖之,再把跨列格子放不下的差额均摊给 它覆盖的可编辑列,最后在有总宽约束时按均摊削减。

    compute_row_heights

    fn compute_row_heights(grid : Array[Array[Placed]], cols : Array[Column], col_widths : Array[Int], pad_l : Int, pad_r : Int, wrap : Bool, on_word_boundary : Bool) -> Array[Int]

    每行的内容行数(不含上下边框)。

    普通格子取自身行数。跨行格子要的行数比它覆盖的几行加起来还多时,把差额均摊 给这些行,跨行内容才放得下。

    display_width

    fn display_width(text : String) -> Int

    单行文本的显示宽度(终端列数)。ANSI 序列不计宽,全角字符计 2 列。

    display_width_multiline

    fn display_width_multiline(text : String) -> Int

    多行文本的显示宽度,取最长的一行。

    layout_rows

    fn layout_rows(rows : Array[Array[Cell]]) -> (Array[Array[Placed]], Array[Int])

    把行数据铺成布局网格:分配位置 → 补空洞 → 插两类占位格。

    同时返回每行进入时被上方跨行格子占用的列数,调用方据此校验行宽是否合理: 行内跨列数之和 + 占用列数 必须等于总列数。

    pad_to

    fn pad_to(text : String, width : Int, align : Align) -> String

    按对齐方式把文本补齐到 width 列。宽度按显示列数算,全角字符不会算错。

    resolve_row_span_offsets

    fn resolve_row_span_offsets(grid : Array[Array[Placed]], row_heights : Array[Int]) -> Unit

    算出跨行占位格相对原格子的内容行偏移。原格子跨了多行时,它的内容要按这些 行的累积高度往下排。

    strip_ansi

    fn strip_ansi(text : String) -> String

    去掉文本里的 ANSI 转义序列,只留下可见字符。宽度计算与截断都先经它。

    truncate_to

    fn truncate_to(text : String, width : Int, symbol : String) -> String

    截断到给定显示宽度,超出部分换成省略符。放得下就原样返回。

    wrap_text

    fn wrap_text(text : String, width : Int, on_word_boundary : Bool) -> Array[String]

    按显示宽度断行。on_word_boundary 为真时优先在空白处断,否则按列硬切。