Compare commits
No commits in common. "master" and "2.0-rc1" have entirely different histories.
13 changed files with 176 additions and 400 deletions
4
.gitignore
vendored
4
.gitignore
vendored
|
@ -1,3 +1,3 @@
|
||||||
|
*.so
|
||||||
|
*.pyc
|
||||||
__pycache__
|
__pycache__
|
||||||
/MANIFEST
|
|
||||||
/dist
|
|
||||||
|
|
|
@ -6,7 +6,6 @@ import sys
|
||||||
from . import app
|
from . import app
|
||||||
from . import aux
|
from . import aux
|
||||||
from . import callback
|
from . import callback
|
||||||
from . import cfg
|
|
||||||
from . import cmd
|
from . import cmd
|
||||||
from . import docs
|
from . import docs
|
||||||
from . import file
|
from . import file
|
||||||
|
|
52
over/app.py
52
over/app.py
|
@ -107,7 +107,6 @@ class Option_sources(enum.Enum):
|
||||||
default = 1
|
default = 1
|
||||||
config_file = 2
|
config_file = 2
|
||||||
command_line = 3
|
command_line = 3
|
||||||
preset = 4
|
|
||||||
|
|
||||||
class Option:
|
class Option:
|
||||||
def __init__(self, name, description, callback, default=Option_sources.none, count=0, overwrite=True, abbr=None, in_cfg_file=True, in_help=True, is_boolean=None):
|
def __init__(self, name, description, callback, default=Option_sources.none, count=0, overwrite=True, abbr=None, in_cfg_file=True, in_help=True, is_boolean=None):
|
||||||
|
@ -220,7 +219,7 @@ def expand_group(token, options):
|
||||||
|
|
||||||
options_by_abbr = {option.abbr: option for option in options.values() if option.abbr}
|
options_by_abbr = {option.abbr: option for option in options.values() if option.abbr}
|
||||||
|
|
||||||
group_positive = token[0] == "+"
|
group_positive = token[0] is "+"
|
||||||
|
|
||||||
for abbr in token[1:]:
|
for abbr in token[1:]:
|
||||||
try:
|
try:
|
||||||
|
@ -305,7 +304,7 @@ class ConfigFile:
|
||||||
self.options = options
|
self.options = options
|
||||||
self.path = path
|
self.path = path
|
||||||
self.ignore_unknown = ignore_unknown
|
self.ignore_unknown = ignore_unknown
|
||||||
self.log = text.Log("over.app.ConfigFile")
|
self.print = text.Output("over.app.ConfigFile")
|
||||||
self.seen_hashes = set()
|
self.seen_hashes = set()
|
||||||
|
|
||||||
def read_config(self):
|
def read_config(self):
|
||||||
|
@ -356,13 +355,13 @@ class ConfigFile:
|
||||||
config_dir = os.path.dirname(self.path)
|
config_dir = os.path.dirname(self.path)
|
||||||
if config_dir.strip() and not os.path.exists(config_dir):
|
if config_dir.strip() and not os.path.exists(config_dir):
|
||||||
os.mkdir(config_dir)
|
os.mkdir(config_dir)
|
||||||
self.log.info("created config directory <c>%s<.>", config_dir)
|
self.print("created config directory <c>%s<.>" %(config_dir))
|
||||||
|
|
||||||
# if the file doesn't exist, create it with a boilerplate header
|
# if the file doesn't exist, create it with a boilerplate header
|
||||||
if not os.path.exists(self.path):
|
if not os.path.exists(self.path):
|
||||||
with open(self.path, "w") as f:
|
with open(self.path, "w") as f:
|
||||||
f.write(header_template %header_args)
|
f.write(header_template %header_args)
|
||||||
self.log.info("created empty config file <c>%s<.>", self.path)
|
self.print("created empty config file <c>%s<.>" %(self.path))
|
||||||
|
|
||||||
# add new or otherwise missing options
|
# add new or otherwise missing options
|
||||||
updated = set()
|
updated = set()
|
||||||
|
@ -370,7 +369,7 @@ class ConfigFile:
|
||||||
with open(self.path, "a") as f:
|
with open(self.path, "a") as f:
|
||||||
for option in self.options.values():
|
for option in self.options.values():
|
||||||
if option.hash not in self.seen_hashes and option.in_cfg_file:
|
if option.hash not in self.seen_hashes and option.in_cfg_file:
|
||||||
self.log.info("adding <W>--<G>%s<.> to config file", option.name)
|
self.print("adding <W>--<G>%s<.> to config file" %(option.name))
|
||||||
|
|
||||||
f.write(docs.config_file_item %(
|
f.write(docs.config_file_item %(
|
||||||
option.hash,
|
option.hash,
|
||||||
|
@ -415,7 +414,7 @@ class Main:
|
||||||
self.name = name
|
self.name = name
|
||||||
self.version = version
|
self.version = version
|
||||||
self.license = license
|
self.license = license
|
||||||
self.log = text.Log(name)
|
self.print = text.Output(name)
|
||||||
self.options = OrderedDict()
|
self.options = OrderedDict()
|
||||||
self.options_by_abbr = OrderedDict()
|
self.options_by_abbr = OrderedDict()
|
||||||
self.docs = OrderedDict()
|
self.docs = OrderedDict()
|
||||||
|
@ -426,7 +425,6 @@ class Main:
|
||||||
self.last_command_line = None
|
self.last_command_line = None
|
||||||
self.using_alternate_config = False
|
self.using_alternate_config = False
|
||||||
self.uncontained_exception_callbacks = [] # (function, args)
|
self.uncontained_exception_callbacks = [] # (function, args)
|
||||||
self.dirs = get_xdg_paths(name)
|
|
||||||
|
|
||||||
for feature_name in self.default_features:
|
for feature_name in self.default_features:
|
||||||
if feature_name in features:
|
if feature_name in features:
|
||||||
|
@ -511,7 +509,7 @@ class Main:
|
||||||
for target in self.targets:
|
for target in self.targets:
|
||||||
tokens.append(target)
|
tokens.append(target)
|
||||||
|
|
||||||
self.log.write(" ".join(tokens), format="<t>")
|
self.print(" ".join(tokens), format="<t>")
|
||||||
|
|
||||||
def activate_debug(self):
|
def activate_debug(self):
|
||||||
raise NotImplementedError("debug is not yet here")
|
raise NotImplementedError("debug is not yet here")
|
||||||
|
@ -540,23 +538,23 @@ class Main:
|
||||||
@while displaying help
|
@while displaying help
|
||||||
"""
|
"""
|
||||||
|
|
||||||
print = text.Log("help", format="<t>", end="\n").write
|
print = text.Output("help", format="<t>", end="\n")
|
||||||
|
|
||||||
# App name and version
|
# App name and version
|
||||||
print("[<W>Application<.>]")
|
print("[<W>Application<.>]")
|
||||||
print(" <W>%s<.>-<c>%s<.> licensed under <W>%s<.>", self.name, self.version, self.license)
|
print(" <W>%s<.>-<c>%s<.> licensed under the <W>%s<.>" %(self.name, self.version, self.license))
|
||||||
print(" using over-%s" %(version.str))
|
print(" using over-%s" %(version.str))
|
||||||
|
|
||||||
# Main features
|
# Main features
|
||||||
print("")
|
print("")
|
||||||
print("[<W>over.app.Main features<.>]")
|
print("[<W>over.app.Main features<.>]")
|
||||||
for feature_name in self.features:
|
for feature_name in self.features:
|
||||||
print(" %s: <B>%s<.> (%s)", " <G>ON<.>" if self.features[feature_name] else "<R>OFF<.>", feature_name, self.default_features[feature_name][1])
|
print(" %s: <B>%s<.> (%s)" %(" <G>ON<.>" if self.features[feature_name] else "<R>OFF<.>", feature_name, self.default_features[feature_name][1]))
|
||||||
|
|
||||||
# App docs
|
# App docs
|
||||||
print("")
|
print("")
|
||||||
for chapter, paragraphs in (alternate_docs or self.docs).items():
|
for chapter, paragraphs in (alternate_docs or self.docs).items():
|
||||||
print("[<W>%s<.>]", chapter)
|
print("[<W>%s<.>]" %(chapter))
|
||||||
|
|
||||||
for paragraph in paragraphs:
|
for paragraph in paragraphs:
|
||||||
print(paragraph, format=" <i><t>")
|
print(paragraph, format=" <i><t>")
|
||||||
|
@ -603,10 +601,10 @@ class Main:
|
||||||
else:
|
else:
|
||||||
colored_current_value = '<M>' + colored_current_value + '<.>'
|
colored_current_value = '<M>' + colored_current_value + '<.>'
|
||||||
|
|
||||||
print(" Current value (%s): %s",
|
print(" Current value (%s): %s" %(
|
||||||
serialize_source(option.source),
|
serialize_source(option.source),
|
||||||
colored_current_value
|
colored_current_value
|
||||||
)
|
))
|
||||||
|
|
||||||
# some misc flags
|
# some misc flags
|
||||||
if not option.in_cfg_file:
|
if not option.in_cfg_file:
|
||||||
|
@ -620,7 +618,7 @@ class Main:
|
||||||
# Current targets, if any
|
# Current targets, if any
|
||||||
if self.targets:
|
if self.targets:
|
||||||
print("[<W>Targets<.>]")
|
print("[<W>Targets<.>]")
|
||||||
print(" <M>%s<.>", cmd.format_invocation(self.targets))
|
print(" <M>%s<.>" %(cmd.format_invocation(self.targets)))
|
||||||
|
|
||||||
self.exit()
|
self.exit()
|
||||||
|
|
||||||
|
@ -728,15 +726,11 @@ class Main:
|
||||||
for action in scheduled_actions:
|
for action in scheduled_actions:
|
||||||
action()
|
action()
|
||||||
|
|
||||||
def setup(self, command_line=None, force_config=None, reset=False):
|
def setup(self, command_line=None, force_config=None):
|
||||||
"""
|
"""
|
||||||
@while determining application configuration
|
@while determining application configuration
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if reset:
|
|
||||||
for option in self.options.values():
|
|
||||||
option.reset(True)
|
|
||||||
|
|
||||||
self.parse_config(force_config)
|
self.parse_config(force_config)
|
||||||
self.parse_command_line(command_line)
|
self.parse_command_line(command_line)
|
||||||
|
|
||||||
|
@ -749,7 +743,7 @@ class Main:
|
||||||
If things go south even here, prints the topmost traceback and gives up in a safe manner.
|
If things go south even here, prints the topmost traceback and gives up in a safe manner.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
self.log.fail("<R>uncontained exception<.> <Y>%s<.> raised", exception_type.__name__)
|
self.print("<R>uncontained exception<.> <Y>%s<.> raised" %(exception_type.__name__), self.print.tl.fail)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
tb_lines = ["", "---------------- Stack trace ----------------", "In program <W>%s<.>" %(self.invocation)]
|
tb_lines = ["", "---------------- Stack trace ----------------", "In program <W>%s<.>" %(self.invocation)]
|
||||||
|
@ -809,26 +803,26 @@ class Main:
|
||||||
else:
|
else:
|
||||||
format = "%s - <i><t>" %((i - 3) * " ")
|
format = "%s - <i><t>" %((i - 3) * " ")
|
||||||
|
|
||||||
self.log.write(line, format=format, end="\n")
|
self.print(line, format=format, end="\n")
|
||||||
|
|
||||||
self.log.write("---------------------------------------------", format="<t>", end="\n")
|
self.print("---------------------------------------------", format="<t>", end="\n")
|
||||||
|
|
||||||
except:
|
except:
|
||||||
self.log.epic("<R>failed to contain exception<.>")
|
self.print("<R>failed to contain exception<.>", self.print.tl.epic)
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
|
|
||||||
if self.uncontained_exception_callbacks:
|
if self.uncontained_exception_callbacks:
|
||||||
self.log.start("executing containment callbacks")
|
self.print("executing containment callbacks", self.print.tl.exec)
|
||||||
l = len(self.uncontained_exception_callbacks)
|
l = len(self.uncontained_exception_callbacks)
|
||||||
|
|
||||||
for i, (cb, ctx) in enumerate(self.uncontained_exception_callbacks):
|
for i, (cb, ctx) in enumerate(self.uncontained_exception_callbacks):
|
||||||
self.log.write("(%d/%d) %s", i+1, l, cb.__name__)
|
self.print("(%d/%d) %s" %(i+1, l, cb.__name__))
|
||||||
try:
|
try:
|
||||||
cb(*ctx)
|
cb(*ctx)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.log.epic("(%d/%d) <r>%s failed<.> (<R>%s<.>)", i+1, l, cb.__name__, e.__class__.__name__, end=":\n")
|
self.print("(%d/%d) <r>%s failed<.> (<R>%s<.>)" %(i+1, l, cb.__name__, e.__class__.__name__), self.print.tl.epic, end=":\n")
|
||||||
exc_info = sys.exc_info()
|
exc_info = sys.exc_info()
|
||||||
traceback.print_exception(*exc_info)
|
traceback.print_exception(*exc_info)
|
||||||
del exc_info
|
del exc_info
|
||||||
|
|
||||||
self.log.done("containment callbacks executed")
|
self.print("containment callbacks executed", self.print.tl.done)
|
||||||
|
|
|
@ -7,7 +7,7 @@ import traceback
|
||||||
from . import text
|
from . import text
|
||||||
|
|
||||||
class DeprecationForwarder:
|
class DeprecationForwarder:
|
||||||
log = text.Log("over.aux.DeprecationForwarder")
|
print = text.Output("over.aux.DeprecationForwarder", stream=sys.stderr)
|
||||||
|
|
||||||
def __init__(self, target, old_name, new_name):
|
def __init__(self, target, old_name, new_name):
|
||||||
self._target = target
|
self._target = target
|
||||||
|
@ -16,6 +16,6 @@ class DeprecationForwarder:
|
||||||
|
|
||||||
def __getattr__(self, name):
|
def __getattr__(self, name):
|
||||||
caller = traceback.extract_stack()[-2]
|
caller = traceback.extract_stack()[-2]
|
||||||
self.log.warn("%s is deprecated, please use %s instead (%s:%d)", self._old_name, self._new_name, caller[0], caller[1])
|
self.print("%s is deprecated, please use %s instead (%s:%d)" %(self._old_name, self._new_name, caller[0], caller[1]), self.print.tl.warn)
|
||||||
|
|
||||||
return getattr(self._target, name)
|
return getattr(self._target, name)
|
||||||
|
|
|
@ -53,62 +53,27 @@ def strings(*args):
|
||||||
|
|
||||||
return out
|
return out
|
||||||
|
|
||||||
def path(exists=False, permissions=None, validators=None):
|
def directory(exists=False, writable=False, gio=False):
|
||||||
"""
|
"""
|
||||||
Returns a path callback that takes a path (str) and verifies if it:
|
Returns a directory callback that raises hell if:
|
||||||
- exists iff `exists` is True
|
- the supplied directory doesn't exist and `exists` is True
|
||||||
- its permissions match those in `permissions` ("rwx" or any subset, e.g. "r--", "-w-", dashes are optional)
|
- isn't writable and `writable` is True
|
||||||
- goes through each (function, message) pair in `validators`, in order
|
- isn't a valid Gio path and `gio` is True
|
||||||
|
|
||||||
A validator is a function that takes a str argument and returns True or False.
|
@while generating a callback
|
||||||
If False is returned, the matching message is raised along with a RuntimeError.
|
|
||||||
The message may contain %s to be replaced by the supplied path. Example:
|
|
||||||
|
|
||||||
validators=[
|
|
||||||
(os.path.isdir, "%s is not a directory"),
|
|
||||||
(os.path.isabs, "%s is a directory, but is not an absolute path.")
|
|
||||||
]
|
|
||||||
|
|
||||||
@while generating a path callback
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
if gio:
|
||||||
|
raise NotImplementedError("Gio support is not yet here")
|
||||||
|
|
||||||
def cb(arg):
|
def cb(arg):
|
||||||
path = os.path.abspath(os.path.expanduser(arg))
|
path = os.path.abspath(os.path.expanduser(arg))
|
||||||
|
|
||||||
if exists and not os.path.exists(path):
|
if not os.path.isdir(path) and exists:
|
||||||
raise FileNotFoundError("%s does not exist" %(arg))
|
raise FileNotFoundError("%s (%s) does not exist" %(arg, path))
|
||||||
|
|
||||||
if permissions:
|
if writable and not os.access(path, os.W_OK | os.X_OK):
|
||||||
if "r" in permissions and not os.access(path, os.R_OK):
|
raise PermissionError("%s exists but is not writable" %(arg))
|
||||||
raise PermissionError("%s is not readable" %(arg))
|
|
||||||
|
|
||||||
if exists or os.path.exists(path):
|
|
||||||
if "w" in permissions and not os.access(path, os.W_OK):
|
|
||||||
raise PermissionError("%s is not writable" %(arg))
|
|
||||||
else:
|
|
||||||
if "w" in permissions and not os.access(os.path.dirname(path), os.W_OK):
|
|
||||||
raise PermissionError("%s cannot be created" %(arg))
|
|
||||||
|
|
||||||
if "x" in permissions and not os.access(path, os.X_OK):
|
|
||||||
raise PermissionError("%s is not executable" %(arg))
|
|
||||||
|
|
||||||
if validators:
|
|
||||||
if hasattr(validators, "__iter__"):
|
|
||||||
src = validators
|
|
||||||
else:
|
|
||||||
src = [validators]
|
|
||||||
|
|
||||||
for v in src:
|
|
||||||
if hasattr(v, "__iter__"):
|
|
||||||
fn, msg_tpl = v
|
|
||||||
else:
|
|
||||||
fn = v
|
|
||||||
msg_tpl = "%%s failed validation by '%s'" %(fn.__name__)
|
|
||||||
|
|
||||||
if not fn(path):
|
|
||||||
msg = msg_tpl %(arg) if "%s" in msg_tpl else msg_tpl
|
|
||||||
|
|
||||||
raise RuntimeError(msg)
|
|
||||||
|
|
||||||
return path
|
return path
|
||||||
|
|
||||||
|
@ -134,15 +99,3 @@ def integer(arg):
|
||||||
return int(arg, 8)
|
return int(arg, 8)
|
||||||
|
|
||||||
return int(arg)
|
return int(arg)
|
||||||
|
|
||||||
def integers(*args):
|
|
||||||
"""
|
|
||||||
@while converting arguments to ints
|
|
||||||
"""
|
|
||||||
|
|
||||||
out = []
|
|
||||||
|
|
||||||
for arg in args:
|
|
||||||
out.append(integer(arg))
|
|
||||||
|
|
||||||
return out
|
|
||||||
|
|
75
over/cfg.py
75
over/cfg.py
|
@ -1,75 +0,0 @@
|
||||||
#! /usr/bin/env python3
|
|
||||||
# encoding: utf-8
|
|
||||||
|
|
||||||
# --------------------------------------------------
|
|
||||||
# Library imports
|
|
||||||
import json
|
|
||||||
import jsmin
|
|
||||||
|
|
||||||
# --------------------------------------------------
|
|
||||||
# Local imports
|
|
||||||
|
|
||||||
# --------------------------------------------------
|
|
||||||
|
|
||||||
class Config:
|
|
||||||
def __init__(self, source=None, readonly=False):
|
|
||||||
"""
|
|
||||||
Can be loaded from a JSON file (str path) or from a python dict.
|
|
||||||
"""
|
|
||||||
|
|
||||||
if type(source) == str:
|
|
||||||
with open(source) as f:
|
|
||||||
initial = json.loads(jsmin.jsmin(f.read()))
|
|
||||||
elif source is None:
|
|
||||||
initial = {}
|
|
||||||
else:
|
|
||||||
initial = source
|
|
||||||
|
|
||||||
assert type(initial) == dict
|
|
||||||
object.__setattr__(self, "raw", initial)
|
|
||||||
object.__setattr__(self, "readonly", readonly)
|
|
||||||
|
|
||||||
def keys(self):
|
|
||||||
return self.raw.keys()
|
|
||||||
|
|
||||||
def items(self):
|
|
||||||
return self.raw.items()
|
|
||||||
|
|
||||||
def values(self):
|
|
||||||
return self.raw.values()
|
|
||||||
|
|
||||||
def __getitem__(self, name):
|
|
||||||
return self.raw[name]
|
|
||||||
|
|
||||||
def __setitem__(self, name, value):
|
|
||||||
if self.readonly:
|
|
||||||
raise AttributeError("object is not writable")
|
|
||||||
|
|
||||||
self.raw[name] = value
|
|
||||||
|
|
||||||
def __setattr__(self, name, value):
|
|
||||||
if self.readonly:
|
|
||||||
raise AttributeError("object is not writable")
|
|
||||||
|
|
||||||
self.raw[name] = value
|
|
||||||
|
|
||||||
def __getattr__(self, name):
|
|
||||||
matches = [key for key in self.raw if key.replace("-", "_") == name]
|
|
||||||
|
|
||||||
if matches:
|
|
||||||
assert len(matches) == 1
|
|
||||||
|
|
||||||
value = self.raw[matches[0]]
|
|
||||||
|
|
||||||
if type(value) == dict:
|
|
||||||
return Config(value)
|
|
||||||
else:
|
|
||||||
return value
|
|
||||||
else:
|
|
||||||
raise KeyError(name)
|
|
||||||
|
|
||||||
def __contains__(self, name):
|
|
||||||
return name in self.raw
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return "Config(%s)" %(" ".join("%s=%s" %(k, "…" if type(v) == dict else v) for k, v in self.raw.items()))
|
|
15
over/cmd.py
15
over/cmd.py
|
@ -30,13 +30,10 @@ def char_in_str(chars, string):
|
||||||
|
|
||||||
# --------------------------------------------------
|
# --------------------------------------------------
|
||||||
|
|
||||||
def escape_quote_invocation(args):
|
def format_invocation(args):
|
||||||
escaped = (arg.replace('"', '\\"') for arg in args)
|
escaped = (arg.replace('"', '\\"') for arg in args)
|
||||||
|
|
||||||
return [('"%s"' %(a) if char_in_str(' $()[];\\"|', a) else a) for a in escaped]
|
return " ".join(('"%s"' %(a) if char_in_str(' $()[];\\"', a) else a) for a in escaped)
|
||||||
|
|
||||||
def format_invocation(args):
|
|
||||||
return " ".join(escape_quote_invocation(args))
|
|
||||||
|
|
||||||
# --------------------------------------------------
|
# --------------------------------------------------
|
||||||
|
|
||||||
|
@ -64,9 +61,6 @@ class Command:
|
||||||
self.__dict__["sequence_original"] = list(sequence)
|
self.__dict__["sequence_original"] = list(sequence)
|
||||||
self.reset()
|
self.reset()
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return " ".join(self.dump(pretty=True))
|
|
||||||
|
|
||||||
def __setattr__(self, name, value):
|
def __setattr__(self, name, value):
|
||||||
found = False
|
found = False
|
||||||
|
|
||||||
|
@ -100,8 +94,7 @@ class Command:
|
||||||
out.append(str(item))
|
out.append(str(item))
|
||||||
|
|
||||||
if pretty:
|
if pretty:
|
||||||
return escape_quote_invocation(out)
|
return [('"%s"' %(a) if char_in_str(" $()[];\\", a) else a) for a in out]
|
||||||
# return [('"%s"' %(a) if (not a or char_in_str(" $()[];\\", a)) else a) for a in out]
|
|
||||||
else:
|
else:
|
||||||
return out
|
return out
|
||||||
|
|
||||||
|
@ -112,7 +105,7 @@ class Command:
|
||||||
stderr capture stderr instead of stdout (can't do both yet)
|
stderr capture stderr instead of stdout (can't do both yet)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
self.__dict__["process"] = subprocess.Popen(self.dump(), stdout=subprocess.PIPE, stderr=subprocess.PIPE, bufsize=16)
|
self.__dict__["process"] = subprocess.Popen(self.dump(), stdout=subprocess.PIPE, stderr=subprocess.PIPE, bufsize=1)
|
||||||
self.__dict__["fifo"] = queue.Queue()
|
self.__dict__["fifo"] = queue.Queue()
|
||||||
self.__dict__["thread"] = threading.Thread(
|
self.__dict__["thread"] = threading.Thread(
|
||||||
target=capture_output,
|
target=capture_output,
|
||||||
|
|
|
@ -13,7 +13,7 @@ over_docs["Options"] = [
|
||||||
"Boolean options can accept 0 words - then they become flags (switches) and are toggled directly by their names: <W>--<g>enabled<.> evaluates to <M>True<.>, <R>--no-<g>enabled<.> to <M>False<.>.",
|
"Boolean options can accept 0 words - then they become flags (switches) and are toggled directly by their names: <W>--<g>enabled<.> evaluates to <M>True<.>, <R>--no-<g>enabled<.> to <M>False<.>.",
|
||||||
"Other flags (options that accept 0 words) directly trigger callbacks. Those are usually referred to as actions. A good example is <W>--<g>help<.>.",
|
"Other flags (options that accept 0 words) directly trigger callbacks. Those are usually referred to as actions. A good example is <W>--<g>help<.>.",
|
||||||
"Options can be referred to by their abbreviated, single character names, if defined by the application. Abbreviated names are interchangeable with full names. Boolean flags get shortened from <W>--<g>enabled<.> to <W>+<G>E<.>, and <R>--no-<g>enabled<.> to <R>-<G>E<.>.",
|
"Options can be referred to by their abbreviated, single character names, if defined by the application. Abbreviated names are interchangeable with full names. Boolean flags get shortened from <W>--<g>enabled<.> to <W>+<G>E<.>, and <R>--no-<g>enabled<.> to <R>-<G>E<.>.",
|
||||||
"Multiple abbreviated options can be grouped together, with one condition: all except the last (rightmost) option must be flags or actions. For example <R>-<G>Exp<.> <M>primary<.> is the same as <R>--no-<g>enabled<.> <R>--no-<g>execute<.> <W>--<g>pool<.> <M>primary<.>, or <W>+<G>Nv<.> <M>drop<.> expands to <W>--<g>normalize<.> <W>--<g>video<.> <M>drop<.>. Notice how the entire group takes on the boolean value of the leading <W>+<.> or <R>-<.>.",
|
"Multiple abbreviated options can be grouped together, with one condition: all except the last (rightmost) option must be flags or actions. For example <R>-<G>Exp<.> <M>primary<.> is the same as <R>--no-<g>enabled<.> <R>--no-<g>execute<.> <W>--<g>pool<.> <M>primary<.> or <W>+<G>Nv<.> <M>drop<.> expands to <W>--<g>normalize<.> <W>--<g>video<.> <M>drop<.>. Notice how the entire group takes on the boolean value of the leading <W>+<.> or <R>-<.>.",
|
||||||
'If an option is overwriting (<c>over<.>.<c>app<.>.<c>Option<.>.<y>overwrite<.> = <M>True<.>) the latest (rightmost) instance of the same option overwrites all previous ones. Otherwise, all instances are used. For example, <W>+<G>v<.> <M>drop<.> <W>+<G>v<.> <M>copy<.> <W>+<G>v<.> <M>x264<.> evaluates to <M>["x264"]<.> if the option is defined as overwriting, whereas if it was not it would evaluate to <M>["drop", "copy", "x264"]<.>.'
|
'If an option is overwriting (<c>over<.>.<c>app<.>.<c>Option<.>.<y>overwrite<.> = <M>True<.>) the latest (rightmost) instance of the same option overwrites all previous ones. Otherwise, all instances are used. For example, <W>+<G>v<.> <M>drop<.> <W>+<G>v<.> <M>copy<.> <W>+<G>v<.> <M>x264<.> evaluates to <M>["x264"]<.> if the option is defined as overwriting, whereas if it was not it would evaluate to <M>["drop", "copy", "x264"]<.>.'
|
||||||
]
|
]
|
||||||
over_docs["Configuration sources"] = ["Each option can take its state either from (in order) the application default value, the config file, or the command line. Combining multiple sources (e.g. extending values in the config file with the command line) is not permitted - a new source always resets the option's state. It is possible for an option to have no value."]
|
over_docs["Configuration sources"] = ["Each option can take its state either from (in order) the application default value, the config file, or the command line. Combining multiple sources (e.g. extending values in the config file with the command line) is not permitted - a new source always resets the option's state. It is possible for an option to have no value."]
|
||||||
|
@ -22,14 +22,14 @@ over_docs["Config File"] = ["If enabled by the application, a config file will b
|
||||||
colors = []
|
colors = []
|
||||||
|
|
||||||
for code, (_, name) in ansi_colors.items():
|
for code, (_, name) in ansi_colors.items():
|
||||||
c = "<<%s> = <%s>%s<.>" %(code, code, name)
|
c = "<C><<%s><.>: <%s>%s<.>" %(code, code, name)
|
||||||
|
|
||||||
if code == "k":
|
if code == "k":
|
||||||
c += ' (this one says "black")'
|
c += ' (this one says "black")'
|
||||||
|
|
||||||
colors.append(c)
|
colors.append(c)
|
||||||
|
|
||||||
over_docs["Available Colors"] = [", ".join(colors)]
|
over_docs["Available Colors"] = colors
|
||||||
|
|
||||||
config_file_header = """# Configuration file for %s-%s
|
config_file_header = """# Configuration file for %s-%s
|
||||||
# Generated by over-%s
|
# Generated by over-%s
|
||||||
|
|
15
over/file.py
15
over/file.py
|
@ -54,18 +54,3 @@ def context_dir(path):
|
||||||
yield
|
yield
|
||||||
finally:
|
finally:
|
||||||
os.chdir(previous)
|
os.chdir(previous)
|
||||||
|
|
||||||
# --------------------------------------------------
|
|
||||||
|
|
||||||
def filelist(d):
|
|
||||||
"""
|
|
||||||
Returns a filelist of d.
|
|
||||||
"""
|
|
||||||
|
|
||||||
files = []
|
|
||||||
|
|
||||||
for root, directories, filenames in os.walk(d):
|
|
||||||
for filename in filenames:
|
|
||||||
files.append(os.path.join(root, filename))
|
|
||||||
|
|
||||||
return files
|
|
||||||
|
|
50
over/misc.py
50
over/misc.py
|
@ -110,17 +110,16 @@ def debugger():
|
||||||
|
|
||||||
# --------------------------------------------------
|
# --------------------------------------------------
|
||||||
|
|
||||||
def hexdump(data, indent=0, width=16, show_header=True, show_offsets=True, show_ascii=True, use_colors=True, initial_offset=0, output=sys.stdout, skip_same=True):
|
def hexdump(data, indent=0, offset=16, show_header=True, show_offsets=True, show_ascii=True, use_colors=True, initial_offset=0, output=sys.stdout):
|
||||||
"""
|
"""
|
||||||
Writes a hex dump of `data` to `output`.
|
Writes a hex dump of `data` to `output`.
|
||||||
|
|
||||||
The output text is indented with spaces and contains `width` bytes per line.
|
The output text is indented with spaces and contains `offset` bytes per line.
|
||||||
If `show_header` is True, a single line with byte numbers preceeds all output.
|
If `show_header` is True, a single line with byte numbers preceeds all output.
|
||||||
If `show_offsets` is True, each line is prefixed with the address of the first byte of that line.
|
If `show_offsets` is True, each line is prefixed with the address of the first byte of that line.
|
||||||
If `show_ascii` is True, each line is suffixed with its ASCII representation. Unprintable characters
|
If `show_ascii` is True, each line is suffixed with its ASCII representation. Unprintable characters
|
||||||
are replaced with a dot.
|
are replaced with a dot.
|
||||||
The `output` must implement a .write(str x) method. If `output` is None, the string is returned instead.
|
The `output` must implement a .write(str x) method. If `output` is None, the string is returned instead.
|
||||||
If `skip_same` is True, lines with identical content are abbreviated (omitted).
|
|
||||||
|
|
||||||
@while creating a hex dump
|
@while creating a hex dump
|
||||||
"""
|
"""
|
||||||
|
@ -146,22 +145,25 @@ def hexdump(data, indent=0, width=16, show_header=True, show_offsets=True, show_
|
||||||
if show_offsets:
|
if show_offsets:
|
||||||
line.append("offset"[:offset_figures].ljust(offset_figures + 2))
|
line.append("offset"[:offset_figures].ljust(offset_figures + 2))
|
||||||
|
|
||||||
for i in range(width):
|
for i in range(offset):
|
||||||
line.append("%2x" %(i))
|
line.append("%2x" %(i))
|
||||||
|
|
||||||
if show_ascii:
|
if show_ascii:
|
||||||
line.append(" *{0}*".format("ASCII".center(width, "-")))
|
line.append(" *{0}*".format("ASCII".center(offset, "-")))
|
||||||
|
|
||||||
output_io.write(" ".join(line) + "\n")
|
output_io.write(" ".join(line) + "\n")
|
||||||
|
|
||||||
skip_cnt = 0
|
|
||||||
last_hex_bytes = None
|
|
||||||
|
|
||||||
while data[ptr:]:
|
while data[ptr:]:
|
||||||
|
if indent:
|
||||||
|
output_io.write(" " * indent)
|
||||||
|
|
||||||
|
if show_offsets:
|
||||||
|
output_io.write(format_str %(initial_offset + ptr))
|
||||||
|
|
||||||
hex_bytes = []
|
hex_bytes = []
|
||||||
ascii_bytes = []
|
ascii_bytes = []
|
||||||
|
|
||||||
for local_i, i in enumerate(range(ptr, ptr+width)):
|
for local_i, i in enumerate(range(ptr, ptr+offset)):
|
||||||
if i < len(data):
|
if i < len(data):
|
||||||
c = data[i]
|
c = data[i]
|
||||||
hex_bytes.append("%02x" %(c))
|
hex_bytes.append("%02x" %(c))
|
||||||
|
@ -171,30 +173,7 @@ def hexdump(data, indent=0, width=16, show_header=True, show_offsets=True, show_
|
||||||
else:
|
else:
|
||||||
ascii_bytes.append(".")
|
ascii_bytes.append(".")
|
||||||
elif i == len(data):
|
elif i == len(data):
|
||||||
hex_bytes.extend([" "] * (width - local_i))
|
hex_bytes.extend([" "] * (offset - local_i))
|
||||||
|
|
||||||
ptr += width
|
|
||||||
|
|
||||||
# the rest is just rendering
|
|
||||||
if skip_same:
|
|
||||||
if hex_bytes == last_hex_bytes:
|
|
||||||
skip_cnt += 1
|
|
||||||
else:
|
|
||||||
if skip_cnt:
|
|
||||||
output_io.write("%s > skipping %d identical lines, %d B\n" %(" "*offset_figures, skip_cnt, skip_cnt * width))
|
|
||||||
|
|
||||||
skip_cnt = 0
|
|
||||||
|
|
||||||
last_hex_bytes = hex_bytes
|
|
||||||
|
|
||||||
if skip_cnt:
|
|
||||||
continue
|
|
||||||
|
|
||||||
if indent:
|
|
||||||
output_io.write(" " * indent)
|
|
||||||
|
|
||||||
if show_offsets:
|
|
||||||
output_io.write(format_str %(initial_offset + ptr - width))
|
|
||||||
|
|
||||||
if use_colors: output_io.write(text.render("<W>"))
|
if use_colors: output_io.write(text.render("<W>"))
|
||||||
output_io.write(" ".join(hex_bytes))
|
output_io.write(" ".join(hex_bytes))
|
||||||
|
@ -206,9 +185,8 @@ def hexdump(data, indent=0, width=16, show_header=True, show_offsets=True, show_
|
||||||
output_io.write(text.render("<.>|") if use_colors else "|")
|
output_io.write(text.render("<.>|") if use_colors else "|")
|
||||||
|
|
||||||
output_io.write("\n")
|
output_io.write("\n")
|
||||||
|
|
||||||
if skip_cnt:
|
ptr += offset
|
||||||
output_io.write("%s > skipping %d identical lines, %d B\n" %(" "*offset_figures, skip_cnt, skip_cnt * width))
|
|
||||||
|
|
||||||
if not output:
|
if not output:
|
||||||
output_io.seek(0)
|
output_io.seek(0)
|
||||||
|
|
269
over/text.py
269
over/text.py
|
@ -15,16 +15,16 @@ import tzlocal
|
||||||
|
|
||||||
# --------------------------------------------------
|
# --------------------------------------------------
|
||||||
|
|
||||||
def lexical_join(words, oxford=True):
|
def lexical_join(words, oxford=False):
|
||||||
"""
|
"""
|
||||||
Joins an iterable of words or sentence fragments into a lexical list:
|
Joins an iterable of words or sentence fragments into a lexical list:
|
||||||
|
|
||||||
>>> lexical_join(["this", "that", "one of them too"])
|
>>> lexical_join(["this", "that", "one of them too"])
|
||||||
"this, that, and one of them too"
|
|
||||||
|
|
||||||
>>> lexical_join(["this", "that", "one of them too"], oxford=False)
|
|
||||||
"this, that and one of them too"
|
"this, that and one of them too"
|
||||||
|
|
||||||
|
>>> lexical_join(["this", "that", "one of them too"], oxford=True)
|
||||||
|
"this, that, and one of them too"
|
||||||
|
|
||||||
>>> lexical_join(["this", "that"])
|
>>> lexical_join(["this", "that"])
|
||||||
"this and that"
|
"this and that"
|
||||||
|
|
||||||
|
@ -49,80 +49,66 @@ def lexical_join(words, oxford=True):
|
||||||
class Unit:
|
class Unit:
|
||||||
"""
|
"""
|
||||||
A object that represents numbers and units in human-readable form.
|
A object that represents numbers and units in human-readable form.
|
||||||
|
|
||||||
|
TODO use significant digits instead of rigid order boundaries
|
||||||
|
TODO float superclass?
|
||||||
|
TODO base_2 prefixes (Ki, Mi, ...)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
_prefixes_base2 = (
|
_prefixes = (
|
||||||
("Yi", 80), ("Zi", 70), ("Ei", 60), ("Pi", 50), ("Ti", 40), ("Gi", 30), ("Mi", 20), ("Ki", 10),
|
|
||||||
("", 0)
|
|
||||||
)
|
|
||||||
|
|
||||||
_prefixes_base10 = (
|
|
||||||
("Y", 24), ("Z", 21), ("E", 18), ("P", 15), ("T", 12), ("G", 9), ("M", 6), ("k", 3),
|
("Y", 24), ("Z", 21), ("E", 18), ("P", 15), ("T", 12), ("G", 9), ("M", 6), ("k", 3),
|
||||||
("", 0), ("d", -1), ("c", -2), ("m", -3), ("µ", -6), ("n", -9),
|
("h", 2), ("D", 1), ("", 0), ("d", -1), ("c", -2), ("m", -3), ("μ", -6), ("n", -9),
|
||||||
("p", -12), ("f", -15), ("a", -18), ("z", -21), ("y", -24)
|
("p", -12), ("f", -15), ("a", -18), ("z", -21), ("y", -24)
|
||||||
)
|
)
|
||||||
|
|
||||||
def __init__(self, value, unit=None, base=10, dimension=1, format="%.2f pU", overshoot=1.5, only_prefixes=[]):
|
def __init__(self,
|
||||||
|
value, unit=None, dimension=1,
|
||||||
|
use_prefixes="YZEPTGMkmμnpfazy", format="%.2f pU",
|
||||||
|
logarithmic=False, log_base=10
|
||||||
|
):
|
||||||
"""
|
"""
|
||||||
param value value to display [int, float]
|
value the numerical value of the variable (int or float)
|
||||||
param unit base unit (e.g. "m") [str]
|
unit the symbol to use, if any (str or None)
|
||||||
param base see Base [int]
|
dimension the dimensionality of the value (1, 2, 3, ...)
|
||||||
param dimension dimensionality of the value (1, 2, 3, ...) [int]
|
use_prefixes which multiplier prefixes to use
|
||||||
param format format string [str]
|
- the default "YZEPTGMkmμnpfazy" omits "c" for centi- and "D" for deca-
|
||||||
param overshoot how much should a lower prefix persist [float]
|
format use printf notation for value (e.g. %010.5f), p for prefix and U for unit
|
||||||
param restrict_prefixes list of whitelisted prefixes; uses everything if empty [list of str]
|
logarithmic when True, log_10 prefixes are used
|
||||||
|
log_base logarithm base value
|
||||||
|
|
||||||
# Base
|
note that deca- is correctly rendered as "da", the "D" is used in use_prefixes only
|
||||||
With linear units, this is either exactly 2 for Base 2 prefixes (Ki, Mi, Gi, ...)
|
|
||||||
or exactly 10 for Base 10 ones (K, M, G, ... as well as m, µ, n, ...).
|
|
||||||
|
|
||||||
Negative bases force logarithmic behavior with the base equal to the absolute
|
|
||||||
value, so e.g. base=-10 forces log10 decibels.
|
|
||||||
|
|
||||||
# Overshoot behavior
|
|
||||||
Overshoot adjusts the boundary between prefixes, e.g. with overshoot=1.0
|
|
||||||
a Unit switches from k to M when the value exceeds 1000. With overshoot=1.5,
|
|
||||||
this happens at 1500.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
assert (base in [2, 10]) or base < 0
|
|
||||||
|
|
||||||
self.value = float(value)
|
self.value = float(value)
|
||||||
self.unit = unit or ""
|
self.unit = unit if unit else ""
|
||||||
self.base = base
|
|
||||||
self.dimension = dimension
|
self.dimension = dimension
|
||||||
|
self.use_prefixes = use_prefixes
|
||||||
self.format = format
|
self.format = format
|
||||||
self.overshoot = overshoot
|
self.logarithmic = logarithmic
|
||||||
|
self.log_base = log_base
|
||||||
|
|
||||||
all_prefixes = self._prefixes_base2 if base == 2 else self._prefixes_base10
|
if self.logarithmic and (self.value < 0 or self.log_base < 0):
|
||||||
|
|
||||||
if only_prefixes:
|
|
||||||
self.prefixes = [(prefix, exp) for prefix, exp in all_prefixes if prefix in only_prefixes]
|
|
||||||
else:
|
|
||||||
self.prefixes = all_prefixes
|
|
||||||
|
|
||||||
if self.base < 0 and self.value < 0:
|
|
||||||
raise ValueError("math domain error (negative values can't be represented in dB)")
|
raise ValueError("math domain error (negative values can't be represented in dB)")
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
# each branch determines a prefix and an adjusted value
|
if self.value == 0.0:
|
||||||
if self.base > 0:
|
e = 0
|
||||||
for prefix, exp in self.prefixes:
|
else:
|
||||||
if exp == 0: # always prefer 1.0
|
e = round(math.log(abs(self.value), 10), 6)
|
||||||
overshoot = 1
|
|
||||||
else:
|
if self.logarithmic:
|
||||||
overshoot = self.overshoot
|
prefix = "dB"
|
||||||
|
value = math.log(self.value, self.log_base) * 10
|
||||||
if self.base ** (exp * self.dimension) * overshoot <= self.value:
|
|
||||||
|
else:
|
||||||
|
for prefix, mul in self._prefixes:
|
||||||
|
if prefix in self.use_prefixes and mul*self.dimension <= e:
|
||||||
break
|
break
|
||||||
|
|
||||||
value = self.value / self.base ** (exp * self.dimension)
|
value = self.value / 10**(mul*self.dimension)
|
||||||
else:
|
|
||||||
prefix = "dB"
|
|
||||||
value = math.log(self.value, -self.base) * 10
|
|
||||||
|
|
||||||
output = self.format %(value)
|
output = self.format %(value)
|
||||||
output = output.replace("p", prefix)
|
output = output.replace("p", prefix if prefix != "D" else "da") # deca- handler
|
||||||
output = output.replace("U", self.unit)
|
output = output.replace("U", self.unit)
|
||||||
|
|
||||||
return output
|
return output
|
||||||
|
@ -297,21 +283,35 @@ def count_leading(text, char):
|
||||||
|
|
||||||
# --------------------------------------------------
|
# --------------------------------------------------
|
||||||
|
|
||||||
class Log:
|
class Output:
|
||||||
class tag:
|
class tag:
|
||||||
info = "INFO"
|
class short:
|
||||||
debug = "<b>DEBG<.>"
|
info = " "
|
||||||
begin = "<W>BEGN<.>"
|
debug = " <c>?<.>"
|
||||||
note = "<C>NOTE<.>"
|
start = "<W>>>><.>"
|
||||||
warn = "<Y>WARN<.>"
|
exec = start
|
||||||
fail = "<R>FAIL<.>"
|
note = " <C>#<.>"
|
||||||
epic = "<R>EPIC<.>"
|
warn = " <Y>!<.>"
|
||||||
done = "<G>DONE<.>"
|
fail = "<R>!!!<.>"
|
||||||
|
done = " <G>*<.>"
|
||||||
|
class long:
|
||||||
|
info = "INFO"
|
||||||
|
debug = "<c>DEBG<.>"
|
||||||
|
start = "<W>EXEC<.>"
|
||||||
|
exec = start
|
||||||
|
note = "<C>NOTE<.>"
|
||||||
|
warn = "<Y>WARN<.>"
|
||||||
|
fail = "<R>FAIL<.>"
|
||||||
|
epic = "<R>EPIC<.>"
|
||||||
|
done = "<G>DONE<.>"
|
||||||
|
|
||||||
|
ts = tag.short
|
||||||
|
tl = tag.long
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Text UI output renderer.
|
Text UI output renderer.
|
||||||
|
|
||||||
Prints messages to stderr with optional eye candy
|
Prints messages to the stdout with optional eye candy
|
||||||
like colors and timestamps.
|
like colors and timestamps.
|
||||||
|
|
||||||
If the output stream is not a tty, colors are disabled.
|
If the output stream is not a tty, colors are disabled.
|
||||||
|
@ -319,13 +319,13 @@ class Log:
|
||||||
Formatting
|
Formatting
|
||||||
==========
|
==========
|
||||||
You can use all formatting characters supported by strftime, as well as:
|
You can use all formatting characters supported by strftime, as well as:
|
||||||
<@> - tag
|
<T> - tag
|
||||||
<n> - name
|
<n> - name
|
||||||
<t> - supplied text
|
<t> - supplied text
|
||||||
<i> - indentation start marker
|
<i> - indentation start marker
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, name, format="[%Y-%m-%d %H:%M:%S %Z] <@> -- <n>, <i><t>", colors=True, end="\n", indent=True, stream=sys.stderr):
|
def __init__(self, name, format="[%Y-%m-%d %H:%M:%S %Z] <@> -- <n>, <i><t>", colors=True, end=".\n", indent=True, stream=sys.stderr):
|
||||||
self.name = name
|
self.name = name
|
||||||
self.format = format
|
self.format = format
|
||||||
self.end = end
|
self.end = end
|
||||||
|
@ -337,12 +337,12 @@ class Log:
|
||||||
def add_sink(self, stream, colors):
|
def add_sink(self, stream, colors):
|
||||||
self.sinks.append((stream, colors))
|
self.sinks.append((stream, colors))
|
||||||
|
|
||||||
def write(self, fmt, *args, tag=None, format=None, end=None):
|
def write(self, text, tag=None, format=None, end=None):
|
||||||
"""
|
"""
|
||||||
@while displaying text
|
@while displaying text
|
||||||
"""
|
"""
|
||||||
|
|
||||||
tag = tag or self.__class__.tag.info
|
tag = tag or self.__class__.tag.long.info
|
||||||
format = format or self.format
|
format = format or self.format
|
||||||
end = end or self.end
|
end = end or self.end
|
||||||
|
|
||||||
|
@ -350,8 +350,6 @@ class Log:
|
||||||
output = output.replace("<@>", tag)
|
output = output.replace("<@>", tag)
|
||||||
output = output.replace("<n>", self.name)
|
output = output.replace("<n>", self.name)
|
||||||
|
|
||||||
text = fmt % args
|
|
||||||
|
|
||||||
if self.indent and "<i>" in format:
|
if self.indent and "<i>" in format:
|
||||||
indent_width = render(output, colors=False).index("<i>")
|
indent_width = render(output, colors=False).index("<i>")
|
||||||
text = paragraph(text, indent=indent_width)[indent_width:]
|
text = paragraph(text, indent=indent_width)[indent_width:]
|
||||||
|
@ -364,29 +362,15 @@ class Log:
|
||||||
sink.write(render(output, colors))
|
sink.write(render(output, colors))
|
||||||
sink.flush()
|
sink.flush()
|
||||||
|
|
||||||
def info(self, *args, **kwargs):
|
def flush(self):
|
||||||
|
"""
|
||||||
|
Dummy method for compatibility.
|
||||||
|
"""
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
||||||
|
def __call__(self, *args, **kwargs):
|
||||||
self.write(*args, **kwargs)
|
self.write(*args, **kwargs)
|
||||||
|
|
||||||
def debug(self, *args, **kwargs):
|
|
||||||
self.write(*args, tag=self.tag.debug, **kwargs)
|
|
||||||
|
|
||||||
def begin(self, *args, **kwargs):
|
|
||||||
self.write(*args, tag=self.tag.begin, **kwargs)
|
|
||||||
|
|
||||||
def note(self, *args, **kwargs):
|
|
||||||
self.write(*args, tag=self.tag.note, **kwargs)
|
|
||||||
|
|
||||||
def warn(self, *args, **kwargs):
|
|
||||||
self.write(*args, tag=self.tag.warn, **kwargs)
|
|
||||||
|
|
||||||
def fail(self, *args, **kwargs):
|
|
||||||
self.write(*args, tag=self.tag.fail, **kwargs)
|
|
||||||
|
|
||||||
def epic(self, *args, **kwargs):
|
|
||||||
self.write(*args, tag=self.tag.epic, **kwargs)
|
|
||||||
|
|
||||||
def done(self, *args, **kwargs):
|
|
||||||
self.write(*args, tag=self.tag.done, **kwargs)
|
|
||||||
|
|
||||||
# --------------------------------------------------
|
# --------------------------------------------------
|
||||||
|
|
||||||
|
@ -407,14 +391,15 @@ def get_terminal_size():
|
||||||
# --------------------------------------------------
|
# --------------------------------------------------
|
||||||
|
|
||||||
class _ProgressBarChannel:
|
class _ProgressBarChannel:
|
||||||
def __init__(self, unit, top, precision, use_prefixes=True, base=10, only_prefixes=[], min_width_raw=0, min_width_rate=0, min_width_time=0, min_width_percent=7):
|
print = Output("over.text._ProgressBarChannel")
|
||||||
|
|
||||||
|
def __init__(self, unit, top, precision, use_prefixes=True, prefix_base2=False, min_width_raw=0, min_width_rate=0, min_width_time=0, min_width_percent=7):
|
||||||
self.unit = unit
|
self.unit = unit
|
||||||
self.top = top
|
self.top = top
|
||||||
self.dynamic_top = top is None
|
self.dynamic_top = top is None
|
||||||
self.base = base
|
self.prefix_base2 = prefix_base2
|
||||||
self.precision = precision
|
self.precision = precision
|
||||||
self.use_prefixes = use_prefixes
|
self.use_prefixes = use_prefixes
|
||||||
self.only_prefixes = only_prefixes
|
|
||||||
self.min_width = {
|
self.min_width = {
|
||||||
"raw": min_width_raw,
|
"raw": min_width_raw,
|
||||||
"rate": min_width_rate,
|
"rate": min_width_rate,
|
||||||
|
@ -424,6 +409,9 @@ class _ProgressBarChannel:
|
||||||
|
|
||||||
self._value = 0
|
self._value = 0
|
||||||
self.set()
|
self.set()
|
||||||
|
|
||||||
|
if prefix_base2:
|
||||||
|
self.print("Unit does not yet support base2 prefixes (e.g. Gi, Mi), using decadic (G, M) instead", self.print.tl.warn)
|
||||||
|
|
||||||
def set(self, value=None):
|
def set(self, value=None):
|
||||||
if value is not None:
|
if value is not None:
|
||||||
|
@ -435,13 +423,10 @@ class _ProgressBarChannel:
|
||||||
if unit is None:
|
if unit is None:
|
||||||
unit = self.unit
|
unit = self.unit
|
||||||
|
|
||||||
u = Unit(
|
u = Unit(value, unit, format="%.{:d}f pU".format(self.precision))
|
||||||
value,
|
|
||||||
unit,
|
if not self.use_prefixes or just == "percent":
|
||||||
format="%.{:d}f pU".format(self.precision),
|
u._prefixes = (("", 0),) # Unit needs fixin"
|
||||||
base=self.base,
|
|
||||||
only_prefixes=[""] if (not self.use_prefixes or just == "percent") else self.only_prefixes
|
|
||||||
)
|
|
||||||
|
|
||||||
s = str(u)
|
s = str(u)
|
||||||
|
|
||||||
|
@ -505,18 +490,20 @@ class ProgressBar:
|
||||||
width is the desired size of the progressbar drawing area in characters (columns)
|
width is the desired size of the progressbar drawing area in characters (columns)
|
||||||
and defaults to terminal width.
|
and defaults to terminal width.
|
||||||
|
|
||||||
|
space_before_unit enables a space character between a value and its unit.
|
||||||
|
|
||||||
channels is a dict that for each channel lists its properties:
|
channels is a dict that for each channel lists its properties:
|
||||||
|
|
||||||
{
|
{
|
||||||
"a": {
|
"a": {
|
||||||
"unit": "f",
|
"unit": "f",
|
||||||
|
"prefix_base2": False,
|
||||||
"top": 39600,
|
"top": 39600,
|
||||||
"precision": 1
|
"precision": 1
|
||||||
},
|
},
|
||||||
"b": {
|
"b": {
|
||||||
"unit": "B",
|
"unit": "o",
|
||||||
"base": 2,
|
"prefix_base2": True,
|
||||||
"only_prefixes": ["Ki", "Mi"],
|
|
||||||
"top": measure_file_size_closure("/path/to/file"),
|
"top": measure_file_size_closure("/path/to/file"),
|
||||||
"precision": 0
|
"precision": 0
|
||||||
}
|
}
|
||||||
|
@ -524,7 +511,7 @@ class ProgressBar:
|
||||||
|
|
||||||
Channel IDs (the "a", "b") are arbitrary lowercase letters ([a-z]).
|
Channel IDs (the "a", "b") are arbitrary lowercase letters ([a-z]).
|
||||||
|
|
||||||
Properties "unit", "base", and "only_prefixes" are passed to over.text.Unit.
|
Properties "unit" and "prefix_base2" are passed to over.core.text.Unit.
|
||||||
"top" is the value of the channel that corresponds to 100%. Channels are
|
"top" is the value of the channel that corresponds to 100%. Channels are
|
||||||
allowed to exceed this value. It can either be a number or a callable. If
|
allowed to exceed this value. It can either be a number or a callable. If
|
||||||
it"s a callable, it will be called without arguments and shall return a number.
|
it"s a callable, it will be called without arguments and shall return a number.
|
||||||
|
@ -671,64 +658,28 @@ class ProgressBar:
|
||||||
|
|
||||||
# --------------------------------------------------
|
# --------------------------------------------------
|
||||||
|
|
||||||
def timestamp_to_hms(t):
|
|
||||||
"""
|
|
||||||
Converts a duration in seconds to a [[?h ]?m ]?s representation.
|
|
||||||
|
|
||||||
>>> timestamp_to_hms(12345)
|
|
||||||
'3h 25m 45s'
|
|
||||||
|
|
||||||
>>> timestamp_to_hms(3600)
|
|
||||||
'1h 0m 0s'
|
|
||||||
|
|
||||||
>>> timestamp_to_hms(1234)
|
|
||||||
'20m 34s'
|
|
||||||
|
|
||||||
>>> timestamp_to_hms(12)
|
|
||||||
'12s'
|
|
||||||
"""
|
|
||||||
|
|
||||||
hh = t // 3600
|
|
||||||
mm = (t % 3600) // 60
|
|
||||||
ss = t % 60
|
|
||||||
|
|
||||||
out = []
|
|
||||||
|
|
||||||
if hh:
|
|
||||||
out.append("%dh" %(hh))
|
|
||||||
|
|
||||||
if mm or hh:
|
|
||||||
out.append("%dm" %(mm))
|
|
||||||
|
|
||||||
out.append("%ds" %(ss))
|
|
||||||
|
|
||||||
return " ".join(out)
|
|
||||||
|
|
||||||
# --------------------------------------------------
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
log = Log("over.text")
|
o = Output("over.text")
|
||||||
log.info("Sample info message")
|
o("Sample info message")
|
||||||
log.debug("Sample debug message")
|
o("Sample debug message", o.tl.debug)
|
||||||
log.begin("Sample action start message")
|
o("Sample action start message", o.tl.start)
|
||||||
log.done("Sample action success message")
|
o("Sample action success message", o.tl.done)
|
||||||
log.warn("Sample warning message")
|
o("Sample warning message", o.tl.warn)
|
||||||
log.fail("Sample error message")
|
o("Sample error message", o.tl.fail)
|
||||||
log.note("Sample note message")
|
|
||||||
|
|
||||||
log.write("Available colors", end=":\n")
|
o("Available colors", end=":\n")
|
||||||
|
|
||||||
for abbr, (code, name) in sorted(ansi_colors.items()):
|
for abbr, (code, name) in sorted(ansi_colors.items()):
|
||||||
log.write("%s = <%s>%s<.>", abbr, abbr, name)
|
o("%s = <%s>%s<.>" %(abbr, abbr, name))
|
||||||
|
|
||||||
log.begin("ProgressBar test")
|
o("ProgressBar test")
|
||||||
|
|
||||||
pb = ProgressBar(
|
pb = ProgressBar(
|
||||||
"§%a (§ra/§za) [§=a>§ A] §sa [§rb/§zb] (ETA §TA)",
|
"§%a (§ra/§za) [§=a>§ A] §sa [§rb/§zb] (ETA §TA)",
|
||||||
{
|
{
|
||||||
"a": {
|
"a": {
|
||||||
"unit": "f",
|
"unit": "f",
|
||||||
"base": 10,
|
"prefix_base2": False,
|
||||||
"top": 39600,
|
"top": 39600,
|
||||||
"precision": 1,
|
"precision": 1,
|
||||||
"min_width_raw": 10,
|
"min_width_raw": 10,
|
||||||
|
@ -737,8 +688,8 @@ if __name__ == "__main__":
|
||||||
"min_width_time": 0
|
"min_width_time": 0
|
||||||
},
|
},
|
||||||
"b": {
|
"b": {
|
||||||
"unit": "B",
|
"unit": "o",
|
||||||
"base": 2,
|
"prefix_base2": True,
|
||||||
"top": 3200,
|
"top": 3200,
|
||||||
"precision": 0,
|
"precision": 0,
|
||||||
"min_width_raw": 10,
|
"min_width_raw": 10,
|
||||||
|
@ -756,4 +707,4 @@ if __name__ == "__main__":
|
||||||
pb.render()
|
pb.render()
|
||||||
time.sleep(0.25)
|
time.sleep(0.25)
|
||||||
|
|
||||||
pb.end(True)
|
pb.end(not True)
|
||||||
|
|
|
@ -20,15 +20,13 @@ class ndict:
|
||||||
{"alpha": 1, "beta": 42}
|
{"alpha": 1, "beta": 42}
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__methods__ = ["values", "items", "keys"]
|
__methods__ = ["values", "items"]
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
object.__setattr__(self, "d", OrderedDict(*args, **kwargs))
|
object.__setattr__(self, "d", OrderedDict(*args, **kwargs))
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
atoms = ["(%s, %s)" %(repr(k), repr(v)) for k, v in self.items()]
|
return "|" + repr(self.d)
|
||||||
|
|
||||||
return "ndict([" + ", ".join(atoms) + "])"
|
|
||||||
|
|
||||||
def __iter__(self):
|
def __iter__(self):
|
||||||
return self.d.__iter__()
|
return self.d.__iter__()
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
#! /usr/bin/env python3
|
#! /usr/bin/env python3
|
||||||
# encoding: utf-8
|
# encoding: utf-8
|
||||||
|
|
||||||
major = 3 # VERSION_MAJOR_IDENTIFIER
|
major = 2 # VERSION_MAJOR_IDENTIFIER
|
||||||
minor = 0 # VERSION_MINOR_IDENTIFIER
|
minor = 0 # VERSION_MINOR_IDENTIFIER
|
||||||
# VERSION_LAST_MM 3.0
|
# VERSION_LAST_MM 2.0
|
||||||
patch = 0 # VERSION_PATCH_IDENTIFIER
|
patch = 0 # VERSION_PATCH_IDENTIFIER
|
||||||
str = "3.0.0" # VERSION_STRING_IDENTIFIER
|
str = "2.0.0" # VERSION_STRING_IDENTIFIER
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue