mirror of
https://github.com/TangentFoxy/argparse.git
synced 2025-07-28 11:02:20 +00:00
Add help_max_width
property for help description autowrapping.
This commit is contained in:
@@ -20,6 +20,8 @@
|
|||||||
usage string autogeneration.
|
usage string autogeneration.
|
||||||
* Added `help_usage_margin` and `help_description_margin` properties
|
* Added `help_usage_margin` and `help_description_margin` properties
|
||||||
for configuring help string autogeneration.
|
for configuring help string autogeneration.
|
||||||
|
* Added `help_max_width` property. If set, descriptions in help string
|
||||||
|
are automatically wrapped to fit into given number of columns.
|
||||||
|
|
||||||
### Improvements
|
### Improvements
|
||||||
|
|
||||||
|
@@ -496,4 +496,102 @@ Options:
|
|||||||
That needs documenting.
|
That needs documenting.
|
||||||
-h, --help Show this help message and exit.]], parser:get_help())
|
-h, --help Show this help message and exit.]], parser:get_help())
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
describe("autowrap", function()
|
||||||
|
it("automatically wraps descriptions to match given max width", function()
|
||||||
|
local parser = Parser "foo"
|
||||||
|
:help_max_width(80)
|
||||||
|
|
||||||
|
parser:option "-f --foo"
|
||||||
|
:description("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor " ..
|
||||||
|
"incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation " ..
|
||||||
|
"ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit " ..
|
||||||
|
"in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat " ..
|
||||||
|
"non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.")
|
||||||
|
parser:option "-b --bar"
|
||||||
|
:description "See above."
|
||||||
|
|
||||||
|
assert.equal([[
|
||||||
|
Usage: foo [-f <foo>] [-b <bar>] [-h]
|
||||||
|
|
||||||
|
Options:
|
||||||
|
-f <foo>, Lorem ipsum dolor sit amet, consectetur adipiscing
|
||||||
|
--foo <foo> elit, sed do eiusmod tempor incididunt ut labore et
|
||||||
|
dolore magna aliqua. Ut enim ad minim veniam, quis
|
||||||
|
nostrud exercitation ullamco laboris nisi ut aliquip ex
|
||||||
|
ea commodo consequat. Duis aute irure dolor in
|
||||||
|
reprehenderit in voluptate velit esse cillum dolore eu
|
||||||
|
fugiat nulla pariatur. Excepteur sint occaecat
|
||||||
|
cupidatat non proident, sunt in culpa qui officia
|
||||||
|
deserunt mollit anim id est laborum.
|
||||||
|
-b <bar>, See above.
|
||||||
|
--bar <bar>
|
||||||
|
-h, --help Show this help message and exit.]], parser:get_help())
|
||||||
|
end)
|
||||||
|
|
||||||
|
it("preserves existing line breaks", function()
|
||||||
|
local parser = Parser "foo"
|
||||||
|
:help_max_width(80)
|
||||||
|
|
||||||
|
parser:option "-f --foo"
|
||||||
|
:description("This is a long line, it should be broken down into several lines. " .. [[
|
||||||
|
It just keeps going and going.
|
||||||
|
This should always be a new line.
|
||||||
|
Another one.
|
||||||
|
]])
|
||||||
|
parser:option "-b --bar"
|
||||||
|
|
||||||
|
assert.equal([[
|
||||||
|
Usage: foo [-f <foo>] [-b <bar>] [-h]
|
||||||
|
|
||||||
|
Options:
|
||||||
|
-f <foo>, This is a long line, it should be broken down into
|
||||||
|
--foo <foo> several lines. It just keeps going and going.
|
||||||
|
This should always be a new line.
|
||||||
|
Another one.
|
||||||
|
-b <bar>,
|
||||||
|
--bar <bar>
|
||||||
|
-h, --help Show this help message and exit.]], parser:get_help())
|
||||||
|
end)
|
||||||
|
|
||||||
|
it("preserves indentation", function()
|
||||||
|
local parser = Parser "foo"
|
||||||
|
:help_max_width(80)
|
||||||
|
|
||||||
|
parser:option "-f --foo"
|
||||||
|
:description("This is a long line, it should be broken down into several lines.\n" ..
|
||||||
|
" This paragraph is indented with three spaces, so when it gets broken down into several lines, " ..
|
||||||
|
"they will be, too.\n\n" ..
|
||||||
|
" That was an empty line there, preserve it.")
|
||||||
|
|
||||||
|
assert.equal([[
|
||||||
|
Usage: foo [-f <foo>] [-h]
|
||||||
|
|
||||||
|
Options:
|
||||||
|
-f <foo>, This is a long line, it should be broken down into
|
||||||
|
--foo <foo> several lines.
|
||||||
|
This paragraph is indented with three spaces, so
|
||||||
|
when it gets broken down into several lines, they
|
||||||
|
will be, too.
|
||||||
|
|
||||||
|
That was an empty line there, preserve it.
|
||||||
|
-h, --help Show this help message and exit.]], parser:get_help())
|
||||||
|
end)
|
||||||
|
|
||||||
|
it("preserves multiple spaces between words", function()
|
||||||
|
local parser = Parser "foo"
|
||||||
|
:help_max_width(80)
|
||||||
|
|
||||||
|
parser:option "-f --foo"
|
||||||
|
:description("This is a long line with two spaces between words, it should be broken down.")
|
||||||
|
|
||||||
|
assert.equal([[
|
||||||
|
Usage: foo [-f <foo>] [-h]
|
||||||
|
|
||||||
|
Options:
|
||||||
|
-f <foo>, This is a long line with two spaces between
|
||||||
|
--foo <foo> words, it should be broken down.
|
||||||
|
-h, --help Show this help message and exit.]], parser:get_help())
|
||||||
|
end)
|
||||||
|
end)
|
||||||
end)
|
end)
|
||||||
|
@@ -251,6 +251,7 @@ local Parser = class({
|
|||||||
typechecked("usage_max_width", "number"),
|
typechecked("usage_max_width", "number"),
|
||||||
typechecked("help_usage_margin", "number"),
|
typechecked("help_usage_margin", "number"),
|
||||||
typechecked("help_description_margin", "number"),
|
typechecked("help_description_margin", "number"),
|
||||||
|
typechecked("help_max_width", "number"),
|
||||||
add_help
|
add_help
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -273,6 +274,7 @@ local Command = class({
|
|||||||
typechecked("usage_max_width", "number"),
|
typechecked("usage_max_width", "number"),
|
||||||
typechecked("help_usage_margin", "number"),
|
typechecked("help_usage_margin", "number"),
|
||||||
typechecked("help_description_margin", "number"),
|
typechecked("help_description_margin", "number"),
|
||||||
|
typechecked("help_max_width", "number"),
|
||||||
typechecked("hidden", "boolean"),
|
typechecked("hidden", "boolean"),
|
||||||
add_help
|
add_help
|
||||||
}, Parser)
|
}, Parser)
|
||||||
@@ -800,6 +802,75 @@ local function split_lines(s)
|
|||||||
return lines
|
return lines
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local function autowrap_line(line, max_length)
|
||||||
|
-- Algorithm for splitting lines is simple and greedy.
|
||||||
|
local result_lines = {}
|
||||||
|
|
||||||
|
-- Preserve original indentation of the line, put this at the beginning of each result line.
|
||||||
|
local indentation = line:match("^( *)")
|
||||||
|
|
||||||
|
-- Parts of the last line being assembled.
|
||||||
|
local line_parts = {}
|
||||||
|
|
||||||
|
-- Length of the current line.
|
||||||
|
local line_length = 0
|
||||||
|
|
||||||
|
-- Index of the next character to consider.
|
||||||
|
local index = 1
|
||||||
|
|
||||||
|
while true do
|
||||||
|
local word_start, word_finish, word = line:find("([^ ]+)", index)
|
||||||
|
|
||||||
|
if not word_start then
|
||||||
|
-- Ignore trailing spaces, if any.
|
||||||
|
break
|
||||||
|
end
|
||||||
|
|
||||||
|
local preceding_spaces = line:sub(index, word_start - 1)
|
||||||
|
index = word_finish + 1
|
||||||
|
|
||||||
|
if (#line_parts == 0) or (line_length + #preceding_spaces + #word <= max_length) then
|
||||||
|
-- Either this is the very first word or it fits as an addition to the current line, add it.
|
||||||
|
table.insert(line_parts, preceding_spaces) -- For the very first word this adds the indentation.
|
||||||
|
table.insert(line_parts, word)
|
||||||
|
line_length = line_length + #preceding_spaces + #word
|
||||||
|
else
|
||||||
|
-- Does not fit, finish current line and put the word into a new one.
|
||||||
|
table.insert(result_lines, table.concat(line_parts))
|
||||||
|
line_parts = {indentation, word}
|
||||||
|
line_length = #indentation + #word
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if #line_parts > 0 then
|
||||||
|
table.insert(result_lines, table.concat(line_parts))
|
||||||
|
end
|
||||||
|
|
||||||
|
if #result_lines == 0 then
|
||||||
|
-- Preserve empty lines.
|
||||||
|
result_lines[1] = ""
|
||||||
|
end
|
||||||
|
|
||||||
|
return result_lines
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Automatically wraps lines within given array,
|
||||||
|
-- attempting to limit line length to `max_length`.
|
||||||
|
-- Existing line splits are preserved.
|
||||||
|
local function autowrap(lines, max_length)
|
||||||
|
local result_lines = {}
|
||||||
|
|
||||||
|
for _, line in ipairs(lines) do
|
||||||
|
local autowrapped_lines = autowrap_line(line, max_length)
|
||||||
|
|
||||||
|
for _, autowrapped_line in ipairs(autowrapped_lines) do
|
||||||
|
table.insert(result_lines, autowrapped_line)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return result_lines
|
||||||
|
end
|
||||||
|
|
||||||
function Parser:_get_element_help(element)
|
function Parser:_get_element_help(element)
|
||||||
local label_lines = element:_get_label_lines()
|
local label_lines = element:_get_label_lines()
|
||||||
local description_lines = split_lines(element:_get_description())
|
local description_lines = split_lines(element:_get_description())
|
||||||
@@ -815,6 +886,12 @@ function Parser:_get_element_help(element)
|
|||||||
local description_margin_len = self:_inherit_property("help_description_margin", 25)
|
local description_margin_len = self:_inherit_property("help_description_margin", 25)
|
||||||
local description_margin = (" "):rep(description_margin_len)
|
local description_margin = (" "):rep(description_margin_len)
|
||||||
|
|
||||||
|
local help_max_width = self:_inherit_property("help_max_width")
|
||||||
|
|
||||||
|
if help_max_width then
|
||||||
|
local description_max_width = math.max(help_max_width - description_margin_len, 10)
|
||||||
|
description_lines = autowrap(description_lines, description_max_width)
|
||||||
|
end
|
||||||
|
|
||||||
if #label_lines[1] >= (description_margin_len - usage_margin_len) then
|
if #label_lines[1] >= (description_margin_len - usage_margin_len) then
|
||||||
for _, label_line in ipairs(label_lines) do
|
for _, label_line in ipairs(label_lines) do
|
||||||
|
Reference in New Issue
Block a user