- minor changes

- add Exception.description property support
- add a few sanity checks to over.app.Main
- added top-level stack frame display to over.app.Main.exception_handler
- the command line parser is now usable :)
This commit is contained in:
Martinez 2016-05-16 00:27:08 +02:00
parent b30cab6926
commit d0fd7e90c5
7 changed files with 91 additions and 42 deletions

View file

@ -7,7 +7,6 @@ from collections import OrderedDict
import enum
import sys
import re
import traceback
# --------------------------------------------------
# Local imports
@ -21,25 +20,44 @@ from . import types
class UnknownAbbreviation(Exception):
"""
This abbreviation doesn't map to any known option.
This abbreviation doesn't map to any known option. If this was a target, place it at the end of the line after a guard (--).
"""
pass
class UnknownOption(Exception):
"""
This option was passed on the command line but is not known.
This option was mentioned in code, the config file or on the command line but is not known.
"""
pass
@property
def description(self):
return "--%s" %(self.args[0])
class ReadingUnsetOption(Exception):
"""
This option has no default, config file or command line value set.
"""
pass
class OptionNameUsed(Exception):
"""
This option's name or abbreviation is already used by a previously defined option.
"""
pass
class IncompleteArguments(Exception):
@property
def description(self):
option_name, option_count, option_remaining = self.args
if option_remaining == option_count:
return "option --%s takes a %d-word argument but has received none" %(option_name, option_count)
else:
return "option --%s takes a %d-word argument but has only received %d" %(option_name, option_count, option_count - option_remaining)
# --------------------------------------------------
class Option_sources(enum.Enum):
@ -74,7 +92,7 @@ class Option:
"""
Adds a value to this option's state. If the `source` is different from `self.source`, resets the state.
@action setting option value
@while setting option value
"""
if source != self.source:
@ -85,10 +103,7 @@ class Option:
if skip_callback:
value = raw_value
else:
if self.count == 1:
value = self.callback(raw_value[0])
else:
value = self.callback(raw_value)
value = self.callback(*raw_value)
if self.overwrite:
self._value = value
@ -109,13 +124,20 @@ class ConfigRouter:
self.options = options
def __getattr__(self, name):
return self.options[name].value
"""
@while retrieving an option's value
"""
try:
return self.options[name].value
except KeyError:
raise UnknownOption(name)
# --------------------------------------------------
def expand_group(token, options):
"""
@action expanding a commandline group
@while expanding a commandline group
"""
output = []
@ -156,13 +178,14 @@ class Main:
self.license = license
self.print = text.Output(name)
self.options = OrderedDict()
self.options_by_abbr = OrderedDict()
self.cfg = ConfigRouter(self.options)
self.targets = []
self.auto_add_help = auto_add_help
self.invocation = cmd.format_invocation(sys.argv)
if use_cfg_file:
# ensure it exists, update it with new values, etc.
# TODO ensure it exists, update it with new values, etc.
...
if handle_exceptions:
@ -180,21 +203,31 @@ class Main:
def add_option(self, *args, **kwargs):
"""
@action creating a new configuration option
@while registering a new configuration option
"""
option = Option(*args, **kwargs)
self.options[option.name] = option
if option.name in self.options:
raise OptionNameUsed("name %s is already in use" %(option.name))
else:
self.options[option.name] = option
if option.abbr:
if option.abbr in self.options_by_abbr:
raise OptionNameUsed("abbreviation %s is already used by --%s" %(option.abbr, self.options_by_abbr[option.abbr].name))
else:
self.options_by_abbr[option.abbr] = option
def add_doc(self, chapter, paragraphs):
# TODO
...
def enable_help(self, name="help", abbr="h"):
"""
Map application help to --name and -abbr, and enable library ---help.
@action adding help commandline options
@while adding help commandline options
"""
self.add_option("help", "Display the application configuration view.", self.help, abbr=abbr, in_cfg_file=False)
@ -214,12 +247,13 @@ class Main:
If `docs` is set to something else, only those docs will be displayed.
@action displaying help
@while displaying help
"""
self.print("called for help!")
self.print("TODO called for help!")
def parse_config(self):
# TODO
...
def parse_cmdline(self, cmdline=None):
@ -238,10 +272,9 @@ class Main:
+A -A +A evaluates to True. A cumulative option would evaluate to a list of
[True, False, True].
@action parsing commandline arguments
@while parsing commandline arguments
"""
# todo update invocation
cmdline = cmdline or sys.argv[1:]
# placing it here ensures --help is the last option in the list of options
@ -252,6 +285,7 @@ class Main:
remaining_payload_tokens = 0
temporary_payload = []
guard_passed = False
invocation = []
for top_token in cmdline:
if not (remaining_payload_tokens or guard_passed) and len(top_token) >= 2 and top_token[0] in "+-" and top_token[1] != "-": # it's a group
@ -260,6 +294,8 @@ class Main:
expanded = [top_token]
for token in expanded:
invocation.append(token)
if not (remaining_payload_tokens or guard_passed) and token[:2] == "--":
if token == "--":
guard_passed = True
@ -294,10 +330,15 @@ class Main:
last_read_option.set_value(temporary_payload, Option_sources.cmdline)
else:
self.targets.append(token)
if remaining_payload_tokens:
raise IncompleteArguments(last_read_option.name, last_read_option.count, remaining_payload_tokens)
self.invocation = cmd.format_invocation(invocation)
def parse(self, cmdline=None):
"""
@action assembling application configuration
@while assembling application configuration
"""
self.parse_config()
@ -305,7 +346,7 @@ class Main:
def exception_handler(self, exception_type, exception, trace):
"""
@action formatting a traceback
@while formatting a traceback
Over Exception handler - prints human readable tracebacks.
"""
@ -314,10 +355,8 @@ class Main:
# todo top level program name
# todo final level exception name and text
# fixme remove traceback
tb_lines = ["", "---------------- Stack trace ----------------", "In program <W>%s<.>" %(self.invocation)]
trace = trace.tb_next
while trace:
frame = trace.tb_frame
@ -340,18 +379,28 @@ class Main:
else:
method = None
method_display_name = "<y>%s<.>" %(method_name)
method_type = "<r>unknown callable<.>"
if method_name == "<module>":
method_type = "module"
else:
method_type = "<r>unknown callable<.>"
# use a docstring-provided action description if available
if method and method.__doc__ and "@action" in method.__doc__:
action = "while <m>%s<.> " %(re.findall("@action (.+)", method.__doc__)[0].strip())
if method and method.__doc__ and "@while" in method.__doc__:
action = "while <m>%s<.> " %(re.findall("@while (.+)", method.__doc__)[0].strip())
else:
action = ""
tb_lines.append("%sin %s <y>%s<.> at %s:%d," %(action, method_type, method_display_name, frame.f_code.co_filename, frame.f_lineno))
reason = ": <R>%s<.>" %(" ".join(exception.args)) if exception.args else ""
tb_lines.append("exception <Y>%s<.> was raised%s (%s)" %(exception_type.__name__, reason, exception.__doc__.strip()))
if hasattr(exception, "description"):
reason = ": <R>%s<.>" %(exception.description)
elif exception.args:
reason = ": <R>%s<.>" %(" ".join(exception.args))
else:
reason = ""
doc = " [%s]" %(exception.__doc__.strip()) if exception.__doc__ else ""
tb_lines.append("exception <Y>%s<.> was raised%s%s" %(exception_type.__name__, reason, doc))
last_i = len(tb_lines) - 1

