use "buffered"
primitive Help
fun general(cs: CommandSpec box): CommandHelp =>
"""
Creates a command help that can print a general program help message.
"""
CommandHelp._create(None, cs)
fun for_command(cs: CommandSpec box, argv: Array[String] box)
: (CommandHelp | SyntaxError)
=>
"""
Creates a command help for a specific command that can print a detailed
help message.
"""
_parse(cs, CommandHelp._create(None, cs), argv)
fun _parse(cs: CommandSpec box, ch: CommandHelp, argv: Array[String] box)
: (CommandHelp | SyntaxError)
=>
if argv.size() > 0 then
try
let cname = argv(0)?
if cs.commands().contains(cname) then
match cs.commands()(cname)?
| let ccs: CommandSpec box =>
let cch = CommandHelp._create(ch, ccs)
return _parse(ccs, cch, argv.slice(1))
end
end
return SyntaxError(cname, "unknown command")
end
end
ch
class box CommandHelp
"""
CommandHelp encapsulates the information needed to generate a user help
message for a given CommandSpec, optionally with a specific command
identified to print help about. Use `Help.general()` or `Help.for_command()`
to create a CommandHelp instance.
"""
let _parent: (CommandHelp box | None)
let _spec: CommandSpec box
new _create(parent': (CommandHelp box | None), spec': CommandSpec box) =>
_parent = parent'
_spec = spec'
fun fullname(): String =>
match _parent
| let p: CommandHelp => p.fullname() + " " + _spec.name()
else
_spec.name()
end
fun string(): String => fullname()
fun help_string(): String =>
"""
Renders the help message as a String.
"""
let w: Writer = Writer
_write_help(w)
let str = recover trn String(w.size()) end
for bytes in w.done().values() do str.append(bytes) end
str
fun print_help(os: OutStream) =>
"""
Prints the help message to an OutStream.
"""
let w: Writer = Writer
_write_help(w)
os.writev(w.done())
fun _write_help(w: Writer) =>
_write_usage(w)
if _spec.descr().size() > 0 then
w.write("\n")
w.write(_spec.descr() + "\n")
end
let options = _all_options()
if options.size() > 0 then
w.write("\nOptions:\n")
_write_options(w, options)
end
if _spec.commands().size() > 0 then
w.write("\nCommands:\n")
_write_commands(w)
end
let args = _spec.args()
if args.size() > 0 then
w.write("\nArgs:\n")
_write_args(w, args)
end
fun _write_usage(w: Writer) =>
w.write("usage: " + fullname())
if _any_options() then
w.write(" [<options>]")
end
if _spec.commands().size() > 0 then
w.write(" <command>")
end
if _spec.args().size() > 0 then
for a in _spec.args().values() do
w.write(" " + a.help_string())
end
else
w.write(" [<args> ...]")
end
w.write("\n")
fun _write_options(w: Writer, options: Array[OptionSpec box] box) =>
let cols = Array[(USize,String,String)]()
for o in options.values() do
cols.push((2, o.help_string(), o.descr()))
end
_Columns.write(w, cols)
fun _write_commands(w: Writer) =>
let cols = Array[(USize,String,String)]()
_list_commands(_spec, cols, 1)
_Columns.write(w, cols)
fun _list_commands(
cs: CommandSpec box,
cols: Array[(USize,String,String)],
level: USize)
=>
for c in cs.commands().values() do
cols.push((level*2, c.help_string(), c.descr()))
_list_commands(c, cols, level + 1)
end
fun _write_args(w: Writer, args: Array[ArgSpec] box) =>
let cols = Array[(USize,String,String)]()
for a in args.values() do
cols.push((2, a.help_string(), a.descr()))
end
_Columns.write(w, cols)
fun _any_options(): Bool =>
if _spec.options().size() > 0 then
true
else
match _parent
| let p: CommandHelp => p._any_options()
else
false
end
end
fun _all_options(): Array[OptionSpec box] =>
let options = Array[OptionSpec box]()
_all_options_fill(options)
options
fun _all_options_fill(options: Array[OptionSpec box]) =>
match _parent
| let p: CommandHelp => p._all_options_fill(options)
end
for o in _spec.options().values() do
options.push(o)
end
primitive _Columns
fun indent(w: Writer, n: USize) =>
var i = n
while i > 0 do
w.write(" ")
i = i - 1
end
fun write(w: Writer, cols: Array[(USize,String,String)]) =>
var widest: USize = 0
for c in cols.values() do
(let c0, let c1, _) = c
let c1s = c0 + c1.size()
if c1s > widest then
widest = c1s
end
end
for c in cols.values() do
(let c0, let c1, let c2) = c
indent(w, 1 + c0)
w.write(c1)
indent(w, (widest - c1.size()) + 2)
w.write(c2 + "\n")
end