|
|
@ -22,6 +22,134 @@ namespace MensBeam\Docopt; |
|
|
|
class Docopt { |
|
|
|
# __version__ = "0.7.2" |
|
|
|
public const VERSION = "0.7.2"; |
|
|
|
|
|
|
|
# def docopt( |
|
|
|
# docstring: Optional[str] = None, |
|
|
|
# argv: Optional[Union[List[str], str]] = None, |
|
|
|
# default_help: bool = True, |
|
|
|
# version: Any = None, |
|
|
|
# options_first: bool = False, |
|
|
|
# more_magic: bool = False, |
|
|
|
# ) -> ParsedOptions: |
|
|
|
/** Parse `argv` based on command-line interface described in `doc`. |
|
|
|
* |
|
|
|
* `docopt` creates your command-line interface based on its |
|
|
|
* description that you pass as `docstring`. Such description can contain |
|
|
|
* --options, <positional-argument>, commands, which could be |
|
|
|
* [optional], (required), (mutually | exclusive) or repeated... |
|
|
|
* |
|
|
|
* Example |
|
|
|
* ------- |
|
|
|
* |
|
|
|
* > use MensBeam\Docopt\Docopt; |
|
|
|
* > $doc = <<<DOCSTRING |
|
|
|
* <<< Usage: |
|
|
|
* <<< my_program tcp <host> <port> [--timeout=<seconds>] |
|
|
|
* <<< my_program serial <port> [--baud=<n>] [--timeout=<seconds>] |
|
|
|
* <<< my_program (-h | --help | --version) |
|
|
|
* <<< |
|
|
|
* <<< Options: |
|
|
|
* <<< -h, --help Show this screen and exit. |
|
|
|
* <<< --baud=<n> Baudrate [default: 9600] |
|
|
|
* <<< DOCSTRING; |
|
|
|
* > $argv = ['tcp', '127.0.0.1', '80', '--timeout', '30']; |
|
|
|
* > echo json_encode(Docopt::docopt($doc, $argv), \JSON_PRETTY_PRINT); |
|
|
|
* { |
|
|
|
* "--baud": "9600", |
|
|
|
* "--help": false, |
|
|
|
* "--timeout": "30", |
|
|
|
* "--version": false, |
|
|
|
* "<host>": "127.0.0.1", |
|
|
|
* "<port>": "80", |
|
|
|
* "serial": false, |
|
|
|
* "tcp": true |
|
|
|
* } |
|
|
|
* |
|
|
|
* @param string $docstring Description of your command-line interface |
|
|
|
* @param string[]|string|null $argv Argument list to be parsed. $_SERVER['argv'] is used if null is provided |
|
|
|
* @param bool $default_help Set to false to disable automatic help on -h or --help options |
|
|
|
* @param \Stringable|string|null $version If passed, the string will be printed if --version is in `argv` |
|
|
|
* @param bool $options_first Set to True to require options precede positional arguments, i.e. to forbid options and positional arguments intermix |
|
|
|
* @param bool $more_magic Try to be extra-helpful; offer advanced pattern-matching and spellcheck |
|
|
|
*/ |
|
|
|
public function docopt( |
|
|
|
string $docstring, // DEVIATION: Our docstring is not optional since we can't get one from the parent context |
|
|
|
$argv = null, |
|
|
|
bool $default_help = true, |
|
|
|
$version = null, |
|
|
|
bool $options_first = false, |
|
|
|
bool $more_magic = false |
|
|
|
): ParsedOptions { |
|
|
|
# argv = sys.argv[1:] if argv is None else argv |
|
|
|
# maybe_frame = inspect.currentframe() |
|
|
|
# if maybe_frame: |
|
|
|
# parent_frame = doc_parent_frame = magic_parent_frame = maybe_frame.f_back |
|
|
|
# if not more_magic: # make sure 'magic' isn't in the calling name |
|
|
|
# while not more_magic and magic_parent_frame: |
|
|
|
# imported_as = {v: k for k, v in magic_parent_frame.f_globals.items() if hasattr(v, "__name__") and v.__name__ == docopt.__name__}.get(docopt) |
|
|
|
# if imported_as and "magic" in imported_as: |
|
|
|
# more_magic = True |
|
|
|
# else: |
|
|
|
# magic_parent_frame = magic_parent_frame.f_back |
|
|
|
# if not docstring: # go look for one, if none exists, raise Exception |
|
|
|
# while not docstring and doc_parent_frame: |
|
|
|
# docstring = doc_parent_frame.f_locals.get("__doc__") |
|
|
|
# if not docstring: |
|
|
|
# doc_parent_frame = doc_parent_frame.f_back |
|
|
|
# if not docstring: |
|
|
|
# raise DocoptLanguageError("Either __doc__ must be defined in the scope of a parent or passed as the first argument.") |
|
|
|
# output_value_assigned = False |
|
|
|
# if more_magic and parent_frame: |
|
|
|
# import dis |
|
|
|
# |
|
|
|
# instrs = dis.get_instructions(parent_frame.f_code) |
|
|
|
# for instr in instrs: |
|
|
|
# if instr.offset == parent_frame.f_lasti: |
|
|
|
# break |
|
|
|
# assert instr.opname.startswith("CALL_") |
|
|
|
# MAYBE_STORE = next(instrs) |
|
|
|
# if MAYBE_STORE and (MAYBE_STORE.opname.startswith("STORE") or MAYBE_STORE.opname.startswith("RETURN")): |
|
|
|
# output_value_assigned = True |
|
|
|
# usage_sections = parse_section("usage:", docstring) |
|
|
|
# if len(usage_sections) == 0: |
|
|
|
# raise DocoptLanguageError('"usage:" section (case-insensitive) not found. Perhaps missing indentation?') |
|
|
|
# if len(usage_sections) > 1: |
|
|
|
# raise DocoptLanguageError('More than one "usage:" (case-insensitive).') |
|
|
|
# options_pattern = re.compile(r"\n\s*?options:", re.IGNORECASE) |
|
|
|
# if options_pattern.search(usage_sections[0]): |
|
|
|
# raise DocoptExit("Warning: options (case-insensitive) was found in usage." "Use a blank line between each section..") |
|
|
|
# DocoptExit.usage = usage_sections[0] |
|
|
|
# options = parse_defaults(docstring) |
|
|
|
# pattern = parse_pattern(formal_usage(DocoptExit.usage), options) |
|
|
|
# pattern_options = set(pattern.flat(Option)) |
|
|
|
# for options_shortcut in pattern.flat(OptionsShortcut): |
|
|
|
# doc_options = parse_defaults(docstring) |
|
|
|
# options_shortcut.children = [opt for opt in doc_options if opt not in pattern_options] |
|
|
|
# parsed_arg_vector = parse_argv(Tokens(argv), list(options), options_first, more_magic) |
|
|
|
# extras(default_help, version, parsed_arg_vector, docstring) |
|
|
|
# matched, left, collected = pattern.fix().match(parsed_arg_vector) |
|
|
|
# if matched and left == []: |
|
|
|
# output_obj = ParsedOptions((a.name, a.value) for a in (pattern.flat() + collected)) |
|
|
|
# target_parent_frame = parent_frame or magic_parent_frame or doc_parent_frame |
|
|
|
# if more_magic and target_parent_frame and not output_value_assigned: |
|
|
|
# if not target_parent_frame.f_globals.get("arguments"): |
|
|
|
# target_parent_frame.f_globals["arguments"] = output_obj |
|
|
|
# return output_obj |
|
|
|
# if left: |
|
|
|
# raise DocoptExit(f"Warning: found unmatched (duplicate?) arguments {left}") |
|
|
|
# raise DocoptExit(collected=collected, left=left) |
|
|
|
} |
|
|
|
|
|
|
|
# magic = magic_docopt = docopt |
|
|
|
/** Helper function to invoke the docopt() method with $more_magic always true */ |
|
|
|
public function magic(string $docstring, $argv = null, bool $default_help = true, $version = null, bool $options_first = false, bool $more_magic = false): ParsedOptions { |
|
|
|
return static::docopt($docstring, $argv, $default_help, $version, $options_first, true); |
|
|
|
} |
|
|
|
|
|
|
|
/** Helper function to invoke the docopt() method with $more_magic always true */ |
|
|
|
public function magic_docopt(string $docstring, $argv = null, bool $default_help = true, $version = null, bool $options_first = false, bool $more_magic = false): ParsedOptions { |
|
|
|
return static::docopt($docstring, $argv, $default_help, $version, $options_first, true); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
class Util { |
|
|
@ -421,6 +549,7 @@ class BranchPattern extends Pattern { |
|
|
|
$either[] = (array) $child->children; |
|
|
|
} |
|
|
|
# for case in either: |
|
|
|
foreach ($either as $case) { |
|
|
|
# for e in [child for child in case if case.count(child) > 1]: |
|
|
|
# if type(e) is Argument or type(e) is Option and e.argcount: |
|
|
|
# if e.value is None: |
|
|
@ -429,6 +558,7 @@ class BranchPattern extends Pattern { |
|
|
|
# e.value = e.value.split() |
|
|
|
# if type(e) is Command or type(e) is Option and e.argcount == 0: |
|
|
|
# e.value = 0 |
|
|
|
} |
|
|
|
# return self |
|
|
|
} |
|
|
|
|
|
|
@ -840,134 +970,3 @@ class BranchPattern extends Pattern { |
|
|
|
|
|
|
|
# def __getattr__(self, name: str) -> Optional[Union[str, bool]]: |
|
|
|
# return self.get(name) or {name: self.get(k) for k in self.keys() if name in [k.lstrip("-"), k.lstrip("<").rstrip(">")]}.get(name) |
|
|
|
|
|
|
|
|
|
|
|
# def docopt( |
|
|
|
# docstring: Optional[str] = None, |
|
|
|
# argv: Optional[Union[List[str], str]] = None, |
|
|
|
# default_help: bool = True, |
|
|
|
# version: Any = None, |
|
|
|
# options_first: bool = False, |
|
|
|
# more_magic: bool = False, |
|
|
|
# ) -> ParsedOptions: |
|
|
|
# """Parse `argv` based on command-line interface described in `doc`. |
|
|
|
|
|
|
|
# `docopt` creates your command-line interface based on its |
|
|
|
# description that you pass as `docstring`. Such description can contain |
|
|
|
# --options, <positional-argument>, commands, which could be |
|
|
|
# [optional], (required), (mutually | exclusive) or repeated... |
|
|
|
|
|
|
|
# Parameters |
|
|
|
# ---------- |
|
|
|
# docstring : str (default: first __doc__ in parent scope) |
|
|
|
# Description of your command-line interface. |
|
|
|
# argv : list of str, optional |
|
|
|
# Argument vector to be parsed. sys.argv[1:] is used if not |
|
|
|
# provided. |
|
|
|
# default_help : bool (default: True) |
|
|
|
# Set to False to disable automatic help on -h or --help |
|
|
|
# options. |
|
|
|
# version : any object |
|
|
|
# If passed, the object will be printed if --version is in |
|
|
|
# `argv`. |
|
|
|
# options_first : bool (default: False) |
|
|
|
# Set to True to require options precede positional arguments, |
|
|
|
# i.e. to forbid options and positional arguments intermix. |
|
|
|
# more_magic : bool (default: False) |
|
|
|
# Try to be extra-helpful; pull results into globals() of caller as 'arguments', |
|
|
|
# offer advanced pattern-matching and spellcheck. |
|
|
|
# Also activates if `docopt` aliased to a name containing 'magic'. |
|
|
|
|
|
|
|
# Returns |
|
|
|
# ------- |
|
|
|
# arguments: dict-like |
|
|
|
# A dictionary, where keys are names of command-line elements |
|
|
|
# such as e.g. "--verbose" and "<path>", and values are the |
|
|
|
# parsed values of those elements. Also supports dot acccess. |
|
|
|
|
|
|
|
# Example |
|
|
|
# ------- |
|
|
|
# >>> from docopt import docopt |
|
|
|
# >>> doc = ''' |
|
|
|
# ... Usage: |
|
|
|
# ... my_program tcp <host> <port> [--timeout=<seconds>] |
|
|
|
# ... my_program serial <port> [--baud=<n>] [--timeout=<seconds>] |
|
|
|
# ... my_program (-h | --help | --version) |
|
|
|
# ... |
|
|
|
# ... Options: |
|
|
|
# ... -h, --help Show this screen and exit. |
|
|
|
# ... --baud=<n> Baudrate [default: 9600] |
|
|
|
# ... ''' |
|
|
|
# >>> argv = ['tcp', '127.0.0.1', '80', '--timeout', '30'] |
|
|
|
# >>> docopt(doc, argv) |
|
|
|
# {'--baud': '9600', |
|
|
|
# '--help': False, |
|
|
|
# '--timeout': '30', |
|
|
|
# '--version': False, |
|
|
|
# '<host>': '127.0.0.1', |
|
|
|
# '<port>': '80', |
|
|
|
# 'serial': False, |
|
|
|
# 'tcp': True} |
|
|
|
|
|
|
|
# """ |
|
|
|
# argv = sys.argv[1:] if argv is None else argv |
|
|
|
# maybe_frame = inspect.currentframe() |
|
|
|
# if maybe_frame: |
|
|
|
# parent_frame = doc_parent_frame = magic_parent_frame = maybe_frame.f_back |
|
|
|
# if not more_magic: # make sure 'magic' isn't in the calling name |
|
|
|
# while not more_magic and magic_parent_frame: |
|
|
|
# imported_as = {v: k for k, v in magic_parent_frame.f_globals.items() if hasattr(v, "__name__") and v.__name__ == docopt.__name__}.get(docopt) |
|
|
|
# if imported_as and "magic" in imported_as: |
|
|
|
# more_magic = True |
|
|
|
# else: |
|
|
|
# magic_parent_frame = magic_parent_frame.f_back |
|
|
|
# if not docstring: # go look for one, if none exists, raise Exception |
|
|
|
# while not docstring and doc_parent_frame: |
|
|
|
# docstring = doc_parent_frame.f_locals.get("__doc__") |
|
|
|
# if not docstring: |
|
|
|
# doc_parent_frame = doc_parent_frame.f_back |
|
|
|
# if not docstring: |
|
|
|
# raise DocoptLanguageError("Either __doc__ must be defined in the scope of a parent or passed as the first argument.") |
|
|
|
# output_value_assigned = False |
|
|
|
# if more_magic and parent_frame: |
|
|
|
# import dis |
|
|
|
|
|
|
|
# instrs = dis.get_instructions(parent_frame.f_code) |
|
|
|
# for instr in instrs: |
|
|
|
# if instr.offset == parent_frame.f_lasti: |
|
|
|
# break |
|
|
|
# assert instr.opname.startswith("CALL_") |
|
|
|
# MAYBE_STORE = next(instrs) |
|
|
|
# if MAYBE_STORE and (MAYBE_STORE.opname.startswith("STORE") or MAYBE_STORE.opname.startswith("RETURN")): |
|
|
|
# output_value_assigned = True |
|
|
|
# usage_sections = parse_section("usage:", docstring) |
|
|
|
# if len(usage_sections) == 0: |
|
|
|
# raise DocoptLanguageError('"usage:" section (case-insensitive) not found. Perhaps missing indentation?') |
|
|
|
# if len(usage_sections) > 1: |
|
|
|
# raise DocoptLanguageError('More than one "usage:" (case-insensitive).') |
|
|
|
# options_pattern = re.compile(r"\n\s*?options:", re.IGNORECASE) |
|
|
|
# if options_pattern.search(usage_sections[0]): |
|
|
|
# raise DocoptExit("Warning: options (case-insensitive) was found in usage." "Use a blank line between each section..") |
|
|
|
# DocoptExit.usage = usage_sections[0] |
|
|
|
# options = parse_defaults(docstring) |
|
|
|
# pattern = parse_pattern(formal_usage(DocoptExit.usage), options) |
|
|
|
# pattern_options = set(pattern.flat(Option)) |
|
|
|
# for options_shortcut in pattern.flat(OptionsShortcut): |
|
|
|
# doc_options = parse_defaults(docstring) |
|
|
|
# options_shortcut.children = [opt for opt in doc_options if opt not in pattern_options] |
|
|
|
# parsed_arg_vector = parse_argv(Tokens(argv), list(options), options_first, more_magic) |
|
|
|
# extras(default_help, version, parsed_arg_vector, docstring) |
|
|
|
# matched, left, collected = pattern.fix().match(parsed_arg_vector) |
|
|
|
# if matched and left == []: |
|
|
|
# output_obj = ParsedOptions((a.name, a.value) for a in (pattern.flat() + collected)) |
|
|
|
# target_parent_frame = parent_frame or magic_parent_frame or doc_parent_frame |
|
|
|
# if more_magic and target_parent_frame and not output_value_assigned: |
|
|
|
# if not target_parent_frame.f_globals.get("arguments"): |
|
|
|
# target_parent_frame.f_globals["arguments"] = output_obj |
|
|
|
# return output_obj |
|
|
|
# if left: |
|
|
|
# raise DocoptExit(f"Warning: found unmatched (duplicate?) arguments {left}") |
|
|
|
# raise DocoptExit(collected=collected, left=left) |
|
|
|
|
|
|
|
|
|
|
|
# magic = magic_docopt = docopt |