Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Replace textUt.ttyWidth = width with a more fine-grained control #238

Merged
merged 1 commit into from
Nov 3, 2024
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
16 changes: 12 additions & 4 deletions RELEASE-NOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,6 @@ RELEASE NOTES

Version: 1.7.8
--------------
- Make `cligen/textUt.ttyWidth` a `var` to enable fully draconian CLauthors
to hard-code any terminal wrapWidth (set `clCfg.widthEnv=""` to block any
run-time CLuser override).

- Allow CLauthors to set `clCfg.minStrQuoting = true` (& CLusers to override
with `[layout]minStrQuoting = true|false|etc.`) to only put string default
values in double quotes if "necessary" (presently containing a hard-coded
Expand All @@ -17,6 +13,18 @@ Version: 1.7.8
`true` default value & the very same for `falseDefault`. `test/Version.nim`
has an example of both `minStrQuoting` & `falseDefault`.

- Add 2 new fields to `ClCfg` to control word wrapping in doc-like and table-
like contexts, also adjustable via the two provided config-file systems.
0=auto terminal width as before, -1 = never wrap, else some specific wrap
column (in an absolute sense, including leading indents). These also apply
to the top-level help & subcommand table for multi-cmds. To truly block
CLusers from adjusting wrap, CLauthor must disable use of `clCfg.widthEnv`,
and either not include a config file system or do their own that disallows
[layout]wrap(Doc|Table) edit. So, while CLauthors have "ultimate" control,
ease is not a priority since CLusers really know the most about what help
output will be used for (eg. piping output to grep, $PAGER, etc.). Sample
code showing fine-grained wrap control is in `test/PassValuesMulti.nim`.

Version: 1.7.7
--------------
- Long overdue simplification of config-file open in `clCfgInit`, `clCfgToml`
Expand Down
18 changes: 11 additions & 7 deletions cligen.nim
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ type # Main defns CLI authors need be aware of (besides top-level API calls)
minStrQuoting*: bool ## Only quote string defaults when necessary
trueDefault*: string ## How to render a default value of "true"
falseDefault*: string ## How to render a default value of "false"
wrapDoc*: int ## Terminal column to wrap at for doc-like &..
wrapTable*: int ##..Table-like sections. 0 => auto -1 => never

HelpOnly* = object of CatchableError ## Ok Ctl Flow Only For --help
VersionOnly* = object of CatchableError ## Ok Ctl Flow Only For --version
Expand Down Expand Up @@ -588,8 +590,9 @@ macro dispatchGen*(pro: typed{nkSym}, cmdName: string="", doc: string="",
if isReq:
result.add(quote do: `mandId`.add(`parNm`))
result.add(quote do: # build one large help string
let ww = wrapWidth(`cf`.widthEnv)
let indentDoc = addPrefix(`prefixId`, wrap(mayRend(`cmtDoc`), ww,
let wwd = wrapWidth(`cf`.widthEnv, `cf`.wrapDoc)
let wwt = wrapWidth(`cf`.widthEnv, `cf`.wrapTable)
let indentDoc = addPrefix(`prefixId`, wrap(mayRend(`cmtDoc`), wwd,
prefixLen=`prefixId`.len))
proc hl(tag, val: string): string = # {.gcsafe.} clCfg access
(`cf`.helpAttr.getOrDefault(tag, "") & val &
Expand All @@ -612,7 +615,7 @@ macro dispatchGen*(pro: typed{nkSym}, cmdName: string="", doc: string="",
alignTable(`tabId`, 2*len(`prefixId`) + 2,
`cf`.hTabColGap, `cf`.hTabMinLast,
`cf`.hTabRowSep, toInts(`cf`.hTabCols),
`cf`.onCols, `cf`.offCols, width=ww)) ]
`cf`.onCols, `cf`.offCols, width=wwt))]
if `apId`.help.len > 0 and `apId`.help[^1] != '\n': #ensure newline @end
`apId`.help &= "\n"
if len(`prefixId`) > 0: # to indent help in a multicmd context
Expand Down Expand Up @@ -970,12 +973,13 @@ proc topLevelHelp*(doc: auto, use: auto, cmd: auto, subCmds: auto,
clCfg.helpAttr.getOrDefault("doc", "") ]
let off= @[ clCfg.helpAttrOff.getOrDefault("cmd", ""),
clCfg.helpAttrOff.getOrDefault("doc", "") ]
let ww = wrapWidth(clCfg.widthEnv)
let docUse = if clCfg.render != nil: wrap(clCfg.render(doc), ww)
else: wrap(doc, ww)
let wwd = wrapWidth(clCfg.widthEnv, clCfg.wrapDoc)
let wwt = wrapWidth(clCfg.widthEnv, clCfg.wrapTable)
let docUse = if clCfg.render != nil: wrap(clCfg.render(doc), wwd)
else: wrap(doc, wwd)
use % [ "doc", docUse, "command", on[0] & cmd & off[0], "ifVersion", ifVsn,
"subcmds", addPrefix(" ", alignTable(pairs, 2, attrOn=on,
attrOff=off, width=ww))]
attrOff=off, width=wwt))]

