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
|
#! /usr/bin/env python3
|
||||||
# encoding: utf-8
|
# encoding: utf-8
|
||||||
|
|
||||||
from . import m
|
import sys
|
||||||
from . import app
|
|
||||||
from . import aux
|
from . import aux
|
||||||
from . import cmd
|
|
||||||
from . import file
|
|
||||||
from . import misc
|
|
||||||
from . import text
|
from . import text
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
@ -15,4 +12,7 @@ except:
|
||||||
aux._print('unable to load C implementation, using python instead', text.prefix.warn)
|
aux._print('unable to load C implementation, using python instead', text.prefix.warn)
|
||||||
from . import python_types as types
|
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 file
|
||||||
from . import text
|
from . import text
|
||||||
from ..version import _version
|
from . import version
|
||||||
|
|
||||||
prefix = text.prefix
|
prefix = text.prefix
|
||||||
|
|
||||||
# FIXME This very seriously needs to be heavily simplified and de-duplicated
|
# FIXME This very seriously needs to be heavily simplified and de-duplicated
|
||||||
# TODO same parser for cmdline and Config
|
# TODO same parser for cmdline and Config
|
||||||
# TODO zsh and fish integration
|
# TODO zsh integration
|
||||||
|
|
||||||
# --------------------------------------------------
|
# --------------------------------------------------
|
||||||
|
|
|
@ -3,10 +3,10 @@
|
||||||
|
|
||||||
# library imports
|
# library imports
|
||||||
import over
|
import over
|
||||||
prefix = over.core.text.prefix
|
prefix = over.text.prefix
|
||||||
|
|
||||||
# local imports
|
# local imports
|
||||||
#import version
|
import version
|
||||||
|
|
||||||
# --------------------------------------------------
|
# --------------------------------------------------
|
||||||
# Exceptions
|
# Exceptions
|
||||||
|
@ -23,9 +23,9 @@ class ConfigurationError(Exception):
|
||||||
# --------------------------------------------------
|
# --------------------------------------------------
|
||||||
|
|
||||||
if __name__ == "__main__":
|
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_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.enable_help("h")
|
||||||
main.parse()
|
main.parse()
|
||||||
|
|
|
@ -12,32 +12,32 @@ import time
|
||||||
# --------------------------------------------------
|
# --------------------------------------------------
|
||||||
|
|
||||||
def lexical_join(words, oxford=False):
|
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'
|
"this, that and one of them too"
|
||||||
|
|
||||||
>>> lexical_join(['this', 'that', 'one of them too'], oxford=True)
|
>>> lexical_join(["this", "that", "one of them too"], oxford=True)
|
||||||
'this, that, and one of them too'
|
"this, that, and one of them too"
|
||||||
|
|
||||||
>>> lexical_join(['this', 'that'])
|
>>> lexical_join(["this", "that"])
|
||||||
'this and that'
|
"this and that"
|
||||||
|
|
||||||
>>> lexical_join(['this'])
|
>>> lexical_join(["this"])
|
||||||
'this'
|
"this"
|
||||||
'''
|
"""
|
||||||
|
|
||||||
l = len(words)
|
l = len(words)
|
||||||
|
|
||||||
if l == 0:
|
if l == 0:
|
||||||
return ''
|
return ""
|
||||||
elif l == 1:
|
elif l == 1:
|
||||||
return words[0]
|
return words[0]
|
||||||
elif l == 2:
|
elif l == 2:
|
||||||
return '%s and %s' %(str(words[0]), str(words[1]))
|
return "%s and %s" %(str(words[0]), str(words[1]))
|
||||||
else:
|
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))
|
u = Unit(value, unit, format="%.{:d}f pU".format(self.precision))
|
||||||
|
|
||||||
if not self.use_prefixes or just == "percent":
|
if not self.use_prefixes or just == "percent":
|
||||||
u._prefixes = (('', 0),) # Unit needs fixin'
|
u._prefixes = (("", 0),) # Unit needs fixin"
|
||||||
|
|
||||||
s = str(u)
|
s = str(u)
|
||||||
|
|
||||||
|
@ -118,8 +118,8 @@ class ProgressBar2:
|
||||||
any other character - a bar consisting of these characters filling the line so that
|
any other character - a bar consisting of these characters filling the line so that
|
||||||
at 100% it fills the entirety of the remaining space
|
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
|
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.
|
is that channel"s complement. Operations z, s, m and h cannot have a complement.
|
||||||
|
|
||||||
Use §§ to display a literal §.
|
Use §§ to display a literal §.
|
||||||
|
|
||||||
|
@ -163,7 +163,7 @@ class ProgressBar2:
|
||||||
Properties "unit" and "prefix_base2" are passed to over.core.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.
|
||||||
"precision" is the displayed floating point precision. Use 0 to force an integer.
|
"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):
|
def set(self, channel_id, value):
|
||||||
"""
|
"""
|
||||||
Sets the channel's value.
|
Sets the channel"s value.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
c = self.channels[channel_id]
|
c = self.channels[channel_id]
|
||||||
|
@ -301,120 +301,43 @@ class ProgressBar2:
|
||||||
|
|
||||||
return output.rjust(just)
|
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:
|
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 use significant digits instead of rigid order boundaries
|
||||||
TODO float superclass?
|
TODO float superclass?
|
||||||
TODO base_2 prefixes (Ki, Mi, ...)
|
TODO base_2 prefixes (Ki, Mi, ...)
|
||||||
'''
|
"""
|
||||||
|
|
||||||
_prefixes = (
|
_prefixes = (
|
||||||
('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),
|
||||||
('h', 2), ('D', 1), ('', 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,
|
def __init__(self,
|
||||||
value, unit=None, dimension=1,
|
value, unit=None, dimension=1,
|
||||||
use_prefixes='YZEPTGMkmμnpfazy', format='%.2f pU',
|
use_prefixes="YZEPTGMkmμnpfazy", format="%.2f pU",
|
||||||
logarithmic=False, log_base=10
|
logarithmic=False, log_base=10
|
||||||
):
|
):
|
||||||
'''
|
"""
|
||||||
value the numerical value of the variable (int or float)
|
value the numerical value of the variable (int or float)
|
||||||
unit the symbol to use, if any (str or None)
|
unit the symbol to use, if any (str or None)
|
||||||
dimension the dimensionality of the value (1, 2, 3, ...)
|
dimension the dimensionality of the value (1, 2, 3, ...)
|
||||||
use_prefixes which multiplier prefixes to use
|
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
|
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
|
logarithmic when True, log_10 prefixes are used
|
||||||
log_base logarithm base value
|
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.value = float(value)
|
||||||
self.unit = unit if unit else ''
|
self.unit = unit if unit else ""
|
||||||
self.dimension = dimension
|
self.dimension = dimension
|
||||||
self.use_prefixes = use_prefixes
|
self.use_prefixes = use_prefixes
|
||||||
self.format = format
|
self.format = format
|
||||||
|
@ -422,7 +345,7 @@ class Unit:
|
||||||
self.log_base = log_base
|
self.log_base = log_base
|
||||||
|
|
||||||
if self.logarithmic and (self.value < 0 or self.log_base < 0):
|
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):
|
def __str__(self):
|
||||||
if self.value == 0.0:
|
if self.value == 0.0:
|
||||||
|
@ -431,7 +354,7 @@ class Unit:
|
||||||
e = round(math.log(abs(self.value), 10), 6)
|
e = round(math.log(abs(self.value), 10), 6)
|
||||||
|
|
||||||
if self.logarithmic:
|
if self.logarithmic:
|
||||||
prefix = 'dB'
|
prefix = "dB"
|
||||||
value = math.log(self.value, self.log_base) * 10
|
value = math.log(self.value, self.log_base) * 10
|
||||||
|
|
||||||
else:
|
else:
|
||||||
|
@ -442,30 +365,27 @@ class Unit:
|
||||||
value = self.value / 10**(mul*self.dimension)
|
value = self.value / 10**(mul*self.dimension)
|
||||||
|
|
||||||
output = self.format %(value)
|
output = self.format %(value)
|
||||||
output = output.replace('p', prefix if prefix != 'D' else 'da') # deca- handler
|
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
|
||||||
|
|
||||||
def __repr__(self):
|
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):
|
def paragraph(text, width=0, indent=0, prefix=None, stamp=None):
|
||||||
'''
|
"""
|
||||||
str text text to format
|
str text text to format
|
||||||
int width required line length; if 0, current terminal width will be used;
|
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 indent how many spaces to indent the text with
|
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)
|
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.
|
Formats text into an indented paragraph that fits the terminal and returns it.
|
||||||
Correctly handles color tags.
|
Correctly handles color tags.
|
||||||
'''
|
"""
|
||||||
|
|
||||||
#words = [x.strip() for x in text.split() if x.strip()]
|
|
||||||
words = text.split()
|
words = text.split()
|
||||||
|
|
||||||
term_width = get_terminal_size()[1]
|
term_width = get_terminal_size()[1]
|
||||||
|
@ -481,9 +401,9 @@ def paragraph(text, width=0, indent=0, prefix=None, stamp=None):
|
||||||
while words:
|
while words:
|
||||||
if indent:
|
if indent:
|
||||||
if first and stamp:
|
if first and stamp:
|
||||||
lines.append([stamp + ' '*(indent-1-len(stamp))])
|
lines.append([stamp + " "*(indent-1-len(stamp))])
|
||||||
else:
|
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
|
first = False
|
||||||
else:
|
else:
|
||||||
|
@ -492,23 +412,23 @@ def paragraph(text, width=0, indent=0, prefix=None, stamp=None):
|
||||||
while words:
|
while words:
|
||||||
word_added = False
|
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))
|
lines[-1].append(words.pop(0))
|
||||||
word_added = True
|
word_added = True
|
||||||
|
|
||||||
elif not word_added and len(lines[-1]) == 1 and indent:
|
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
|
# 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:
|
if len_word < width:
|
||||||
lines[-1] = [' '*(width - len_word - 1), words.pop(0)]
|
lines[-1] = [" "*(width - len_word - 1), words.pop(0)]
|
||||||
else:
|
else:
|
||||||
lines[-1] = [words.pop(0)]
|
lines[-1] = [words.pop(0)]
|
||||||
word_added = True
|
word_added = True
|
||||||
break
|
break
|
||||||
|
|
||||||
elif not word_added and not lines[-1] and not indent:
|
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)]
|
lines[-1] = [words.pop(0)]
|
||||||
word_added = True
|
word_added = True
|
||||||
break
|
break
|
||||||
|
@ -521,41 +441,47 @@ def paragraph(text, width=0, indent=0, prefix=None, stamp=None):
|
||||||
if prefix:
|
if prefix:
|
||||||
line.insert(0, 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:
|
class prefix:
|
||||||
info = (' ', 'INFO')
|
info = (" ", "INFO")
|
||||||
debug = (' §b?§/', '§bDEBG§/')
|
debug = (" §b?§/", "§bDEBG§/")
|
||||||
start = ('§B>>>§/', '§BEXEC§/')
|
start = ("§B>>>§/", "§BEXEC§/")
|
||||||
exec = start
|
exec = start
|
||||||
warn = (' §y#§/', '§yWARN§/')
|
warn = (" §y#§/", "§yWARN§/")
|
||||||
fail = ('§r!!!§/', '§rFAIL§/')
|
fail = ("§r!!!§/", "§rFAIL§/")
|
||||||
done = (' §g*§/', '§gDONE§/')
|
done = (" §g*§/", "§gDONE§/")
|
||||||
|
|
||||||
colortags = {
|
colortags = {
|
||||||
'§r': '\x1b[31;01m',
|
"r": "\x1b[31;01m",
|
||||||
'§g': '\x1b[32;01m',
|
"g": "\x1b[32;01m",
|
||||||
'§y': '\x1b[33;01m',
|
"y": "\x1b[33;01m",
|
||||||
'§b': '\x1b[34;01m',
|
"b": "\x1b[34;01m",
|
||||||
'§m': '\x1b[35;01m',
|
"m": "\x1b[35;01m",
|
||||||
'§c': '\x1b[36;01m',
|
"c": "\x1b[36;01m",
|
||||||
'§B': '\x1b[01m',
|
"B": "\x1b[01m",
|
||||||
'§/': '\x1b[39;49;00m'
|
"": "\x1b[39;49;00m" # reset
|
||||||
}
|
}
|
||||||
|
|
||||||
def render(text, colors=True):
|
def render(text, colors=True):
|
||||||
'''
|
"""
|
||||||
Processes text with color tags and either
|
Processes text with color tags and either
|
||||||
removes them (with colors=False) or replaces
|
removes them (with colors=False) or replaces
|
||||||
them with terminal color codes.
|
them with terminal color codes.
|
||||||
'''
|
"""
|
||||||
|
|
||||||
text = str(text)
|
text = str(text)
|
||||||
|
tags = set(colortags.keys())
|
||||||
|
output = []
|
||||||
|
|
||||||
|
for
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if colors:
|
if colors:
|
||||||
tags = re.findall('§[^§]', text)
|
tags = re.findall(r"[{:s}]\$".format("".join(tags)), text)
|
||||||
|
|
||||||
for tag in tags:
|
for tag in tags:
|
||||||
try:
|
try:
|
||||||
|
@ -563,19 +489,19 @@ def render(text, colors=True):
|
||||||
except KeyError:
|
except KeyError:
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
text = re.sub('§[^§]', '', text)
|
text = re.sub("§[^§]", "", text)
|
||||||
|
|
||||||
# unescape actual paragraphs
|
# unescape actual paragraphs
|
||||||
text = re.sub('§§', '§', text)
|
text = re.sub("§§", "§", text)
|
||||||
|
|
||||||
return text
|
return text
|
||||||
|
|
||||||
# --------------------------------------------------
|
# --------------------------------------------------
|
||||||
|
|
||||||
def char_length(string):
|
def char_length(string):
|
||||||
'''
|
"""
|
||||||
Returns the length of a string minus all formatting tags.
|
Returns the length of a string minus all formatting tags.
|
||||||
'''
|
"""
|
||||||
|
|
||||||
plain_string = render(string, colors=False)
|
plain_string = render(string, colors=False)
|
||||||
|
|
||||||
|
@ -584,7 +510,7 @@ def char_length(string):
|
||||||
# --------------------------------------------------
|
# --------------------------------------------------
|
||||||
|
|
||||||
class Output:
|
class Output:
|
||||||
'''
|
"""
|
||||||
Text UI output renderer.
|
Text UI output renderer.
|
||||||
|
|
||||||
Prints messages to the stdout with optional eye candy
|
Prints messages to the stdout with optional eye candy
|
||||||
|
@ -592,18 +518,18 @@ class Output:
|
||||||
|
|
||||||
Usage:
|
Usage:
|
||||||
>>> from over import Output, prefix
|
>>> from over import Output, prefix
|
||||||
>>> say = Output('test', timestamp=True)
|
>>> say = Output("test", timestamp=True)
|
||||||
>>> say('system initialized')
|
>>> say("system initialized")
|
||||||
[2013-02-28 16:41:28] INFO -- test, 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
|
[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
|
!!! I just realized this will not work
|
||||||
|
|
||||||
TODO use a generic format string
|
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.name = name
|
||||||
self.timestamp = timestamp
|
self.timestamp = timestamp
|
||||||
self.colors = colors
|
self.colors = colors
|
||||||
|
@ -631,15 +557,15 @@ class Output:
|
||||||
|
|
||||||
# [2012-11-11 16:52:06] INFO -- ahoj
|
# [2012-11-11 16:52:06] INFO -- ahoj
|
||||||
if timestamp:
|
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(prefix[1])
|
||||||
output.append(' -- ')
|
output.append(" -- ")
|
||||||
elif prefix:
|
elif prefix:
|
||||||
output.append(prefix[0])
|
output.append(prefix[0])
|
||||||
output.append(' ')
|
output.append(" ")
|
||||||
|
|
||||||
if display_name and self.name:
|
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(paragraph(str(text), indent=indent))
|
||||||
output.append(str(text))
|
output.append(str(text))
|
||||||
|
@ -647,7 +573,7 @@ class Output:
|
||||||
if suffix:
|
if suffix:
|
||||||
output.append(suffix)
|
output.append(suffix)
|
||||||
|
|
||||||
output = ''.join(output)
|
output = "".join(output)
|
||||||
|
|
||||||
self.stream.write(render(output, colors))
|
self.stream.write(render(output, colors))
|
||||||
self.stream.flush()
|
self.stream.flush()
|
||||||
|
@ -655,19 +581,19 @@ class Output:
|
||||||
# --------------------------------------------------
|
# --------------------------------------------------
|
||||||
|
|
||||||
def get_terminal_size():
|
def get_terminal_size():
|
||||||
'''
|
"""
|
||||||
Returns current terminal's (rows, cols).
|
Returns current terminal"s (rows, cols).
|
||||||
'''
|
"""
|
||||||
|
|
||||||
terminal = sys.stdout.fileno()
|
terminal = sys.stdout.fileno()
|
||||||
try:
|
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:
|
except IOError:
|
||||||
return (40, 80)
|
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