mirror of
https://github.com/YunoHost/moulinette.git
synced 2024-09-03 20:06:31 +02:00
[fix] Show positional arguments first in --help / usage (#138)
* Show positional arguments first in --help / usage * Updating reference link * Adding annotations about diff/tweak with respect to original code
This commit is contained in:
parent
39891485c5
commit
e10a40657d
1 changed files with 111 additions and 1 deletions
|
@ -1,5 +1,6 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
import re
|
||||||
import os
|
import os
|
||||||
import errno
|
import errno
|
||||||
import logging
|
import logging
|
||||||
|
@ -501,7 +502,8 @@ class _ExtendedSubParsersAction(argparse._SubParsersAction):
|
||||||
class ExtendedArgumentParser(argparse.ArgumentParser):
|
class ExtendedArgumentParser(argparse.ArgumentParser):
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super(ExtendedArgumentParser, self).__init__(*args, **kwargs)
|
super(ExtendedArgumentParser, self).__init__(
|
||||||
|
formatter_class=PositionalsFirstHelpFormatter, *args, **kwargs)
|
||||||
|
|
||||||
# Register additional actions
|
# Register additional actions
|
||||||
self.register('action', 'callback', _CallbackAction)
|
self.register('action', 'callback', _CallbackAction)
|
||||||
|
@ -551,3 +553,111 @@ class ExtendedArgumentParser(argparse.ArgumentParser):
|
||||||
value = super(ExtendedArgumentParser, self)._get_values(
|
value = super(ExtendedArgumentParser, self)._get_values(
|
||||||
action, arg_strings)
|
action, arg_strings)
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
# This is copy-pasta from the original argparse.HelpFormatter :
|
||||||
|
# https://github.com/python/cpython/blob/1e73dbbc29c96d0739ffef92db36f63aa1aa30da/Lib/argparse.py#L293-L383
|
||||||
|
# tweaked to display positional arguments first in usage/--help
|
||||||
|
#
|
||||||
|
# This is motivated by the "bug" / inconsistent behavior described here :
|
||||||
|
# http://bugs.python.org/issue9338
|
||||||
|
# and fix is inspired from here :
|
||||||
|
# https://stackoverflow.com/questions/26985650/argparse-do-not-catch-positional-arguments-with-nargs/26986546#26986546
|
||||||
|
class PositionalsFirstHelpFormatter(argparse.HelpFormatter):
|
||||||
|
|
||||||
|
def _format_usage(self, usage, actions, groups, prefix):
|
||||||
|
if prefix is None:
|
||||||
|
# TWEAK : not using gettext here...
|
||||||
|
prefix = 'usage: '
|
||||||
|
|
||||||
|
# if usage is specified, use that
|
||||||
|
if usage is not None:
|
||||||
|
usage = usage % dict(prog=self._prog)
|
||||||
|
|
||||||
|
# if no optionals or positionals are available, usage is just prog
|
||||||
|
elif usage is None and not actions:
|
||||||
|
usage = '%(prog)s' % dict(prog=self._prog)
|
||||||
|
|
||||||
|
# if optionals and positionals are available, calculate usage
|
||||||
|
elif usage is None:
|
||||||
|
prog = '%(prog)s' % dict(prog=self._prog)
|
||||||
|
|
||||||
|
# split optionals from positionals
|
||||||
|
optionals = []
|
||||||
|
positionals = []
|
||||||
|
for action in actions:
|
||||||
|
if action.option_strings:
|
||||||
|
optionals.append(action)
|
||||||
|
else:
|
||||||
|
positionals.append(action)
|
||||||
|
|
||||||
|
# build full usage string
|
||||||
|
format = self._format_actions_usage
|
||||||
|
# TWEAK here : positionals first
|
||||||
|
action_usage = format(positionals + optionals, groups)
|
||||||
|
usage = ' '.join([s for s in [prog, action_usage] if s])
|
||||||
|
|
||||||
|
# wrap the usage parts if it's too long
|
||||||
|
text_width = self._width - self._current_indent
|
||||||
|
if len(prefix) + len(usage) > text_width:
|
||||||
|
|
||||||
|
# break usage into wrappable parts
|
||||||
|
part_regexp = r'\(.*?\)+|\[.*?\]+|\S+'
|
||||||
|
opt_usage = format(optionals, groups)
|
||||||
|
pos_usage = format(positionals, groups)
|
||||||
|
opt_parts = re.findall(part_regexp, opt_usage)
|
||||||
|
pos_parts = re.findall(part_regexp, pos_usage)
|
||||||
|
assert ' '.join(opt_parts) == opt_usage
|
||||||
|
assert ' '.join(pos_parts) == pos_usage
|
||||||
|
|
||||||
|
# helper for wrapping lines
|
||||||
|
def get_lines(parts, indent, prefix=None):
|
||||||
|
lines = []
|
||||||
|
line = []
|
||||||
|
if prefix is not None:
|
||||||
|
line_len = len(prefix) - 1
|
||||||
|
else:
|
||||||
|
line_len = len(indent) - 1
|
||||||
|
for part in parts:
|
||||||
|
if line_len + 1 + len(part) > text_width:
|
||||||
|
lines.append(indent + ' '.join(line))
|
||||||
|
line = []
|
||||||
|
line_len = len(indent) - 1
|
||||||
|
line.append(part)
|
||||||
|
line_len += len(part) + 1
|
||||||
|
if line:
|
||||||
|
lines.append(indent + ' '.join(line))
|
||||||
|
if prefix is not None:
|
||||||
|
lines[0] = lines[0][len(indent):]
|
||||||
|
return lines
|
||||||
|
|
||||||
|
# if prog is short, follow it with optionals or positionals
|
||||||
|
if len(prefix) + len(prog) <= 0.75 * text_width:
|
||||||
|
indent = ' ' * (len(prefix) + len(prog) + 1)
|
||||||
|
# START TWEAK : pos_parts first, then opt_parts
|
||||||
|
if pos_parts:
|
||||||
|
lines = get_lines([prog] + pos_parts, indent, prefix)
|
||||||
|
lines.extend(get_lines(opt_parts, indent))
|
||||||
|
elif opt_parts:
|
||||||
|
lines = get_lines([prog] + opt_parts, indent, prefix)
|
||||||
|
# END TWEAK
|
||||||
|
else:
|
||||||
|
lines = [prog]
|
||||||
|
|
||||||
|
# if prog is long, put it on its own line
|
||||||
|
else:
|
||||||
|
indent = ' ' * len(prefix)
|
||||||
|
parts = pos_parts + opt_parts
|
||||||
|
lines = get_lines(parts, indent)
|
||||||
|
if len(lines) > 1:
|
||||||
|
lines = []
|
||||||
|
# TWEAK here : pos_parts first, then opt_part
|
||||||
|
lines.extend(get_lines(pos_parts, indent))
|
||||||
|
lines.extend(get_lines(opt_parts, indent))
|
||||||
|
lines = [prog] + lines
|
||||||
|
|
||||||
|
# join lines into usage
|
||||||
|
usage = '\n'.join(lines)
|
||||||
|
|
||||||
|
# prefix with 'usage:'
|
||||||
|
return '%s%s\n\n' % (prefix, usage)
|
||||||
|
|
Loading…
Reference in a new issue