float: # """Calculates the normalized Levenshtein distance between two string # arguments. The result will be a float in the range [0.0, 1.0], with 1.0 # signifying the biggest possible distance between strings with these lengths # """ protected static function levenshtein_norm(string $source, string $target): float { // NOTE: We split the strings into arrays of UTF-8 characters to match Python's behaviour (for UTF-8 input) // TODO: How is argv encoded in Windows and macOS? Does the Windows Command Prompt differ from Powershell? $source = preg_split('/(? int: # """Computes the Levenshtein # (https://en.wikipedia.org/wiki/Levenshtein_distance) # and restricted Damerau-Levenshtein # (https://en.wikipedia.org/wiki/Damerau%E2%80%93Levenshtein_distance) # distances between two Unicode strings with given lengths using the # Wagner-Fischer algorithm # (https://en.wikipedia.org/wiki/Wagner%E2%80%93Fischer_algorithm). # These distances are defined recursively, since the distance between two # strings is just the cost of adjusting the last one or two characters plus # the distance between the prefixes that exclude these characters (e.g. the # distance between "tester" and "tested" is 1 + the distance between "teste" # and "teste"). The Wagner-Fischer algorithm retains this idea but eliminates # redundant computations by storing the distances between various prefixes in # a matrix that is filled in iteratively. # """ protected static function levenshtein(array $source, array $target): int { // NOTE: Our implementation operates on character arrays rather than strings # # Create matrix of correct size (this is s_len + 1 * t_len + 1 so that the # # empty prefixes "" can also be included). The leftmost column represents # # transforming various source prefixes into an empty string, which can # # always be done by deleting all characters in the respective prefix, and # # the top row represents transforming the empty string into various target # # prefixes, which can always be done by inserting every character in the # # respective prefix. The ternary used to build the list should ensure that # # this row and column are now filled correctly # s_range = range(len(source) + 1) # t_range = range(len(target) + 1) # matrix = [[(i if j == 0 else j) for j in t_range] for i in s_range] $s_end = sizeof($source) + 1; $t_end = sizeof($target) + 1; $matrix = []; for ($i = 0; $i <= $s_end; $i++) { $matrix[$i] = []; for ($j = 0; $j <= $t_end; $j++) { $matrix[$i][$j] = $j ?: $i; } } # # Iterate through rest of matrix, filling it in with Levenshtein # # distances for the remaining prefix combinations # for i in s_range[1:]: # for j in t_range[1:]: for ($i = 1; $i <= $s_end; $i++) { for ($j = 1; $j <= $t_end; $j++) { # # Applies the recursive logic outlined above using the values # # stored in the matrix so far. The options for the last pair of # # characters are deletion, insertion, and substitution, which # # amount to dropping the source character, the target character, # # or both and then calculating the distance for the resulting # # prefix combo. If the characters at this point are the same, the # # situation can be thought of as a free substitution # del_dist = matrix[i - 1][j] + 1 # ins_dist = matrix[i][j - 1] + 1 # sub_trans_cost = 0 if source[i - 1] == target[j - 1] else 1 # sub_dist = matrix[i - 1][j - 1] + sub_trans_cost $del_dist = $matrix[$i - 1][$j] + 1; $ins_dist = $matrix[$i][$j - 1] + 1; $sub_trans_cost = ($source[$i - 1] === $target[$j - 1]) ? 0 : 1; $sub_dist = $matrix[$i - 1][$j - 1] + $sub_trans_cost; # # Choose option that produces smallest distance # matrix[i][j] = min(del_dist, ins_dist, sub_dist) $matrix[$i][$j] = min($del_dist, $ins_dist, $sub_dist); } } # # At this point, the matrix is full, and the biggest prefixes are just the # # strings themselves, so this is the desired distance # return matrix[len(source)][len(target)] return $matrix[sizeof($source)][sizeof($target)]; } } # class DocoptLanguageError(Exception): # """Error in construction of usage-message by developer.""" /** Error in construction of usage-message by developer */ class DocoptLanguageError extends \Exception { } # class DocoptExit(SystemExit): # """Exit in case user invoked program with incorrect arguments.""" /** Exit in case user invoked program with incorrect arguments */ class DocoptExit extends \Exception { // NOTE: In Python this exception is special in that it will cleanly kill // the program if not caught. PHP has no such feature, so in our // implementation it is not special, but we implement special handling // elsewhere # usage = "" public static $usage = ""; public $collected = []; public $left = []; # def __init__(self, message: str = "", collected: List["Pattern"] = None, left: List["Pattern"] = None) -> None: public function __construct(string $message = "", ?array $collected = null, ?array $left = null) { # self.collected = collected if collected is not None else [] # self.left = left if left is not None else [] $this->collected = $collected ?? []; $this->left = $left ?? []; # SystemExit.__init__(self, (message + "\n" + self.usage).strip()) // DEVIATION: This does not strip non-ASCII whitespace parent::__construct(trim($message."\n".self::$usage)); } } # class Pattern: # def __init__(self, name: Optional[str], value: Optional[Union[List[str], str, int]] = None) -> None: # self._name, self.value = name, value # @property # def name(self) -> Optional[str]: # return self._name # def __eq__(self, other: Any) -> bool: # return repr(self) == repr(other) # def __hash__(self) -> int: # return hash(repr(self)) # def transform(pattern: "BranchPattern") -> "Either": # """Expand pattern into an (almost) equivalent one, but with single Either. # Example: ((-a | -b) (-c | -d)) => (-a -c | -a -d | -b -c | -b -d) # Quirks: [-a] => (-a), (-a...) => (-a -a) # """ # result = [] # groups = [[pattern]] # while groups: # children = groups.pop(0) # parents = [Required, NotRequired, OptionsShortcut, Either, OneOrMore] # if any(t in map(type, children) for t in parents): # child = [c for c in children if type(c) in parents][0] # children.remove(child) # if type(child) is Either: # for c in child.children: # groups.append([c] + children) # elif type(child) is OneOrMore: # groups.append(child.children * 2 + children) # else: # groups.append(child.children + children) # else: # result.append(children) # return Either(*[Required(*e) for e in result]) # TSingleMatch = Tuple[Union[int, None], Union["LeafPattern", None]] # class LeafPattern(Pattern): # """Leaf/terminal node of a pattern tree.""" # def __repr__(self) -> str: # return "%s(%r, %r)" % (self.__class__.__name__, self.name, self.value) # def single_match(self, left: List["LeafPattern"]) -> TSingleMatch: # raise NotImplementedError # pragma: no cover # def flat(self, *types) -> List["LeafPattern"]: # return [self] if not types or type(self) in types else [] # def match(self, left: List["LeafPattern"], collected: List["Pattern"] = None) -> Tuple[bool, List["LeafPattern"], List["Pattern"]]: # collected = [] if collected is None else collected # increment: Optional[Any] = None # pos, match = self.single_match(left) # if match is None or pos is None: # return False, left, collected # left_ = left[:pos] + left[(pos + 1) :] # same_name = [a for a in collected if a.name == self.name] # if type(self.value) == int and len(same_name) > 0: # if isinstance(same_name[0].value, int): # same_name[0].value += 1 # return True, left_, collected # if type(self.value) == int and not same_name: # match.value = 1 # return True, left_, collected + [match] # if same_name and type(self.value) == list: # if type(match.value) == str: # increment = [match.value] # if same_name[0].value is not None and increment is not None: # if isinstance(same_name[0].value, type(increment)): # same_name[0].value += increment # return True, left_, collected # elif not same_name and type(self.value) == list: # if isinstance(match.value, str): # match.value = [match.value] # return True, left_, collected + [match] # return True, left_, collected + [match] # class BranchPattern(Pattern): # """Branch/inner node of a pattern tree.""" # def __init__(self, *children) -> None: # self.children = list(children) # def match(self, left: List["Pattern"], collected: List["Pattern"] = None) -> Any: # raise NotImplementedError # pragma: no cover # def fix(self) -> "BranchPattern": # self.fix_identities() # self.fix_repeating_arguments() # return self # def fix_identities(self, uniq: Optional[Any] = None) -> None: # """Make pattern-tree tips point to same object if they are equal.""" # flattened = self.flat() # uniq = list(set(flattened)) if uniq is None else uniq # for i, child in enumerate(self.children): # if not hasattr(child, "children"): # assert child in uniq # self.children[i] = uniq[uniq.index(child)] # else: # child.fix_identities(uniq) # return None # def fix_repeating_arguments(self) -> "BranchPattern": # """Fix elements that should accumulate/increment values.""" # either = [list(child.children) for child in transform(self).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 # return self # def __repr__(self) -> str: # return "%s(%s)" % (self.__class__.__name__, ", ".join(repr(a) for a in self.children)) # def flat(self, *types) -> Any: # if type(self) in types: # return [self] # return sum([child.flat(*types) for child in self.children], []) # class Argument(LeafPattern): # def single_match(self, left: List[LeafPattern]) -> TSingleMatch: # for n, pattern in enumerate(left): # if type(pattern) is Argument: # return n, Argument(self.name, pattern.value) # return None, None # class Command(Argument): # def __init__(self, name: Union[str, None], value: bool = False) -> None: # self._name, self.value = name, value # def single_match(self, left: List[LeafPattern]) -> TSingleMatch: # for n, pattern in enumerate(left): # if type(pattern) is Argument: # if pattern.value == self.name: # return n, Command(self.name, True) # else: # break # return None, None # class Option(LeafPattern): # def __init__(self, short: Optional[str] = None, longer: Optional[str] = None, argcount: int = 0, value: Union[List[str], str, int, None] = False) -> None: # assert argcount in (0, 1) # self.short, self.longer, self.argcount = short, longer, argcount # self.value = None if value is False and argcount else value # @classmethod # def parse(class_, option_description: str) -> "Option": # short, longer, argcount, value = None, None, 0, False # options, _, description = option_description.strip().partition(" ") # options = options.replace(",", " ").replace("=", " ") # for s in options.split(): # if s.startswith("--"): # longer = s # elif s.startswith("-"): # short = s # else: # argcount = 1 # if argcount: # matched = re.findall(r"\[default: (.*)\]", description, flags=re.I) # value = matched[0] if matched else None # return class_(short, longer, argcount, value) # def single_match(self, left: List[LeafPattern]) -> TSingleMatch: # for n, pattern in enumerate(left): # if self.name == pattern.name: # return n, pattern # return None, None # @property # def name(self) -> Optional[str]: # return self.longer or self.short # def __repr__(self) -> str: # return "Option(%r, %r, %r, %r)" % (self.short, self.longer, self.argcount, self.value) # class Required(BranchPattern): # def match(self, left: List["Pattern"], collected: List["Pattern"] = None) -> Any: # collected = [] if collected is None else collected # original_collected = collected # original_left = left # for pattern in self.children: # matched, left, collected = pattern.match(left, collected) # if not matched: # return False, original_left, original_collected # return True, left, collected # class NotRequired(BranchPattern): # def match(self, left: List["Pattern"], collected: List["Pattern"] = None) -> Any: # collected = [] if collected is None else collected # for pattern in self.children: # _, left, collected = pattern.match(left, collected) # return True, left, collected # class OptionsShortcut(NotRequired): # """Marker/placeholder for [options] shortcut.""" # class OneOrMore(BranchPattern): # def match(self, left: List[Pattern], collected: List[Pattern] = None) -> Any: # assert len(self.children) == 1 # collected = [] if collected is None else collected # original_collected = collected # original_left = left # last_left = None # matched = True # times = 0 # while matched: # matched, left, collected = self.children[0].match(left, collected) # times += 1 if matched else 0 # if last_left == left: # break # last_left = left # if times >= 1: # return True, left, collected # return False, original_left, original_collected # class Either(BranchPattern): # def match(self, left: List["Pattern"], collected: List["Pattern"] = None) -> Any: # collected = [] if collected is None else collected # outcomes = [] # for pattern in self.children: # matched, _, _ = outcome = pattern.match(left, collected) # if matched: # outcomes.append(outcome) # if outcomes: # return min(outcomes, key=lambda outcome: len(outcome[1])) # return False, left, collected # class Tokens(list): # def __init__(self, source: Union[List[str], str], error: Union[Type[DocoptExit], Type[DocoptLanguageError]] = DocoptExit) -> None: # if isinstance(source, list): # self += source # else: # self += source.split() # self.error = error # @staticmethod # def from_pattern(source: str) -> "Tokens": # source = re.sub(r"([\[\]\(\)\|]|\.\.\.)", r" \1 ", source) # fragments = [s for s in re.split(r"\s+|(\S*<.*?>)", source) if s] # return Tokens(fragments, error=DocoptLanguageError) # def move(self) -> Optional[str]: # return self.pop(0) if len(self) else None # def current(self) -> Optional[str]: # return self[0] if len(self) else None # def parse_longer(tokens: Tokens, options: List[Option], argv: bool = False, more_magic: bool = False) -> List[Pattern]: # """longer ::= '--' chars [ ( ' ' | '=' ) chars ] ;""" # current_token = tokens.move() # if current_token is None or not current_token.startswith("--"): # raise tokens.error(f"parse_longer got what appears to be an invalid token: {current_token}") # pragma: no cover # longer, maybe_eq, maybe_value = current_token.partition("=") # if maybe_eq == maybe_value == "": # value = None # else: # value = maybe_value # similar = [o for o in options if o.longer and longer == o.longer] # start_collision = len([o for o in options if o.longer and longer in o.longer and o.longer.startswith(longer)]) > 1 # if argv and not len(similar) and not start_collision: # similar = [o for o in options if o.longer and longer in o.longer and o.longer.startswith(longer)] # # try advanced matching # if more_magic and not similar: # corrected = [(longer, o) for o in options if o.longer and levenshtein_norm(longer, o.longer) < 0.25] # if corrected: # print(f"NB: Corrected {corrected[0][0]} to {corrected[0][1].longer}") # similar = [correct for (original, correct) in corrected] # if len(similar) > 1: # raise tokens.error(f"{longer} is not a unique prefix: {similar}?") # pragma: no cover # elif len(similar) < 1: # argcount = 1 if maybe_eq == "=" else 0 # o = Option(None, longer, argcount) # options.append(o) # if tokens.error is DocoptExit: # o = Option(None, longer, argcount, value if argcount else True) # else: # o = Option(similar[0].short, similar[0].longer, similar[0].argcount, similar[0].value) # if o.argcount == 0: # if value is not None: # raise tokens.error("%s must not have an argument" % o.longer) # else: # if value is None: # if tokens.current() in [None, "--"]: # raise tokens.error("%s requires argument" % o.longer) # value = tokens.move() # if tokens.error is DocoptExit: # o.value = value if value is not None else True # return [o] # def parse_shorts(tokens: Tokens, options: List[Option], more_magic: bool = False) -> List[Pattern]: # """shorts ::= '-' ( chars )* [ [ ' ' ] chars ] ;""" # token = tokens.move() # if token is None or not token.startswith("-") or token.startswith("--"): # raise ValueError(f"parse_shorts got what appears to be an invalid token: {token}") # pragma: no cover # left = token.lstrip("-") # parsed: List[Pattern] = [] # while left != "": # short, left = "-" + left[0], left[1:] # transformations: Dict[Union[None, str], Callable[[str], str]] = {None: lambda x: x} # if more_magic: # transformations["lowercase"] = lambda x: x.lower() # transformations["uppercase"] = lambda x: x.upper() # # try identity, lowercase, uppercase, iff such resolves uniquely (ie if upper and lowercase are not both defined) # similar: List[Option] = [] # de_abbreviated = False # for transform_name, transform in transformations.items(): # transformed = list(set([transform(o.short) for o in options if o.short])) # no_collisions = len([o for o in options if o.short and transformed.count(transform(o.short)) == 1]) # == len(transformed) # if no_collisions: # similar = [o for o in options if o.short and transform(o.short) == transform(short)] # if similar: # if transform_name: # print(f"NB: Corrected {short} to {similar[0].short} via {transform_name}") # break # # if transformations do not resolve, try abbreviations of 'longer' forms iff such resolves uniquely (ie if no two longer forms begin with the same letter) # if not similar and more_magic: # abbreviated = [transform(o.longer[1:3]) for o in options if o.longer and not o.short] + [ # transform(o.short) for o in options if o.short and not o.longer # ] # nonredundantly_abbreviated_options = [o for o in options if o.longer and abbreviated.count(short) == 1] # no_collisions = len(nonredundantly_abbreviated_options) == len(abbreviated) # if no_collisions: # for o in options: # if not o.short and o.longer and transform(short) == transform(o.longer[1:3]): # similar = [o] # print(f"NB: Corrected {short} to {similar[0].longer} via abbreviation (case change: {transform_name})") # break # if len(similar): # de_abbreviated = True # break # if len(similar) > 1: # raise tokens.error("%s is specified ambiguously %d times" % (short, len(similar))) # elif len(similar) < 1: # o = Option(short, None, 0) # options.append(o) # if tokens.error is DocoptExit: # o = Option(short, None, 0, True) # else: # if de_abbreviated: # option_short_value = None # else: # option_short_value = transform(short) # o = Option(option_short_value, similar[0].longer, similar[0].argcount, similar[0].value) # value = None # current_token = tokens.current() # if o.argcount != 0: # if left == "": # if current_token is None or current_token == "--": # raise tokens.error("%s requires argument" % short) # else: # value = tokens.move() # else: # value = left # left = "" # if tokens.error is DocoptExit: # o.value = value if value is not None else True # parsed.append(o) # return parsed # def parse_pattern(source: str, options: List[Option]) -> Required: # tokens = Tokens.from_pattern(source) # result = parse_expr(tokens, options) # if tokens.current() is not None: # raise tokens.error("unexpected ending: %r" % " ".join(tokens)) # return Required(*result) # def parse_expr(tokens: Tokens, options: List[Option]) -> List[Pattern]: # """expr ::= seq ( '|' seq )* ;""" # result: List[Pattern] = [] # seq_0: List[Pattern] = parse_seq(tokens, options) # if tokens.current() != "|": # return seq_0 # if len(seq_0) > 1: # result.append(Required(*seq_0)) # else: # result += seq_0 # while tokens.current() == "|": # tokens.move() # seq_1 = parse_seq(tokens, options) # if len(seq_1) > 1: # result += [Required(*seq_1)] # else: # result += seq_1 # return [Either(*result)] # def parse_seq(tokens: Tokens, options: List[Option]) -> List[Pattern]: # """seq ::= ( atom [ '...' ] )* ;""" # result: List[Pattern] = [] # while tokens.current() not in [None, "]", ")", "|"]: # atom = parse_atom(tokens, options) # if tokens.current() == "...": # atom = [OneOrMore(*atom)] # tokens.move() # result += atom # return result # def parse_atom(tokens: Tokens, options: List[Option]) -> List[Pattern]: # """atom ::= '(' expr ')' | '[' expr ']' | 'options' # | longer | shorts | argument | command ; # """ # token = tokens.current() # if not token: # return [Command(tokens.move())] # pragma: no cover # elif token in "([": # tokens.move() # matching = {"(": ")", "[": "]"}[token] # pattern = {"(": Required, "[": NotRequired}[token] # matched_pattern = pattern(*parse_expr(tokens, options)) # if tokens.move() != matching: # raise tokens.error("unmatched '%s'" % token) # return [matched_pattern] # elif token == "options": # tokens.move() # return [OptionsShortcut()] # elif token.startswith("--") and token != "--": # return parse_longer(tokens, options) # elif token.startswith("-") and token not in ("-", "--"): # return parse_shorts(tokens, options) # elif token.startswith("<") and token.endswith(">") or token.isupper(): # return [Argument(tokens.move())] # else: # return [Command(tokens.move())] # def parse_argv(tokens: Tokens, options: List[Option], options_first: bool = False, more_magic: bool = False) -> List[Pattern]: # """Parse command-line argument vector. # If options_first: # argv ::= [ longer | shorts ]* [ argument ]* [ '--' [ argument ]* ] ; # else: # argv ::= [ longer | shorts | argument ]* [ '--' [ argument ]* ] ; # """ # def isanumber(x): # try: # float(x) # return True # except ValueError: # return False # parsed: List[Pattern] = [] # current_token = tokens.current() # while current_token is not None: # if current_token == "--": # return parsed + [Argument(None, v) for v in tokens] # elif current_token.startswith("--"): # parsed += parse_longer(tokens, options, argv=True, more_magic=more_magic) # elif current_token.startswith("-") and current_token != "-" and not isanumber(current_token): # parsed += parse_shorts(tokens, options, more_magic=more_magic) # elif options_first: # return parsed + [Argument(None, v) for v in tokens] # else: # parsed.append(Argument(None, tokens.move())) # current_token = tokens.current() # return parsed # def parse_defaults(docstring: str) -> List[Option]: # defaults = [] # for s in parse_section("options:", docstring): # options_literal, _, s = s.partition(":") # if " " in options_literal: # _, _, options_literal = options_literal.partition(" ") # assert options_literal.lower().strip() == "options" # split = re.split(r"\n[ \t]*(-\S+?)", "\n" + s)[1:] # split = [s1 + s2 for s1, s2 in zip(split[::2], split[1::2])] # for s in split: # if s.startswith("-"): # arg, _, description = s.partition(" ") # flag, _, var = arg.replace("=", " ").partition(" ") # option = Option.parse(s) # defaults.append(option) # return defaults # def parse_section(name: str, source: str) -> List[str]: # pattern = re.compile("^([^\n]*" + name + "[^\n]*\n?(?:[ \t].*?(?:\n|$))*)", re.IGNORECASE | re.MULTILINE) # r = [s.strip() for s in pattern.findall(source) if s.strip().lower() != name.lower()] # return r # def formal_usage(section: str) -> str: # _, _, section = section.partition(":") # drop "usage:" # pu = section.split() # return "( " + " ".join(") | (" if s == pu[0] else s for s in pu[1:]) + " )" # def extras(default_help: bool, version: None, options: List[Pattern], docstring: str) -> None: # if default_help and any((o.name in ("-h", "--help")) and o.value for o in options if isinstance(o, Option)): # print(docstring.strip("\n")) # sys.exit() # if version and any(o.name == "--version" and o.value for o in options if isinstance(o, Option)): # print(version) # sys.exit() # class ParsedOptions(dict): # def __repr__(self): # return "{%s}" % ",\n ".join("%r: %r" % i for i in sorted(self.items())) # 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