diff --git a/lib/Docopt.php b/lib/Docopt.php index e02890c..d6a1939 100644 --- a/lib/Docopt.php +++ b/lib/Docopt.php @@ -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, , commands, which could be + * [optional], (required), (mutually | exclusive) or repeated... + * + * Example + * ------- + * + * > use MensBeam\Docopt\Docopt; + * > $doc = << [--timeout=] + * <<< my_program serial [--baud=] [--timeout=] + * <<< my_program (-h | --help | --version) + * <<< + * <<< Options: + * <<< -h, --help Show this screen and exit. + * <<< --baud= 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, + * "": "127.0.0.1", + * "": "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,14 +549,16 @@ class BranchPattern extends Pattern { $either[] = (array) $child->children; } # for case in either: - # 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: - # e.value = [] - # elif type(e.value) is not list: - # e.value = e.value.split() - # if type(e) is Command or type(e) is Option and e.argcount == 0: - # e.value = 0 + 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: + # e.value = [] + # elif type(e.value) is not list: + # 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, , 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 "", and values are the -# parsed values of those elements. Also supports dot acccess. - -# Example -# ------- -# >>> from docopt import docopt -# >>> doc = ''' -# ... Usage: -# ... my_program tcp [--timeout=] -# ... my_program serial [--baud=] [--timeout=] -# ... my_program (-h | --help | --version) -# ... -# ... Options: -# ... -h, --help Show this screen and exit. -# ... --baud= Baudrate [default: 9600] -# ... ''' -# >>> argv = ['tcp', '127.0.0.1', '80', '--timeout', '30'] -# >>> docopt(doc, argv) -# {'--baud': '9600', -# '--help': False, -# '--timeout': '30', -# '--version': False, -# '': '127.0.0.1', -# '': '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 \ No newline at end of file