major reorganization of file structure

This commit is contained in:
Martinez 2016-05-11 17:07:48 +02:00
parent 65b0233a9d
commit 04b75a400c
33 changed files with 112 additions and 503 deletions

3
Changes Normal file
View file

@ -0,0 +1,3 @@
replaced text.ProgressBar with text.ProgressBar2
simplified text.paragraph
color tags changed from §g...§/ to g$...$

View file

@ -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

View file

@ -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()'

View file

@ -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)

View file

@ -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)

View file

@ -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)

View file

@ -1,4 +0,0 @@
#! /bin/zsh
# encoding: utf-8
echo "- nothing to do"

8
aux.py
View file

@ -1,8 +0,0 @@
#! /usr/bin/env python3
# encoding: utf-8
import sys
from . import core
_print = core.text.Output('over', stream=sys.stderr)

View file

@ -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

View file

@ -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"

View file

@ -1,4 +0,0 @@
#! /bin/zsh
# encoding: utf-8
echo "- nothing to do"

View file

@ -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

View file

@ -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
# -------------------------------------------------- # --------------------------------------------------

View file

@ -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()

View file

@ -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
View 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))

View file

View file

@ -1,4 +0,0 @@
#! /bin/zsh
# encoding: utf-8
echo "- nothing to do"

View file

@ -1,4 +0,0 @@
#! /usr/bin/env python3
# encoding: utf-8
_version = (0, '00000000') # OVER_VERSION_IDENTIFIER