Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,9 @@ serde_json = "1.0"
dirs = "5.0"
reqwest = { version = "0.11", features = ["json", "blocking", "multipart"] }
tokio = { version = "1.35", features = ["full"] }
comfy-table = "7.0"
# custom_styling: measure cell width with ANSI escapes stripped, so ANSI_COLOR
# output lines up under --table-style (the default renderer already does this)
comfy-table = { version = "7.0", features = ["custom_styling"] }
csv = "1.3"
ratatui = "0.29"
tui-input = { version = "0.11", features = ["crossterm"] }
Expand Down
70 changes: 70 additions & 0 deletions scripts/lrt.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
# lrt - most recently modified files, an `ls -alrt` built on sql-cli.
#
# Load it from your PowerShell profile:
# . C:\path\to\sql-cli\scripts\lrt.ps1
#
# Usage:
# lrt # 20 newest files in the current directory
# lrt 50 src -r # 50 newest under src, recursive
# lrt -g '*.log' # only files matching a glob
# lrt -d # include directories
# lrt -style utf8 # any style from `sql-cli --list-table-styles`
#
# The default style is `plain` (no borders). It needs a sql-cli built with the
# ANSI width fix (after v1.85.4); on older builds coloured cells misalign in
# every style except `default`, so use `lrt -style default` there.
function lrt {
param(
[int]$n = 20,
[string]$path = '.',
[switch]$r,
[string]$g,
[switch]$d,
[string]$style = 'plain'
)
$p = $path -replace "'", "''"
$opts = @()
if ($r) { $opts += 'RECURSIVE' }
if ($g) { $opts += "GLOB '$($g -replace "'", "''")'" }
$dirFilter = if ($d) { '' } else { 'AND is_dir = false' }
# recursive: show the path relative to the root, not the full \\?\C:\... path
$root = ('\\?\' + (Resolve-Path $path).ProviderPath.TrimEnd('\') + '\') -replace "'", "''"
$nameCol = if ($r) { "REPLACE(path, '$root', '')" } else { 'name' }
# The date functions work in UTC; shift to local time for display. This uses
# today's offset, so files from the other side of a DST change show an hour out.
$off = [int][TimeZoneInfo]::Local.GetUtcOffset((Get-Date)).TotalMinutes

$sql = @"
WITH f AS (FILE PATH '$p' $($opts -join ' ')),
g AS (
SELECT $nameCol AS name, ext, size, is_dir,
DATEADD('minute', $off, modified) AS lm,
DATEDIFF('second', modified, NOW()) AS secs
FROM f WHERE depth > 0 $dirFilter
)
SELECT
FORMAT_DATE(lm, '%Y-%m-%d') AS date,
FORMAT_DATE(lm, '%H:%M:%S') AS time,
CASE
WHEN secs < 60 THEN ANSI_COLOR('bright_green', secs || 's')
WHEN secs < 3600 THEN ANSI_COLOR('green', FLOOR(secs / 60) || 'm')
WHEN secs < 86400 THEN ANSI_COLOR('yellow', FLOOR(secs / 3600) || 'h')
WHEN secs < 604800 THEN FLOOR(secs / 86400) || 'd'
ELSE ANSI_COLOR('bright_black', FLOOR(secs / 86400) || 'd')
END AS age,
IIF(is_dir, '<dir>', FORMAT_BYTES(size)) AS size,
CASE
WHEN is_dir THEN ANSI_COLOR('bright_blue', name)
WHEN ext IN ('rs', 'py', 'sql', 'ps1', 'cs', 'lua') THEN ANSI_COLOR('cyan', name)
WHEN ext IN ('md', 'txt') THEN ANSI_COLOR('magenta', name)
WHEN ext IN ('log', 'csv', 'json') THEN ANSI_COLOR('yellow', name)
WHEN ext IN ('exe', 'dll', 'zip') THEN ANSI_COLOR('red', name)
ELSE name
END AS name
FROM g
ORDER BY secs ASC
LIMIT $n
"@
# The "# Query completed" footer goes to stderr
sql-cli -q $sql -o table --table-style $style --max-col-width 0 2>$null
}
60 changes: 59 additions & 1 deletion tests/python_tests/test_table_output_alignment.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
"""

import os
import re
import subprocess
import sys
import unicodedata
Expand All @@ -26,8 +27,15 @@ def _sql_cli_binary():
return base_dir / "target" / "release" / f"sql-cli{suffix}"


ANSI_ESCAPE = re.compile(r"\x1b\[[0-9;]*[A-Za-z]")


def _display_width(text):
"""Column count of a string, matching the Rust side's unicode-width use."""
"""Column count of a string, matching the Rust side's unicode-width use.

ANSI escapes (from ANSI_COLOR and friends) take no columns on a terminal.
"""
text = ANSI_ESCAPE.sub("", text)
return sum(2 if unicodedata.east_asian_width(ch) in ("W", "F") else 1 for ch in text)


Expand Down Expand Up @@ -147,6 +155,56 @@ def test_markdown_style_is_also_aligned(tmp_path):
assert_lines_aligned(lines)


# Coloured cells: the default renderer always sized columns by display width, but
# the comfy-table styles counted the escape bytes, so a cell like
# ANSI_COLOR('yellow', '3h') was padded as if it were 11 columns wide.

ALL_TABLE_STYLES = [
"default",
"ascii",
"ascii-condensed",
"ascii-borders",
"ascii-horizontal",
"ascii-noborders",
"markdown",
"utf8",
"utf8-condensed",
"utf8-borders",
"utf8-horizontal",
"utf8-noborders",
"plain",
]

COLOURED_QUERY = (
"SELECT ANSI_COLOR('yellow', Project) AS project, "
"ANSI_COLOR('green', DurationSecs) AS secs, DurationSecs AS raw "
"FROM projects"
)


@pytest.mark.parametrize("style", ALL_TABLE_STYLES)
def test_coloured_cells_are_aligned_in_every_style(tmp_path, style):
lines = render_table(
PROJECTS_CSV,
COLOURED_QUERY,
extra_args=["--table-style", style, "--max-col-width", "0"],
tmp_path=tmp_path,
)
assert any("\x1b[" in line for line in lines), "expected ANSI colour in the output"
assert_lines_aligned(lines)


@pytest.mark.parametrize("style", ["default", "ascii", "utf8-noborders", "plain"])
def test_coloured_cells_stay_aligned_when_truncated(tmp_path, style):
lines = render_table(
PROJECTS_CSV,
COLOURED_QUERY,
extra_args=["--table-style", style, "--max-col-width", "12"],
tmp_path=tmp_path,
)
assert_lines_aligned(lines)


# Markdown is a --table-style, but it reads as an output format, so `-o markdown`
# used to be rejected outright. It is now shorthand for the table + markdown style.

Expand Down
Loading