View file

@ -11,7 +11,7 @@ def touch(path, times=None):
`times` is a tuple of (atime, mtime) and defaults to "now".
@action touching a file
@while touching a file
"""
path = os.path.expanduser(path)
@ -25,7 +25,7 @@ def count_lines(path):
"""
A reasonably fast and memory-lean line counter.
@action counting lines in a file
@while counting lines in a file
"""
lines = 0

View file

@ -11,7 +11,7 @@ def import_module(path):
Based on the work of Yuval Greenfield released into the public domain.
@action importing a module
@while importing a module
"""
import imp
@ -101,7 +101,7 @@ def hexdump(data, indent=0, offset=16, show_header=True, show_offsets=True, show
are replaced with a dot.
The `output` must implement a .write(str x) method. If `output` is None, the string is returned instead.
@action creating a hex dump
@while creating a hex dump
"""
import io

View file

@ -145,7 +145,7 @@ def render(text, colors=True):
Color tags are <x> where x is the color code from ansi_colors.
<.> resets the color. Use <<x> for a literal <x>.
@action coloring text
@while coloring text
"""
text = str(text)
@ -191,7 +191,7 @@ def rfind(text, C, start, length):
indices = [i for i, c in enumerate(text[start:]) if c == C]
# return the original index at the same position as the last in colorless
return start + indices[len(indices_colorless) - 1]
return start + (indices[len(indices_colorless) - 1] if indices_colorless else 0)
def paragraph(text, width=0, indent=0, stamp=None):
"""
@ -203,7 +203,7 @@ def paragraph(text, width=0, indent=0, stamp=None):
Formats text into an indented paragraph that fits the terminal and returns it.
Correctly handles colors.
@action formatting a paragraph
@while formatting a paragraph
"""
fit_into_width = (width or get_terminal_size()[1]) - indent
@ -329,7 +329,7 @@ class Output:
def print(self, text, tag=None, format=None, colors=None, end=None):
"""
@action displaying text
@while displaying text
"""
tag = tag or self.__class__.tag.long.info
@ -498,7 +498,7 @@ class ProgressBar:
it"s a callable, it will be called without arguments and shall return a number.
"precision" is the displayed floating point precision. Use 0 to force an integer.
@action creating a ProgressBar
@while creating a ProgressBar
"""
self.format = format
@ -523,7 +523,7 @@ class ProgressBar:
"""
Renders the progressbar into a stream. If clear is True, clears the previous content first.
@action drawing a progressbar update
@while drawing a progressbar update
"""
width = self.width or get_terminal_size()[1]

View file

@ -22,7 +22,7 @@ class ndict(OrderedDict):
def __getattr__(self, name):
"""
@action looking up an attribute
@while looking up an attribute
"""
if name in self:

View file

@ -22,7 +22,7 @@ cdef class ndict(OrderedDict):
def __getattr__(self, str name):
"""
@action looking up an attribute
@while looking up an attribute
"""
if name in self:

View file

@ -4,5 +4,5 @@
major = 0 # VERSION_MAJOR_IDENTIFIER
minor = 0 # VERSION_MINOR_IDENTIFIER
# VERSION_LAST_MM 0.0
patch = 3 # VERSION_PATCH_IDENTIFIER
patch = 4 # VERSION_PATCH_IDENTIFIER
str = ".".join(str(v) for v in (major, minor, patch))