major reorganization of file structure
This commit is contained in:
parent
65b0233a9d
commit
04b75a400c
33 changed files with 112 additions and 503 deletions
3
Changes
Normal file
3
Changes
Normal file
|
@ -0,0 +1,3 @@
|
|||
replaced text.ProgressBar with text.ProgressBar2
|
||||
simplified text.paragraph
|
||||
color tags changed from §g...§/ to g$...$
|
12
__init__.py
12
__init__.py
|
@ -1,12 +0,0 @@
|
|||
#! /usr/bin/env python3
|
||||
# encoding: utf-8
|
||||
|
||||
import time
|
||||
import sys
|
||||
|
||||
#from . import ag
|
||||
from . import core
|
||||
from . import aux
|
||||
#from . import m
|
||||
#from . import serial
|
||||
|
|
@ -1,160 +0,0 @@
|
|||
from collections import OrderedDict
|
||||
|
||||
cdef class Aggregate:
|
||||
def __init__(Aggregate self, *modules):
|
||||
self._attrs = {} # attr_name, module
|
||||
self._modules = OrderedDict() # module_name, module
|
||||
self._provided = {} # keyword, module
|
||||
self._common = {} # method_name, method_wrapper
|
||||
|
||||
for module in modules:
|
||||
self._link_module(module)
|
||||
|
||||
def _list_modules(Aggregate self):
|
||||
d = {}
|
||||
|
||||
for attr, module in self._attrs.items():
|
||||
if module not in d:
|
||||
d[module] = set()
|
||||
|
||||
d[module].add(attr)
|
||||
|
||||
return d
|
||||
|
||||
def _link_module(Aggregate self, Module module):
|
||||
# check for name (=type) collision
|
||||
if module._name in self._modules.keys():
|
||||
raise ModuleCollision(module._name, None, 'name', None)
|
||||
|
||||
# check if requirements are satisfied
|
||||
unsatisfied_deps = module._requires - set(self._provided.keys())
|
||||
if unsatisfied_deps:
|
||||
raise ModuleDependencyError(module._name, unsatisfied_deps)
|
||||
|
||||
# check for new module declaring a common method that we already provide as non-common
|
||||
new_commons = module._common - set(self._common.keys())
|
||||
common_collisions = {nc for nc in new_commons if nc in self._attrs and not nc in self._common.keys()}
|
||||
if common_collisions:
|
||||
colliding_module_names = {self._attrs[x]._name for x in common_collisions}
|
||||
raise ModuleCollision(module._name, colliding_module_names, 'non-common method', common_collisions)
|
||||
|
||||
# check for an attr collision
|
||||
module_attrs = {x for x in dir(module) if x[0] != '_'}
|
||||
attr_collisions = (module_attrs - module._common) & (set(self._attrs.keys()) | set(self._common.keys()))
|
||||
if attr_collisions:
|
||||
colliding_module_names = set()
|
||||
for collision in attr_collisions:
|
||||
if collision in self._attrs:
|
||||
colliding_module_names.add(self._attrs[collision]._name)
|
||||
|
||||
if collision in self._common:
|
||||
colliding_module_names.add(self._common[collision]._name)
|
||||
|
||||
raise ModuleCollision(module._name, colliding_module_names, 'attribute', attr_collisions)
|
||||
|
||||
# check for a provided keyword collision
|
||||
provided_collisions = module._provides & set(self._provided.keys())
|
||||
if provided_collisions:
|
||||
colliding_module_names = {self._provided[x]._name for x in provided_collisions}
|
||||
raise ModuleCollision(module._name, colliding_module_names, 'provided keyword', provided_collisions)
|
||||
|
||||
# link the module
|
||||
self._modules[module._name] = module
|
||||
|
||||
for keyword in module._provides:
|
||||
self._provided[keyword] = module
|
||||
|
||||
for attr in (module_attrs - module._common):
|
||||
self._attrs[attr] = module
|
||||
|
||||
# create and/or populate CommonMethod wrappers to common methods
|
||||
for method_name in module._common:
|
||||
if method_name not in module_attrs:
|
||||
raise CommonMethodMissing(method_name, module._name)
|
||||
|
||||
if method_name not in self._common:
|
||||
self._common[method_name] = CommonMethod(method_name)
|
||||
|
||||
self._common[method_name].link_module(module)
|
||||
|
||||
# hand the module a reference to us
|
||||
module._top = self
|
||||
|
||||
# call the module's _on_link method, if it has one
|
||||
if hasattr(module, '_on_link'):
|
||||
module._on_link()
|
||||
|
||||
def _unlink_module(Aggregate self, str module_name):
|
||||
if not module_name in self._modules:
|
||||
raise ModuleDoesntExist(module_name)
|
||||
|
||||
module = self._modules[module_name]
|
||||
|
||||
# check reverse dependencies
|
||||
global_deps = set()
|
||||
for m in self._modules.values():
|
||||
global_deps.update(m._requires)
|
||||
|
||||
reverse_deps = module._provides & global_deps
|
||||
if reverse_deps:
|
||||
raise ModuleDependencyError(module_name, reverse_deps, unlink=True)
|
||||
|
||||
# remove from all pools
|
||||
for aname, mod in list(self._attrs.items()):
|
||||
if mod._name == module_name:
|
||||
del self._attrs[aname]
|
||||
|
||||
del self._modules[module_name]
|
||||
|
||||
for ename in module._provides:
|
||||
del self._provided[ename]
|
||||
|
||||
# remove _common wrappers
|
||||
for method_name in module._common:
|
||||
self._common[method_name].unlink_module(module_name)
|
||||
|
||||
# clear _top reference
|
||||
module._top = None
|
||||
|
||||
def _merge_in(Aggregate self, Aggregate other_ag):
|
||||
for module_name, module in other_ag._modules.items():
|
||||
if module_name not in self._modules:
|
||||
self._link_module(module)
|
||||
|
||||
def __getattr__(Aggregate self, str aname):
|
||||
if aname in self._attrs:
|
||||
return getattr(self._attrs[aname], aname)
|
||||
elif aname in self._common:
|
||||
return self._common[aname]
|
||||
else:
|
||||
raise AttributeError("Aggregate has no attribute '%s'" %(aname))
|
||||
|
||||
def __setattr__(Aggregate self, str aname, avalue):
|
||||
if aname not in self._attrs:
|
||||
raise AttributeError("Aggregate has no attribute '%s'" %(aname))
|
||||
else:
|
||||
setattr(self._attrs[aname], aname, avalue)
|
||||
|
||||
def _get_type(Aggregate self):
|
||||
return tuple(self._modules.keys())
|
||||
|
||||
def __repr__(Aggregate self):
|
||||
module_count = len(self._modules)
|
||||
|
||||
if module_count:
|
||||
lines = ['Aggregate(']
|
||||
|
||||
|
||||
for i, module in enumerate(self._modules.values()):
|
||||
if i + 1 < module_count:
|
||||
comma = ','
|
||||
else:
|
||||
comma = ''
|
||||
|
||||
lines.append(' %s%s' %(repr(module), comma))
|
||||
|
||||
lines.append(')')
|
||||
|
||||
return '\n'.join(lines)
|
||||
else:
|
||||
return 'Aggregate()'
|
|
@ -1,30 +0,0 @@
|
|||
cdef class CommonMethod:
|
||||
def __init__(CommonMethod self, str method_name):
|
||||
self.method_name = method_name
|
||||
self.module_names = []
|
||||
self.modules = []
|
||||
|
||||
def link_module(CommonMethod self, Module module):
|
||||
self.module_names.append(module._name)
|
||||
self.modules.append(module)
|
||||
|
||||
def unlink_module(CommonMethod self, module_name):
|
||||
i = self.module_names.index(module_name)
|
||||
|
||||
del self.module_names[i]
|
||||
del self.modules[i]
|
||||
|
||||
def __call__(CommonMethod self, *args):
|
||||
return_values = []
|
||||
|
||||
for module in self.modules:
|
||||
return_values.append(getattr(module, self.method_name).__call__(*args))
|
||||
|
||||
return return_values
|
||||
|
||||
def __repr__(self):
|
||||
return 'CommonMethod(name=%s)' %(self.method_name)
|
||||
|
||||
@property
|
||||
def _name(self):
|
||||
return repr(self)
|
|
@ -1,20 +0,0 @@
|
|||
cdef class Module:
|
||||
def __init__(self, name, provides=set(), requires=set(), common=set()):
|
||||
'''
|
||||
* Modules are identified by their name which has to be unique within the Aggregate's namespace.
|
||||
* The provides sequence contains keywords which are copied into Aggregate's _provided list.
|
||||
Two modules providing the same export can't be linked into the same Aggregate.
|
||||
* The requires sequence contains keywords that have to be already present in the Aggregate's
|
||||
_provided list before linking.
|
||||
* All names in the Module's namespace that don't begin with an underscore will be exported
|
||||
into the Aggregate's namespace.
|
||||
* If you want to call one method on multiple modules, these modules must all export the method name
|
||||
in their 'common' set. Otherwise a name collision is raised. Their methods will be called in the
|
||||
same order in which the modules were linked.
|
||||
'''
|
||||
|
||||
self._top = None
|
||||
self._name = name
|
||||
self._provides = set(provides)
|
||||
self._requires = set(requires)
|
||||
self._common = set(common)
|
|
@ -1,49 +0,0 @@
|
|||
'''
|
||||
Overwatch Aggregate Object type
|
||||
|
||||
TODO Method overrides: _common gets renamed to _call_all and we add _call_last.
|
||||
'''
|
||||
|
||||
class ModuleCollision(Exception):
|
||||
def __init__(self, colliding_module_name, resident_module_names, item_type, items):
|
||||
self.colliding_module_name = colliding_module_name
|
||||
self.resident_module_names = resident_module_names
|
||||
self.item_type = item_type
|
||||
self.items = items
|
||||
|
||||
def __str__(self):
|
||||
if self.item_type == 'name':
|
||||
return "Unable to link module '%s': name already present in Aggregate." %(self.colliding_module_name)
|
||||
else:
|
||||
return "Unable to link module '%s': %s(s) '%s' already provided by module(s) '%s'." %(self.colliding_module_name,
|
||||
self.item_type, "', '".join(self.items), "', '".join(self.resident_module_names))
|
||||
|
||||
class ModuleDependencyError(Exception):
|
||||
def __init__(self, module_name, dependencies, unlink=False):
|
||||
self.module_name = module_name
|
||||
self.dependencies = dependencies
|
||||
self.unlink = unlink
|
||||
|
||||
def __str__(self):
|
||||
if self.unlink:
|
||||
return "Unable to unlink module '%s': Aggregate depends on its export(s) '%s'." %(self.module_name,
|
||||
"', '".join(self.dependencies))
|
||||
else:
|
||||
return "Unable to link module '%s': requirement '%s' unsatisfied by Aggregate." %(self.module_name,
|
||||
"', '".join(self.dependencies))
|
||||
|
||||
class CommonMethodMissing(Exception):
|
||||
def __init__(self, method_name, module_name):
|
||||
self.method_name = method_name
|
||||
self.module_name = module_name
|
||||
|
||||
def __str__(self):
|
||||
return "Unable to link module '%s': method '%s' (declared as common) does not exist in module." %(
|
||||
self.module_name, self.method_name)
|
||||
|
||||
class ModuleDoesntExist(Exception):
|
||||
def __init__(self, module_name):
|
||||
self.module_name = module_name
|
||||
|
||||
def __str__(self):
|
||||
return "Unable to unlink module '%s': not linked to Aggregate." %(self.module_name)
|
|
@ -1,4 +0,0 @@
|
|||
#! /bin/zsh
|
||||
# encoding: utf-8
|
||||
|
||||
echo "- nothing to do"
|
8
aux.py
8
aux.py
|
@ -1,8 +0,0 @@
|
|||
#! /usr/bin/env python3
|
||||
# encoding: utf-8
|
||||
|
||||
import sys
|
||||
|
||||
from . import core
|
||||
|
||||
_print = core.text.Output('over', stream=sys.stderr)
|
10
build-all.sh
10
build-all.sh
|
@ -1,10 +0,0 @@
|
|||
#! /bin/zsh
|
||||
# encoding: utf-8
|
||||
|
||||
for dir in ag core m serial
|
||||
do
|
||||
echo "Building Cython implementations in ${dir}:"
|
||||
cd "$dir"
|
||||
./build.sh
|
||||
cd ..
|
||||
done
|
|
@ -1,22 +0,0 @@
|
|||
#! /bin/zsh
|
||||
# encoding: utf-8
|
||||
|
||||
setopt extendedglob
|
||||
|
||||
function die() {
|
||||
echo "\n\n>>> Failed during ${1}, aborting."
|
||||
exit 1
|
||||
}
|
||||
|
||||
CFLAGS=(-Wall -pedantic -std=c99 -fPIC)
|
||||
LFLAGS=(-shared)
|
||||
|
||||
echo "- translating from Python to C"
|
||||
cython -f -3 --fast-fail -X embedsignature=True cython_types.pyx -o cython_types.c || die "translating"
|
||||
|
||||
echo "- compiling and linking"
|
||||
gcc $CFLAGS -I/usr/include/python3.5m -pthread -c cython_types.c || die "compilation"
|
||||
gcc $LFLAGS -L/usr/lib -lpython3.5m cython_types.o -o cython_types.so || die "linking"
|
||||
rm -f cython_types.{c,o}
|
||||
|
||||
echo "- done"
|
|
@ -1,4 +0,0 @@
|
|||
#! /bin/zsh
|
||||
# encoding: utf-8
|
||||
|
||||
echo "- nothing to do"
|
|
@ -1,12 +1,9 @@
|
|||
#! /usr/bin/env python3
|
||||
# encoding: utf-8
|
||||
|
||||
from . import m
|
||||
from . import app
|
||||
import sys
|
||||
|
||||
from . import aux
|
||||
from . import cmd
|
||||
from . import file
|
||||
from . import misc
|
||||
from . import text
|
||||
|
||||
try:
|
||||
|
@ -15,4 +12,7 @@ except:
|
|||
aux._print('unable to load C implementation, using python instead', text.prefix.warn)
|
||||
from . import python_types as types
|
||||
|
||||
textui = aux.DeprecationForwarder(text, 'over.core.textui', 'over.core.text')
|
||||
core = aux.DeprecationForwarder(sys.modules[__name__], 'over.core', 'over')
|
||||
textui = aux.DeprecationForwarder(text, 'over.core.textui', 'over.text')
|
||||
|
||||
del sys
|
|
@ -12,13 +12,13 @@ except:
|
|||
|
||||
from . import file
|
||||
from . import text
|
||||
from ..version import _version
|
||||
from . import version
|
||||
|
||||
prefix = text.prefix
|
||||
|
||||
# FIXME This very seriously needs to be heavily simplified and de-duplicated
|
||||
# TODO same parser for cmdline and Config
|
||||
# TODO zsh and fish integration
|
||||
# TODO zsh integration
|
||||
|
||||
# --------------------------------------------------
|
||||
|
|
@ -3,10 +3,10 @@
|
|||
|
||||
# library imports
|
||||
import over
|
||||
prefix = over.core.text.prefix
|
||||
prefix = over.text.prefix
|
||||
|
||||
# local imports
|
||||
#import version
|
||||
import version
|
||||
|
||||
# --------------------------------------------------
|
||||
# Exceptions
|
||||
|
@ -23,9 +23,9 @@ class ConfigurationError(Exception):
|
|||
# --------------------------------------------------
|
||||
|
||||
if __name__ == "__main__":
|
||||
main = over.core.app.Main("short-name", "Human Readable Name", version.str, "LICENSE", use_cfg_file=False)
|
||||
main = over.app.Main("short-name", "Human Readable Name", version.str, "LICENSE", use_cfg_file=False)
|
||||
main.add_option("option", "type", "default", "Description.", short_name="s")
|
||||
main.add_help("Description", ["What it does."])
|
||||
main.add_help("Description", ["What it does.", "Another paragraph."])
|
||||
main.enable_help("h")
|
||||
main.parse()
|
||||
|
|
@ -12,32 +12,32 @@ import time
|
|||
# --------------------------------------------------
|
||||
|
||||
def lexical_join(words, oxford=False):
|
||||
'''
|
||||
"""
|
||||
Joins an iterable of words or sentence fragments into a lexical list:
|
||||
|
||||
>>> lexical_join(['this', 'that', 'one of them too'])
|
||||
'this, that and 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=True)
|
||||
'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'])
|
||||
'this and that'
|
||||
>>> lexical_join(["this", "that"])
|
||||
"this and that"
|
||||
|
||||
>>> lexical_join(['this'])
|
||||
'this'
|
||||
'''
|
||||
>>> lexical_join(["this"])
|
||||
"this"
|
||||
"""
|
||||
|
||||
l = len(words)
|
||||
|
||||
if l == 0:
|
||||
return ''
|
||||
return ""
|
||||
elif l == 1:
|
||||
return words[0]
|
||||
elif l == 2:
|
||||
return '%s and %s' %(str(words[0]), str(words[1]))
|
||||
return "%s and %s" %(str(words[0]), str(words[1]))
|
||||
else:
|
||||
return '%s%s and %s' %(', '.join(str(w) for w in words[:-1]), ',' if oxford else '', str(words[-1]))
|
||||
return "%s%s and %s" %(", ".join(str(w) for w in words[:-1]), "," if oxford else "", str(words[-1]))
|
||||
|
||||
# --------------------------------------------------
|
||||
|
||||
|
@ -75,7 +75,7 @@ class _ProgressBarChannel:
|
|||
u = Unit(value, unit, format="%.{:d}f pU".format(self.precision))
|
||||
|
||||
if not self.use_prefixes or just == "percent":
|
||||
u._prefixes = (('', 0),) # Unit needs fixin'
|
||||
u._prefixes = (("", 0),) # Unit needs fixin"
|
||||
|
||||
s = str(u)
|
||||
|
||||
|
@ -118,8 +118,8 @@ class ProgressBar2:
|
|||
any other character - a bar consisting of these characters filling the line so that
|
||||
at 100% it fills the entirety of the remaining space
|
||||
|
||||
A channel ID as a lowercase letter is the channel's actual value. An uppercase letter
|
||||
is that channel's complement. Operations z, s, m and h cannot have a complement.
|
||||
A channel ID as a lowercase letter is the channel"s actual value. An uppercase letter
|
||||
is that channel"s complement. Operations z, s, m and h cannot have a complement.
|
||||
|
||||
Use §§ to display a literal §.
|
||||
|
||||
|
@ -163,7 +163,7 @@ class ProgressBar2:
|
|||
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
|
||||
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.
|
||||
"precision" is the displayed floating point precision. Use 0 to force an integer.
|
||||
"""
|
||||
|
||||
|
@ -174,7 +174,7 @@ class ProgressBar2:
|
|||
|
||||
def set(self, channel_id, value):
|
||||
"""
|
||||
Sets the channel's value.
|
||||
Sets the channel"s value.
|
||||
"""
|
||||
|
||||
c = self.channels[channel_id]
|
||||
|
@ -301,120 +301,43 @@ class ProgressBar2:
|
|||
|
||||
return output.rjust(just)
|
||||
|
||||
class ProgressBar:
|
||||
'''
|
||||
An animated progress bar.
|
||||
|
||||
TODO derive Wait() from this
|
||||
'''
|
||||
|
||||
def __init__(self, width, top, unit, reverse=False):
|
||||
'''
|
||||
width width of the 'widget' including all text
|
||||
top the 100% value
|
||||
unit name of the unit to display
|
||||
reverse True to expand the progress bar from right to left
|
||||
'''
|
||||
|
||||
self.width = width
|
||||
self.value = 0
|
||||
self.top = top
|
||||
self.unit = unit
|
||||
self.reverse = reverse
|
||||
|
||||
self.old_len = 0
|
||||
self.t_start = None
|
||||
|
||||
_print("over.core.text.ProgressBar is deprecated and will be replaced by ProgressBar2 soon", prefix.warn)
|
||||
|
||||
def draw(self):
|
||||
if not self.t_start:
|
||||
self.t_start = time.time()
|
||||
|
||||
if self.old_len:
|
||||
sys.stderr.write('\b' * self.old_len)
|
||||
|
||||
transferred = str(Unit(self.value, self.unit))
|
||||
dt = time.time() - self.t_start
|
||||
|
||||
if dt > 0:
|
||||
speed = self.value / dt
|
||||
else:
|
||||
speed = 0.0
|
||||
|
||||
speed = str(Unit(speed, '%s/s' %(self.unit)))
|
||||
|
||||
available_width = self.width - len(transferred) - len(speed) - 5
|
||||
|
||||
ratio = self.value / self.top
|
||||
pb_done = '=' * int(available_width * ratio)
|
||||
pb_rem = ' ' * int(available_width * (1 - ratio))
|
||||
symbol = '>'
|
||||
|
||||
if self.reverse:
|
||||
symbol = '<'
|
||||
pb_done, pb_rem = pb_rem, pb_done
|
||||
|
||||
text = '%s [%s%s%s] %s' %(transferred, pb_done, symbol, pb_rem, speed)
|
||||
|
||||
sys.stderr.write(text)
|
||||
|
||||
current_len = len(text)
|
||||
tail = self.old_len - current_len
|
||||
self.old_len = current_len
|
||||
|
||||
if tail > 0:
|
||||
sys.stderr.write(' ' * tail)
|
||||
sys.stderr.write('\b' * tail)
|
||||
|
||||
sys.stderr.flush()
|
||||
|
||||
def update(self, value):
|
||||
self.value = value
|
||||
|
||||
self.draw()
|
||||
|
||||
def blank(self):
|
||||
sys.stderr.write('\r' + self.old_len * ' ' + '\r')
|
||||
sys.stderr.flush()
|
||||
|
||||
# --------------------------------------------------
|
||||
|
||||
class Unit:
|
||||
'''
|
||||
"""
|
||||
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 = (
|
||||
('Y', 24), ('Z', 21), ('E', 18), ('P', 15), ('T', 12), ('G', 9), ('M', 6), ('k', 3),
|
||||
('h', 2), ('D', 1), ('', 0), ('d', -1), ('c', -2), ('m', -3), ('μ', -6), ('n', -9),
|
||||
('p', -12), ('f', -15), ('a', -18), ('z', -21), ('y', -24)
|
||||
("Y", 24), ("Z", 21), ("E", 18), ("P", 15), ("T", 12), ("G", 9), ("M", 6), ("k", 3),
|
||||
("h", 2), ("D", 1), ("", 0), ("d", -1), ("c", -2), ("m", -3), ("μ", -6), ("n", -9),
|
||||
("p", -12), ("f", -15), ("a", -18), ("z", -21), ("y", -24)
|
||||
)
|
||||
|
||||
def __init__(self,
|
||||
value, unit=None, dimension=1,
|
||||
use_prefixes='YZEPTGMkmμnpfazy', format='%.2f pU',
|
||||
use_prefixes="YZEPTGMkmμnpfazy", format="%.2f pU",
|
||||
logarithmic=False, log_base=10
|
||||
):
|
||||
'''
|
||||
"""
|
||||
value the numerical value of the variable (int or float)
|
||||
unit the symbol to use, if any (str or None)
|
||||
dimension the dimensionality of the value (1, 2, 3, ...)
|
||||
use_prefixes which multiplier prefixes to use
|
||||
- the default 'YZEPTGMkmμnpfazy' omits 'c' for centi- and 'D' for deca-
|
||||
- the default "YZEPTGMkmμnpfazy" omits "c" for centi- and "D" for deca-
|
||||
format use printf notation for value (e.g. %010.5f), p for prefix and U for unit
|
||||
logarithmic when True, log_10 prefixes are used
|
||||
log_base logarithm base value
|
||||
|
||||
note that deca- is correctly rendered as 'da', the 'D' is used in use_prefixes only
|
||||
'''
|
||||
note that deca- is correctly rendered as "da", the "D" is used in use_prefixes only
|
||||
"""
|
||||
|
||||
self.value = float(value)
|
||||
self.unit = unit if unit else ''
|
||||
self.unit = unit if unit else ""
|
||||
self.dimension = dimension
|
||||
self.use_prefixes = use_prefixes
|
||||
self.format = format
|
||||
|
@ -422,7 +345,7 @@ class Unit:
|
|||
self.log_base = log_base
|
||||
|
||||
if self.logarithmic and (self.value < 0 or self.log_base < 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):
|
||||
if self.value == 0.0:
|
||||
|
@ -431,7 +354,7 @@ class Unit:
|
|||
e = round(math.log(abs(self.value), 10), 6)
|
||||
|
||||
if self.logarithmic:
|
||||
prefix = 'dB'
|
||||
prefix = "dB"
|
||||
value = math.log(self.value, self.log_base) * 10
|
||||
|
||||
else:
|
||||
|
@ -442,30 +365,27 @@ class Unit:
|
|||
value = self.value / 10**(mul*self.dimension)
|
||||
|
||||
output = self.format %(value)
|
||||
output = output.replace('p', prefix if prefix != 'D' else 'da') # deca- handler
|
||||
output = output.replace('U', self.unit)
|
||||
output = output.replace("p", prefix if prefix != "D" else "da") # deca- handler
|
||||
output = output.replace("U", self.unit)
|
||||
|
||||
return output
|
||||
|
||||
def __repr__(self):
|
||||
return 'over.core.text.Unit(%s)' %(self)
|
||||
return "over.text.Unit(%s)" %(self)
|
||||
|
||||
# --------------------------------------------------
|
||||
|
||||
def paragraph(text, width=0, indent=0, prefix=None, stamp=None):
|
||||
'''
|
||||
"""
|
||||
str text text to format
|
||||
int width required line length; if 0, current terminal width will be used;
|
||||
- if negative, current terminal width minus supplied amount will be used
|
||||
int width required line length; if 0, current terminal width will be used
|
||||
int indent how many spaces to indent the text with
|
||||
str prefix prefix each line with it; not counted in width (offsets the lines)
|
||||
str stamp placed over the first line's indent (stretching the indent if necessary)
|
||||
|
||||
Formats text into an indented paragraph that fits the terminal and returns it.
|
||||
Correctly handles color tags.
|
||||
'''
|
||||
"""
|
||||
|
||||
#words = [x.strip() for x in text.split() if x.strip()]
|
||||
words = text.split()
|
||||
|
||||
term_width = get_terminal_size()[1]
|
||||
|
@ -481,9 +401,9 @@ def paragraph(text, width=0, indent=0, prefix=None, stamp=None):
|
|||
while words:
|
||||
if indent:
|
||||
if first and stamp:
|
||||
lines.append([stamp + ' '*(indent-1-len(stamp))])
|
||||
lines.append([stamp + " "*(indent-1-len(stamp))])
|
||||
else:
|
||||
lines.append([' '*(indent-1)]) # first word = indent minus one space (that's gonna get back while joining)
|
||||
lines.append([" "*(indent-1)]) # first word = indent minus one space (that's gonna get back while joining)
|
||||
|
||||
first = False
|
||||
else:
|
||||
|
@ -492,23 +412,23 @@ def paragraph(text, width=0, indent=0, prefix=None, stamp=None):
|
|||
while words:
|
||||
word_added = False
|
||||
|
||||
if len(re.sub('§.', '', ' '.join(lines[-1]))) + len(re.sub('§.', '', words[0])) + 1 <= width:
|
||||
if len(re.sub("§.", "", " ".join(lines[-1]))) + len(re.sub("§.", "", words[0])) + 1 <= width:
|
||||
lines[-1].append(words.pop(0))
|
||||
word_added = True
|
||||
|
||||
elif not word_added and len(lines[-1]) == 1 and indent:
|
||||
# no word added and just the indent's in here = word's too long -> screw indent
|
||||
# no word added and just the indent"s in here = word"s too long -> screw indent
|
||||
# we might try to keep at least a part of the indent - if possible
|
||||
len_word = len(re.sub('§.', '', words[0]))
|
||||
len_word = len(re.sub("§.", "", words[0]))
|
||||
if len_word < width:
|
||||
lines[-1] = [' '*(width - len_word - 1), words.pop(0)]
|
||||
lines[-1] = [" "*(width - len_word - 1), words.pop(0)]
|
||||
else:
|
||||
lines[-1] = [words.pop(0)]
|
||||
word_added = True
|
||||
break
|
||||
|
||||
elif not word_added and not lines[-1] and not indent:
|
||||
# no word added, empty line = word's too long -> screw indent
|
||||
# no word added, empty line = word"s too long -> screw indent
|
||||
lines[-1] = [words.pop(0)]
|
||||
word_added = True
|
||||
break
|
||||
|
@ -521,41 +441,47 @@ def paragraph(text, width=0, indent=0, prefix=None, stamp=None):
|
|||
if prefix:
|
||||
line.insert(0, prefix)
|
||||
|
||||
lines_tmp.append(' '.join(line)) # put words together
|
||||
lines_tmp.append(" ".join(line)) # put words together
|
||||
|
||||
return '\n'.join(lines_tmp) # put lines together
|
||||
return "\n".join(lines_tmp) # put lines together
|
||||
|
||||
class prefix:
|
||||
info = (' ', 'INFO')
|
||||
debug = (' §b?§/', '§bDEBG§/')
|
||||
start = ('§B>>>§/', '§BEXEC§/')
|
||||
info = (" ", "INFO")
|
||||
debug = (" §b?§/", "§bDEBG§/")
|
||||
start = ("§B>>>§/", "§BEXEC§/")
|
||||
exec = start
|
||||
warn = (' §y#§/', '§yWARN§/')
|
||||
fail = ('§r!!!§/', '§rFAIL§/')
|
||||
done = (' §g*§/', '§gDONE§/')
|
||||
warn = (" §y#§/", "§yWARN§/")
|
||||
fail = ("§r!!!§/", "§rFAIL§/")
|
||||
done = (" §g*§/", "§gDONE§/")
|
||||
|
||||
colortags = {
|
||||
'§r': '\x1b[31;01m',
|
||||
'§g': '\x1b[32;01m',
|
||||
'§y': '\x1b[33;01m',
|
||||
'§b': '\x1b[34;01m',
|
||||
'§m': '\x1b[35;01m',
|
||||
'§c': '\x1b[36;01m',
|
||||
'§B': '\x1b[01m',
|
||||
'§/': '\x1b[39;49;00m'
|
||||
"r": "\x1b[31;01m",
|
||||
"g": "\x1b[32;01m",
|
||||
"y": "\x1b[33;01m",
|
||||
"b": "\x1b[34;01m",
|
||||
"m": "\x1b[35;01m",
|
||||
"c": "\x1b[36;01m",
|
||||
"B": "\x1b[01m",
|
||||
"": "\x1b[39;49;00m" # reset
|
||||
}
|
||||
|
||||
def render(text, colors=True):
|
||||
'''
|
||||
"""
|
||||
Processes text with color tags and either
|
||||
removes them (with colors=False) or replaces
|
||||
them with terminal color codes.
|
||||
'''
|
||||
"""
|
||||
|
||||
text = str(text)
|
||||
tags = set(colortags.keys())
|
||||
output = []
|
||||
|
||||
for
|
||||
|
||||
|
||||
|
||||
if colors:
|
||||
tags = re.findall('§[^§]', text)
|
||||
tags = re.findall(r"[{:s}]\$".format("".join(tags)), text)
|
||||
|
||||
for tag in tags:
|
||||
try:
|
||||
|
@ -563,19 +489,19 @@ def render(text, colors=True):
|
|||
except KeyError:
|
||||
pass
|
||||
else:
|
||||
text = re.sub('§[^§]', '', text)
|
||||
text = re.sub("§[^§]", "", text)
|
||||
|
||||
# unescape actual paragraphs
|
||||
text = re.sub('§§', '§', text)
|
||||
text = re.sub("§§", "§", text)
|
||||
|
||||
return text
|
||||
|
||||
# --------------------------------------------------
|
||||
|
||||
def char_length(string):
|
||||
'''
|
||||
"""
|
||||
Returns the length of a string minus all formatting tags.
|
||||
'''
|
||||
"""
|
||||
|
||||
plain_string = render(string, colors=False)
|
||||
|
||||
|
@ -584,7 +510,7 @@ def char_length(string):
|
|||
# --------------------------------------------------
|
||||
|
||||
class Output:
|
||||
'''
|
||||
"""
|
||||
Text UI output renderer.
|
||||
|
||||
Prints messages to the stdout with optional eye candy
|
||||
|
@ -592,18 +518,18 @@ class Output:
|
|||
|
||||
Usage:
|
||||
>>> from over import Output, prefix
|
||||
>>> say = Output('test', timestamp=True)
|
||||
>>> say('system initialized')
|
||||
>>> say = Output("test", timestamp=True)
|
||||
>>> say("system initialized")
|
||||
[2013-02-28 16:41:28] INFO -- test, system initialized
|
||||
>>> say('system is FUBAR', prefix.fail)
|
||||
>>> say("system is FUBAR", prefix.fail)
|
||||
[2013-02-28 16:41:46] FAIL -- test, system is FUBAR
|
||||
>>> say('I just realized this will not work', prefix.fail, timestamp=False)
|
||||
>>> say("I just realized this will not work", prefix.fail, timestamp=False)
|
||||
!!! I just realized this will not work
|
||||
|
||||
TODO use a generic format string
|
||||
'''
|
||||
"""
|
||||
|
||||
def __init__(self, name, timestamp=True, colors=True, default_prefix=prefix.info, default_suffix='.\n', stream=sys.stderr):
|
||||
def __init__(self, name, timestamp=True, colors=True, default_prefix=prefix.info, default_suffix=".\n", stream=sys.stderr):
|
||||
self.name = name
|
||||
self.timestamp = timestamp
|
||||
self.colors = colors
|
||||
|
@ -631,15 +557,15 @@ class Output:
|
|||
|
||||
# [2012-11-11 16:52:06] INFO -- ahoj
|
||||
if timestamp:
|
||||
output.append(time.strftime('[%Y-%m-%d %H:%M:%S] '))
|
||||
output.append(time.strftime("[%Y-%m-%d %H:%M:%S] "))
|
||||
output.append(prefix[1])
|
||||
output.append(' -- ')
|
||||
output.append(" -- ")
|
||||
elif prefix:
|
||||
output.append(prefix[0])
|
||||
output.append(' ')
|
||||
output.append(" ")
|
||||
|
||||
if display_name and self.name:
|
||||
output.append('%s, ' %(self.name))
|
||||
output.append("%s, " %(self.name))
|
||||
|
||||
#output.append(paragraph(str(text), indent=indent))
|
||||
output.append(str(text))
|
||||
|
@ -647,7 +573,7 @@ class Output:
|
|||
if suffix:
|
||||
output.append(suffix)
|
||||
|
||||
output = ''.join(output)
|
||||
output = "".join(output)
|
||||
|
||||
self.stream.write(render(output, colors))
|
||||
self.stream.flush()
|
||||
|
@ -655,19 +581,19 @@ class Output:
|
|||
# --------------------------------------------------
|
||||
|
||||
def get_terminal_size():
|
||||
'''
|
||||
Returns current terminal's (rows, cols).
|
||||
'''
|
||||
"""
|
||||
Returns current terminal"s (rows, cols).
|
||||
"""
|
||||
|
||||
terminal = sys.stdout.fileno()
|
||||
try:
|
||||
return struct.unpack('HHHH', fcntl.ioctl(terminal, termios.TIOCGWINSZ, struct.pack('HHHH', 0, 0, 0, 0)))
|
||||
return struct.unpack("HHHH", fcntl.ioctl(terminal, termios.TIOCGWINSZ, struct.pack("HHHH", 0, 0, 0, 0)))
|
||||
except IOError:
|
||||
return (40, 80)
|
||||
|
||||
# --------------------------------------------------
|
||||
|
||||
_print = Output('over.core.text', stream=sys.stderr)
|
||||
_print = Output("over.core.text", stream=sys.stderr)
|
||||
|
||||
# --------------------------------------------------
|
||||
|
7
over/version.py
Normal file
7
over/version.py
Normal file
|
@ -0,0 +1,7 @@
|
|||
#! /usr/bin/env python3
|
||||
# encoding: utf-8
|
||||
|
||||
major = 0
|
||||
minor = 0
|
||||
patch = 0 # OVER_VERSION_PATCH_IDENTIFIER
|
||||
str = ".".join(str(v) for v in (major, minor, patch))
|
|
@ -1,4 +0,0 @@
|
|||
#! /bin/zsh
|
||||
# encoding: utf-8
|
||||
|
||||
echo "- nothing to do"
|
|
@ -1,4 +0,0 @@
|
|||
#! /usr/bin/env python3
|
||||
# encoding: utf-8
|
||||
|
||||
_version = (0, '00000000') # OVER_VERSION_IDENTIFIER
|
Loading…
Add table
Add a link
Reference in a new issue