Skip to content

Commit

Permalink
Replace 'text' with 'human' and 'plain' output mode.
Browse files Browse the repository at this point in the history
  • Loading branch information
floitsch committed Oct 2, 2024
1 parent d32f607 commit 2de9bd0
Show file tree
Hide file tree
Showing 6 changed files with 226 additions and 30 deletions.
8 changes: 4 additions & 4 deletions src/cli.toit
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,15 @@ export Config
/**
An object giving access to common operations for CLI programs.
If no ui is given uses $Ui.console.
If no ui is given uses $Ui.human.
*/
interface Cli:
constructor
name/string
--ui/Ui?=null
--cache/Cache?=null
--config/Config?=null:
if not ui: ui = Ui.console
if not ui: ui = Ui.human
return Cli_ name --ui=ui --cache=cache --config=config
/**
The name of the application.
Expand Down Expand Up @@ -319,9 +319,9 @@ class Command:
options_ = options_.copy
is-copied = true
option := OptionEnum "output-format"
["text", "json"]
["human", "plain", "json"]
--help="Specify the format used when printing to the console."
--default="text"
--default="human"
options_.add option
if not has-verbose-flag:
if not is-copied:
Expand Down
2 changes: 1 addition & 1 deletion src/parser_.toit
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import .ui
import .utils_
import host.pipe

class StderrPrinter_ extends PrinterBase:
class StderrPrinter_ extends HumanPrinterBase:
needs-structured --kind/int -> bool: return false
emit-structured --kind/int o/any: unreachable
print_ o:
Expand Down
97 changes: 81 additions & 16 deletions src/ui.toit
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ create-ui-from-args_ args/List:
output-format = arg["--output-format=".size..]

if verbose-level == null: verbose-level = "info"
if output-format == null: output-format = "text"
if output-format == null: output-format = "human"

level/int := ?
if verbose-level == "debug": level = Ui.DEBUG-LEVEL
Expand All @@ -50,8 +50,12 @@ create-ui-from-args_ args/List:

if output-format == "json":
return Ui.json --level=level
else if output-format == "plain":
return Ui.plain --level=level
else if output-format == "human":
return Ui.human --level=level
else:
return Ui.console --level=level
throw "Invalid output format: $output-format"

interface Printer:
/** Emits the given $msg of the given message-$kind. */
Expand Down Expand Up @@ -84,7 +88,10 @@ interface Printer:
/** Emits the given $json-object of the given message-$kind. */
emit-structured --kind/int json-object/any

abstract class PrinterBase implements Printer:
/**
A printer that prints human-readable output.
*/
abstract class HumanPrinterBase implements Printer:

abstract needs-structured --kind/int -> bool
abstract emit-structured --kind/int object/any
Expand Down Expand Up @@ -175,6 +182,61 @@ abstract class PrinterBase implements Printer:
print_ "│ $(padded-row.join " ") │"
print_ "└─$(bars.join "─┴─")─┘"

/**
A printer that prints output in simple text format.
Typically, this printer is used in shell scripts or other non-interactive
environments, where JSON output is not desired.
*/
abstract class PlainPrinterBase implements Printer:

abstract needs-structured --kind/int -> bool
abstract emit-structured --kind/int object/any

abstract print_ str/string

emit --kind/int msg/string:
print_ msg

emit-list --kind/int list/List --title/string?:
emit-list_ list

emit-list_ list/List:
list.do:
// TODO(florian): should the entries be recursively pretty-printed?
print_ "$it"

emit-map --kind/int map/Map --title/string?:
emit-map_ map --prefix=""

emit-map_ map/Map --prefix/string:
map.do: | key value |
if value is Map:
emit-map_ value --prefix="$prefix$key."
else if value is List:
print_ "$prefix$key: $(value.join ", ")"
else:
// TODO(florian): should the entries handle lists as well.
print_ "$prefix$key: $value"

emit-table --kind/int table/List --title/string?=null --header/Map:
column-count := header.size
column-sizes := header.map: 0

table.do: | row/Map |
header.do --keys: | key/string |
entry/string := "$row[key]"
column-sizes.update key: | old/int | max old (entry.size --runes)

table.do: | row |
out := ""
spacing := ""
column-sizes.do: | key size |
entry := "$row[key]"
out += "$spacing$entry"
spacing = " " * (1 + size - (entry.size --runes))
print_ out

/**
A class for handling input/output from the user.
Expand Down Expand Up @@ -203,8 +265,11 @@ class Ui:
if not DEBUG-LEVEL >= level >= SILENT-LEVEL:
throw "Invalid level: $level"

constructor.console --level/int=NORMAL-LEVEL:
return Ui --level=level --printer=ConsolePrinter
constructor.human --level/int=NORMAL-LEVEL:
return Ui --level=level --printer=HumanPrinter

constructor.plain --level/int=NORMAL-LEVEL:
return Ui --level=level --printer=PlainPrinter

constructor.json --level/int=NORMAL-LEVEL:
return Ui --level=level --printer=JsonPrinter
Expand Down Expand Up @@ -632,29 +697,29 @@ class Ui:
--level=level or this.level
--printer=printer or this.printer_

/**
Prints the given $str using $print.
class HumanPrinter extends HumanPrinterBase:
needs-structured --kind/int -> bool: return false

This function is necessary, as $ConsolePrinter has its own 'print' method,
which shadows the global one.
*/
global-print_ str/string:
print str
print_ str/string:
print str

emit-structured --kind/int object/any:
unreachable

class ConsolePrinter extends PrinterBase:
class PlainPrinter extends PlainPrinterBase:
needs-structured --kind/int -> bool: return false

print_ str/string:
global-print_ str
print str

emit-structured --kind/int object/any:
unreachable

class JsonPrinter extends PrinterBase:
class JsonPrinter extends PlainPrinterBase:
needs-structured --kind/int -> bool: return kind == Ui.RESULT

print_ str/string:
print-on-stderr_ str

emit-structured --kind/int object/any:
global-print_ (json.stringify object)
print (json.stringify object)
2 changes: 1 addition & 1 deletion tests/help_test.toit
Original file line number Diff line number Diff line change
Expand Up @@ -635,7 +635,7 @@ test-options:
cmd.run --add-ui-help []
expected = """
Global options:
--output-format text|json Specify the format used when printing to the console. (default: text)
--output-format human|plain|json Specify the format used when printing to the console. (default: human)
--verbose Enable verbose output. Shorthand for --verbosity-level=verbose.
--verbosity-level debug|info|verbose|quiet|silent Specify the verbosity level. (default: info)
"""
Expand Down
2 changes: 1 addition & 1 deletion tests/test_ui.toit
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ class TestUi extends cli.Ui:
abort:
throw "abort"

class TestPrinter extends cli.PrinterBase:
class TestPrinter extends cli.HumanPrinterBase:
ui_/TestUi? := null

constructor:
Expand Down
145 changes: 138 additions & 7 deletions tests/ui-test.toit
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import cli.ui show *
import encoding.json
import expect show *

class TestPrinter extends PrinterBase:
class TestHumanPrinter extends HumanPrinterBase:
stdout/string := ""
structured/List := []

Expand All @@ -28,6 +28,30 @@ class TestPrinter extends PrinterBase:
emit-structured --kind/int o:
structured.add o


class TestPlainPrinter extends PlainPrinterBase:
needs-structured_/bool

stdout/string := ""
stderr/string := ""

constructor --needs-structured/bool:
needs-structured_ = needs-structured

needs-structured --kind/int -> bool:
return needs-structured_

reset:
stdout = ""
stderr = ""

print_ str/string:
stdout += "$str\n"

emit-structured --kind/int structured:
stdout += (json.stringify structured)


class TestJsonPrinter extends JsonPrinter:
stdout/string := ""
stderr/string := ""
Expand All @@ -43,12 +67,13 @@ class TestJsonPrinter extends JsonPrinter:
stdout += (json.stringify structured)

main:
test-console
test-human
test-plain
test-structured
test-json

test-console:
printer := TestPrinter --no-needs-structured
test-human:
printer := TestHumanPrinter --no-needs-structured
ui := Ui --printer=printer

ui.emit --info "hello"
Expand Down Expand Up @@ -171,8 +196,114 @@ test-console:
""" printer.stdout
printer.reset

test-plain:
printer := TestPlainPrinter --no-needs-structured
ui := Ui --printer=printer

ui.emit --info "hello"
expect-equals "hello\n" printer.stdout
printer.reset

ui.emit --info ["hello", "world"]
expect-equals "[hello, world]\n" printer.stdout
printer.reset

ui.emit-list --result ["hello", "world"]
expect-equals "hello\nworld\n" printer.stdout
printer.reset

ui.emit-list --result --title="French" ["bonjour", "monde"]
expect-equals "bonjour\nmonde\n" printer.stdout
printer.reset

ui.emit-table --result --header={"x": "x", "y": "y"} [
{ "x": "a", "y": "b" },
{ "x": "c", "y": "d" },
]
expect-equals """
a b
c d
""" printer.stdout
printer.reset

ui.emit-table --result --header={ "left": "long", "right": "even longer" } [
{ "left": "a", "right": "short" },
{ "left": "longer", "right": "d" },
]
expect-equals """
a short
longer d
""" printer.stdout
printer.reset

ui.emit-table --result --header={"left": "no", "right": "rows"} []
expect-equals "" printer.stdout
printer.reset

ui.emit-table --result --header={"left": "with", "right": "ints"} [
{ "left": 1, "right": 2, },
{ "left": 3, "right": 4, },
]
expect-equals """
1 2
3 4
""" printer.stdout
printer.reset

ui.emit --info {
"a": "b",
"c": "d",
}
expect-equals "{a: b, c: d}\n" printer.stdout
printer.reset

ui.emit-map --result {
"a": "b",
"c": "d",
}
expect-equals """
a: b
c: d
""" printer.stdout
printer.reset

// Nested maps.
ui.emit-map --result {
"a": {
"b": "c",
"d": "e",
},
"f": "g",
}
expect-equals """
a.b: c
a.d: e
f: g
""" printer.stdout
printer.reset

ui.emit --info "foo"
expect-equals "foo\n" printer.stdout
printer.reset

ui.emit --warning "foo"
expect-equals "foo\n" printer.stdout
printer.reset

ui.emit --error "foo"
expect-equals "foo\n" printer.stdout
printer.reset

ui.emit-map --result {
"entry with int": 499,
}
expect-equals """
entry with int: 499
""" printer.stdout
printer.reset

test-structured:
printer := TestPrinter --needs-structured
printer := TestHumanPrinter --needs-structured
ui := Ui --printer=printer

ui.emit --info "hello"
Expand Down Expand Up @@ -233,9 +364,9 @@ test-json:
expect-equals "{\"foo\":1,\"bar\":2}" printer.stdout

ui.emit --warning "some warning"
expect-equals "Warning: some warning\n" printer.stderr
expect-equals "some warning\n" printer.stderr
printer.reset

ui.emit --error "some error"
expect-equals "Error: some error\n" printer.stderr
expect-equals "some error\n" printer.stderr
printer.reset

0 comments on commit 2de9bd0

Please sign in to comment.