proc docDefault(n: NimNode): NimNode =
if n.len > 1: newStrLitNode(summaryOfModule(n[1][0]))
Expand Down
4 changes: 3 additions & 1 deletion cligen/clCfgInit.nim
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,10 @@ proc apply(c: var ClCfg, path: string, plain=false) =
c.noHelpHelp = e.value.optionNormalize in yes
of "minstrquoting":
c.minStrQuoting = e.value.optionNormalize in yes
of "truedefault": c.trueDefault = e.value
of "truedefault" : c.trueDefault = e.value
of "falsedefault": c.falseDefault = e.value
of "wrapDoc" : c.wrapDoc = e.value.parseInt
of "wrapTable" : c.wrapTable = e.value.parseInt
else:
stderr.write path & ":" & " unexpected setting " & e.key & "\n" &
"Expecting: rowseparator columngap leastfinal required columns " &
Expand Down
2 changes: 2 additions & 0 deletions cligen/clCfgToml.nim
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ proc apply(c: var ClCfg, cfgFile: string, plain=false) =
of "minstrquoting": c.minStrQuoting = v2.getBool()
of "truedefault" : c.trueDefault = v2.getStr()
of "falsedefault": c.falseDefault = v2.getStr()
of "wrapDoc" : c.wrapDoc = v2.getInt()
of "wrapTable" : c.wrapTable = v2.getInt()
else:
stderr.write(&"{cfgFile}: unknown keyword {k2} in the [{k1}] section\n")
of "syntax":
Expand Down
10 changes: 6 additions & 4 deletions cligen/textUt.nim
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ iterator optimalWrap(w: openArray[int], words: openArray[string], m=80,
yield i..r[i]
i = r[i] + 1

var ttyWidth* = terminalWidth()
let ttyWidth* = terminalWidth()
var errno {.importc, header: "<errno.h>".}: cint
errno = 0 #XXX stdlib.terminal should probably clear errno for all client code

Expand Down Expand Up @@ -181,7 +181,9 @@ proc extraSpace(w0, sep, w1: string): bool {.inline.} =
proc wrap*(s: string; maxWidth=ttyWidth, power=3, prefixLen=0): string =
## Multi-paragraph with indent==>pre-formatted optimal line wrapping using
## the badness metric *sum excessSpace^power*.
let maxWidth = maxWidth - 2 * prefixLen
if maxWidth == -1: return s # -1 => wrap disabled
let maxW = if maxWidth == 0: ttyWidth else: maxWidth
let maxWidth = maxW - 2 * prefixLen
for tup in s.paragraphs:
let (pre, para) = tup
if pre:
Expand Down Expand Up @@ -237,7 +239,7 @@ proc alignTable*(tab: TextTab, prefixLen=0, colGap=2, minLast=16, rowSep="",
let last = cols[^1]
for row in tab:
for c in cols: wCol[c] = max(wCol[c], row[c].measure)
var wTerm = width - prefixLen
let wTerm = (if width==0:ttyWidth elif width == -1:1000 else:width)-prefixLen
var leader = (cols.len - 1) * colGap
for c in cols[0 .. ^2]: leader += wCol[c]
template doCol(c: int): untyped =
Expand All @@ -256,7 +258,7 @@ proc alignTable*(tab: TextTab, prefixLen=0, colGap=2, minLast=16, rowSep="",
result &= '\n'
continue
let wLast = max(minLast, wTerm - leader)
var wrapped = if '\n' in row[last]: row[last].split("\n")
var wrapped = if width == -1 or '\n' in row[last]: row[last].split("\n")
else: wrap(row[last], wLast).split("\n")
result &= attrOn[last] & (if wrapped.len>0: wrapped[0] else: "")
if wrapped.len == 1:
Expand Down
9 changes: 7 additions & 2 deletions test/PassValuesMulti.nim
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ proc show(gamma=1, iota=2.0, verb=false, paths: seq[string]): int =
return 42

proc punt(zeta=1, eta=2.0, verb=false, names: seq[string]): int =
## Another entry point; here we echoResult
## Another entry point; here we can echoResult
echo "zeta:", zeta, " eta:", eta, " verb:", verb
for i, n in names: echo "args[", i, "]: ", n
return 12345
Expand Down Expand Up @@ -44,13 +44,18 @@ Run "$command help" to get *comprehensive* help.$ifVersion"""

clCfg.version = "0.0.1" #or maybe nimbleFile.fromNimble("version")
clCfg.reqSep = true
clCfg.widthEnv = "" # Disable CLIGEN_WIDTH CLuser override via impossible $""

var noVsn = clCfg
clCfg.wrapDoc = 32; clCfg.wrapTable = 36
{.pop.}
noVsn.version = ""
noVsn.hTabMinLast = 1 # 11 < default=16 & code does a max(minLast, target).
noVsn.wrapDoc = 11; noVsn.wrapTable = -1
const demohelp = { "verb": "on=chatty, off=quiet" }.toTable
dispatchMulti([ "multi", doc = docLine, usage = topLvlUse ],
[ demo, usage=hlUse, help = demohelp ],
[ show, cmdName="print", usage=hlUse, short = { "gamma": 'z' }],
[ punt, echoResult=true, usage=hlUse, cf=noVsn ],
[ punt, echoResult=true, usage=hlUse, cf=noVsn, help={"zeta":
"very long help string for zeta to show no-wrapping"} ],
[ nel_Ly, cmdName="nel-ly", usage=hlUse, noAutoEcho=true ] )
49 changes: 34 additions & 15 deletions test/ref
Original file line number Diff line number Diff line change
Expand Up @@ -652,16 +652,24 @@ Options:
-a=, --a2= strings {} append 1 val to a2

==> test/PassValuesMulti.out <==
Infer & generate command-line interface/option/argument parser
Infer & generate command-line
interface/option/argument parser
Usage:
PassValuesMulti {SUBCMD} [sub-command options & parameters]

SUBCMDs:
help print comprehensive or per-cmd help
demo demo entry point with varied, meaningless parameters.
print show entry point with varied, meaningless parameters.
punt Another entry point; here we echoResult
nel-ly Yet another entry point; here we block autoEcho
help print comprehensive or
per-cmd help
demo demo entry point with
varied, meaningless
parameters.
print show entry point with
varied, meaningless
parameters.
punt Another entry point; here
we can echoResult
nel-ly Yet another entry point;
here we block autoEcho

PassValuesMulti {-h|--help} or with no args at all prints this message.
PassValuesMulti --help-syntax gives general cligen syntax help.
Expand All @@ -678,31 +686,42 @@ for top-level/all subcommands. Usage is like:
where subcommand syntaxes are as follows:

demo [optional-params] [files: string...]
 demo entry point with varied, meaningless parameters.
 demo entry point with
varied, meaningless
parameters.
Options:
--version bool false print version
-a=, --alpha= int 1 set alpha
-b=, --beta= float 2.0 set beta
-v, --verb bool false on=chatty, off=quiet
-v, --verb bool false on=chatty,
off=quiet
-i=, --item= string "" set item

print [optional-params] [paths: string...]
 show entry point with varied, meaningless parameters.
 show entry point with
varied, meaningless
parameters.
Options:
--version bool false print version
-z=, --gamma= int 1 set gamma
-i=, --iota= float 2.0 set iota
-v, --verb bool false set verb

punt [optional-params] [names: string...]
 Another entry point; here we echoResult
 Another
entry
point;
here
we can
echoResult
Options:
-z=, --zeta= int 1 set zeta
-z=, --zeta= int 1 very long help string for zeta to show no-wrapping
-e=, --eta= float 2.0 set eta
-v, --verb bool false set verb

nel-ly [optional-params] [names: string...]
 Yet another entry point; here we block autoEcho
 Yet another entry point;
here we block autoEcho
Options:
--version bool false print version
--hooves= int 4 set hooves
Expand Down Expand Up @@ -976,9 +995,9 @@ Options:

==> test/TwoNondefaultedSeq.out <==
test/TwoNondefaultedSeq.nim(9, 11) template/generic instantiation of `dispatch` from here
cligen.nim(922, 14) template/generic instantiation of `dispatchCf` from here
cligen.nim(909, 14) template/generic instantiation of `dispatchGen` from here
cligen.nim(303, 16) Warning: cligen only supports one seq param for positional args; using `args`, not `stuff`. Use `positional` parameter to `dispatch` to override this. [User]
cligen.nim(925, 14) template/generic instantiation of `dispatchCf` from here
cligen.nim(912, 14) template/generic instantiation of `dispatchGen` from here
cligen.nim(305, 16) Warning: cligen only supports one seq param for positional args; using `args`, not `stuff`. Use `positional` parameter to `dispatch` to override this. [User]
Usage:
demo [REQUIRED,optional-params] [args: string...]
demo entry point with varied, meaningless parameters.
Expand Down
Loading