first revision

This commit is contained in:
gwen 2012-05-13 20:48:51 +02:00
commit 8b16814ab4
86 changed files with 10427 additions and 0 deletions

0
__init__.py Normal file
View file

7
autolib.py Normal file
View file

@ -0,0 +1,7 @@
"enables us to carry out a calculation and return an option's value"
# FIXME: import eosfunc here
def identical(name, config, *args):
return "identical" + name

460
config.py Normal file
View file

@ -0,0 +1,460 @@
# -*- coding: utf-8 -*-
"pretty small and local configuration management tool"
# The original `Config` design model is unproudly borrowed from
# the rough gus of pypy: pypy: http://codespeak.net/svn/pypy/dist/pypy/config/
from error import (HiddenOptionError, ConfigError, NotFoundError,
AmbigousOptionError, ConflictConfigError, NoMatchingOptionFound,
SpecialOwnersError, MandatoryError, MethodCallError,
DisabledOptionError, ModeOptionError)
from option import (OptionDescription, Option, SymLinkOption, group_types,
apply_requires, modes)
import autolib
# ____________________________________________________________
# automatic Option object
special_owners = ['auto', 'fill']
def special_owner_factory(name, owner, default=None,
callback=None, config=None):
# auto behavior: carries out a calculation
if owner == 'auto':
return auto_factory(name, callback, config)
# fill behavior: carries out a calculation only if a default value isn't set
if owner == 'fill':
if default == None:
return auto_factory(name, callback, config)
else:
return default
def auto_factory(name, callback, config):
try:
return getattr(autolib, callback)(name, config)
except AttributeError:
raise SpecialOwnersError("callback: {0} not found for "
"option: {1}".format(callback, name))
# ____________________________________________________________
class Config(object):
_cfgimpl_hidden = True
_cfgimpl_disabled = True
_cfgimpl_mandatory = True
_cfgimpl_frozen = False
_cfgimpl_owner = "user"
_cfgimpl_toplevel = None
_cfgimpl_mode = 'normal'
def __init__(self, descr, parent=None, **overrides):
self._cfgimpl_descr = descr
self._cfgimpl_value_owners = {}
self._cfgimpl_parent = parent
# `Config()` indeed supports the configuration `Option()`'s values...
self._cfgimpl_values = {}
self._cfgimpl_previous_values = {}
# XXX warnings are a great idea, let's make up a better use of it
self._cfgimpl_warnings = []
self._cfgimpl_toplevel = self._cfgimpl_get_toplevel()
# `freeze()` allows us to carry out this calculation again if necessary
self._cfgimpl_frozen = self._cfgimpl_toplevel._cfgimpl_frozen
#
self._cfgimpl_build(overrides)
def _validate_duplicates(self, children):
duplicates = []
for dup in children:
if dup._name not in duplicates:
duplicates.append(dup._name)
else:
raise ConflictConfigError('duplicate option name: <%s>' % \
dup._name)
def _cfgimpl_build(self, overrides):
self._validate_duplicates(self._cfgimpl_descr._children)
for child in self._cfgimpl_descr._children:
if isinstance(child, Option):
self._cfgimpl_values[child._name] = child.getdefault()
self._cfgimpl_value_owners[child._name] = 'default'
elif isinstance(child, OptionDescription):
self._validate_duplicates(child._children)
self._cfgimpl_values[child._name] = Config(child, parent=self)
self.override(overrides)
def cfgimpl_update(self):
"dynamically adds `Option()` or `OptionDescription()`"
# Nothing is static. Everything evolve.
# FIXME this is an update for new options in the schema only
# see the update_child() method of the descr object
for child in self._cfgimpl_descr._children:
if isinstance(child, Option):
if child._name not in self._cfgimpl_values:
self._cfgimpl_values[child._name] = child.getdefault()
self._cfgimpl_value_owners[child._name] = 'default'
elif isinstance(child, OptionDescription):
if child._name not in self._cfgimpl_values:
self._cfgimpl_values[child._name] = Config(child, parent=self)
def override(self, overrides):
for name, value in overrides.iteritems():
homeconfig, name = self._cfgimpl_get_home_by_path(name)
# if there are special_owners, impossible to override
if homeconfig._cfgimpl_value_owners[name] in special_owners:
raise SpecialOwnersError("cannot override option: {0} because "
"of its special owner".format(name))
homeconfig.setoption(name, value, 'default')
def cfgimpl_set_owner(self, owner):
self._cfgimpl_owner = owner
for child in self._cfgimpl_descr._children:
if isinstance(child, OptionDescription):
self._cfgimpl_values[child._name].cfgimpl_set_owner(owner)
# ____________________________________________________________
def cfgimpl_hide(self):
if self._cfgimpl_parent != None:
raise MethodCallError("this method root_hide() shall not be"
"used with non-root Config() object")
rootconfig = self._cfgimpl_get_toplevel()
rootconfig._cfgimpl_hidden = True
def cfgimpl_show(self):
if self._cfgimpl_parent != None:
raise MethodCallError("this method root_hide() shall not be"
"used with non-root Config() object")
rootconfig = self._cfgimpl_get_toplevel()
rootconfig._cfgimpl_hidden = False
# ____________________________________________________________
def cfgimpl_disable(self):
if self._cfgimpl_parent != None:
raise MethodCallError("this method root_hide() shall not be"
"used with non-root Confit() object")
rootconfig = self._cfgimpl_get_toplevel()
rootconfig._cfgimpl_disabled = True
def cfgimpl_enable(self):
if self._cfgimpl_parent != None:
raise MethodCallError("this method root_hide() shall not be"
"used with non-root Confit() object")
rootconfig = self._cfgimpl_get_toplevel()
rootconfig._cfgimpl_disabled = False
# ____________________________________________________________
def __setattr__(self, name, value):
if '.' in name:
homeconfig, name = self._cfgimpl_get_home_by_path(name)
return setattr(homeconfig, name, value)
if name.startswith('_cfgimpl_'):
self.__dict__[name] = value
return
if self._cfgimpl_frozen and getattr(self, name) != value:
raise TypeError("trying to change a value in a frozen config"
": {0} {1}".format(name, value))
if type(getattr(self._cfgimpl_descr, name)) != SymLinkOption:
self._validate(name, getattr(self._cfgimpl_descr, name))
self.setoption(name, value, self._cfgimpl_owner)
def _validate(self, name, opt_or_descr):
if not type(opt_or_descr) == OptionDescription:
apply_requires(opt_or_descr, self)
# hidden options
if self._cfgimpl_toplevel._cfgimpl_hidden and \
(opt_or_descr._is_hidden() or self._cfgimpl_descr._is_hidden()):
raise HiddenOptionError("trying to access to a hidden option:"
" {0}".format(name))
# disabled options
if self._cfgimpl_toplevel._cfgimpl_disabled and \
(opt_or_descr._is_disabled() or self._cfgimpl_descr._is_disabled()):
raise DisabledOptionError("this option is disabled:"
" {0}".format(name))
# expert options
# XXX currently doesn't look at the group, is it really necessary ?
if self._cfgimpl_toplevel._cfgimpl_mode != 'normal':
if opt_or_descr.get_mode() != 'normal':
raise ModeOptionError("this option's mode is not normal:"
" {0}".format(name))
if type(opt_or_descr) == OptionDescription:
apply_requires(opt_or_descr, self)
def __getattr__(self, name):
# attribute access by passing a path,
# for instance getattr(self, "creole.general.family.adresse_ip_eth0")
if '.' in name:
homeconfig, name = self._cfgimpl_get_home_by_path(name)
return getattr(homeconfig, name)
opt_or_descr = getattr(self._cfgimpl_descr, name)
# symlink options
if type(opt_or_descr) == SymLinkOption:
return getattr(self, opt_or_descr.path)
self._validate(name, opt_or_descr)
# special attributes
if name.startswith('_cfgimpl_'):
# if it were in __dict__ it would have been found already
return self.__dict__[name]
raise AttributeError("%s object has no attribute %s" %
(self.__class__, name))
if name not in self._cfgimpl_values:
raise AttributeError("%s object has no attribute %s" %
(self.__class__, name))
if name in self._cfgimpl_value_owners:
owner = self._cfgimpl_value_owners[name]
# special owners
if owner in special_owners:
return special_owner_factory(name, owner,
default=opt_or_descr.getdefault(),
callback=opt_or_descr.getcallback(),
config=self)
# mandatory options
if not isinstance(opt_or_descr, OptionDescription):
homeconfig = self._cfgimpl_get_toplevel()
mandatory = homeconfig._cfgimpl_mandatory
if opt_or_descr.is_mandatory() and mandatory:
if self._cfgimpl_values[name] == None\
and opt_or_descr.getdefault() == None:
raise MandatoryError("option: {0} is mandatory "
"and shall have a value".format(name))
return self._cfgimpl_values[name]
def __dir__(self):
#from_type = dir(type(self))
from_dict = list(self.__dict__)
extras = list(self._cfgimpl_values)
return sorted(set(extras + from_dict))
def unwrap_from_name(self, name):
# didn't have to stoop so low: `self.get()` must be the proper method
# **and it is slow**: it recursively searches into the namespaces
paths = self.getpaths(allpaths=True)
opts = dict([(path, self.unwrap_from_path(path)) for path in paths])
all_paths = [p.split(".") for p in self.getpaths()]
for pth in all_paths:
if name in pth:
return opts[".".join(pth)]
raise NotFoundError("name: {0} not found".format(name))
def unwrap_from_path(self, path):
# didn't have to stoop so low, `geattr(self, path)` is much better
# **fast**: finds the option directly in the appropriate namespace
if '.' in path:
homeconfig, path = self._cfgimpl_get_home_by_path(path)
return getattr(homeconfig._cfgimpl_descr, path)
return getattr(self._cfgimpl_descr, path)
def __delattr__(self, name):
# if you use delattr you are responsible for all bad things happening
if name.startswith('_cfgimpl_'):
del self.__dict__[name]
return
self._cfgimpl_value_owners[name] = 'default'
opt = getattr(self._cfgimpl_descr, name)
if isinstance(opt, OptionDescription):
raise AttributeError("can't option subgroup")
self._cfgimpl_values[name] = getattr(opt, 'default', None)
def setoption(self, name, value, who=None):
if who == None:
who == self._cfgimpl_owner
child = getattr(self._cfgimpl_descr, name)
if type(child) != SymLinkOption:
if name not in self._cfgimpl_values:
raise AttributeError('unknown option %s' % (name,))
# special owners, a value with a owner *auto* cannot be changed
oldowner = self._cfgimpl_value_owners[child._name]
if oldowner == 'auto':
if who == 'auto':
raise ConflictConfigError('cannot override value to %s for '
'option %s' % (value, name))
if oldowner == who:
oldvalue = getattr(self, name)
if oldvalue == value: #or who in ("default",):
return
child.setoption(self, value, who)
# if the value owner is 'auto', set the option to hidden
if who == 'auto':
if not child._is_hidden():
child.hide()
self._cfgimpl_value_owners[name] = who
else:
homeconfig = self._cfgimpl_get_toplevel()
child.setoption(homeconfig, value, who)
def set(self, **kwargs):
all_paths = [p.split(".") for p in self.getpaths(allpaths=True)]
for key, value in kwargs.iteritems():
key_p = key.split('.')
candidates = [p for p in all_paths if p[-len(key_p):] == key_p]
if len(candidates) == 1:
name = '.'.join(candidates[0])
homeconfig, name = self._cfgimpl_get_home_by_path(name)
try:
getattr(homeconfig, name)
except MandatoryError:
pass
except Exception, e:
raise e # HiddenOptionError or DisabledOptionError
homeconfig.setoption(name, value, self._cfgimpl_owner)
elif len(candidates) > 1:
raise AmbigousOptionError(
'more than one option that ends with %s' % (key, ))
else:
raise NoMatchingOptionFound(
'there is no option that matches %s'
' or the option is hidden or disabled'% (key, ))
def get(self, name):
paths = self.getpaths(allpaths=True)
pathsvalues = []
for path in paths:
pathname = path.split('.')[-1]
if pathname == name:
try:
value = getattr(self, path)
return value
except Exception, e:
raise e
raise NotFoundError("option {0} not found in config".format(name))
def _cfgimpl_get_home_by_path(self, path):
"""returns tuple (config, name)"""
path = path.split('.')
for step in path[:-1]:
self = getattr(self, step)
return self, path[-1]
def _cfgimpl_get_toplevel(self):
while self._cfgimpl_parent is not None:
self = self._cfgimpl_parent
return self
def add_warning(self, warning):
self._cfgimpl_get_toplevel()._cfgimpl_warnings.append(warning)
def get_warnings(self):
return self._cfgimpl_get_toplevel()._cfgimpl_warnings
# ____________________________________________________________
# freeze and read-write statuses
def cfgimpl_freeze(self):
rootconfig = self._cfgimpl_get_toplevel()
rootconfig._cfgimpl_frozen = True
self._cfgimpl_frozen = True
def cfgimpl_unfreeze(self):
rootconfig = self._cfgimpl_get_toplevel()
rootconfig._cfgimpl_frozen = False
self._cfgimpl_frozen = False
def is_frozen(self):
# it should be the same value as self._cfgimpl_frozen...
rootconfig = self._cfgimpl_get_toplevel()
return rootconfig.__dict__['_cfgimpl_frozen']
def cfgimpl_read_only(self):
# hung up on freeze, hidden and disabled concepts
self.cfgimpl_freeze()
rootconfig = self._cfgimpl_get_toplevel()
rootconfig._cfgimpl_hidden = False
rootconfig._cfgimpl_disabled = True
rootconfig._cfgimpl_mandatory = True
def cfgimpl_set_mode(self, mode):
# normal or expert mode
rootconfig = self._cfgimpl_get_toplevel()
if mode not in modes:
raise ConfigError("mode {0} not available".format(mode))
rootconfig._cfgimpl_mode = mode
def cfgimpl_read_write(self):
# hung up on freeze, hidden and disabled concepts
self.cfgimpl_unfreeze()
rootconfig = self._cfgimpl_get_toplevel()
rootconfig._cfgimpl_hidden = True
rootconfig._cfgimpl_disabled = False
rootconfig._cfgimpl_mandatory = False
# ____________________________________________________________
def getkey(self):
return self._cfgimpl_descr.getkey(self)
def __hash__(self):
return hash(self.getkey())
def __eq__(self, other):
return self.getkey() == other.getkey()
def __ne__(self, other):
return not self == other
def __iter__(self):
# iteration only on Options (not OptionDescriptions)
for child in self._cfgimpl_descr._children:
if isinstance(child, Option):
try:
yield child._name, getattr(self, child._name)
except:
pass # hidden, disabled option group
def iter_groups(self, group_type=None):
"iteration on OptionDescriptions"
if group_type == None:
groups = group_types
else:
if group_type not in group_types:
raise TypeError("Unknown group_type: {0}".format(group_type))
groups = [group_type]
for child in self._cfgimpl_descr._children:
if isinstance(child, OptionDescription):
try:
if child.get_group_type() in groups:
yield child._name, getattr(self, child._name)
except:
pass # hidden, disabled option
def __str__(self, indent=""):
lines = []
children = [(child._name, child)
for child in self._cfgimpl_descr._children]
children.sort()
for name, child in children:
if self._cfgimpl_value_owners.get(name, None) == 'default':
continue
value = getattr(self, name)
if isinstance(value, Config):
substr = value.__str__(indent + " ")
else:
substr = "%s %s = %s" % (indent, name, value)
if substr:
lines.append(substr)
if indent and not lines:
return '' # hide subgroups with all default values
lines.insert(0, "%s[%s]" % (indent, self._cfgimpl_descr._name,))
return '\n'.join(lines)
def getpaths(self, include_groups=False, allpaths=False):
"""returns a list of all paths in self, recursively, taking care of
the context (hidden/disabled)
"""
paths = []
for path in self._cfgimpl_descr.getpaths(include_groups=include_groups):
try:
value = getattr(self, path)
except Exception, e:
if not allpaths:
pass # hidden or disabled option
else:
paths.append(path) # hidden or disabled option added
else:
paths.append(path)
return paths
def make_dict(config, flatten=False):
paths = config.getpaths()
pathsvalues = []
for path in paths:
if flatten:
pathname = path.split('.')[-1]
else:
pathname = path
try:
value = getattr(config, path)
pathsvalues.append((pathname, value))
except:
pass # this just a hidden or disabled option
options = dict(pathsvalues)
return options
# ____________________________________________________________

14
doc/Changelog Normal file
View file

@ -0,0 +1,14 @@
2012-03-23
- set_group_type (instead of set_descr())
- iteration utilities (for -> on option, iter_group -> on
OptionDescriptions (group of options)
- hide and disable for option groups (and subgroups) -> not OK
2012-03-20
- get() method for recursive attribute access
- make_path() in a flatten way
- ro and rw

25
doc/Makefile Normal file
View file

@ -0,0 +1,25 @@
SRC=$(wildcard *.txt)
HTMLFRAGMENT=$(addsuffix .html, $(basename $(SRC)))
.SUFFIXES:
.PHONY: all clean
all: html code
# make -C ./build/code all
# make -C ./build/test all
# make -C ./build all
html: $(HTMLFRAGMENT)
%.html: %.txt
./rst2html.py --stylesheet ./build/style.css $< > ./build/$@
code:
./code2html
clean:
make -C ./build clean
make -C ./pydoc/ clean
# make -C ./build/test clean

6
doc/build/Makefile vendored Normal file
View file

@ -0,0 +1,6 @@
.PHONY: clean
.SUFFIXES:
clean:
rm -f *.html
rm -f api/*.html

1
doc/build/api/Readme vendored Normal file
View file

@ -0,0 +1 @@
API's directory

BIN
doc/build/architecture.dia vendored Normal file

Binary file not shown.

BIN
doc/build/architecture.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

1080
doc/build/default.css vendored Normal file

File diff suppressed because it is too large Load diff

255
doc/build/docutils.css vendored Normal file
View file

@ -0,0 +1,255 @@
.first {
margin-top: 0 ! important }
.last {
margin-bottom: 0 ! important }
.hidden {
display: none }
a.toc-backref {
text-decoration: none ;
color: inherit }
blockquote.epigraph {
margin: 2em 5em }
dl.docutils dd {
margin-bottom: 0.5em }
dl.docutils dt {
font-weight: bold }
dl dt { line-height: 150% }
div.abstract {
margin: 2em 5em }
div.abstract p.topic-title {
font-weight: bold ;
text-align: center }
div.admonition, div.attention, div.caution, div.danger, div.error,
div.hint, div.important, div.note, div.tip, div.warning {
margin: 2em ;
border: medium outset ;
padding: 1em }
div.admonition p.admonition-title, div.hint p.admonition-title,
div.important p.admonition-title, div.note p.admonition-title,
div.tip p.admonition-title {
font-weight: bold ;
font-family: sans-serif }
div.attention p.admonition-title, div.caution p.admonition-title,
div.danger p.admonition-title, div.error p.admonition-title,
div.warning p.admonition-title {
color: red ;
font-weight: bold ;
font-family: sans-serif }
div.compound .compound-first, div.compound .compound-middle {
margin-bottom: 0.5em }
div.compound .compound-last, div.compound .compound-middle {
margin-top: 0.5em }
div.dedication {
margin: 2em 5em ;
text-align: center ;
font-style: italic }
div.dedication p.topic-title {
font-weight: bold ;
font-style: normal }
div.document {
width: 600px ;
margin-left: 5em ;
margin-right: 5em }
div.figure {
margin-left: 2em }
div.footer, div.header {
font-size: smaller }
div.line-block {
display: block ;
margin-top: 1em ;
margin-bottom: 1em }
div.line-block div.line-block {
margin-top: 0 ;
margin-bottom: 0 ;
margin-left: 1.5em }
div.sidebar {
margin-left: 1em ;
border: medium outset ;
padding: 1em ;
background-color: #ffffee ;
width: 40% ;
float: right ;
clear: right }
div.sidebar p.rubric {
font-family: sans-serif ;
font-size: medium }
div.system-messages {
margin: 5em }
div.system-messages h1 {
color: red }
div.system-message {
border: medium outset ;
padding: 1em }
div.system-message p.system-message-title {
color: red ;
font-weight: bold }
div.topic {
margin: 2em }
h1, h2, h3, h4, h5 {
font-family: sans-serif ;
line-height: 150% ;
color: orange} /* #666 } */
h1.title {
text-align: center
}
h2.subtitle {
text-align: center }
hr.docutils {
width: 75% }
ol.simple, ul.simple {
margin-bottom: 1em }
ol.arabic {
list-style: decimal }
ol.loweralpha {
list-style: lower-alpha }
ol.upperalpha {
list-style: upper-alpha }
ol.lowerroman {
list-style: lower-roman }
ol.upperroman {
list-style: upper-roman }
p.attribution {
text-align: right ;
margin-left: 50% }
p.caption {
font-style: italic }
p.credits {
font-style: italic ;
font-size: smaller }
p.label {
white-space: nowrap }
p.rubric {
font-weight: bold ;
font-size: larger ;
color: maroon ;
text-align: center }
p.sidebar-title {
font-family: sans-serif ;
font-weight: bold ;
font-size: larger }
p.sidebar-subtitle {
font-family: sans-serif ;
font-weight: bold }
p.topic-title {
font-weight: bold }
pre.address {
margin-bottom: 0 ;
margin-top: 0 ;
font-family: serif ;
font-size: 100% }
pre.line-block {
font-family: serif ;
font-size: 100% }
pre.literal-block, pre.doctest-block {
margin-left: 2em ;
margin-right: 2em ;
font-size: small ;
background-color: #eeeeee }
span.classifier {
font-family: sans-serif ;
font-style: oblique }
span.classifier-delimiter {
font-family: sans-serif ;
font-weight: bold }
span.interpreted {
font-family: sans-serif }
span.option {
white-space: nowrap }
span.option-argument {
font-style: italic }
span.pre {
white-space: pre }
span.problematic {
color: red }
table.citation {
border-left: solid thin gray }
table.docinfo {
/* float: right ; */
margin: 2em 4em ;
color: #666 }
table.docutils {
margin-top: 0.5em ;
margin-bottom: 0.5em }
table.footnote {
border-left: solid thin black }
table.docutils td, table.docutils th,
table.docinfo td, table.docinfo th {
padding-left: 0.5em ;
padding-right: 0.5em ;
vertical-align: top }
th.docinfo-name, th.field-name {
font-weight: bold ;
text-align: right ;
white-space: nowrap }
h1 tt.docutils, h2 tt.docutils, h3 tt.docutils,
h4 tt.docutils, h5 tt.docutils, h6 tt.docutils {
font-size: 100% }
tt.docutils {
background-color: #eeeeee }
ul.auto-toc {
list-style-type: none }

32
doc/build/style.css vendored Normal file
View file

@ -0,0 +1,32 @@
@import url(docutils.css);
@import url(default.css);
a:link {
color: orange;
font-weight: bold;
text-decoration: none;
}
a:visited {
text-decoration: none;
color: #999999;
}
a:hover {
text-decoration: none;
color: #999999;
}
a:active {
text-decoration: none;
color: #999999;
}
.header {
color: orange;
background-color: white;
padding: 1em;
}
.footer {
color: #666;
background-color: inherit;
font-size: 75%;
}

BIN
doc/build/tiramisu.jpeg vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

BIN
doc/build/tiramisu.tar.gz vendored Normal file

Binary file not shown.

72
doc/code2html Executable file
View file

@ -0,0 +1,72 @@
#!/usr/bin/env python
import types
from os.path import join
from inspect import getsource, getmembers, isclass, isfunction, ismethod, ismodule
from importlib import import_module
root="./build/api"
htmltmpl = """
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<title>{title}</title>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<meta http-equiv="content-type" content="application/xhtml+xml; charset=UTF-8">
<meta http-equiv="content-style-type" content="text/css">
<meta http-equiv="expires" content="0">
</head>
<body>
<pre>
{content}
</pre>
</body>
</html>
"""
def write_source(name, content):
fh = file(join(root, name)+'.html', 'w')
fh.write(format_html(name, content))
fh.close()
def format_html(title, content):
return htmltmpl.format(title=title, content=content)
def parse_module(module):
module = import_module(module)
write_source(module.__name__, getsource(module))
# classes = [(cls, value) for cls, value in getattr(module, '__dict__').items() if value == types.ClassType]
classes = getmembers(module, isclass)
for name, obj in classes:
write_source(module.__name__ + '.' + name, getsource(obj))
# methods = [(meth, value) for meth, value in getattr(obj, '__dict__').items() if type(value) == types.MethodType]
methods = getmembers(obj, ismethod)
for meth, value in methods:
write_source(module.__name__ + '.' + name + '.' + meth, getsource(value))
#functions = [(func, value) for func, value in getattr(module, '__dict__').items() if type(value) == types.FunctionType]
functions = getmembers(module, isfunction)
for name, obj in functions:
write_source(module.__name__ + '.' + name, getsource(obj))
def process_modules():
from glob import glob
from os.path import abspath, dirname, normpath, splitext, basename
here = abspath(__file__)
directory = dirname(here)
pyfiles = glob(normpath(join(directory, '..', '*.py')))
for pyf in pyfiles:
pyf = splitext(basename(pyf))[0]
modname = 'tiramisu.' + pyf
if not '__init__' in modname:
parse_module(modname)
pyfiles = glob(normpath(join(directory, '..', 'test', '*.py')))
for pyf in pyfiles:
pyf = splitext(basename(pyf))[0]
modname = 'tiramisu.test.' + pyf
if not '__init__' in modname:
parse_module(modname)
process_modules()

158
doc/config.txt Normal file
View file

@ -0,0 +1,158 @@
.. default-role:: literal
=======================
Configuration Handling
=======================
:module: :api:`config.py`
:tests: - :api:`test_config.py`
- :api:`test_option_setting.py`
Main Assumption
===============
Configuration option objects :api:`config.Config()` are produced at the
entry points and handed down to where they are actually used. This keeps
configuration local but available everywhere and consistent.
`Config` and `Option` objects
==============================
Configuration option objects can be created in different ways. Let's perform
very basic `Config` object manipulations:
::
>>> from tiramisu.config import Config
>>> from tiramisu.option import OptionDescription, BoolOption
>>> descr = OptionDescription("optgroup", "", [
... BoolOption("bool", "", default=False)])
>>>
>>> config = Config(descr)
>>> config.bool
False
>>> config.bool = True
>>> config.bool
True
Take a look at :api:`test_config.test_base_config()` or
:api:`test_config.test_base_config_and_groups()`.
Accessing the configuration `Option`'s
-----------------------------------------
The `Config` object attribute access notation stands for the value of the
configuration's `Option`. That is, the `Config`'s object attribute is the name
of the `Option`, and the value is the value accessed by the `__getattr__`
attribute access mechanism.
If the attribute of the `Config` called by `__getattr__` has not been set before
(by the classic `__setattr__` mechanism), the default value of the `Option`
object is returned, and if no `Option` has been declared in the
`OptionDescription` (that is the schema of the configuration), an
`AttributeError` is raised.
::
>>> gcdummy = BoolOption('dummy', 'dummy', default=False)
>>> gcdummy._name
'dummy'
>>> gcdummy.getdefault()
False
>>> descr = OptionDescription('tiramisu', '', [gcdummy])
>>> cfg = Config(descr)
>>> cfg.dummy
False
>>> cfg.dummy = True
>>> cfg.dummy
True
>>> cfg.idontexist
AttributeError: 'OptionDescription' object has no attribute 'idontexist'
The configuration `Option` objects (in this case the `BoolOption`), are
organized into a tree into nested `OptionDescription` objects. Every
option has a name, as does every option group. The parts of the full
name of the option are separated by dots: e.g.
``config.optgroup.optname``.
**Can you repeat it, what is the protocol of accessing a config's attribute ?**
1. If the option has not been declared, an `AttributeError` is raised,
2. If an option is declared, but neither a value nor a default value has
been set, the returned value is `None`,
3. If an option is declared and a default value has been set, but no value
has been set, the returned value is the default value of the option,
4. If an option is declared, and a value has been set, the returned value is
the value of the option.
If you do not want to use the pythonic way, that is the attribute access
way to obtain the value of the configuration option, you can also search
for it recursively in the whole config namespaces with the ``get()``
method :
::
>>> config.get('bool')
True
To find the right option, `get()` searches recursively into the whole
tree. For example, to find an option which is in the `gc` namespace
there are two possibilites.
If you know the path:
::
>>> config.gc.dummy
False
If you don't remember the path:
::
>>> config.get('dummy')
False
Setting the values of the options
----------------------------------------
An important part of the setting of the configuration consists of setting the
values of the configuration options. There are different ways of setting values,
the first one is of course the `__setattr__` method
::
cfg.name = value
wich has the same effect that the "global" `set()` method : it expects that
the value owner is the default :ref:`glossary#valueowner`
::
cfg.set(name=value)
The global `setoption()` method of the config objects can set a value with a specific owner
::
cfg.setoption('name', value, 'owner')
Finally, the local `setoption()` method directly in the `Option` object can be
used. While the `Option` object refers to his parent, the config knows that the
value has been changed and no bad side effect won't occur
::
>>> booloption = BoolOption('bool', 'Test boolean option', default=True)
>>> descr = OptionDescription('descr', '', [booloption])
>>> cfg = Config(descr)
>>> booloption.setoption(cfg, False, 'owner')
>>> cfg.bool
>>> False

103
doc/configapi.txt Normal file
View file

@ -0,0 +1,103 @@
.. default-role:: literal
Config API Details
==================
:module: :api:`config.py`
:test cases: - :api:`test_config_api.py`
- :api:`test_config_big_example.py`
The handling of options is split into two parts: the description of
which options are available, what their possible values and defaults are
and how they are organized into a tree. A specific choice of options is
bundled into a configuration object which has a reference to its option
description (and therefore makes sure that the configuration values
adhere to the option description).
The configuration object
-------------------------
:api:`config.Config()` object that lives in :api:`config.py` hold the
choosen values for the options (or the default value for the
:api:`option.Option()` object, if no choice was made).
A `Config` object is informed by an :api:`option.OptionDescription`
instance. The attributes of the ``Config`` objects are the names of the
children of the ``OptionDescription``.
Here are the (useful) methods on ``Config``:
:api:`config.Config.__init__(self, descr, **overrides)`:
``descr`` is an instance of :api:`option.OptionDescription` that
describes the configuration object. ``override`` can be used to
set different default values (see method ``override``).
:api:`config.Config.override(self, overrides)`:
override default values. This marks the overridden values as defaults.
``overrides`` is a dictionary of path strings to values.
:api:`config.Config.set(self, **kwargs)`:
"do what I mean"-interface to option setting. Searches all paths
starting from that config for matches of the optional arguments
and sets the found option if the match is not ambiguous.
:api:`config.Config.get(self, name)`:
the behavior is much like the attribute access way, except that
the search for the option is performed recursively in the whole
configuration tree.
:api:`config.Config.cfgimpl_read_write()`:
configuration level `read_write` status, see :doc:`status`
:api:`config.Config.cfgimpl_read_only()`:
configuration level `read_only` status, see :doc:`status`
Here are some private attributes of a `Config()` object, for a
comprehension of the internal merchanism:
- `_cfgimpl_descr =` :api:`option.OptionDescription()`,
e.g. the :ref:`optionapi#schema`
- `_cfgimpl_values` contains the :api:`option.Option()`'s values.
Yes, the values of the options: remember that the values are stored **inside**
the :api:`config.Config()` and not in the `Option()`
`_cfgimpl_values` contains something like that
::
{'int': 0, 'wantframework': False, 'objspace': 'std', 'bool': False,
'str': 'abc', 'gc': <config.Config object at 0xa33f8ec>, 'wantref': False}
We can see that values can also be config objects, it's the
sub-namespaces that are stored in the values as `Config()` objects.
convenience utilities (iteration, exports...)
-----------------------------------------------
With this :api:`config.Config()` configuration management entry point,
it is possible to
- `iter` on config, notice that there is an iteration order wich is
the order of the :ref:`optionapi#schema` specification entries,
- compare two configs (equality),
- export the whole config into a `dict` with :api:`config.make_dict()`,
- `validate()` an option value into a config, see :doc:`consistency`.
:api:`option.Option()` objects in a config are iterable in the pythonic
way, that is something like `[(name, value) for name, value in config]`.
To iter on groups in the same manner, use the
:api:`config.Config.iter_groups()` method wich yields generators too.
**iteration utilities**
:api:`config.Config.__iter__()`
Pythonesque way of parsing group's ordered options.
:api:`config.Config.iter_groups(group_type=None)`:
To iter on groups objects only.
All groups are returned if `group_type` is `None`, otherwise the groups
can be filtered by categories (families, or whatever).

96
doc/consistency.txt Normal file
View file

@ -0,0 +1,96 @@
.. default-role:: literal
The global configuration's consistency
========================================
:module: :api:`config.py`
:tests: :api:`test_option_consistency.py`
Option's values type validation
--------------------------------
When a value is set to the option, the value is validated by the
option's :api:`option.Option()` validator's type.
Notice that if the option is `multi`, that is the `multi` attribute is set to
`True`, then the validation of the option value accepts a list of values
of the same type.
Requirements
------------
Configuration options can specify requirements as parameters at the init
time, the specification of some links between options or groups allows
to carry out a dependencies calculation. For example, an option can ben
hidden if another option has been set with some expected value. This is
just an example, because the possibilities are hudge.
A requirement is specified using a list of triplets. The first element
of the triplet gives the path of the option that is required, the second
element is the value wich is expected to trigger the callback, and the
third one is the callback's action name (`hide`, `show`...)::
stroption = StrOption('str', 'Test string option', default="abc",
requires=[('int', 1, 'hide')])
Take a look at an example here
:api:`test_option_consistency.test_hidden_if_in()`
Config updates
---------------
New configuration options and groups can be dynamically added.
The configuration has to be *updated* after that the description has been
passed to the Config objet, see:
::
>>> config = Config(descr)
>>> newoption = BoolOption('newoption', 'dummy twoo', default=False)
>>> descr.add_child(newoption)
>>> config.update()
>>> config.newoption
False
in
- :api:`test_option_consistency.test_newoption_add_in_descr()`
- :api:`test_option_consistency.test_newoption_add_in_subdescr()`
- :api:`test_option_consistency.test_newoption_add_in_config()`
Validation upon a whole configuration object
----------------------------------------------
An option's integrity can be validated towards a whole configuration.
This type of validation is very open. Let's take a use case : an option
has a certain value, and the value of this option can change the owner
of another option or option group... Everything is possible.
FIXME : put an example here
Identical option names
----------------------
If an :api:`option.Option()` happens to be defined twice in the
:ref:`glossary#schema` (e.g. the :api:`option.OptionDescription()`),
:that is the two options actually have the same name, an exception is raised.
The calculation is currently carried out in the samespace, for example
if `config.gc.name` is defined, another option in `gc` with the name
`name` is **not** allowed, whereas `config.whateverelse.name` is still
allowed.
.. the calculation was carried out by the requires, wich is not a goog idead
Type constraints with the `multi` type
----------------------------------------
By convention, if a multi option has somme requires, the constraints on
the multi type is in all the OptionGroup (a group has to be `multi`, and
a multi of the same length).
See :api:`test_option_consistency.test_multi_constraints()`

View file

@ -0,0 +1,82 @@
.. default-role:: literal
.. include:: inc/preambule.txt
Accès aux variables
====================
Protocole d'accès aux valeurs
-------------------------------
**Créole**
- Si la variable n'a pas été déclarée, une erreur est levée
- Si la variable a été déclarée, mais qu'aucune valeur n'a été définie, (ni valeur affectée, ni valeur par défaut) la valeur retournée est `[]` ou `""` ou `[""]` ou `["",""]`,
- Si la variable a été déclarée et qu'une valeur par défaut a été définie, la valeur retournée et la valeur par défaut,
- Si la variable a été déclarée et qu'une valeur a été définie, la valeur retournée est la valeur de la variable.
**tiramisu**
- Si la variable n'a pas été déclarée, une erreur est levée
- Si la variable a été déclarée, mais qu'aucune valeur n'a été définie, (ni valeur affectée, ni valeur par défaut) la valeur retournée est `None`,
- Si la variable a été déclarée et qu'une valeur par défaut a été définie, la valeur retournée et la valeur par défaut,
- Si la variable a été déclarée et qu'une valeur a été définie, la valeur retournée est la valeur de la variable.
la différence tient au fait de la valeur nulle (`None`) qui a été mal définie
dès le début dans `Créole`.
Accès Créole par "dictionnaire"
--------------------------------
La définition est dans le `XML`
::
<family name="general">
<variable name="adresse_ip_eth0">
Le dictionnaire est chargé dans un `EoleDict()`
::
from creole.cfgparser import EoleDict
eoldict = EoleDict(...)
Un export dans un dictionnaire est necessaire pour manipuler les données
::
from creole.parsedico import parse_dico
flatdict = parse_dico(eoldict)
assert dico['ip'] == '10.10.1.11'
le resultat de l'accès aux données vient de `typeole.EoleVar('ip').get_value()`
Accès `tiramisu` par espace de nommage
----------------------------------------
- espaces de nommages ;
- c'est la configuration qui est responsable de l'accès aux valeurs ;
- une configuration par accès direct (pas d'export) ;
- un point d'entrée unique aisément manipulable grâce aux espaces de nommage.
::
from tiramisu.config import Config
from tiramisu.option import OptionDescription
subdescr = OptionDescription("creole", [IPOption('ip')])
descr = OptionDescription("creole", [subdescr])
config = Config(descr)
assert config.creole.general.ip == '10.10.1.11'
Les valeurs sont dépendantes **de la configuration** et donc la responsabilité
des valeurs dépend de la configuration et pas de la variable elle-même.

View file

@ -0,0 +1,109 @@
.. default-role:: literal
.. include:: inc/preambule.txt
Cohérence des valeurs des variables
====================================
type des variables
-------------------
**Créole**
pas d'unicité du type abstrait : `Multivar`, `CreoleVar` et `TypedVar`
- `String`
- `Ip`
- `Netmask`
- `Number`
- `Boolean`
- `OuiNon`
**tiramisu**
unicité du type abstrait : `Option()`
pas de nouveau type multivalué, mais un attribut des types existants::
>>> from option import BoolOption
>>> boolopt = BoolOption('bool', 'description de bool', multi=True)
tous les types Créole, plus
- `SymlinkOption`
- `CheckOption` qui permet de définir les "oui/non", "On/Off"
Validations suivant l'organisation en familles
-----------------------------------------------
**Créole**
**Organisation par accumulation de références sur des dictionnaires (`EoleDict`)**
On peut charger un EoleDict avec des variables qui pointent vers des families
qui n'existent pas, aucune validation n'est faite (confiance absolument faite au
moment du chargemzent du XML)
exemple, dans l'espace de nommage racine::
<variables>
<variable name="adresse_ip_eth0">
::
from creole.parsedico import parse_dico
flatdict = parse_dico(eoldict)
dico['adresse_ip_eth0']
KeyError: 'adresse_ip_eth0'
**Tiramisu**
**Organisation par arborescence.**
Un espace de nommage doit systématiquement être défini, la variable n'est
accessible **que** par un path.
Variables présentes deux fois
-------------------------------
- Créole : pas de validation possible
- tiramisu : comportement règlable (on autorise l'unicité ou pas)
- dans Créole les valeurs sont **fausses** (c'est la dernière variable qui qui gagne)
Il faut faire confiance au XML
::
<family name="general">
<variable name="adresse_ip_eth0">
<valeur>toto
<family name="services">
<variable name="adresse_ip_eth0">
<valeur>tutu
dans `gen_config` la valeur retenue est::
general/adresse_ip_eth0 -> tutu
services/adresse_ip_eth0 -> tutu
dans `parsedico`, la variable est écrasée::
>>> from creole.parsedico import parse_dico
>>> d = parse_dico()
>>> d['adresse_ip_eth0']
tutu
dans tiramisu::
>>> config.general.adresse_ip_eth0
toto
>>> config.services.adresse_ip_eth0
tutu

View file

@ -0,0 +1,113 @@
.. default-role:: literal
.. include:: inc/preambule.txt
Etats et statuts des options de configuration
================================================
état des variables et lisibilité de l'API
-------------------------------------------
**Creole**
`EoleVar()`
- `get_value()`
- `get_final_value()`
- `get_final_value_at_index()`
- `check_value()`
- `get_prec_value()`
- `get_calculated_value()` -> automatique
**tiramisu**
`Option()`
- **aucune API** d'accès à la valeur d'une option au niveau de l'option de configuration
- `option.getdefault()`
- `option.setoption(config, value, owner)`
variables "automatiques"
------------------------------
si `owner` == 'auto', la variable est automatique et la configuration le sait,
elle lance alors les fonctions de calcul à chaque évaluation
dans Créole, c'est validé aux niveau de la variable par un appel à `eval_func()`
Accès suivant les états de la configuration
--------------------------------------------
- disabled
- hidden
- mode (normal/expert)
- obligatoire (mandatory)
- ...
- `EoleVar.hidden`
- `EoleVar.disabled`
pas d'objet `Family` dans Créole donc l'organisation des hiérarchie de
hidden est opaque
- `EoleDict.families['hidden']` pour avoir accès à l'état d'une famille
dans Tiramisu
- `hidden` au niveau `Option`, `OptionDescription` et **aussi** au niveau de
la configuration ce qui permet d'avoir des états (inexistant dans `Créole`)
.. maitres/esclaves avec Créole : `mavar.get_slaves()`
`hidden_if_in`, `hidden_if_not_in`
-------------------------------------
La notion est généralisée dans tiramisu avec les `requires`.
Dans Créole : très difficile de conserver une cohérence des `hidden_if_in`
quand il y en a plusieurs.
Dans Tiramisu : validation et levée d'exception si les **requirements** sont
incohérents, action inverse si aucun requires n'est matché.
exemple de requires
::
<family name="clamav">
<variable name="activer_clam">
<variable name="activer_clam_exim">
<valeur>non
<variable name="activer_clam_samba">
<valeur>oui
<condition name='hidden_if_in' source='activer_clam_exim'>
<param>non
<target type='variable'>activer_clam
<!-- ça hide (momentanément)-->
<condition name='hidden_if_in' source='activer_clam_samba'>
<param>non
<target type='variable'>activer_clam
<!-- ça show (et c'est le dernier qui a raison) -->
:résultat: `activer_clam` est visible, c'est la dernière condition qui a raison
avec tiramisu, `activer_clam` **dans les même conditions**, est cachée.
::
>>> activer_clam = StrOption('activer_clam', 'activer clamav',
requires=[('activer_clam_exim', 'non', 'hide'),
('activer_clam_samba', 'non', 'hide'),])
>>> config.clamav.activer_clam_exim = 'non'
>>> config.clamav.activer_clam_samba = 'oui'
>>> config.clamav.activer_clam
>>> Traceback (most recent call last):
File "<stdin>", line 1, in <module>
HiddenOptionError("trying to access to a hidden option:activer_clam")
>>>

View file

@ -0,0 +1,7 @@
%.odt: %.txt
rst2odt --create-links --custom-odt-footer="Page %p% de %P%" --endnotes-end-doc --no-generator --stylesheet=styles.odt $< $@
%.html: %.txt
rst2html --stylesheet ./build/style.css $< > ./build/$@

View file

@ -0,0 +1,6 @@
.PHONY: clean
.SUFFIXES:
clean:
rm -f *.html
rm -f api/*.html

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,255 @@
.first {
margin-top: 0 ! important }
.last {
margin-bottom: 0 ! important }
.hidden {
display: none }
a.toc-backref {
text-decoration: none ;
color: inherit }
blockquote.epigraph {
margin: 2em 5em }
dl.docutils dd {
margin-bottom: 0.5em }
dl.docutils dt {
font-weight: bold }
dl dt { line-height: 150% }
div.abstract {
margin: 2em 5em }
div.abstract p.topic-title {
font-weight: bold ;
text-align: center }
div.admonition, div.attention, div.caution, div.danger, div.error,
div.hint, div.important, div.note, div.tip, div.warning {
margin: 2em ;
border: medium outset ;
padding: 1em }
div.admonition p.admonition-title, div.hint p.admonition-title,
div.important p.admonition-title, div.note p.admonition-title,
div.tip p.admonition-title {
font-weight: bold ;
font-family: sans-serif }
div.attention p.admonition-title, div.caution p.admonition-title,
div.danger p.admonition-title, div.error p.admonition-title,
div.warning p.admonition-title {
color: red ;
font-weight: bold ;
font-family: sans-serif }
div.compound .compound-first, div.compound .compound-middle {
margin-bottom: 0.5em }
div.compound .compound-last, div.compound .compound-middle {
margin-top: 0.5em }
div.dedication {
margin: 2em 5em ;
text-align: center ;
font-style: italic }
div.dedication p.topic-title {
font-weight: bold ;
font-style: normal }
div.document {
width: 600px ;
margin-left: 5em ;
margin-right: 5em }
div.figure {
margin-left: 2em }
div.footer, div.header {
font-size: smaller }
div.line-block {
display: block ;
margin-top: 1em ;
margin-bottom: 1em }
div.line-block div.line-block {
margin-top: 0 ;
margin-bottom: 0 ;
margin-left: 1.5em }
div.sidebar {
margin-left: 1em ;
border: medium outset ;
padding: 1em ;
background-color: #ffffee ;
width: 40% ;
float: right ;
clear: right }
div.sidebar p.rubric {
font-family: sans-serif ;
font-size: medium }
div.system-messages {
margin: 5em }
div.system-messages h1 {
color: red }
div.system-message {
border: medium outset ;
padding: 1em }
div.system-message p.system-message-title {
color: red ;
font-weight: bold }
div.topic {
margin: 2em }
h1, h2, h3, h4, h5 {
font-family: sans-serif ;
line-height: 150% ;
color: orange} /* #666 } */
h1.title {
text-align: center
}
h2.subtitle {
text-align: center }
hr.docutils {
width: 75% }
ol.simple, ul.simple {
margin-bottom: 1em }
ol.arabic {
list-style: decimal }
ol.loweralpha {
list-style: lower-alpha }
ol.upperalpha {
list-style: upper-alpha }
ol.lowerroman {
list-style: lower-roman }
ol.upperroman {
list-style: upper-roman }
p.attribution {
text-align: right ;
margin-left: 50% }
p.caption {
font-style: italic }
p.credits {
font-style: italic ;
font-size: smaller }
p.label {
white-space: nowrap }
p.rubric {
font-weight: bold ;
font-size: larger ;
color: maroon ;
text-align: center }
p.sidebar-title {
font-family: sans-serif ;
font-weight: bold ;
font-size: larger }
p.sidebar-subtitle {
font-family: sans-serif ;
font-weight: bold }
p.topic-title {
font-weight: bold }
pre.address {
margin-bottom: 0 ;
margin-top: 0 ;
font-family: serif ;
font-size: 100% }
pre.line-block {
font-family: serif ;
font-size: 100% }
pre.literal-block, pre.doctest-block {
margin-left: 2em ;
margin-right: 2em ;
font-size: small ;
background-color: #eeeeee }
span.classifier {
font-family: sans-serif ;
font-style: oblique }
span.classifier-delimiter {
font-family: sans-serif ;
font-weight: bold }
span.interpreted {
font-family: sans-serif }
span.option {
white-space: nowrap }
span.option-argument {
font-style: italic }
span.pre {
white-space: pre }
span.problematic {
color: red }
table.citation {
border-left: solid thin gray }
table.docinfo {
/* float: right ; */
margin: 2em 4em ;
color: #666 }
table.docutils {
margin-top: 0.5em ;
margin-bottom: 0.5em }
table.footnote {
border-left: solid thin black }
table.docutils td, table.docutils th,
table.docinfo td, table.docinfo th {
padding-left: 0.5em ;
padding-right: 0.5em ;
vertical-align: top }
th.docinfo-name, th.field-name {
font-weight: bold ;
text-align: right ;
white-space: nowrap }
h1 tt.docutils, h2 tt.docutils, h3 tt.docutils,
h4 tt.docutils, h5 tt.docutils, h6 tt.docutils {
font-size: 100% }
tt.docutils {
background-color: #eeeeee }
ul.auto-toc {
list-style-type: none }

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

View file

@ -0,0 +1,76 @@
<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="generator" content="Docutils 0.9: http://docutils.sourceforge.net/" />
<title>rapports eole</title>
<style type="text/css">
@import url(docutils.css);
@import url(default.css);
a:link {
color: orange;
font-weight: bold;
text-decoration: none;
}
a:visited {
text-decoration: none;
color: #999999;
}
a:hover {
text-decoration: none;
color: #999999;
}
a:active {
text-decoration: none;
color: #999999;
}
.header {
color: orange;
background-color: white;
padding: 1em;
}
.footer {
color: #666;
background-color: inherit;
font-size: 75%;
}
</style>
</head>
<body>
<div class="document">
<img alt="imgs/eol.png" class="align-right" src="imgs/eol.png" />
<table class="docutils field-list" frame="void" rules="none">
<col class="field-name" />
<col class="field-body" />
<tbody valign="top">
<tr class="field"><th class="field-name">date:</th><td class="field-body">mai 2012</td>
</tr>
<tr class="field"><th class="field-name">description:</th><td class="field-body">rapports <tt class="docutils literal">Créole</tt>, compatibilités <tt class="docutils literal">Creole</tt> et <tt class="docutils literal">tiramisu</tt></td>
</tr>
</tbody>
</table>
<div class="section" id="vue-d-ensemble-des-rapports">
<h1>Vue d'ensemble des rapports</h1>
<p>Les rapports ci-dessous résument et permettent de donner des points d'appui à
des discussions de recherche et développement concernant l'évolution du
projet <tt class="docutils literal">Creole</tt> (comprenant <tt class="docutils literal">Creole_Serv</tt>). Il y a aussi le support de
documentation développeur <tt class="docutils literal">tiramisu</tt> (en anglais) qui constitue une bonne
base pour connaître et comprendre plus en détails les motivations de
la nouvelle implementation.</p>
<ul class="simple">
<li><a class="reference external" href="pdfreport/D01AccesVariables.pdf">D01AccesVariables.pdf</a></li>
<li><a class="reference external" href="pdfreport/D02CoherenceVariables.pdf">D02CoherenceVariables.pdf</a></li>
<li><a class="reference external" href="pdfreport/D03ReglesEtats.pdf">D03ReglesEtats.pdf</a></li>
</ul>
</div>
</div>
</body>
</html>

View file

@ -0,0 +1,22 @@
#!/usr/bin/env python
import sys
from glob import glob
from os.path import isfile, dirname, abspath, join, basename, splitext
from rst import Rest, Paragraph, Strong, ListItem, Link
here = abspath(dirname(__file__))
html = glob(join(here, '*.pdf'))
basehtml = [basename(htm) for htm in html]
basehtml.sort()
content = Rest()
for htm in basehtml:
link = Link( htm , "pdfreport/" +htm)
content.add(ListItem(link))
sys.stdout.write(content.text())

View file

@ -0,0 +1,410 @@
# unproudly borrowed from pypy :
# http://codespeak.net/svn/pypy/trunk/pypy/tool/rest/rst.py
""" reStructuredText generation tools
provides an api to build a tree from nodes, which can be converted to
ReStructuredText on demand
note that not all of ReST is supported, a usable subset is offered, but
certain features aren't supported, and also certain details (like how links
are generated, or how escaping is done) can not be controlled
"""
import re
def escape(txt):
"""escape ReST markup"""
if not isinstance(txt, str) and not isinstance(txt, unicode):
txt = str(txt)
# XXX this takes a very naive approach to escaping, but it seems to be
# sufficient...
for c in '\\*`|:_':
txt = txt.replace(c, '\\%s' % (c,))
return txt
class RestError(Exception):
""" raised on containment errors (wrong parent) """
class AbstractMetaclass(type):
def __new__(cls, *args):
obj = super(AbstractMetaclass, cls).__new__(cls, *args)
parent_cls = obj.parentclass
if parent_cls is None:
return obj
if not isinstance(parent_cls, list):
class_list = [parent_cls]
else:
class_list = parent_cls
if obj.allow_nesting:
class_list.append(obj)
for _class in class_list:
if not _class.allowed_child:
_class.allowed_child = {obj:True}
else:
_class.allowed_child[obj] = True
return obj
class AbstractNode(object):
""" Base class implementing rest generation
"""
sep = ''
__metaclass__ = AbstractMetaclass
parentclass = None # this exists to allow parent to know what
# children can exist
allow_nesting = False
allowed_child = {}
defaults = {}
_reg_whitespace = re.compile('\s+')
def __init__(self, *args, **kwargs):
self.parent = None
self.children = []
for child in args:
self._add(child)
for arg in kwargs:
setattr(self, arg, kwargs[arg])
def join(self, *children):
""" add child nodes
returns a reference to self
"""
for child in children:
self._add(child)
return self
def add(self, child):
""" adds a child node
returns a reference to the child
"""
self._add(child)
return child
def _add(self, child):
if child.__class__ not in self.allowed_child:
raise RestError("%r cannot be child of %r" % \
(child.__class__, self.__class__))
self.children.append(child)
child.parent = self
def __getitem__(self, item):
return self.children[item]
def __setitem__(self, item, value):
self.children[item] = value
def text(self):
""" return a ReST string representation of the node """
return self.sep.join([child.text() for child in self.children])
def wordlist(self):
""" return a list of ReST strings for this node and its children """
return [self.text()]
class Rest(AbstractNode):
""" Root node of a document """
sep = "\n\n"
def __init__(self, *args, **kwargs):
AbstractNode.__init__(self, *args, **kwargs)
self.links = {}
def render_links(self, check=False):
"""render the link attachments of the document"""
assert not check, "Link checking not implemented"
if not self.links:
return ""
link_texts = []
# XXX this could check for duplicates and remove them...
for link, target in self.links.iteritems():
link_texts.append(".. _`%s`: %s" % (escape(link), target))
return "\n" + "\n".join(link_texts) + "\n\n"
def text(self):
outcome = []
if (isinstance(self.children[0], Transition) or
isinstance(self.children[-1], Transition)):
raise ValueError, ('document must not begin or end with a '
'transition')
for child in self.children:
outcome.append(child.text())
# always a trailing newline
text = self.sep.join([i for i in outcome if i]) + "\n"
return text + self.render_links()
class Transition(AbstractNode):
""" a horizontal line """
parentclass = Rest
def __init__(self, char='-', width=80, *args, **kwargs):
self.char = char
self.width = width
super(Transition, self).__init__(*args, **kwargs)
def text(self):
return (self.width - 1) * self.char
class Paragraph(AbstractNode):
""" simple paragraph """
parentclass = Rest
sep = " "
indent = ""
# FIXME
width = 880
def __init__(self, *args, **kwargs):
# make shortcut
args = list(args)
for num, arg in enumerate(args):
if isinstance(arg, str):
args[num] = Text(arg)
super(Paragraph, self).__init__(*args, **kwargs)
def text(self):
texts = []
for child in self.children:
texts += child.wordlist()
buf = []
outcome = []
lgt = len(self.indent)
def grab(buf):
outcome.append(self.indent + self.sep.join(buf))
texts.reverse()
while texts:
next = texts[-1]
if not next:
texts.pop()
continue
if lgt + len(self.sep) + len(next) <= self.width or not buf:
buf.append(next)
lgt += len(next) + len(self.sep)
texts.pop()
else:
grab(buf)
lgt = len(self.indent)
buf = []
grab(buf)
return "\n".join(outcome)
class SubParagraph(Paragraph):
""" indented sub paragraph """
indent = " "
class Title(Paragraph):
""" title element """
parentclass = Rest
belowchar = "="
abovechar = ""
def text(self):
txt = self._get_text()
lines = []
if self.abovechar:
lines.append(self.abovechar * len(txt))
lines.append(txt)
if self.belowchar:
lines.append(self.belowchar * len(txt))
return "\n".join(lines)
def _get_text(self):
txt = []
for node in self.children:
txt += node.wordlist()
return ' '.join(txt)
class AbstractText(AbstractNode):
parentclass = [Paragraph, Title]
start = ""
end = ""
def __init__(self, _text):
self._text = _text
def text(self):
text = self.escape(self._text)
return self.start + text + self.end
def escape(self, text):
if not isinstance(text, str) and not isinstance(text, unicode):
text = str(text)
if self.start:
text = text.replace(self.start, '\\%s' % (self.start,))
if self.end and self.end != self.start:
text = text.replace(self.end, '\\%s' % (self.end,))
return text
class Text(AbstractText):
def wordlist(self):
text = escape(self._text)
return self._reg_whitespace.split(text)
class LiteralBlock(AbstractText):
parentclass = Rest
start = '::\n\n'
def text(self):
if not self._text.strip():
return ''
text = self.escape(self._text).split('\n')
for i, line in enumerate(text):
if line.strip():
text[i] = ' %s' % (line,)
return self.start + '\n'.join(text)
class Em(AbstractText):
start = "*"
end = "*"
class Strong(AbstractText):
start = "**"
end = "**"
class Quote(AbstractText):
start = '``'
end = '``'
class Anchor(AbstractText):
start = '_`'
end = '`'
class Footnote(AbstractText):
def __init__(self, note, symbol=False):
raise NotImplemented('XXX')
class Citation(AbstractText):
def __init__(self, text, cite):
raise NotImplemented('XXX')
class ListItem(Paragraph):
allow_nesting = True
item_chars = '*+-'
def text(self):
idepth = self.get_indent_depth()
indent = self.indent + (idepth + 1) * ' '
txt = '\n\n'.join(self.render_children(indent))
ret = []
item_char = self.item_chars[idepth]
ret += [indent[len(item_char)+1:], item_char, ' ', txt[len(indent):]]
return ''.join(ret)
def render_children(self, indent):
txt = []
buffer = []
def render_buffer(fro, to):
if not fro:
return
p = Paragraph(indent=indent, *fro)
p.parent = self.parent
to.append(p.text())
for child in self.children:
if isinstance(child, AbstractText):
buffer.append(child)
else:
if buffer:
render_buffer(buffer, txt)
buffer = []
txt.append(child.text())
render_buffer(buffer, txt)
return txt
def get_indent_depth(self):
depth = 0
current = self
while (current.parent is not None and
isinstance(current.parent, ListItem)):
depth += 1
current = current.parent
return depth
class OrderedListItem(ListItem):
item_chars = ["#."] * 5
class DListItem(ListItem):
item_chars = None
def __init__(self, term, definition, *args, **kwargs):
self.term = term
super(DListItem, self).__init__(definition, *args, **kwargs)
def text(self):
idepth = self.get_indent_depth()
indent = self.indent + (idepth + 1) * ' '
txt = '\n\n'.join(self.render_children(indent))
ret = []
ret += [indent[2:], self.term, '\n', txt]
return ''.join(ret)
class Link(AbstractText):
start = '`'
end = '`_'
def __init__(self, _text, target):
self._text = _text
self.target = target
self.rest = None
def text(self):
if self.rest is None:
self.rest = self.find_rest()
if self.rest.links.get(self._text, self.target) != self.target:
raise ValueError('link name %r already in use for a different '
'target' % (self.target,))
self.rest.links[self._text] = self.target
return AbstractText.text(self)
def find_rest(self):
# XXX little overkill, but who cares...
next = self
while next.parent is not None:
next = next.parent
return next
class InternalLink(AbstractText):
start = '`'
end = '`_'
class LinkTarget(Paragraph):
def __init__(self, name, target):
self.name = name
self.target = target
def text(self):
return ".. _`%s`:%s\n" % (self.name, self.target)
class Substitution(AbstractText):
def __init__(self, text, **kwargs):
raise NotImplemented('XXX')
class Directive(Paragraph):
indent = ' '
def __init__(self, name, *args, **options):
self.name = name
self.content = args
super(Directive, self).__init__()
self.options = options
def text(self):
# XXX not very pretty...
txt = '.. %s::' % (self.name,)
options = '\n'.join([' :%s: %s' % (k, v) for (k, v) in
self.options.iteritems()])
if options:
txt += '\n%s' % (options,)
if self.content:
txt += '\n'
for item in self.content:
txt += '\n ' + item
return txt

View file

@ -0,0 +1,32 @@
@import url(docutils.css);
@import url(default.css);
a:link {
color: orange;
font-weight: bold;
text-decoration: none;
}
a:visited {
text-decoration: none;
color: #999999;
}
a:hover {
text-decoration: none;
color: #999999;
}
a:active {
text-decoration: none;
color: #999999;
}
.header {
color: orange;
background-color: white;
padding: 1em;
}
.footer {
color: #666;
background-color: inherit;
font-size: 75%;
}

View file

@ -0,0 +1,12 @@
.. container:: rubric
**Rédacteurs**
| Gwenaël Rémond (gremond@cadoles.com)
| Emmanuel Garette (egarette@cadoles.com)
**Référence**
| ``tiramisu/doc/eole-reports``
| ``git clone ssh://gitosis@git.cadol.es:2222/tiramisu.git``

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

View file

@ -0,0 +1,18 @@
.. csv-table::
.. image:: inc/logo.png, .. image:: inc/eol.png
.. container:: title
Rapports de discussions de recherche et développements
------------
.. container:: subtitle
Comparaison ``tiramisu`` et ``Créole``
.. include:: 00-Redacteur.txt

View file

@ -0,0 +1,33 @@
.. default-role:: literal
.. title:: rapports eole
.. image:: imgs/eol.png
:align: right
:date: mai 2012
:description: rapports `Créole`, compatibilités `Creole` et `tiramisu`
Vue d'ensemble des rapports
===================================
Les rapports ci-dessous résument et permettent de donner des points d'appui à
des discussions de recherche et développement concernant l'évolution du
projet `Creole` (comprenant `Creole_Serv`). Il y a aussi le support de
documentation développeur `tiramisu` (en anglais) qui constitue une bonne
base pour connaître et comprendre plus en détails les motivations de
la nouvelle implementation.
* `D01AccesVariables.pdf`_
* `D02CoherenceVariables.pdf`_
* `D03ReglesEtats.pdf`_
.. _`D03ReglesEtats.pdf`: pdfreport/D03ReglesEtats.pdf
.. _`D02CoherenceVariables.pdf`: pdfreport/D02CoherenceVariables.pdf
.. _`D01AccesVariables.pdf`: pdfreport/D01AccesVariables.pdf

Binary file not shown.

View file

@ -0,0 +1,12 @@
SRC=$(wildcard *.tex)
OBJ=$(subst .tex,.pdf,$(SRC))
pdf: $(OBJ)
%.pdf: %.tex
pdflatex $<
clean:
rm -f $(OBJ)
rm -f *.aux *.log *.toc *.snm *.out *.nav

View file

@ -0,0 +1,16 @@
\begin{frame}
\frametitle{Comparaison entre le noyau de Créole et Tiramisu}
\begin{itemize}
\item \emph{Créole} : \texttt{cfgparser.py + typeeole.py} $ \Rightarrow 2500$ lignes ;
\item \emph{Tiramisu} : \texttt{config.py + option.py} $ \Rightarrow 800$ lignes ;
\item Et en plus :
\begin{itemize}
\item \emph{Créole} valide le type mais pas la structure (fait confiance au \texttt{XML}) ;
\item \emph{Créole} difficile d'ajouter un type à cause de la métaclasse ;
\item \emph{Tiramisu} valide le type \emph{et} la structure, ajout de types aisé.
\end{itemize}
\item \texttt{eole-report/D02CoherenceVariables.pdf}
\end{itemize}
\end{frame}

View file

@ -0,0 +1,33 @@
\begin{frame}
\frametitle{Définition d'un gestionnaire de configuration}
\begin{itemize}
\item \emph{dictionnaire} de données (au sens python) ;
\item clefs-valeurs, mais quelles valeurs exactement ? ;
\item \texttt{eole-report/D01AccesVariables.pdf}
\end{itemize}
\end{frame}
\begin{frame}
\frametitle{Définition d'un gestionnaire de configuration}
\begin{itemize}
\item espaces de nommages ;
\item c'est la configuration qui est responsable de l'accès aux valeurs ;
\item une configuration aisément manipulable ;
\item un point d'entrée unique.
\item \texttt{eole-report/D01AccesVariables.pdf}
\end{itemize}
\end{frame}
\begin{frame}
\frametitle{Définition d'un gestionnaire de configuration 2}
\begin{itemize}
\item serveur de données de configuration ;
\item $1^{ere}$ méthode : exportation (snapshot) d'un état de la config $\Rightarrow$ Créole ;
\item $2^{eme}$ méthode : JIT (just in time) calculation, une modification
de l'état de la configuration est possible \emph{pendant} la manipulation et l'utilisation $\Rightarrow$ Tiramisu.
\item \texttt{doc/getting-started.html}
\end{itemize}
\end{frame}

View file

@ -0,0 +1,51 @@
\begin{frame}
\frametitle{Organisation en espace de nommage}
\begin{itemize}
\item dans \emph{tiramisu} l'accent est mis sur l'organisation arborescente des données ;
\item la validation des options de configuration se fait par l'appartenance aux groupes (families, master/slaves \dots) ;
\item l'organisation en groupes est unifiée par l'espace de nommage ;
\item la lisibilité de l'API excellente, contrairement à \emph{Creole}
\item \texttt{eole-report/D03ReglesEtats.pdf}
\item lisibilité d'une config : \texttt{tiramisu/report/build/index.html} rapport html d'une config
\end{itemize}
\end{frame}
\begin{frame}
\frametitle{Etats de la configuration}
\begin{itemize}
\item système d'états de la configuration par droits d'accès
\item \texttt{read write}, \texttt{read only};
\item correspond à \texttt{freeze}, \texttt{hidden}, \texttt{disabled} \dots ;
\item \texttt{doc/status.html}
\item \texttt{eole-report/D03ReglesEtats.pdf}
\end{itemize}
\end{frame}
\begin{frame}
\frametitle{hidden if in, hidden if not in}
\begin{itemize}
\item les hidden if in, disabled if, \dots sont généralisés
\item dans tiramisu, ce sont des pré-requis sur une (des) variables
\item \texttt{eole-report/D03ReglesEtats.pdf}
\item \texttt{doc/consistency.html}
\end{itemize}
\end{frame}
\begin{frame}
\frametitle{compatibilité Créole : ce qui reste à faire}
\begin{itemize}
\item tous les options spéciales sont implémentées (auto, fill, obligatoire, \dots)
\item tous les états sont implémentés (hidden, disabled, mode (normal/expert), \dots)
\item reste la librairie des fonctions pour les variables automatiques
\item les "valprec" (valeur précédentes)
\item fixer les comportement des hides (sous-groupes récursifs, \dots)
\item validations master/slaves, validations globales (au regard de la configuration entière) éventuellement
\end{itemize}
\end{frame}

View file

@ -0,0 +1,47 @@
%%presentation
\documentclass{beamer}
\usepackage{beamerthemetree}
%%impression
%\documentclass[a4paper,9pt]{extarticle}
%\usepackage{beamerarticle}
%%
% class FR
\usepackage[T1]{fontenc}
\usepackage[utf8]{inputenc}
\usepackage[frenchb]{babel}
% image
\usepackage{graphicx}
% code
%\usepackage{listings}
%\lstset{language=python,
% caption=Descriptive Caption Text,
% label=DescriptiveLabel,
% tabsize=2,
% frame=tb,
% basicstyle=\small,
% }
\usepackage{alltt}
\usecolortheme{crane}
\beamertemplatetransparentcovered
% le logo
%\logo{\includegraphics[height=1cm]{ban.png}}
\title{Présentation de Tiramisu}
\subtitle{gestionnaire de configuration}
\author{REMOND Gwenaël}
\institute{Cadoles}
\date{\today}
\begin{document}
\frame{\titlepage}
\include{definition}
\include{comparaison}
\include{statut}
\end{document}

3
doc/epydoc.sh Executable file
View file

@ -0,0 +1,3 @@
epydoc --css grayscale -o ./pydoc ../config.py ../option.py
#apirst2html.py --stylesheet=docutils.css --external-api=epydoc --external-api-root=epydoc:./api/ --external-api-file=epydoc:./api/api-objects.txt doc.txt > doc.htm

70
doc/gaspacho.txt Normal file
View file

@ -0,0 +1,70 @@
- abstract values from `gaspacho`
Les types possibles :
- sans valeur : `boolean`
- avec valeur : `unicode` (un texte libre), `integer` (un chiffre), `enum` (une liste de choix prédéfinies) et `list` (une liste de choix libres).
Les types sans valeurs sont les plus simples. Par exemple cette règle nattend
aucune valeur particulière Vérifier que Firefox est le navigateur par défaut.
Alors que celle-ci attend une adresse IP Configuration du serveur proxy manuelle.
Il existe un autre type (multi) qui permet de mêler plusieurs types.
Il sagit bien de définir ici le type de la règle (et uniquement de la règle).
- configuration levels in `creole`
*thu, 28 april 2011*
Exemple de niveau de configuration (dans l'ordre) :
1. - Coeur
2.
- Coeur
- gen_config
3.
- Coeur
- gen_config
- EAD
4.
- Coeur
- EAD
5.
- Coeur
- baculaconfig.py
(`fill` : calcule une valeur jusqu'à ce que l'utilisateur change la
valeur)
Gestion des ACL en écriture :
Le coeur charge les variables
- si auto : seul le coeur peut la modifier (cas 1) ;
- si fill : le coeur calcule une valeur tant que pas configuré par
l'utilisateur. L'utilisateur peut modifier (cas 2 ou 3) ;
- des variables modifiables que par gen_config (cas 2) ;
- des variables modifiables par gen_config ou l'EAD (cas 3) ;
- des variables d'autres applications (cas 4 et 5).
Gestion des ACLs en lecture :
- seule une application peut lire certaines variables (exemple un mot de
passe).

68
doc/getting-started.txt Normal file
View file

@ -0,0 +1,68 @@
==================================
`Tiramisu` - Getting Started
==================================
What is Configuration handling ?
=================================
Due to more and more available configuration options required to set up
an operating system, it became quite annoying to hand the necessary
options to where they are actually used and even more annoying to add
new options. To circumvent these problems the configuration management
was introduced.
What is Tiramisu ?
===================
Tiramisu is yet another configuration handler, wich aims at producing
flexible and fast configuration options access. The main advantages are
its access :ref:`glossary#rules` and the fact that the configuration 's
consistency is preserved at any time, see :ref:`glossary#consistency`.
There are type and structures's validations for configuration options,
and validations towards the whole configuration.
Last but not least, configuration options can be reached and changed
according to the access rules from nearly everywhere in the OS boxes,
e.g. the containers via the `http/json` server.
Just the facts
==============
.. _gettingtiramisu:
Download
---------
To obtain a copy of the sources, check it out from the repository using
`git`. We suggest using `git` if one wants to access the current development.
::
git clone ssh://gitosis@git.cadol.es:2222/tiramisu.git
This will get you a fresh checkout of the code repository in a local
directory named ``tiramisu``.
Understanding Tiramisu's architecture
--------------------------------------
The :ref:`glossary#schema` is loaded from an XML file, and the values of
the configuration options are recovered from a `.ini` like file.
By now, all the in-depth informations about the configuration are stored
in a **single** object, the :api:`config.Config()` object, wich is
responsible of nearly everything. All the necessary options are stored
into a configuration object, which is available nearly everywhere, so
that adding new options becomes trivial.
This `Config()` is available from everywhere with the help of an http server
that serves configuration datas as `json` strings (take a look at the server
here: :api:`server`).
.. figure:: architecture.png
The basics of Tiramisu's architecture.
Once loaded, http server serves the :api:`config.Config()` object, that is,
the configuration options and the configuration groups.

94
doc/glossary.txt Normal file
View file

@ -0,0 +1,94 @@
.. default-role:: literal
glossary
==========
.. _configuration:
**configuration**
Global configuration object, wich contains the whole configuration
options *and* their descriptions (option types and group)
.. _`option description`:
.. _`schema`:
**schema**:
**option description**
see :api:`option.OptionDescription`, see :ref:`optionapi#schema`
The schema of a configuration :
- the option types
- how they are organised in groups or even subgroups, that's why we
call them **groups** too.
.. _`configoption`:
**configuration option**
An option object wich has a name and a value and can be accessed
from the configuration object
.. _`defaultvalue`:
**default value**
Default value of a configuration option. The default value can be
set at instanciation time, or even at any moment. Remember that if
you reset the default value, the owner reset to `default`
.. _`rules`:
**acces rules**
Access rules are : :api:`config.Config.cfgimpl_read_write()` or
:api:`config.Config.cfgimpl_read_only()`, see :doc:`status`
**freeze**
A whole configuration can be frozen (used in read only access). See
:doc:`status` for details.
.. _`valueowner`:
**value owner**
When an option is modified, including at the instanciation, we
always know who has modified it. It's the owner of the option, see
:doc:`status` for more details.
**hidden option**
a hidden option has a different behaviour on regards to the access
of the value in the configuration, see :doc:`status` for more details.
**disabled option**
FIXME
**fill option**
FIXME
**auto option**
FIXME
.. _mandatory:
**mandatory option**
A mandatory option is a configuration option wich value has to be
set, that is the default value cannot be `None`, see
:ref:`optionapi#optioninit`
.. _consistency:
**consistency**
Preserve the consistency in a whole configuration is a tricky thing,
tiramisu takes care of it for you, see :doc:`consistency` for details.

37
doc/index.txt Normal file
View file

@ -0,0 +1,37 @@
.. default-role:: literal
.. meta::
:description: configuration management
:keywords: config, configuration
.. title:: tiramisu
.. |version| replace:: 0.1
The tasting of `Tiramisu`
=========================
.. image:: tiramisu.jpeg
:height: 150px
`Tiramisu`
is a cool, refreshing Italian dessert,
it is also a configuration management tool.
It's a pretty small, local (that is, straight on the operating system)
configuration handler.
- :doc:`getting-started`: where to go from here,
- :doc:`config` explains the good praticies of configuration handling,
- :doc:`configapi` and :doc:`optionapi` describe the API's details,
- :doc:`status` for a summary of the `Option`'s and `Config`'s statuses,
- :doc:`consistency` for the local and global integrity constraints,
- :doc:`glossary` describes the specific terms used in Tiramisu.

127
doc/optionapi.txt Normal file
View file

@ -0,0 +1,127 @@
.. default-role:: literal
Options API Details
=====================
:module: :api:`option.py`
.. _schema:
Description of Options
----------------------
All the constructors take a ``name`` and a ``doc`` argument as first
arguments to give the option or option group a name and to document it.
Most constructors take a ``default`` argument that specifies the default
value of the option. If this argument is not supplied the default value
is assumed to be ``None``.
Appart from that, the `Option` object is not supposed to contain any
other value than the `tainted` attribute, which is explained later. The
container of the value is in the `Config` object.
``OptionDescription``
+++++++++++++++++++++
This class is used to group suboptions.
``__init__(self, name, doc, children)``
``children`` is a list of option descriptions (including
``OptionDescription`` instances for nested namespaces).
``set_group_type(self, group_name)``
Three available group_types : `default`, `family`, `group` and
`master` (for master~slave group type). Notice that for a
master~slave group, the name of the group and the name of the
master option are identical.
`Options description` objects lives in the `_cfgimpl_descr` config attribute.
If you need to access an option object, you can do it with the OptionDescription
object. Not only the value of the option by attribute access, but the option
object itself that lives behind the scene. It can always be accessed internally
with the `_cfgimpl_descr` attribute of the `config` objects. For example, with a
option named `name` in a `gc` group the `name` object can be accessed like
this::
conf._cfgimpl_descr.name
of sub configs with ::
conf.gc._cfgimpl_descr.name
This is a binding. The option objects are in the `_children` config's attribute.
Why accessing an option object ? It is possible for example freeze the
configuration option
::
conf.gc._cfgimpl_descr.dummy.freeze()
or to hide it, or disable it, or... anything.
.. _optioninit:
generic option ``__init__`` method:
``__init__(name, doc, default=None, requires=None, multi=False, mandatory=False)``
:``default``: specifies the default value of the option.
:``requires``: is a list of names of options located anywhere in the configuration.
:``multi``: means the value can be a list.
:``mandatory``: see :ref:`glossary#mandatory`.
.. _optiontype:
``BoolOption``
++++++++++++++
Represents a choice between ``True`` and ``False``.
``IntOption``
+++++++++++++
Represents a choice of an integer.
``FloatOption``
+++++++++++++++
Represents a choice of a floating point number.
``StrOption``
+++++++++++++
Represents the choice of a string.
``SymLinkOption``
++++++++++++++++++
Redirects to another configuration option in the configuration, that is :
- retrieves the value of the tagert,
- can set the value of the target too.
``__init__(self, name, path)``
`path` is the path to the target, the option
``IPOption``
+++++++++++++
Represents the choice of an ip.
``NetmaskOption``
+++++++++++++++++++
Represents the choice of a netmask.
``ChoiceOption``
++++++++++++++++
Represents a choice out of several objects. The option can also have the value
``None``.
``__init__(self, name, doc, values, default=None, requires=None)``
``values`` is a list of values the option can possibly take.

5
doc/pydoc/Makefile Normal file
View file

@ -0,0 +1,5 @@
.PHONY: clean
.SUFFIXES:
clean:
rm -f *.html

BIN
doc/pydoc/crarr.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 340 B

322
doc/pydoc/epydoc.css Normal file
View file

@ -0,0 +1,322 @@
/* Epydoc CSS Stylesheet
*
* This stylesheet can be used to customize the appearance of epydoc's
* HTML output.
*
*/
/* Default Colors & Styles
* - Set the default foreground & background color with 'body'; and
* link colors with 'a:link' and 'a:visited'.
* - Use bold for decision list terms.
* - The heading styles defined here are used for headings *within*
* docstring descriptions. All headings used by epydoc itself use
* either class='epydoc' or class='toc' (CSS styles for both
* defined below).
*/
body { background: #ffffff; color: #000000; }
p { margin-top: 0.5em; margin-bottom: 0.5em; }
a:link { color: #000000; }
a:visited { color: #404040; }
dt { font-weight: bold; }
h1 { font-size: +140%; font-style: italic;
font-weight: bold; }
h2 { font-size: +125%; font-style: italic;
font-weight: bold; }
h3 { font-size: +110%; font-style: italic;
font-weight: normal; }
code { font-size: 100%; }
/* N.B.: class, not pseudoclass */
a.link { font-family: monospace; }
/* Page Header & Footer
* - The standard page header consists of a navigation bar (with
* pointers to standard pages such as 'home' and 'trees'); a
* breadcrumbs list, which can be used to navigate to containing
* classes or modules; options links, to show/hide private
* variables and to show/hide frames; and a page title (using
* <h1>). The page title may be followed by a link to the
* corresponding source code (using 'span.codelink').
* - The footer consists of a navigation bar, a timestamp, and a
* pointer to epydoc's homepage.
*/
h1.epydoc { margin: 0; font-size: +140%; font-weight: bold; }
h2.epydoc { font-size: +130%; font-weight: bold; }
h3.epydoc { font-size: +115%; font-weight: bold;
margin-top: 0.2em; }
td h3.epydoc { font-size: +115%; font-weight: bold;
margin-bottom: 0; }
table.navbar { background: #c0c0c0; color: #000000;
border: 2px groove #d0d0d0; }
table.navbar table { color: #000000; }
th.navbar-select { background: #b0b0b0;
color: #000000; }
table.navbar a { text-decoration: none; }
table.navbar a:link { color: #000000; }
table.navbar a:visited { color: #404040; }
span.breadcrumbs { font-size: 85%; font-weight: bold; }
span.options { font-size: 70%; }
span.codelink { font-size: 85%; }
td.footer { font-size: 85%; }
/* Table Headers
* - Each summary table and details section begins with a 'header'
* row. This row contains a section title (marked by
* 'span.table-header') as well as a show/hide private link
* (marked by 'span.options', defined above).
* - Summary tables that contain user-defined groups mark those
* groups using 'group header' rows.
*/
td.table-header { background: #b0b0b0; color: #000000;
border: 1px solid #808080; }
td.table-header table { color: #000000; }
td.table-header table a:link { color: #000000; }
td.table-header table a:visited { color: #404040; }
span.table-header { font-size: 120%; font-weight: bold; }
th.group-header { background: #e0e0e0; color: #000000;
text-align: left; font-style: italic;
font-size: 115%;
border: 1px solid #808080; }
/* Summary Tables (functions, variables, etc)
* - Each object is described by a single row of the table with
* two cells. The left cell gives the object's type, and is
* marked with 'code.summary-type'. The right cell gives the
* object's name and a summary description.
* - CSS styles for the table's header and group headers are
* defined above, under 'Table Headers'
*/
table.summary { border-collapse: collapse;
background: #f0f0f0; color: #000000;
border: 1px solid #808080;
margin-bottom: 0.5em; }
td.summary { border: 1px solid #808080; }
code.summary-type { font-size: 85%; }
table.summary a:link { color: #000000; }
table.summary a:visited { color: #404040; }
/* Details Tables (functions, variables, etc)
* - Each object is described in its own div.
* - A single-row summary table w/ table-header is used as
* a header for each details section (CSS style for table-header
* is defined above, under 'Table Headers').
*/
table.details { border-collapse: collapse;
background: #f0f0f0; color: #000000;
border: 1px solid #808080;
margin: .2em 0 0 0; }
table.details table { color: #000000; }
table.details a:link { color: #000000; }
table.details a:visited { color: #404040; }
/* Fields */
dl.fields { margin-left: 2em; margin-top: 1em;
margin-bottom: 1em; }
dl.fields dd ul { margin-left: 0em; padding-left: 0em; }
dl.fields dd ul li ul { margin-left: 2em; padding-left: 0em; }
div.fields { margin-left: 2em; }
div.fields p { margin-bottom: 0.5em; }
/* Index tables (identifier index, term index, etc)
* - link-index is used for indices containing lists of links
* (namely, the identifier index & term index).
* - index-where is used in link indices for the text indicating
* the container/source for each link.
* - metadata-index is used for indices containing metadata
* extracted from fields (namely, the bug index & todo index).
*/
table.link-index { border-collapse: collapse;
background: #f0f0f0; color: #000000;
border: 1px solid #808080; }
td.link-index { border-width: 0px; }
table.link-index a:link { color: #000000; }
table.link-index a:visited { color: #404040; }
span.index-where { font-size: 70%; }
table.metadata-index { border-collapse: collapse;
background: #f0f0f0; color: #000000;
border: 1px solid #808080;
margin: .2em 0 0 0; }
td.metadata-index { border-width: 1px; border-style: solid; }
table.metadata-index a:link { color: #000000; }
table.metadata-index a:visited { color: #404040; }
/* Function signatures
* - sig* is used for the signature in the details section.
* - .summary-sig* is used for the signature in the summary
* table, and when listing property accessor functions.
* */
.sig-name { color: #606060; }
.sig-arg { color: #808080; }
.sig-default { color: #202020; }
.summary-sig { font-family: monospace; }
.summary-sig-name { color: #606060; font-weight: bold; }
table.summary a.summary-sig-name:link
{ color: #606060; font-weight: bold; }
table.summary a.summary-sig-name:visited
{ color: #606060; font-weight: bold; }
.summary-sig-arg { color: #606060; }
.summary-sig-default { color: #181818; }
/* Subclass list
*/
ul.subclass-list { display: inline; }
ul.subclass-list li { display: inline; }
/* To render variables, classes etc. like functions */
table.summary .summary-name { color: #606060; font-weight: bold;
font-family: monospace; }
table.summary
a.summary-name:link { color: #606060; font-weight: bold;
font-family: monospace; }
table.summary
a.summary-name:visited { color: #606060; font-weight: bold;
font-family: monospace; }
/* Variable values
* - In the 'variable details' sections, each varaible's value is
* listed in a 'pre.variable' box. The width of this box is
* restricted to 80 chars; if the value's repr is longer than
* this it will be wrapped, using a backslash marked with
* class 'variable-linewrap'. If the value's repr is longer
* than 3 lines, the rest will be ellided; and an ellipsis
* marker ('...' marked with 'variable-ellipsis') will be used.
* - If the value is a string, its quote marks will be marked
* with 'variable-quote'.
* - If the variable is a regexp, it is syntax-highlighted using
* the re* CSS classes.
*/
pre.variable { padding: .5em; margin: 0;
background: #e4e4e4; color: #000000;
border: 1px solid #888888; }
.variable-linewrap { color: #404040; font-weight: bold; }
.variable-ellipsis { color: #404040; font-weight: bold; }
.variable-quote { color: #404040; font-weight: bold; }
.variable-group { color: #808080; font-weight: bold; }
.variable-op { color: #404040; font-weight: bold; }
.variable-string { color: #606060; }
.variable-unknown { color: #000000; font-weight: bold; }
.re { color: #000000; }
.re-char { color: #606060; }
.re-op { color: #000000; }
.re-group { color: #303030; }
.re-ref { color: #404040; }
/* Base tree
* - Used by class pages to display the base class hierarchy.
*/
pre.base-tree { font-size: 80%; margin: 0; }
/* Frames-based table of contents headers
* - Consists of two frames: one for selecting modules; and
* the other listing the contents of the selected module.
* - h1.toc is used for each frame's heading
* - h2.toc is used for subheadings within each frame.
*/
h1.toc { text-align: center; font-size: 105%;
margin: 0; font-weight: bold;
padding: 0; }
h2.toc { font-size: 100%; font-weight: bold;
margin: 0.5em 0 0 -0.3em; }
/* Syntax Highlighting for Source Code
* - doctest examples are displayed in a 'pre.py-doctest' block.
* If the example is in a details table entry, then it will use
* the colors specified by the 'table pre.py-doctest' line.
* - Source code listings are displayed in a 'pre.py-src' block.
* Each line is marked with 'span.py-line' (used to draw a line
* down the left margin, separating the code from the line
* numbers). Line numbers are displayed with 'span.py-lineno'.
* The expand/collapse block toggle button is displayed with
* 'a.py-toggle' (Note: the CSS style for 'a.py-toggle' should not
* modify the font size of the text.)
* - If a source code page is opened with an anchor, then the
* corresponding code block will be highlighted. The code
* block's header is highlighted with 'py-highlight-hdr'; and
* the code block's body is highlighted with 'py-highlight'.
* - The remaining py-* classes are used to perform syntax
* highlighting (py-string for string literals, py-name for names,
* etc.)
*/
pre.py-doctest { padding: .5em; margin: 1em;
background: #f0f0f0; color: #000000;
border: 1px solid #888888; }
table pre.py-doctest { background: #e4e4e4;
color: #000000; }
pre.py-src { border: 2px solid #000000;
background: #f0f0f0; color: #000000; }
.py-line { border-left: 2px solid #000000;
margin-left: .2em; padding-left: .4em; }
.py-lineno { font-style: italic; font-size: 90%;
padding-left: .5em; }
a.py-toggle { text-decoration: none; }
div.py-highlight-hdr { border-top: 2px solid #000000;
border-bottom: 2px solid #000000;
background: #e8e8e8; }
div.py-highlight { border-bottom: 2px solid #000000;
background: #e0e0e0; }
.py-prompt { color: #505050; font-weight: bold;}
.py-more { color: #505050; font-weight: bold;}
.py-string { color: #606060; }
.py-comment { color: #303030; }
.py-keyword { color: #000000; }
.py-output { color: #404040; }
.py-name { color: #000000; }
.py-name:link { color: #000000 !important; }
.py-name:visited { color: #000000 !important; }
.py-number { color: #505050; }
.py-defname { color: #000000; font-weight: bold; }
.py-def-name { color: #000000; font-weight: bold; }
.py-base-class { color: #000000; }
.py-param { color: #000000; }
.py-docstring { color: #606060; }
.py-decorator { color: #404040; }
/* Use this if you don't want links to names underlined: */
/*a.py-name { text-decoration: none; }*/
/* Graphs & Diagrams
* - These CSS styles are used for graphs & diagrams generated using
* Graphviz dot. 'img.graph-without-title' is used for bare
* diagrams (to remove the border created by making the image
* clickable).
*/
img.graph-without-title { border: none; }
img.graph-with-title { border: 1px solid #000000; }
span.graph-title { font-weight: bold; }
span.graph-caption { }
/* General-purpose classes
* - 'p.indent-wrapped-lines' defines a paragraph whose first line
* is not indented, but whose subsequent lines are.
* - The 'nomargin-top' class is used to remove the top margin (e.g.
* from lists). The 'nomargin' class is used to remove both the
* top and bottom margin (but not the left or right margin --
* for lists, that would cause the bullets to disappear.)
*/
p.indent-wrapped-lines { padding: 0 0 0 7em; text-indent: -7em;
margin: 0; }
.nomargin-top { margin-top: 0; }
.nomargin { margin-top: 0; margin-bottom: 0; }
/* HTML Log */
div.log-block { padding: 0; margin: .5em 0 .5em 0;
background: #f0f0f0; color: #000000;
border: 1px solid #000000; }
div.log-error { padding: .1em .3em .1em .3em; margin: 4px;
background: #b0b0b0; color: #000000;
border: 1px solid #000000; }
div.log-warning { padding: .1em .3em .1em .3em; margin: 4px;
background: #ffffff; color: #000000;
border: 1px solid #000000; }
div.log-info { padding: .1em .3em .1em .3em; margin: 4px;
background: #ffffff; color: #000000;
border: 1px solid #000000; }
h2.log-hdr { background: #b0b0b0; color: #000000;
margin: 0; padding: 0em 0.5em 0em 0.5em;
border-bottom: 1px solid #000000; font-size: 110%; }
p.log { font-weight: bold; margin: .5em 0 .5em 0; }
tr.opt-changed { color: #000000; font-weight: bold; }
tr.opt-default { color: #606060; }
pre.log { margin: 0; padding: 0; padding-left: 1em; }

293
doc/pydoc/epydoc.js Normal file
View file

@ -0,0 +1,293 @@
function toggle_private() {
// Search for any private/public links on this page. Store
// their old text in "cmd," so we will know what action to
// take; and change their text to the opposite action.
var cmd = "?";
var elts = document.getElementsByTagName("a");
for(var i=0; i<elts.length; i++) {
if (elts[i].className == "privatelink") {
cmd = elts[i].innerHTML;
elts[i].innerHTML = ((cmd && cmd.substr(0,4)=="show")?
"hide&nbsp;private":"show&nbsp;private");
}
}
// Update all DIVs containing private objects.
var elts = document.getElementsByTagName("div");
for(var i=0; i<elts.length; i++) {
if (elts[i].className == "private") {
elts[i].style.display = ((cmd && cmd.substr(0,4)=="hide")?"none":"block");
}
else if (elts[i].className == "public") {
elts[i].style.display = ((cmd && cmd.substr(0,4)=="hide")?"block":"none");
}
}
// Update all table rows containing private objects. Note, we
// use "" instead of "block" becaue IE & firefox disagree on what
// this should be (block vs table-row), and "" just gives the
// default for both browsers.
var elts = document.getElementsByTagName("tr");
for(var i=0; i<elts.length; i++) {
if (elts[i].className == "private") {
elts[i].style.display = ((cmd && cmd.substr(0,4)=="hide")?"none":"");
}
}
// Update all list items containing private objects.
var elts = document.getElementsByTagName("li");
for(var i=0; i<elts.length; i++) {
if (elts[i].className == "private") {
elts[i].style.display = ((cmd && cmd.substr(0,4)=="hide")?
"none":"");
}
}
// Update all list items containing private objects.
var elts = document.getElementsByTagName("ul");
for(var i=0; i<elts.length; i++) {
if (elts[i].className == "private") {
elts[i].style.display = ((cmd && cmd.substr(0,4)=="hide")?"none":"block");
}
}
// Set a cookie to remember the current option.
document.cookie = "EpydocPrivate="+cmd;
}
function show_private() {
var elts = document.getElementsByTagName("a");
for(var i=0; i<elts.length; i++) {
if (elts[i].className == "privatelink") {
cmd = elts[i].innerHTML;
if (cmd && cmd.substr(0,4)=="show")
toggle_private();
}
}
}
function getCookie(name) {
var dc = document.cookie;
var prefix = name + "=";
var begin = dc.indexOf("; " + prefix);
if (begin == -1) {
begin = dc.indexOf(prefix);
if (begin != 0) return null;
} else
{ begin += 2; }
var end = document.cookie.indexOf(";", begin);
if (end == -1)
{ end = dc.length; }
return unescape(dc.substring(begin + prefix.length, end));
}
function setFrame(url1, url2) {
parent.frames[1].location.href = url1;
parent.frames[2].location.href = url2;
}
function checkCookie() {
var cmd=getCookie("EpydocPrivate");
if (cmd && cmd.substr(0,4)!="show" && location.href.indexOf("#_") < 0)
toggle_private();
}
function toggleCallGraph(id) {
var elt = document.getElementById(id);
if (elt.style.display == "none")
elt.style.display = "block";
else
elt.style.display = "none";
}
function expand(id) {
var elt = document.getElementById(id+"-expanded");
if (elt) elt.style.display = "block";
var elt = document.getElementById(id+"-expanded-linenums");
if (elt) elt.style.display = "block";
var elt = document.getElementById(id+"-collapsed");
if (elt) { elt.innerHTML = ""; elt.style.display = "none"; }
var elt = document.getElementById(id+"-collapsed-linenums");
if (elt) { elt.innerHTML = ""; elt.style.display = "none"; }
var elt = document.getElementById(id+"-toggle");
if (elt) { elt.innerHTML = "-"; }
}
function collapse(id) {
var elt = document.getElementById(id+"-expanded");
if (elt) elt.style.display = "none";
var elt = document.getElementById(id+"-expanded-linenums");
if (elt) elt.style.display = "none";
var elt = document.getElementById(id+"-collapsed-linenums");
if (elt) { elt.innerHTML = "<br />"; elt.style.display="block"; }
var elt = document.getElementById(id+"-toggle");
if (elt) { elt.innerHTML = "+"; }
var elt = document.getElementById(id+"-collapsed");
if (elt) {
elt.style.display = "block";
var indent = elt.getAttribute("indent");
var pad = elt.getAttribute("pad");
var s = "<tt class='py-lineno'>";
for (var i=0; i<pad.length; i++) { s += "&nbsp;" }
s += "</tt>";
s += "&nbsp;&nbsp;<tt class='py-line'>";
for (var i=0; i<indent.length; i++) { s += "&nbsp;" }
s += "<a href='#' onclick='expand(\"" + id;
s += "\");return false'>...</a></tt><br />";
elt.innerHTML = s;
}
}
function toggle(id) {
elt = document.getElementById(id+"-toggle");
if (elt.innerHTML == "-")
collapse(id);
else
expand(id);
return false;
}
function highlight(id) {
var elt = document.getElementById(id+"-def");
if (elt) elt.className = "py-highlight-hdr";
var elt = document.getElementById(id+"-expanded");
if (elt) elt.className = "py-highlight";
var elt = document.getElementById(id+"-collapsed");
if (elt) elt.className = "py-highlight";
}
function num_lines(s) {
var n = 1;
var pos = s.indexOf("\n");
while ( pos > 0) {
n += 1;
pos = s.indexOf("\n", pos+1);
}
return n;
}
// Collapse all blocks that mave more than `min_lines` lines.
function collapse_all(min_lines) {
var elts = document.getElementsByTagName("div");
for (var i=0; i<elts.length; i++) {
var elt = elts[i];
var split = elt.id.indexOf("-");
if (split > 0)
if (elt.id.substring(split, elt.id.length) == "-expanded")
if (num_lines(elt.innerHTML) > min_lines)
collapse(elt.id.substring(0, split));
}
}
function expandto(href) {
var start = href.indexOf("#")+1;
if (start != 0 && start != href.length) {
if (href.substring(start, href.length) != "-") {
collapse_all(4);
pos = href.indexOf(".", start);
while (pos != -1) {
var id = href.substring(start, pos);
expand(id);
pos = href.indexOf(".", pos+1);
}
var id = href.substring(start, href.length);
expand(id);
highlight(id);
}
}
}
function kill_doclink(id) {
var parent = document.getElementById(id);
parent.removeChild(parent.childNodes.item(0));
}
function auto_kill_doclink(ev) {
if (!ev) var ev = window.event;
if (!this.contains(ev.toElement)) {
var parent = document.getElementById(this.parentID);
parent.removeChild(parent.childNodes.item(0));
}
}
function doclink(id, name, targets_id) {
var elt = document.getElementById(id);
// If we already opened the box, then destroy it.
// (This case should never occur, but leave it in just in case.)
if (elt.childNodes.length > 1) {
elt.removeChild(elt.childNodes.item(0));
}
else {
// The outer box: relative + inline positioning.
var box1 = document.createElement("div");
box1.style.position = "relative";
box1.style.display = "inline";
box1.style.top = 0;
box1.style.left = 0;
// A shadow for fun
var shadow = document.createElement("div");
shadow.style.position = "absolute";
shadow.style.left = "-1.3em";
shadow.style.top = "-1.3em";
shadow.style.background = "#404040";
// The inner box: absolute positioning.
var box2 = document.createElement("div");
box2.style.position = "relative";
box2.style.border = "1px solid #a0a0a0";
box2.style.left = "-.2em";
box2.style.top = "-.2em";
box2.style.background = "white";
box2.style.padding = ".3em .4em .3em .4em";
box2.style.fontStyle = "normal";
box2.onmouseout=auto_kill_doclink;
box2.parentID = id;
// Get the targets
var targets_elt = document.getElementById(targets_id);
var targets = targets_elt.getAttribute("targets");
var links = "";
target_list = targets.split(",");
for (var i=0; i<target_list.length; i++) {
var target = target_list[i].split("=");
links += "<li><a href='" + target[1] +
"' style='text-decoration:none'>" +
target[0] + "</a></li>";
}
// Put it all together.
elt.insertBefore(box1, elt.childNodes.item(0));
//box1.appendChild(box2);
box1.appendChild(shadow);
shadow.appendChild(box2);
box2.innerHTML =
"Which <b>"+name+"</b> do you want to see documentation for?" +
"<ul style='margin-bottom: 0;'>" +
links +
"<li><a href='#' style='text-decoration:none' " +
"onclick='kill_doclink(\""+id+"\");return false;'>"+
"<i>None of the above</i></a></li></ul>";
}
return false;
}
function get_anchor() {
var href = location.href;
var start = href.indexOf("#")+1;
if ((start != 0) && (start != href.length))
return href.substring(start, href.length);
}
function redirect_url(dottedName) {
// Scan through each element of the "pages" list, and check
// if "name" matches with any of them.
for (var i=0; i<pages.length; i++) {
// Each page has the form "<pagename>-m" or "<pagename>-c";
// extract the <pagename> portion & compare it to dottedName.
var pagename = pages[i].substring(0, pages[i].length-2);
if (pagename == dottedName.substring(0,pagename.length)) {
// We've found a page that matches `dottedName`;
// construct its URL, using leftover `dottedName`
// content to form an anchor.
var pagetype = pages[i].charAt(pages[i].length-1);
var url = pagename + ((pagetype=="m")?"-module.html":
"-class.html");
if (dottedName.length > pagename.length)
url += "#" + dottedName.substring(pagename.length+1,
dottedName.length);
return url;
}
}
}

82
doc/rst2html.py Executable file
View file

@ -0,0 +1,82 @@
#!/usr/bin/python
# unproudly borrowed from David Goodger's rst2html.py
"""
A minimal front end to the Docutils Publisher, producing HTML.
"""
try:
import locale
locale.setlocale(locale.LC_ALL, '')
except:
pass
from docutils.core import publish_cmdline, default_description
# ____________________________________________________________
from docutils import nodes, utils
from docutils.parsers.rst import roles
"""
description of the new roles:
`:api:` : link to the code
- code.py becomes api/code.html
- code.Code.code_test becomes api/code.Code.code_test.html
- code.Code() becomes api/code.Code.html
`:doc:`a link to an internal file
example become example.html
ref: link with anchor as in an external file
:ref:`toto#titi` becomes toto.html#titi
"""
from os.path import splitext
def api_reference_role(role, rawtext, text, lineno, inliner,
options={}, content=[]):
basename = text
if "(" in text:
basename = text.split("(")[0]
if ".py" in text:
basename = splitext(text)[0]
if "test_" in text:
refuri = "api/" + "tiramisu.test." + basename + '.html'
else:
refuri = "api/" + "tiramisu." + basename + '.html'
roles.set_classes(options)
node = nodes.reference(rawtext, utils.unescape(text), refuri=refuri,
**options)
return [node], []
roles.register_local_role('api', api_reference_role)
def doc_reference_role(role, rawtext, text, lineno, inliner,
options={}, content=[]):
refuri = text + '.html'
roles.set_classes(options)
node = nodes.reference(rawtext, utils.unescape(text), refuri=refuri,
**options)
return [node], []
roles.register_local_role('doc', doc_reference_role)
def ref_reference_role(role, rawtext, text, lineno, inliner,
options={}, content=[]):
fname, anchor = text.split('#')
refuri = fname + '.html#' + anchor
roles.set_classes(options)
node = nodes.reference(rawtext, utils.unescape(anchor), refuri=refuri,
**options)
return [node], []
roles.register_local_role('ref', ref_reference_role)
# ____________________________________________________________
description = ('Generates (X)HTML documents from standalone reStructuredText '
'sources. ' + default_description)
publish_cmdline(writer_name='html', description=description)

181
doc/status.txt Normal file
View file

@ -0,0 +1,181 @@
.. default-role:: literal
Configuration status
======================
:module: :api:`config.py`
:tests: - :api:`test_option_owner.py`
- :api:`test_option_type.py`
- :api:`test_option_default.py`
Available configuration statuses
----------------------------------
These configuration statuses corresponds to specific global attributes :
**read write status**
The configuration can be accessed by `__get__` and `__set__`
properties, except for the `hidden` configuration options but, yes, it is
possible to modify a disabled option.
To enable read-write status, call
:api:`config.Config.cfgimpl_read_write()`
**read only status**
The whole configuration is `frozen`, that is modifiying a value is
forbidden. We can access to a configuration option only with the
`__getattr__` property.
The configuration has not an access to the hidden options
but can read the disabled options.
To enable read only status, call :api:`config.Config.cfgimpl_read_only()`
.. csv-table:: **Configuration's statuses summary**
:header: " ", "Hidden", "Disabled"
"read only status", `False`, `True`
"read-write status", `True`, `False`
Freezing a configuration
---------------------------
It is possible to *freeze* a single `Option` object with
:api:`option.Option.freeze()`. If you try to modify a frozen option, it
raises a `TypeError: trying to change a frozen option object`.
At the configuration level, :api:`config.Config.cfgimpl_freeze()` freeze
the whole configuration options.
- :api:`test_option_type.test_freeze_one_option()`
- :api:`test_option_type.test_frozen_value()`
- :api:`test_option_type.test_freeze()`
Restricted access to an `Option()`
-----------------------------------
Configuration options access statuses are defined at configuration level
that corresponds to theses :api:`option.Option()`'s attribute:
**hidden**
This means that an option raises an `HiddenOptionError` if we try to access
the value of the option.
See `hide()` or `show()` in `Option()` that comes from
:api:`option.HiddenBaseType`
corresponding convenience API provided:
`hide()`:
set the `hidden` attribute to `True`
`show()`:
set the `hidden` attribute to `False`
**disabled**
This means that an option *doesn't exists* (doesn't say anything
much more thant an `AttibuteAccess` error)
See in :api:`option.DisabledBaseType` the origins of
`Option.enable()` or `Option.disable()`
corresponding convenience API provided:
`disable()`:
set the `disabled` attribute to `True`
`enable()`:
set the `disabled` attribute to `False`
mode
a mode is `normal` or `expert`, just a category of `Option()` or
group wich determines if an option is easy to choose or not,
available methods are:
`get_mode()`:
returns the current mode
`set_mode(mode)`:
sets a new mode
see it in :api:`option.ModeBaseType`
Value owners
-------------
Every configuration option has a **owner**. When the option is
instanciated, the owner is `default` because a default value has been
set (including `None`, take a look at the tests).
The `value_owner` is the man who did it. Yes, the man who changed the value of the
configuration option.
- At the instance of the `Config` object, the value owner is `default` because
the default values are set at the instance of the configuration option object,
::
# let's expect there is an option named 'name'
config = Config(descr, bool=False)
# the override method has been called
config._cfgimpl_value_owners['name'] == 'default'
- at the modification of an option, the owner is `default_owner`, (which is `user`)
::
# modification of the value by attribute access
config.gc.dummy = True
assert config.gc._cfgimpl_value_owners['dummy'] == 'user'
assert config._cfgimpl_values['gc']._cfgimpl_value_owners['dummy'] == 'user'
- the default owner can be set with the `set_owner()` method
::
config.set_owner('spam')
config.set(dummy=True)
assert config.gc._cfgimpl_value_owners['dummy'] == 'spam'
assert config._cfgimpl_values['gc']._cfgimpl_value_owners['dummy'] == 'spam'
Special owners
---------------
If the owner of a configuration option is `auto` or `fill` the behavior of the
access of the value changes. In fact, there is nothing in the value.
The value comes from somewhere else (typically, it is calculated by the
operation system).
**auto**
This means that it is a calculated value and therefore automatically
protected it cannot be modified by attribute access once the owner
is `auto`.
The configuration option is hidden and a fonction in a specific
library is called for the computation of the value.
**fill**
if the configuration option has a default value, the default is
returned, otherwise the value is calculated
The default values behavior
----------------------------
Configuration options have default values that are stored in the
`Option()` object itself. Default values, the `default`, can be set in
various ways.
.. FIXME : ADD DETAILS HERE
If a default value is modified by overriding it, not only the value of
the option resets to the default that is proposed, but the owner is
modified too, it is reseted to `default`.

95
doc/todo.txt Normal file
View file

@ -0,0 +1,95 @@
:date: 17 avril
- lever une exception parlante (pour l'instant, c'est une "KeyError")
lorsqu'on essaye d'affecter quelque chose
à un groupe, genre
::
cfg = Config(descr)
cfg.gc = "uvw"
alors que gc est un groupe
:date: 12 avril
- faire un mode dégradé avec des warnings
- validations de longueur des maitres/esclaves ailleurs à sortir des requires
et à mettre dans des validators
:date: 3 avril 2012
- hide sur les sous-sous groupe : il faut que ça hide **tout** les sous-groupe
récursivement
groupes `master/slaves`:
faut-il coder les multi avec des requires, ou bien simplement
un groupe avec comme variable le nom du groupe ?
auto, fill, obligatoire
2012-03-22
**groupe master**
faire une api du genre : `Option().is_master()`
pour cela, tester `if self.parent._name == self._name: return True`
- mettre un attribut `auto` aux options de configuration, de manière à
ce qu'elles sachent quelle fonction eos appeler (que ça soit une info
dans l'option ou bien au niveau de la config ?)
le fait de détecter un "auto" vient du owner, mais il faut savoir
quelle fonction appeler
A documenter
-------------
- les variables multiples
- expliquer les urls du json dans la doc
- documenter le typage des options descriptions descr_type
A ajouter
---------
Option -> attribut help (en plus de doc)
get_help() (à mettre en class Type avec Doc aussi)
separator -> pas pour l'instant
fill, auto, obligatoire
nouveau type :
type option (dérivé de ChoiceOPtion) dans lequel il y a des nouvelles valeurs
possibles (pas de validations) ou plutôt une StringOption qui propose un choix
de valeurs par défault de type liste.
:date: 24 mars
- hide pour les sous-sous config (récursivement) et pas seulement une
seule sous-config (ou bien, quelque chose de réglable)
- validate global : vérifier à l'init de la conf qu'une variable
n'existe pas déjà, etc
:date: 26 janvier
- un attribut eosfunc pour auto + les paramètres à donner à la fonction
pareil pour le fill (function et paramètres)
reset
-------
**à discuter** : ça correspond exactement au override,
ou bien au opt.setoption(None, 'default')
**si la valeur par défaut est définie, un __get__ ne pourra jamais
renvoyer None.** ce qui est bloquant. Il faut pouvoir revenir à None.
pour supprimer la valeur d'une options (et revenir à la valeur par défault)
cfg.reset() (supprime _cfgimpl_value[name]) et _cfgimpl_value_owner[name])
reset()

25
error.py Normal file
View file

@ -0,0 +1,25 @@
class AmbigousOptionError(Exception):
pass
class NoMatchingOptionFound(AttributeError):
pass
class ConfigError(Exception):
pass
class ConflictConfigError(ConfigError):
pass
class HiddenOptionError(AttributeError):
pass
class DisabledOptionError(AttributeError):
pass
class NotFoundError(Exception):
pass
class MethodCallError(Exception):
pass
class RequiresError(Exception):
pass
class MandatoryError(Exception):
pass
class SpecialOwnersError(Exception):
pass
class ModeOptionError(Exception):
pass

482
option.py Normal file
View file

@ -0,0 +1,482 @@
# -*- coding: utf-8 -*-
"pretty small and local configuration management tool"
from error import (ConfigError, ConflictConfigError, NotFoundError,
RequiresError)
available_actions = ['hide', 'show', 'enable', 'disable']
reverse_actions = {'hide': 'show', 'show': 'hide',
'disable':'enable', 'enable': 'disable'}
# ____________________________________________________________
# OptionDescription authorized group_type values
group_types = ['default', 'family', 'group', 'master']
# Option and OptionDescription modes
modes = ['normal', 'expert']
# ____________________________________________________________
# interfaces
class HiddenBaseType(object):
hidden = False
def hide(self):
self.hidden = True
def show(self):
self.hidden = False
def _is_hidden(self):
# dangerous method: how an Option can determine its status by itself ?
return self.hidden
class DisabledBaseType(object):
disabled = False
def disable(self):
self.disabled = True
def enable(self):
self.disabled = False
def _is_disabled(self):
return self.disabled
class ModeBaseType(object):
mode = 'normal'
def get_mode(self):
return self.mode
def set_mode(self, mode):
if mode not in modes:
raise TypeError("Unknown mode: {0}".format(mode))
self.mode = mode
# ____________________________________________________________
class Option(HiddenBaseType, DisabledBaseType, ModeBaseType):
#reminder: an Option object is **not** a container for the value
_frozen = False
def __init__(self, name, doc, default=None, requires=None,
mandatory=False, multi=False, callback=None, mode='normal'):
self._name = name
self.doc = doc
self._requires = requires
self._mandatory = mandatory
self.multi = multi
self.callback = callback
if mode not in modes:
raise ConfigError("mode {0} not available".format(mode))
self.mode = mode
if default != None:
if not self.validate(default):
raise ConfigError("invalid default value {0} "
"for option {1}".format(default, name))
self.default = default
def validate(self, value):
raise NotImplementedError('abstract base class')
def getdefault(self):
return self.default
def getdoc(self):
return self.doc
def getcallback(self):
return self.callback
def setowner(self, config, who):
name = self._name
if self._frozen:
raise TypeError("trying to change a frozen option's owner: %s" % name)
if who in ['auto', 'fill']: # XXX special_owners to be imported from config
if self.callback == None:
raise SpecialOwnersError("no callback specified for"
"option {0}".format(name))
config._cfgimpl_value_owners[name] = who
def setoption(self, config, value, who):
name = self._name
if self._frozen:
raise TypeError('trying to change a frozen option object: %s' % name)
# we want the possibility to reset everything
if who == "default" and value is None:
self.default = None
return
if not self.validate(value):
raise ConfigError('invalid value %s for option %s' % (value, name))
if who == "default":
# changes the default value (and therefore resets the previous value)
self.default = value
apply_requires(self, config)
# FIXME put the validation for the multi somewhere else
# # it is a multi **and** it has requires
# if self.multi == True:
# if type(value) != list:
# raise TypeError("value {0} must be a list".format(value))
# if self._requires is not None:
# for reqname in self._requires:
# # FIXME : verify that the slaves are all multi
# #option = getattr(config._cfgimpl_descr, reqname)
# # if not option.multi == True:
# # raise ConflictConfigError("an option with requires "
# # "has to be a list type : {0}".format(name))
# if len(config._cfgimpl_values[reqname]) != len(value):
# raise ConflictConfigError("an option with requires "
# "has not the same length of the others "
# "in the group : {0}".format(reqname))
config._cfgimpl_previous_values[name] = config._cfgimpl_values[name]
config._cfgimpl_values[name] = value
def getkey(self, value):
return value
def freeze(self):
self._frozen = True
return True
def unfreeze(self):
self._frozen = False
# ____________________________________________________________
def is_multi(self):
return self.multi
def is_mandatory(self):
return self._mandatory
class ChoiceOption(Option):
opt_type = 'string'
def __init__(self, name, doc, values, default=None, requires=None,
multi=False, mandatory=False):
self.values = values
super(ChoiceOption, self).__init__(name, doc, default=default,
requires=requires, multi=multi, mandatory=mandatory)
def setoption(self, config, value, who):
name = self._name
super(ChoiceOption, self).setoption(config, value, who)
def validate(self, value):
if self.multi == False:
return value is None or value in self.values
else:
for val in value:
if not (val is None or val in self.values):
return False
return True
class BoolOption(Option):
opt_type = 'bool'
def __init__(self, *args, **kwargs):
super(BoolOption, self).__init__(*args, **kwargs)
# def __init__(self, name, doc, default=None, requires=None,
# validator=None, multi=False, mandatory=False):
# super(BoolOption, self).__init__(name, doc, default=default,
# requires=requires, multi=multi, mandatory=mandatory)
#self._validator = validator
def validate(self, value):
if self.multi == False:
return isinstance(value, bool)
else:
try:
for val in value:
if not isinstance(val, bool):
return False
except Exception:
return False
return True
# FIXME config level validator
# def setoption(self, config, value, who):
# name = self._name
# if value and self._validator is not None:
# toplevel = config._cfgimpl_get_toplevel()
# self._validator(toplevel)
# super(BoolOption, self).setoption(config, value, who)
class IntOption(Option):
opt_type = 'int'
def __init__(self, *args, **kwargs):
super(IntOption, self).__init__(*args, **kwargs)
def validate(self, value):
if self.multi == False:
try:
int(value)
except TypeError:
return False
return True
else:
for val in value:
try:
int(val)
except TypeError:
return False
return True
def setoption(self, config, value, who):
try:
super(IntOption, self).setoption(config, value, who)
except TypeError, e:
raise ConfigError(*e.args)
class FloatOption(Option):
opt_type = 'float'
def __init__(self, *args, **kwargs):
super(FloatOption, self).__init__(*args, **kwargs)
def validate(self, value):
if self.multi == False:
try:
float(value)
except TypeError:
return False
return True
else:
for val in value:
try:
float(val)
except TypeError:
return False
return True
def setoption(self, config, value, who):
try:
super(FloatOption, self).setoption(config, float(value), who)
except TypeError, e:
raise ConfigError(*e.args)
class StrOption(Option):
opt_type = 'string'
def __init__(self, *args, **kwargs):
super(StrOption, self).__init__(*args, **kwargs)
def validate(self, value):
if self.multi == False:
return isinstance(value, str)
else:
for val in value:
if not isinstance(val, str):
return False
else:
return True
def setoption(self, config, value, who):
try:
super(StrOption, self).setoption(config, value, who)
except TypeError, e:
raise ConfigError(*e.args)
class SymLinkOption(object): #(HiddenBaseType, DisabledBaseType):
opt_type = 'symlink'
def __init__(self, name, path):
self._name = name
self.path = path
def setoption(self, config, value, who):
try:
setattr(config, self.path, value) # .setoption(self.path, value, who)
except TypeError, e:
raise ConfigError(*e.args)
class IPOption(Option):
opt_type = 'ip'
def __init__(self, *args, **kwargs):
super(IPOption, self).__init__(*args, **kwargs)
def validate(self, value):
# by now the validation is nothing but a string, use IPy instead
if self.multi == False:
return isinstance(value, str)
else:
for val in value:
if not isinstance(val, str):
return False
else:
return True
def setoption(self, config, value, who):
try:
super(IPOption, self).setoption(config, value, who)
except TypeError, e:
raise ConfigError(*e.args)
class NetmaskOption(Option):
opt_type = 'netmask'
def __init__(self, *args, **kwargs):
super(NetmaskOption, self).__init__(*args, **kwargs)
def validate(self, value):
# by now the validation is nothing but a string, use IPy instead
if self.multi == False:
return isinstance(value, str)
else:
for val in value:
if not isinstance(val, str):
return False
else:
return True
def setoption(self, config, value, who):
try:
super(NetmaskOption, self).setoption(config, value, who)
except TypeError, e:
raise ConfigError(*e.args)
class ArbitraryOption(Option):
def __init__(self, name, doc, default=None, defaultfactory=None,
requires=None, multi=False, mandatory=False):
super(ArbitraryOption, self).__init__(name, doc, requires=requires,
multi=multi, mandatory=mandatory)
self.defaultfactory = defaultfactory
if defaultfactory is not None:
assert default is None
def validate(self, value):
return True
def getdefault(self):
if self.defaultfactory is not None:
return self.defaultfactory()
return self.default
class OptionDescription(HiddenBaseType, DisabledBaseType, ModeBaseType):
group_type = 'default'
def __init__(self, name, doc, children, requires=None):
self._name = name
self.doc = doc
self._children = children
self._requires = requires
self._build()
def getdoc(self):
return self.doc
def _build(self):
for child in self._children:
setattr(self, child._name, child)
def add_child(self, child):
"dynamically adds a configuration option"
#Nothing is static. Even the Mona Lisa is falling apart.
for ch in self._children:
if isinstance(ch, Option):
if child._name == ch._name:
raise ConflictConfigError("existing option : {0}".format(
child._name))
self._children.append(child)
setattr(self, child._name, child)
def update_child(self, child):
"modification of an existing option"
# XXX : corresponds to the `redefine`, is it usefull
pass
def getkey(self, config):
return tuple([child.getkey(getattr(config, child._name))
for child in self._children])
def getpaths(self, include_groups=False, currpath=None):
"""returns a list of all paths in self, recursively
currpath should not be provided (helps with recursion)
"""
if currpath is None:
currpath = []
paths = []
for option in self._children:
attr = option._name
if attr.startswith('_cfgimpl'):
continue
value = getattr(self, attr)
if isinstance(value, OptionDescription):
if include_groups:
paths.append('.'.join(currpath + [attr]))
currpath.append(attr)
paths += value.getpaths(include_groups=include_groups,
currpath=currpath)
currpath.pop()
else:
paths.append('.'.join(currpath + [attr]))
return paths
# ____________________________________________________________
def set_group_type(self, group_type):
if group_type in group_types:
self.group_type = group_type
else:
raise ConfigError('not allowed value for group_type : {0}'.format(
group_type))
def get_group_type(self):
return self.group_type
# ____________________________________________________________
def hide(self):
super(OptionDescription, self).hide()
# FIXME : AND THE SUBCHILDREN ?
for child in self._children:
if isinstance(child, OptionDescription):
child.hide()
def show(self):
# FIXME : AND THE SUBCHILDREN ??
super(OptionDescription, self).show()
for child in self._children:
if isinstance(child, OptionDescription):
child.show()
# ____________________________________________________________
def disable(self):
super(OptionDescription, self).disable()
# FIXME : AND THE SUBCHILDREN ?
for child in self._children:
if isinstance(child, OptionDescription):
child.disable()
def enable(self):
# FIXME : AND THE SUBCHILDREN ?
super(OptionDescription, self).enable()
for child in self._children:
if isinstance(child, OptionDescription):
child.enable()
# ____________________________________________________________
def apply_requires(opt, config):
if hasattr(opt, '_requires'):
if opt._requires is not None:
# malformed requirements
rootconfig = config._cfgimpl_get_toplevel()
for req in opt._requires:
if not type(req) == tuple and len(req) in (3, 4):
raise RequiresError("malformed requirements for option:"
" {0}".format(opt._name))
# all actions **must** be identical
actions = [req[2] for req in opt._requires]
action = actions[0]
for act in actions:
if act != action:
raise RequiresError("malformed requirements for option:"
" {0}".format(opt._name))
# filters the callbacks
matches = False
for req in opt._requires:
if len(req) == 3:
name, expected, action = req
inverted = False
if len(req) == 4:
name, expected, action, inverted = req
if inverted == 'inverted':
inverted = True
homeconfig, shortname = \
rootconfig._cfgimpl_get_home_by_path(name)
# FIXME: doesn't work with 'auto' or 'fill' yet
# (copy the code from the __getattr__
if shortname in homeconfig._cfgimpl_values:
value = homeconfig._cfgimpl_values[shortname]
if (not inverted and value == expected) or \
(inverted and value != expected):
if action not in available_actions:
raise RequiresError("malformed requirements"
" for option: {0}".format(opt._name))
getattr(opt, action)() #.hide() or show() or...
matches = True
else: # option doesn't exist ! should not happen...
raise NotFoundError("required option not found: "
"{0}".format(name))
# no callback has been triggered, then just reverse the action
if not matches:
getattr(opt, reverse_actions[action])()

15
report/Makefile Normal file
View file

@ -0,0 +1,15 @@
.SUFFIXES:
.PHONY: all clean
all: html
generate:
python ./generate.py
html: generate
make -C ./build all
clean:
make -C ./build clean

0
report/__init__.py Normal file
View file

18
report/build/Makefile Normal file
View file

@ -0,0 +1,18 @@
SRC=$(wildcard *.txt)
HTMLFRAGMENT=$(addsuffix .html, $(basename $(SRC)))
.SUFFIXES:
.PHONY: all clean
all: html
html: $(HTMLFRAGMENT)
%.html: %.txt
./rst2html.py --stylesheet ./style.css $< > $@
clean:
rm -f *.html
rm -f *.txt

540
report/build/basic.css Normal file
View file

@ -0,0 +1,540 @@
/*
* basic.css
* ~~~~~~~~~
*
* Sphinx stylesheet -- basic theme.
*
* :copyright: Copyright 2007-2011 by the Sphinx team, see AUTHORS.
* :license: BSD, see LICENSE for details.
*
*/
/* -- main layout ----------------------------------------------------------- */
div.clearer {
clear: both;
}
/* -- relbar ---------------------------------------------------------------- */
div.related {
width: 100%;
font-size: 90%;
}
div.related h3 {
display: none;
}
div.related ul {
margin: 0;
padding: 0 0 0 10px;
list-style: none;
}
div.related li {
display: inline;
}
div.related li.right {
float: right;
margin-right: 5px;
}
/* -- sidebar --------------------------------------------------------------- */
div.sphinxsidebarwrapper {
padding: 10px 5px 0 10px;
}
div.sphinxsidebar {
float: left;
width: 230px;
margin-left: -100%;
font-size: 90%;
}
div.sphinxsidebar ul {
list-style: none;
}
div.sphinxsidebar ul ul,
div.sphinxsidebar ul.want-points {
margin-left: 20px;
list-style: square;
}
div.sphinxsidebar ul ul {
margin-top: 0;
margin-bottom: 0;
}
div.sphinxsidebar form {
margin-top: 10px;
}
div.sphinxsidebar input {
border: 1px solid #98dbcc;
font-family: sans-serif;
font-size: 1em;
}
div.sphinxsidebar input[type="text"] {
width: 170px;
}
div.sphinxsidebar input[type="submit"] {
width: 30px;
}
img {
border: 0;
}
/* -- search page ----------------------------------------------------------- */
ul.search {
margin: 10px 0 0 20px;
padding: 0;
}
ul.search li {
padding: 5px 0 5px 20px;
background-image: url(file.png);
background-repeat: no-repeat;
background-position: 0 7px;
}
ul.search li a {
font-weight: bold;
}
ul.search li div.context {
color: #888;
margin: 2px 0 0 30px;
text-align: left;
}
ul.keywordmatches li.goodmatch a {
font-weight: bold;
}
/* -- index page ------------------------------------------------------------ */
table.contentstable {
width: 90%;
}
table.contentstable p.biglink {
line-height: 150%;
}
a.biglink {
font-size: 1.3em;
}
span.linkdescr {
font-style: italic;
padding-top: 5px;
font-size: 90%;
}
/* -- general index --------------------------------------------------------- */
table.indextable {
width: 100%;
}
table.indextable td {
text-align: left;
vertical-align: top;
}
table.indextable dl, table.indextable dd {
margin-top: 0;
margin-bottom: 0;
}
table.indextable tr.pcap {
height: 10px;
}
table.indextable tr.cap {
margin-top: 10px;
background-color: #f2f2f2;
}
img.toggler {
margin-right: 3px;
margin-top: 3px;
cursor: pointer;
}
div.modindex-jumpbox {
border-top: 1px solid #ddd;
border-bottom: 1px solid #ddd;
margin: 1em 0 1em 0;
padding: 0.4em;
}
div.genindex-jumpbox {
border-top: 1px solid #ddd;
border-bottom: 1px solid #ddd;
margin: 1em 0 1em 0;
padding: 0.4em;
}
/* -- general body styles --------------------------------------------------- */
a.headerlink {
visibility: hidden;
}
h1:hover > a.headerlink,
h2:hover > a.headerlink,
h3:hover > a.headerlink,
h4:hover > a.headerlink,
h5:hover > a.headerlink,
h6:hover > a.headerlink,
dt:hover > a.headerlink {
visibility: visible;
}
div.body p.caption {
text-align: inherit;
}
div.body td {
text-align: left;
}
.field-list ul {
padding-left: 1em;
}
.first {
margin-top: 0 !important;
}
p.rubric {
margin-top: 30px;
font-weight: bold;
}
img.align-left, .figure.align-left, object.align-left {
clear: left;
float: left;
margin-right: 1em;
}
img.align-right, .figure.align-right, object.align-right {
clear: right;
float: right;
margin-left: 1em;
}
img.align-center, .figure.align-center, object.align-center {
display: block;
margin-left: auto;
margin-right: auto;
}
.align-left {
text-align: left;
}
.align-center {
text-align: center;
}
.align-right {
text-align: right;
}
/* -- sidebars -------------------------------------------------------------- */
div.sidebar {
margin: 0 0 0.5em 1em;
border: 1px solid #ddb;
padding: 7px 7px 0 7px;
background-color: #ffe;
width: 40%;
float: right;
}
p.sidebar-title {
font-weight: bold;
}
/* -- topics ---------------------------------------------------------------- */
div.topic {
border: 1px solid #ccc;
padding: 7px 7px 0 7px;
margin: 10px 0 10px 0;
}
p.topic-title {
font-size: 1.1em;
font-weight: bold;
margin-top: 10px;
}
/* -- admonitions ----------------------------------------------------------- */
div.admonition {
margin-top: 10px;
margin-bottom: 10px;
padding: 7px;
}
div.admonition dt {
font-weight: bold;
}
div.admonition dl {
margin-bottom: 0;
}
p.admonition-title {
margin: 0px 10px 5px 0px;
font-weight: bold;
}
div.body p.centered {
text-align: center;
margin-top: 25px;
}
/* -- tables ---------------------------------------------------------------- */
table.docutils {
border: 0;
border-collapse: collapse;
}
table.docutils td, table.docutils th {
padding: 1px 8px 1px 5px;
border-top: 0;
border-left: 0;
border-right: 0;
border-bottom: 1px solid #aaa;
}
table.field-list td, table.field-list th {
border: 0 !important;
}
table.footnote td, table.footnote th {
border: 0 !important;
}
th {
text-align: left;
padding-right: 5px;
}
table.citation {
border-left: solid 1px gray;
margin-left: 1px;
}
table.citation td {
border-bottom: none;
}
/* -- other body styles ----------------------------------------------------- */
ol.arabic {
list-style: decimal;
}
ol.loweralpha {
list-style: lower-alpha;
}
ol.upperalpha {
list-style: upper-alpha;
}
ol.lowerroman {
list-style: lower-roman;
}
ol.upperroman {
list-style: upper-roman;
}
dl {
margin-bottom: 15px;
}
dd p {
margin-top: 0px;
}
dd ul, dd table {
margin-bottom: 10px;
}
dd {
margin-top: 3px;
margin-bottom: 10px;
margin-left: 30px;
}
dt:target, .highlighted {
background-color: #fbe54e;
}
dl.glossary dt {
font-weight: bold;
font-size: 1.1em;
}
.field-list ul {
margin: 0;
padding-left: 1em;
}
.field-list p {
margin: 0;
}
.refcount {
color: #060;
}
.optional {
font-size: 1.3em;
}
.versionmodified {
font-style: italic;
}
.system-message {
background-color: #fda;
padding: 5px;
border: 3px solid red;
}
.footnote:target {
background-color: #ffa;
}
.line-block {
display: block;
margin-top: 1em;
margin-bottom: 1em;
}
.line-block .line-block {
margin-top: 0;
margin-bottom: 0;
margin-left: 1.5em;
}
.guilabel, .menuselection {
font-family: sans-serif;
}
.accelerator {
text-decoration: underline;
}
.classifier {
font-style: oblique;
}
abbr, acronym {
border-bottom: dotted 1px;
cursor: help;
}
/* -- code displays --------------------------------------------------------- */
pre {
overflow: auto;
overflow-y: hidden; /* fixes display issues on Chrome browsers */
}
td.linenos pre {
padding: 5px 0px;
border: 0;
background-color: transparent;
color: #aaa;
}
table.highlighttable {
margin-left: 0.5em;
}
table.highlighttable td {
padding: 0 0.5em 0 0.5em;
}
tt.descname {
background-color: transparent;
font-weight: bold;
font-size: 1.2em;
}
tt.descclassname {
background-color: transparent;
}
tt.xref, a tt {
background-color: transparent;
font-weight: bold;
}
h1 tt, h2 tt, h3 tt, h4 tt, h5 tt, h6 tt {
background-color: transparent;
}
.viewcode-link {
float: right;
}
.viewcode-back {
float: right;
font-family: sans-serif;
}
div.viewcode-block:target {
margin: -1px -10px;
padding: 0 10px;
}
/* -- math display ---------------------------------------------------------- */
img.math {
vertical-align: middle;
}
div.body div.math p {
text-align: center;
}
span.eqno {
float: right;
}
/* -- printout stylesheet --------------------------------------------------- */
@media print {
div.document,
div.documentwrapper,
div.bodywrapper {
margin: 0 !important;
width: 100%;
}
div.sphinxsidebar,
div.related,
div.footer,
#top-link {
display: none;
}
}

38
report/build/rst2html.py Executable file
View file

@ -0,0 +1,38 @@
#!/usr/bin/python
# unproudly borrowed from David Goodger's rst2html.py
""" A minimal front end to the Docutils Publisher, producing HTML with a
`config` role
"""
try:
import locale
locale.setlocale(locale.LC_ALL, '')
except:
pass
from docutils.core import publish_cmdline, default_description
# ____________________________________________________________
from docutils import nodes, utils
from docutils.parsers.rst import roles
# ____________________________________________________________
#register a :config: ReST link role for use in documentation
def config_reference_role(role, rawtext, text, lineno, inliner,
options={}, content=[]):
basename = text
refuri = "report/build" + basename + '.html'
roles.set_classes(options)
node = nodes.reference(rawtext, utils.unescape(text), refuri=refuri,
**options)
return [node], []
roles.register_local_role('config', config_reference_role)
# ____________________________________________________________
description = ('Generates (X)HTML documents from standalone reStructuredText '
'sources. ' + default_description)
publish_cmdline(writer_name='html', description=description)

795
report/build/style.css Normal file
View file

@ -0,0 +1,795 @@
/*
* rtd.css
* ~~~~~~~~~~~~~~~
*
* Sphinx stylesheet -- sphinxdoc theme. Originally created by
* Armin Ronacher for Werkzeug.
*
* Customized for ReadTheDocs by Eric Pierce & Eric Holscher
*
* :copyright: Copyright 2007-2010 by the Sphinx team, see AUTHORS.
* :license: BSD, see LICENSE for details.
*
*/
/* RTD colors
* light blue: #e8ecef
* medium blue: #8ca1af
* dark blue: #465158
* dark grey: #444444
*
* white hover: #d1d9df;
* medium blue hover: #697983;
* green highlight: #8ecc4c
* light blue (project bar): #e8ecef
*/
@import url("basic.css");
/* PAGE LAYOUT -------------------------------------------------------------- */
body {
font: 100%/1.5 "ff-meta-web-pro-1","ff-meta-web-pro-2",Arial,"Helvetica Neue",sans-serif;
text-align: center;
color: black;
background-color: #465158;
padding: 0;
margin: 0;
}
div.document {
text-align: left;
background-color: #e8ecef;
}
div.bodywrapper {
background-color: #ffffff;
border-left: 1px solid #ccc;
border-bottom: 1px solid #ccc;
margin: 0 0 0 16em;
}
div.body {
margin: 0;
padding: 0.5em 1.3em;
min-width: 20em;
}
div.related {
font-size: 1em;
background-color: #465158;
}
div.documentwrapper {
float: left;
width: 100%;
background-color: #e8ecef;
}
/* HEADINGS --------------------------------------------------------------- */
h1 {
margin: 0;
padding: 0.7em 0 0.3em 0;
font-size: 1.5em;
line-height: 1.15;
color: #111;
clear: both;
}
h2 {
margin: 2em 0 0.2em 0;
font-size: 1.35em;
padding: 0;
color: #465158;
}
h3 {
margin: 1em 0 -0.3em 0;
font-size: 1.2em;
color: #6c818f;
}
div.body h1 a, div.body h2 a, div.body h3 a, div.body h4 a, div.body h5 a, div.body h6 a {
color: black;
}
h1 a.anchor, h2 a.anchor, h3 a.anchor, h4 a.anchor, h5 a.anchor, h6 a.anchor {
display: none;
margin: 0 0 0 0.3em;
padding: 0 0.2em 0 0.2em;
color: #aaa !important;
}
h1:hover a.anchor, h2:hover a.anchor, h3:hover a.anchor, h4:hover a.anchor,
h5:hover a.anchor, h6:hover a.anchor {
display: inline;
}
h1 a.anchor:hover, h2 a.anchor:hover, h3 a.anchor:hover, h4 a.anchor:hover,
h5 a.anchor:hover, h6 a.anchor:hover {
color: #777;
background-color: #eee;
}
/* LINKS ------------------------------------------------------------------ */
/* Normal links get a pseudo-underline */
a {
color: #444;
text-decoration: none;
border-bottom: 1px solid #ccc;
}
/* Links in sidebar, TOC, index trees and tables have no underline */
.sphinxsidebar a,
.toctree-wrapper a,
.indextable a,
#indices-and-tables a {
color: #444;
text-decoration: none;
border-bottom: none;
}
/* Most links get an underline-effect when hovered */
a:hover,
div.toctree-wrapper a:hover,
.indextable a:hover,
#indices-and-tables a:hover {
color: #111;
text-decoration: none;
border-bottom: 1px solid #111;
}
/* Footer links */
div.footer a {
color: #86989B;
text-decoration: none;
border: none;
}
div.footer a:hover {
color: #a6b8bb;
text-decoration: underline;
border: none;
}
/* Permalink anchor (subtle grey with a red hover) */
div.body a.headerlink {
color: #ccc;
font-size: 1em;
margin-left: 6px;
padding: 0 4px 0 4px;
text-decoration: none;
border: none;
}
div.body a.headerlink:hover {
color: #c60f0f;
border: none;
}
/* NAVIGATION BAR --------------------------------------------------------- */
div.related ul {
height: 2.5em;
}
div.related ul li {
margin: 0;
padding: 0.65em 0;
float: left;
display: block;
color: white; /* For the >> separators */
font-size: 0.8em;
}
div.related ul li.right {
float: right;
margin-right: 5px;
color: transparent; /* Hide the | separators */
}
/* "Breadcrumb" links in nav bar */
div.related ul li a {
order: none;
background-color: inherit;
font-weight: bold;
margin: 6px 0 6px 4px;
line-height: 1.75em;
color: #ffffff;
padding: 0.4em 0.8em;
border: none;
border-radius: 3px;
}
/* previous / next / modules / index links look more like buttons */
div.related ul li.right a {
margin: 0.375em 0;
background-color: #697983;
text-shadow: 0 1px rgba(0, 0, 0, 0.5);
border-radius: 3px;
-webkit-border-radius: 3px;
-moz-border-radius: 3px;
}
/* All navbar links light up as buttons when hovered */
div.related ul li a:hover {
background-color: #8ca1af;
color: #ffffff;
text-decoration: none;
border-radius: 3px;
-webkit-border-radius: 3px;
-moz-border-radius: 3px;
}
/* Take extra precautions for tt within links */
a tt,
div.related ul li a tt {
background: inherit !important;
color: inherit !important;
}
/* SIDEBAR ---------------------------------------------------------------- */
div.sphinxsidebarwrapper {
padding: 0;
}
div.sphinxsidebar {
margin: 0;
margin-left: -100%;
float: left;
top: 3em;
left: 0;
padding: 0 1em;
width: 14em;
font-size: 1em;
text-align: left;
background-color: #e8ecef;
}
div.sphinxsidebar img {
max-width: 12em;
}
div.sphinxsidebar h3,
div.sphinxsidebar h4,
div.sphinxsidebar p.logo {
margin: 1.2em 0 0.3em 0;
font-size: 1em;
padding: 0;
color: #222222;
font-family: "ff-meta-web-pro-1", "ff-meta-web-pro-2", "Arial", "Helvetica Neue", sans-serif;
}
div.sphinxsidebar h3 a {
color: #444444;
}
div.sphinxsidebar ul,
div.sphinxsidebar p {
margin-top: 0;
padding-left: 0;
line-height: 130%;
background-color: #e8ecef;
}
/* No bullets for nested lists, but a little extra indentation */
div.sphinxsidebar ul ul {
list-style-type: none;
margin-left: 1.5em;
padding: 0;
}
/* A little top/bottom padding to prevent adjacent links' borders
* from overlapping each other */
div.sphinxsidebar ul li {
padding: 1px 0;
}
/* A little left-padding to make these align with the ULs */
div.sphinxsidebar p.topless {
padding-left: 0 0 0 1em;
}
/* Make these into hidden one-liners */
div.sphinxsidebar ul li,
div.sphinxsidebar p.topless {
white-space: nowrap;
overflow: hidden;
}
/* ...which become visible when hovered */
div.sphinxsidebar ul li:hover,
div.sphinxsidebar p.topless:hover {
overflow: visible;
}
/* Search text box and "Go" button */
#searchbox {
margin-top: 2em;
margin-bottom: 1em;
background: #ddd;
padding: 0.5em;
border-radius: 6px;
-moz-border-radius: 6px;
-webkit-border-radius: 6px;
}
#searchbox h3 {
margin-top: 0;
}
/* Make search box and button abut and have a border */
input,
div.sphinxsidebar input {
border: 1px solid #999;
float: left;
}
/* Search textbox */
input[type="text"] {
margin: 0;
padding: 0 3px;
height: 20px;
width: 144px;
border-top-left-radius: 3px;
border-bottom-left-radius: 3px;
-moz-border-radius-topleft: 3px;
-moz-border-radius-bottomleft: 3px;
-webkit-border-top-left-radius: 3px;
-webkit-border-bottom-left-radius: 3px;
}
/* Search button */
input[type="submit"] {
margin: 0 0 0 -1px; /* -1px prevents a double-border with textbox */
height: 22px;
color: #444;
background-color: #e8ecef;
padding: 1px 4px;
font-weight: bold;
border-top-right-radius: 3px;
border-bottom-right-radius: 3px;
-moz-border-radius-topright: 3px;
-moz-border-radius-bottomright: 3px;
-webkit-border-top-right-radius: 3px;
-webkit-border-bottom-right-radius: 3px;
}
input[type="submit"]:hover {
color: #ffffff;
background-color: #8ecc4c;
}
div.sphinxsidebar p.searchtip {
clear: both;
padding: 0.5em 0 0 0;
background: #ddd;
color: #666;
font-size: 0.9em;
}
/* Sidebar links are unusual */
div.sphinxsidebar li a,
div.sphinxsidebar p a {
background: #e8ecef; /* In case links overlap main content */
border-radius: 3px;
-moz-border-radius: 3px;
-webkit-border-radius: 3px;
border: 1px solid transparent; /* To prevent things jumping around on hover */
padding: 0 5px 0 5px;
}
div.sphinxsidebar li a:hover,
div.sphinxsidebar p a:hover {
color: #111;
text-decoration: none;
border: 1px solid #888;
}
div.sphinxsidebar p.logo a {
border: 0;
}
/* Tweak any link appearing in a heading */
div.sphinxsidebar h3 a {
}
/* OTHER STUFF ------------------------------------------------------------ */
cite, code, tt {
font-family: 'Consolas', 'Deja Vu Sans Mono',
'Bitstream Vera Sans Mono', monospace;
font-size: 0.95em;
letter-spacing: 0.01em;
}
tt {
background-color: #f2f2f2;
color: #444;
}
tt.descname, tt.descclassname, tt.xref {
border: 0;
}
hr {
border: 1px solid #abc;
margin: 2em;
}
pre, #_fontwidthtest {
font-family: 'Consolas', 'Deja Vu Sans Mono',
'Bitstream Vera Sans Mono', monospace;
margin: 1em 2em;
font-size: 0.95em;
letter-spacing: 0.015em;
line-height: 120%;
padding: 0.5em;
border: 1px solid #ccc;
background-color: #eee;
border-radius: 6px;
-moz-border-radius: 6px;
-webkit-border-radius: 6px;
}
pre a {
color: inherit;
text-decoration: underline;
}
td.linenos pre {
margin: 1em 0em;
}
td.code pre {
margin: 1em 0em;
}
div.quotebar {
background-color: #f8f8f8;
max-width: 250px;
float: right;
padding: 2px 7px;
border: 1px solid #ccc;
}
div.topic {
background-color: #f8f8f8;
}
table {
border-collapse: collapse;
margin: 0 -0.5em 0 -0.5em;
}
table td, table th {
padding: 0.2em 0.5em 0.2em 0.5em;
}
/* ADMONITIONS AND WARNINGS ------------------------------------------------- */
/* Shared by admonitions, warnings and sidebars */
div.admonition,
div.warning,
div.sidebar {
font-size: 0.9em;
margin: 2em;
padding: 0;
/*
border-radius: 6px;
-moz-border-radius: 6px;
-webkit-border-radius: 6px;
*/
}
div.admonition p,
div.warning p,
div.sidebar p {
margin: 0.5em 1em 0.5em 1em;
padding: 0;
}
div.admonition pre,
div.warning pre,
div.sidebar pre {
margin: 0.4em 1em 0.4em 1em;
}
div.admonition p.admonition-title,
div.warning p.admonition-title,
div.sidebar p.sidebar-title {
margin: 0;
padding: 0.1em 0 0.1em 0.5em;
color: white;
font-weight: bold;
font-size: 1.1em;
text-shadow: 0 1px rgba(0, 0, 0, 0.5);
}
div.admonition ul, div.admonition ol,
div.warning ul, div.warning ol,
div.sidebar ul, div.sidebar ol {
margin: 0.1em 0.5em 0.5em 3em;
padding: 0;
}
/* Admonitions and sidebars only */
div.admonition, div.sidebar {
border: 1px solid #609060;
background-color: #e9ffe9;
}
div.admonition p.admonition-title,
div.sidebar p.sidebar-title {
background-color: #70A070;
border-bottom: 1px solid #609060;
}
/* Warnings only */
div.warning {
border: 1px solid #900000;
background-color: #ffe9e9;
}
div.warning p.admonition-title {
background-color: #b04040;
border-bottom: 1px solid #900000;
}
/* Sidebars only */
div.sidebar {
max-width: 30%;
}
div.versioninfo {
margin: 1em 0 0 0;
border: 1px solid #ccc;
background-color: #DDEAF0;
padding: 8px;
line-height: 1.3em;
font-size: 0.9em;
}
.viewcode-back {
font-family: 'Lucida Grande', 'Lucida Sans Unicode', 'Geneva',
'Verdana', sans-serif;
}
div.viewcode-block:target {
background-color: #f4debf;
border-top: 1px solid #ac9;
border-bottom: 1px solid #ac9;
}
dl {
margin: 1em 0 2.5em 0;
}
/* Highlight target when you click an internal link */
dt:target {
background: #ffe080;
}
/* Don't highlight whole divs */
div.highlight {
background: transparent;
}
/* But do highlight spans (so search results can be highlighted) */
span.highlight {
background: #ffe080;
}
div.footer {
background-color: #465158;
color: #eeeeee;
padding: 0 2em 2em 2em;
clear: both;
font-size: 0.8em;
text-align: center;
}
p {
margin: 0.8em 0 0.5em 0;
}
.section p img.math {
margin: 0;
}
.section p img {
margin: 1em 2em;
}
/* MOBILE LAYOUT -------------------------------------------------------------- */
@media screen and (max-width: 600px) {
h1, h2, h3, h4, h5 {
position: relative;
}
ul {
padding-left: 1.25em;
}
div.bodywrapper a.headerlink, #indices-and-tables h1 a {
color: #e6e6e6;
font-size: 80%;
float: right;
line-height: 1.8;
position: absolute;
right: -0.7em;
visibility: inherit;
}
div.bodywrapper h1 a.headerlink, #indices-and-tables h1 a {
line-height: 1.5;
}
pre {
font-size: 0.7em;
overflow: auto;
word-wrap: break-word;
white-space: pre-wrap;
}
div.related ul {
height: 2.5em;
padding: 0;
text-align: left;
}
div.related ul li {
clear: both;
color: #465158;
padding: 0.2em 0;
}
div.related ul li:last-child {
border-bottom: 1px dotted #8ca1af;
padding-bottom: 0.4em;
margin-bottom: 1em;
width: 100%;
}
div.related ul li a {
color: #465158;
padding-right: 0;
}
div.related ul li a:hover {
background: inherit;
color: inherit;
}
div.related ul li.right {
clear: none;
padding: 0.65em 0;
margin-bottom: 0.5em;
}
div.related ul li.right a {
color: #fff;
padding-right: 0.8em;
}
div.related ul li.right a:hover {
background-color: #8ca1af;
}
div.body {
clear: both;
min-width: 0;
word-wrap: break-word;
}
div.bodywrapper {
margin: 0 0 0 0;
}
div.sphinxsidebar {
float: none;
margin: 0;
width: auto;
}
div.sphinxsidebar input[type="text"] {
height: 2em;
line-height: 2em;
width: 70%;
}
div.sphinxsidebar input[type="submit"] {
height: 2em;
margin-left: 0.5em;
width: 20%;
}
div.sphinxsidebar p.searchtip {
background: inherit;
margin-bottom: 1em;
}
div.sphinxsidebar ul li, div.sphinxsidebar p.topless {
white-space: normal;
}
.bodywrapper img {
display: block;
margin-left: auto;
margin-right: auto;
max-width: 100%;
}
div.documentwrapper {
float: none;
}
div.admonition, div.warning, pre, blockquote {
margin-left: 0em;
margin-right: 0em;
}
.body p img {
margin: 0;
}
#searchbox {
background: transparent;
}
.related:not(:first-child) li {
display: none;
}
.related:not(:first-child) li.right {
display: block;
}
div.footer {
padding: 1em;
}
.rtd_doc_footer .badge {
float: none;
margin: 1em auto;
position: static;
}
.rtd_doc_footer .badge.revsys-inline {
margin-right: auto;
margin-bottom: 2em;
}
table.indextable {
display: block;
width: auto;
}
.indextable tr {
display: block;
}
.indextable td {
display: block;
padding: 0;
width: auto !important;
}
.indextable td dt {
margin: 1em 0;
}
ul.search {
margin-left: 0.25em;
}
ul.search li div.context {
font-size: 90%;
line-height: 1.1;
margin-bottom: 1;
margin-left: 0;
}
}

99
report/generate.py Normal file
View file

@ -0,0 +1,99 @@
from os.path import dirname, join
from rst import Rest, Paragraph, Strong, OrderedListItem, ListItem, Title, Link, Transition
from rst import Directive, Em, Quote, Text
from tiramisu.option import *
from tiramisu.config import *
#from makerestdoc import *
docdir = join(dirname(__file__), 'build')
def make_rst_file(filename, rstcontent):
fh = file(filename, 'w')
fh.write(rstcontent.text())
fh.close()
def descr_content(path, prefix, descr, root=False):
content = Rest()
title = Title(abovechar="", belowchar="=")
if root:
title.join(Text("Configuration's overview for: "), Quote(descr._name))
else:
title.join(Text("Group's overview for: "), Quote(descr._name))
content.add(title)
content.add(ListItem().join(Strong("name:"), Text(descr._name)))
if not root:
content.add(ListItem().join(Strong("path:"), Text(path)))
content.add(ListItem().join(Strong("description:"), Text(descr.doc)))
if not root:
content.add(ListItem().join(Strong("container:"), Text(prefix)))
if not root:
content.add(ListItem().join(Strong("type:"), Text(descr.group_type)))
if not root:
content.add(ListItem().join(Strong("requirements:"), Text(str(descr._requires))))
content.add(ListItem().join(Strong("is hidden:"), Text(str(descr._is_hidden()))))
content.add(ListItem().join(Strong("is disabled:"), Text(str(descr._is_disabled()))))
content.add(Transition())
content.add(Title(abovechar="", belowchar="-").join(Text("Ordered list of childrens for:"), Text(path)))
for opt in descr._children:
name = opt._name
link = Link(name + ":", join(path + '.' + name + ".html"))
# because of SympLink opt
if hasattr(opt, 'doc'):
doc = opt.doc
else:
doc = name
content.add(OrderedListItem(link, Text(opt.doc)))
content.add(Transition())
content.add(Paragraph(Link("back to index", "index.html")))
make_rst_file(join(docdir, path + '.txt'), content)
if root:
make_rst_file(join(docdir, 'index.txt'), content)
def opt_rst_content(path, prefix, descr, value):
content = Rest()
title = Title(abovechar="", belowchar="=")
title.join(Text("Configuration's option overview for: "), Quote(descr._name))
content.add(title)
content.add(ListItem().join(Strong("name:"), Text(descr._name)))
content.add(ListItem().join(Strong("value:"), Text(str(value))))
content.add(ListItem().join(Strong("path:"), Text(path)))
content.add(ListItem().join(Strong("container:"), Text(prefix)))
if isinstance(descr, ChoiceOption):
content.add(ListItem().join(Strong("possible values:"), Text(str(descr.values))))
if not isinstance(descr, SymLinkOption):
content.add(ListItem().join(Strong("type:"), Text(str(descr.opt_type))))
content.add(ListItem().join(Strong("default:"), Text(str(descr.getdefault()))))
content.add(ListItem().join(Strong("description:"), Text(str(descr.getdoc()))))
content.add(ListItem().join(Strong("requirements:"), Text(str(descr._requires))))
content.add(ListItem().join(Strong("is hidden:"), Text(str(descr._is_hidden()))))
content.add(ListItem().join(Strong("is disabled:"), Text(str(descr._is_disabled()))))
content.add(ListItem().join(Strong("is frozen:"), Text(str(descr._frozen))))
content.add(ListItem().join(Strong("is multi:"), Text(str(descr.multi))))
content.add(ListItem().join(Strong("is mandatory:"), Text(str(descr.is_mandatory()))))
else:
content.add(ListItem().join(Strong("links to:"), Text(str(descr.path))))
content.add(Transition())
content.add(Paragraph(Link("back to container", join(prefix + ".html"))))
make_rst_file(join(docdir, path + '.txt'), content)
def make_rest_overview(cfg, title=True):
rootname = cfg._cfgimpl_descr._name
descr_content(rootname, rootname, cfg._cfgimpl_descr, root=True)
#cfg.cfgimpl_read_write()
cfg._cfgimpl_disabled = False
cfg._cfgimpl_hidden = False
for path in cfg.getpaths(include_groups=True, allpaths=True):
child = cfg.unwrap_from_path(path)
fullpath = rootname + '.' + path
prefix = fullpath.rsplit(".", 1)[0]
if isinstance(child, OptionDescription):
descr_content(fullpath, prefix, child)
else:
value = getattr(cfg, path)
opt_rst_content(fullpath, prefix, child, value)
if __name__ == '__main__':
from test_config_big_example import get_example_config
make_rest_overview(get_example_config())
# ____________________________________________________________

115
report/makerestdoc.py Normal file
View file

@ -0,0 +1,115 @@
from tiramisu.config import Config
from tiramisu import option
# we shall keep extendable types out of the reach of unexceptional guys like us
# horror __metaclass__ = extendabletype
def get_fullpath(opt, path):
if path:
return "%s.%s" % (path, opt._name)
else:
return opt._name
class Option:
def make_rest_doc(self, path=""):
fullpath = get_fullpath(self, path)
result = Rest(
Title(fullpath, abovechar="=", belowchar="="),
ListItem(Strong("name:"), self._name),
ListItem(Strong("description:"), self.doc))
return result
class ChoiceOption(Option, option.ChoiceOption):
def make_rest_doc(self, path=""):
content = super(ChoiceOption, self).make_rest_doc(path)
content.add(ListItem(Strong("option type:"), "choice option"))
content.add(ListItem(Strong("possible values:"),
*[ListItem(str(val)) for val in self.values]))
if self.default is not None:
content.add(ListItem(Strong("default:"), str(self.default)))
# requirements = []
#
# for val in self.values:
# if val not in self._requires:
# continue
# req = self._requires[val]
# requirements.append(ListItem("value '%s' requires:" % (val, ),
# *[ListItem(Link(opt, opt + ".html"),
# "to be set to '%s'" % (rval, ))
# for (opt, rval) in req]))
# if requirements:
# content.add(ListItem(Strong("requirements:"), *requirements))
return content
class BoolOption(Option, option.BoolOption):
def make_rest_doc(self, path=""):
content = super(BoolOption, self).make_rest_doc(path)
fullpath = get_fullpath(self, path)
content.add(ListItem(Strong("option type:"), "boolean option"))
if self.default is not None:
content.add(ListItem(Strong("default:"), str(self.default)))
# if self._requires is not None:
# requirements = [ListItem(Link(opt, opt + ".html"),
# "must be set to '%s'" % (rval, ))
# for (opt, rval) in self._requires]
# if requirements:
# content.add(ListItem(Strong("requirements:"), *requirements))
return content
class IntOption(Option, option.IntOption):
def make_rest_doc(self, path=""):
content = super(IntOption, self).make_rest_doc(path)
content.add(ListItem(Strong("option type:"), "integer option"))
if self.default is not None:
content.add(ListItem(Strong("default:"), str(self.default)))
return content
class FloatOption(Option, option.FloatOption):
def make_rest_doc(self, path=""):
content = super(FloatOption, self).make_rest_doc(path)
content.add(ListItem(Strong("option type:"), "float option"))
if self.default is not None:
content.add(ListItem(Strong("default:"), str(self.default)))
return content
class StrOption(Option, option.StrOption):
def make_rest_doc(self, path=""):
content = super(StrOption, self).make_rest_doc(path)
content.add(ListItem(Strong("option type:"), "string option"))
if self.default is not None:
content.add(ListItem(Strong("default:"), str(self.default)))
return content
#class ArbitraryOption:
# def make_rest_doc(self, path=""):
# content = super(ArbitraryOption, self).make_rest_doc(path)
# content.add(ListItem(Strong("option type:"),
# "arbitrary option (mostly internal)"))
# if self.default is not None:
# content.add(ListItem(Strong("default:"), str(self.default)))
# elif self.defaultfactory is not None:
# content.add(ListItem(Strong("factory for the default value:"),
# str(self.defaultfactory)))
# return content
class OptionDescription(option.OptionDescription):
def make_rest_doc(self, path=""):
fullpath = get_fullpath(self, path)
content = Rest(
Title(fullpath, abovechar="=", belowchar="="))
toctree = []
for child in self._children:
subpath = fullpath + "." + child._name
toctree.append(subpath)
content.add(Directive("toctree", *toctree, **{'maxdepth': 4}))
content.join(
ListItem(Strong("name:"), self._name),
ListItem(Strong("description:"), self.doc))
stack = []
curr = content
# config = Config(self)
return content
# ____________________________________________________________

410
report/rst.py Normal file
View file

@ -0,0 +1,410 @@
# unproudly borrowed from pypy :
# http://codespeak.net/svn/pypy/trunk/pypy/tool/rest/rst.py
""" reStructuredText generation tools
provides an api to build a tree from nodes, which can be converted to
ReStructuredText on demand
note that not all of ReST is supported, a usable subset is offered, but
certain features aren't supported, and also certain details (like how links
are generated, or how escaping is done) can not be controlled
"""
import re
def escape(txt):
"""escape ReST markup"""
if not isinstance(txt, str) and not isinstance(txt, unicode):
txt = str(txt)
# XXX this takes a very naive approach to escaping, but it seems to be
# sufficient...
for c in '\\*`|:_':
txt = txt.replace(c, '\\%s' % (c,))
return txt
class RestError(Exception):
""" raised on containment errors (wrong parent) """
class AbstractMetaclass(type):
def __new__(cls, *args):
obj = super(AbstractMetaclass, cls).__new__(cls, *args)
parent_cls = obj.parentclass
if parent_cls is None:
return obj
if not isinstance(parent_cls, list):
class_list = [parent_cls]
else:
class_list = parent_cls
if obj.allow_nesting:
class_list.append(obj)
for _class in class_list:
if not _class.allowed_child:
_class.allowed_child = {obj:True}
else:
_class.allowed_child[obj] = True
return obj
class AbstractNode(object):
""" Base class implementing rest generation
"""
sep = ''
__metaclass__ = AbstractMetaclass
parentclass = None # this exists to allow parent to know what
# children can exist
allow_nesting = False
allowed_child = {}
defaults = {}
_reg_whitespace = re.compile('\s+')
def __init__(self, *args, **kwargs):
self.parent = None
self.children = []
for child in args:
self._add(child)
for arg in kwargs:
setattr(self, arg, kwargs[arg])
def join(self, *children):
""" add child nodes
returns a reference to self
"""
for child in children:
self._add(child)
return self
def add(self, child):
""" adds a child node
returns a reference to the child
"""
self._add(child)
return child
def _add(self, child):
if child.__class__ not in self.allowed_child:
raise RestError("%r cannot be child of %r" % \
(child.__class__, self.__class__))
self.children.append(child)
child.parent = self
def __getitem__(self, item):
return self.children[item]
def __setitem__(self, item, value):
self.children[item] = value
def text(self):
""" return a ReST string representation of the node """
return self.sep.join([child.text() for child in self.children])
def wordlist(self):
""" return a list of ReST strings for this node and its children """
return [self.text()]
class Rest(AbstractNode):
""" Root node of a document """
sep = "\n\n"
def __init__(self, *args, **kwargs):
AbstractNode.__init__(self, *args, **kwargs)
self.links = {}
def render_links(self, check=False):
"""render the link attachments of the document"""
assert not check, "Link checking not implemented"
if not self.links:
return ""
link_texts = []
# XXX this could check for duplicates and remove them...
for link, target in self.links.iteritems():
link_texts.append(".. _`%s`: %s" % (escape(link), target))
return "\n" + "\n".join(link_texts) + "\n\n"
def text(self):
outcome = []
if (isinstance(self.children[0], Transition) or
isinstance(self.children[-1], Transition)):
raise ValueError, ('document must not begin or end with a '
'transition')
for child in self.children:
outcome.append(child.text())
# always a trailing newline
text = self.sep.join([i for i in outcome if i]) + "\n"
return text + self.render_links()
class Transition(AbstractNode):
""" a horizontal line """
parentclass = Rest
def __init__(self, char='-', width=80, *args, **kwargs):
self.char = char
self.width = width
super(Transition, self).__init__(*args, **kwargs)
def text(self):
return (self.width - 1) * self.char
class Paragraph(AbstractNode):
""" simple paragraph """
parentclass = Rest
sep = " "
indent = ""
# FIXME
width = 880
def __init__(self, *args, **kwargs):
# make shortcut
args = list(args)
for num, arg in enumerate(args):
if isinstance(arg, str):
args[num] = Text(arg)
super(Paragraph, self).__init__(*args, **kwargs)
def text(self):
texts = []
for child in self.children:
texts += child.wordlist()
buf = []
outcome = []
lgt = len(self.indent)
def grab(buf):
outcome.append(self.indent + self.sep.join(buf))
texts.reverse()
while texts:
next = texts[-1]
if not next:
texts.pop()
continue
if lgt + len(self.sep) + len(next) <= self.width or not buf:
buf.append(next)
lgt += len(next) + len(self.sep)
texts.pop()
else:
grab(buf)
lgt = len(self.indent)
buf = []
grab(buf)
return "\n".join(outcome)
class SubParagraph(Paragraph):
""" indented sub paragraph """
indent = " "
class Title(Paragraph):
""" title element """
parentclass = Rest
belowchar = "="
abovechar = ""
def text(self):
txt = self._get_text()
lines = []
if self.abovechar:
lines.append(self.abovechar * len(txt))
lines.append(txt)
if self.belowchar:
lines.append(self.belowchar * len(txt))
return "\n".join(lines)
def _get_text(self):
txt = []
for node in self.children:
txt += node.wordlist()
return ' '.join(txt)
class AbstractText(AbstractNode):
parentclass = [Paragraph, Title]
start = ""
end = ""
def __init__(self, _text):
self._text = _text
def text(self):
text = self.escape(self._text)
return self.start + text + self.end
def escape(self, text):
if not isinstance(text, str) and not isinstance(text, unicode):
text = str(text)
if self.start:
text = text.replace(self.start, '\\%s' % (self.start,))
if self.end and self.end != self.start:
text = text.replace(self.end, '\\%s' % (self.end,))
return text
class Text(AbstractText):
def wordlist(self):
text = escape(self._text)
return self._reg_whitespace.split(text)
class LiteralBlock(AbstractText):
parentclass = Rest
start = '::\n\n'
def text(self):
if not self._text.strip():
return ''
text = self.escape(self._text).split('\n')
for i, line in enumerate(text):
if line.strip():
text[i] = ' %s' % (line,)
return self.start + '\n'.join(text)
class Em(AbstractText):
start = "*"
end = "*"
class Strong(AbstractText):
start = "**"
end = "**"
class Quote(AbstractText):
start = '``'
end = '``'
class Anchor(AbstractText):
start = '_`'
end = '`'
class Footnote(AbstractText):
def __init__(self, note, symbol=False):
raise NotImplemented('XXX')
class Citation(AbstractText):
def __init__(self, text, cite):
raise NotImplemented('XXX')
class ListItem(Paragraph):
allow_nesting = True
item_chars = '*+-'
def text(self):
idepth = self.get_indent_depth()
indent = self.indent + (idepth + 1) * ' '
txt = '\n\n'.join(self.render_children(indent))
ret = []
item_char = self.item_chars[idepth]
ret += [indent[len(item_char)+1:], item_char, ' ', txt[len(indent):]]
return ''.join(ret)
def render_children(self, indent):
txt = []
buffer = []
def render_buffer(fro, to):
if not fro:
return
p = Paragraph(indent=indent, *fro)
p.parent = self.parent
to.append(p.text())
for child in self.children:
if isinstance(child, AbstractText):
buffer.append(child)
else:
if buffer:
render_buffer(buffer, txt)
buffer = []
txt.append(child.text())
render_buffer(buffer, txt)
return txt
def get_indent_depth(self):
depth = 0
current = self
while (current.parent is not None and
isinstance(current.parent, ListItem)):
depth += 1
current = current.parent
return depth
class OrderedListItem(ListItem):
item_chars = ["#."] * 5
class DListItem(ListItem):
item_chars = None
def __init__(self, term, definition, *args, **kwargs):
self.term = term
super(DListItem, self).__init__(definition, *args, **kwargs)
def text(self):
idepth = self.get_indent_depth()
indent = self.indent + (idepth + 1) * ' '
txt = '\n\n'.join(self.render_children(indent))
ret = []
ret += [indent[2:], self.term, '\n', txt]
return ''.join(ret)
class Link(AbstractText):
start = '`'
end = '`_'
def __init__(self, _text, target):
self._text = _text
self.target = target
self.rest = None
def text(self):
if self.rest is None:
self.rest = self.find_rest()
if self.rest.links.get(self._text, self.target) != self.target:
raise ValueError('link name %r already in use for a different '
'target' % (self.target,))
self.rest.links[self._text] = self.target
return AbstractText.text(self)
def find_rest(self):
# XXX little overkill, but who cares...
next = self
while next.parent is not None:
next = next.parent
return next
class InternalLink(AbstractText):
start = '`'
end = '`_'
class LinkTarget(Paragraph):
def __init__(self, name, target):
self.name = name
self.target = target
def text(self):
return ".. _`%s`:%s\n" % (self.name, self.target)
class Substitution(AbstractText):
def __init__(self, text, **kwargs):
raise NotImplemented('XXX')
class Directive(Paragraph):
indent = ' '
def __init__(self, name, *args, **options):
self.name = name
self.content = args
super(Directive, self).__init__()
self.options = options
def text(self):
# XXX not very pretty...
txt = '.. %s::' % (self.name,)
options = '\n'.join([' :%s: %s' % (k, v) for (k, v) in
self.options.iteritems()])
if options:
txt += '\n%s' % (options,)
if self.content:
txt += '\n'
for item in self.content:
txt += '\n ' + item
return txt

View file

@ -0,0 +1,27 @@
# coding: utf-8
from tiramisu.config import *
from tiramisu.option import *
all_modules = ['amon', 'sphynx', 'zephir']
gcoption = ChoiceOption('name', 'GC name', ['ref', 'framework'], 'ref')
gcdummy = BoolOption('dummy', 'dummy', default=False)
objspaceoption = ChoiceOption('objspace', 'Object space',
['std', 'thunk'], 'std')
booloption = BoolOption('bool', 'Test boolean option', default=True)
intoption = IntOption('int', 'Test int option', default=0)
floatoption = FloatOption('float', 'Test float option', default=2.3)
stroption = StrOption('str', 'Test string option', default="abc")
boolop = BoolOption('boolop', 'Test boolean option op', default=True)
wantref_option = BoolOption('wantref', 'Test requires', default=False)
wantframework_option = BoolOption('wantframework', 'Test requires',
default=False)
gcgroup = OptionDescription('gc', 'doc pour gc', [gcoption, gcdummy, floatoption])
descr = OptionDescription('essai', 'une éééééé doc pour essai', [gcgroup, booloption, objspaceoption,
wantref_option, stroption,
wantframework_option,
intoption, boolop])
def get_example_config():
return Config(descr)

0
test/__init__.py Normal file
View file

13
test/autopath.py Normal file
View file

@ -0,0 +1,13 @@
"""automatically sets the PYTHONPATH before running the unit tests
This is supposed to be used in development mode (i.e. testing from a fresh
checkout)
"""
from os.path import dirname, abspath, join, normpath
import sys
HERE = dirname(abspath(__file__))
PATH = normpath(join(HERE, '..'))
if PATH not in sys.path:
sys.path.insert(1, PATH)

96
test/test_config.py Normal file
View file

@ -0,0 +1,96 @@
#this test is much more to test that **it's there** and answers attribute access
import autopath
from py.test import raises
from config import *
from option import *
def make_description():
gcoption = ChoiceOption('name', 'GC name', ['ref', 'framework'], 'ref')
gcdummy = BoolOption('dummy', 'dummy', default=False)
objspaceoption = ChoiceOption('objspace', 'Object space',
['std', 'thunk'], 'std')
booloption = BoolOption('bool', 'Test boolean option', default=True)
intoption = IntOption('int', 'Test int option', default=0)
floatoption = FloatOption('float', 'Test float option', default=2.3)
stroption = StrOption('str', 'Test string option', default="abc")
boolop = BoolOption('boolop', 'Test boolean option op', default=True)
wantref_option = BoolOption('wantref', 'Test requires', default=False)
wantframework_option = BoolOption('wantframework', 'Test requires',
default=False)
gcgroup = OptionDescription('gc', '', [gcoption, gcdummy, floatoption])
descr = OptionDescription('tiram', '', [gcgroup, booloption, objspaceoption,
wantref_option, stroption,
wantframework_option,
intoption, boolop])
return descr
def test_base_config():
gcdummy = BoolOption('dummy', 'dummy', default=False)
descr = OptionDescription('tiramisu', '', [gcdummy])
cfg = Config(descr)
assert cfg.dummy == False
dm = cfg.unwrap_from_path('dummy')
assert dm._name == 'dummy'
def test_base_config_and_groups():
descr = make_description()
# overrides the booloption default value
config = Config(descr, bool=False)
assert config.gc.name == 'ref'
assert config.bool == False
nm = config.unwrap_from_path('gc.name')
assert nm._name == 'name'
gc = config.unwrap_from_path('gc')
assert gc._name == 'gc'
nm = config.unwrap_from_name('name')
assert nm._name == 'name'
def test_base_config_in_a_tree():
"how options are organized into a tree"
descr = make_description()
config = Config(descr, bool=False)
assert config.gc.name == 'ref'
config.gc.name = 'framework'
assert config.gc.name == 'framework'
assert getattr(config, "gc.name") == 'framework'
assert config.objspace == 'std'
config.objspace = 'thunk'
assert config.objspace == 'thunk'
assert config.gc.float == 2.3
assert config.int == 0
config.gc.float = 3.4
config.int = 123
assert config.gc.float == 3.4
assert config.int == 123
assert not config.wantref
assert config.str == "abc"
config.str = "def"
assert config.str == "def"
raises(AttributeError, 'config.gc.foo = "bar"')
config = Config(descr, bool=False)
assert config.gc.name == 'ref'
config.wantframework = True
def test_config_values():
"_cfgimpl_values appears to be a simple dict"
descr = make_description()
config = Config(descr, bool=False)
config.set(dummy=False)
assert config.gc._cfgimpl_values == {'dummy': False, 'float': 2.3, 'name': 'ref'}
def test_cfgimpl_get_home_by_path():
descr = make_description()
config = Config(descr, bool=False)
assert config._cfgimpl_get_home_by_path('gc.dummy')[1] == 'dummy'
assert config._cfgimpl_get_home_by_path('dummy')[1] == 'dummy'
assert config.getpaths(include_groups=False) == ['gc.name', 'gc.dummy', 'gc.float', 'bool', 'objspace', 'wantref', 'str', 'wantframework', 'int', 'boolop']
assert config.getpaths(include_groups=True) == ['gc', 'gc.name', 'gc.dummy', 'gc.float', 'bool', 'objspace', 'wantref', 'str', 'wantframework', 'int', 'boolop']

167
test/test_config_api.py Normal file
View file

@ -0,0 +1,167 @@
"configuration objects global API"
import autopath
from py.test import raises
from config import *
from option import *
def make_description():
gcoption = ChoiceOption('name', 'GC name', ['ref', 'framework'], 'ref')
gcdummy = BoolOption('dummy', 'dummy', default=False)
objspaceoption = ChoiceOption('objspace', 'Object space',
['std', 'thunk'], 'std')
booloption = BoolOption('bool', 'Test boolean option', default=True)
intoption = IntOption('int', 'Test int option', default=0)
floatoption = FloatOption('float', 'Test float option', default=2.3)
stroption = StrOption('str', 'Test string option', default="abc")
boolop = BoolOption('boolop', 'Test boolean option op', default=True)
wantref_option = BoolOption('wantref', 'Tests', default=False)
wantframework_option = BoolOption('wantframework', 'Test', default=False)
gcgroup = OptionDescription('gc', '', [gcoption, gcdummy, floatoption])
descr = OptionDescription('tiramisu', '', [gcgroup, booloption, objspaceoption,
wantref_option, stroption,
wantframework_option,
intoption, boolop])
return descr
def test_compare_configs():
"config object comparison"
descr = make_description()
conf1 = Config(descr)
conf2 = Config(descr, wantref=True)
assert conf1 != conf2
assert hash(conf1) != hash(conf2)
assert conf1.getkey() != conf2.getkey()
conf1.wantref = True
assert conf1 == conf2
assert hash(conf1) == hash(conf2)
assert conf1.getkey() == conf2.getkey()
# ____________________________________________________________
def test_iter_config():
"iteration on config object"
s = StrOption("string", "", default="string")
s2 = StrOption("string2", "", default="string2")
descr = OptionDescription("options", "", [s,s2])
config = Config(descr)
assert [(name, value) for name, value in config] == \
[('string', 'string'), ('string2', 'string2')]
def test_iter_subconfig():
"iteration on config sub object"
descr = make_description()
conf = Config(descr)
for (name, value), (gname, gvalue) in \
zip(conf.gc, [("name", "ref"), ("dummy", False)]):
assert name == gname
assert value == gvalue
#____________________________________________________________
def test_getpaths():
descr = make_description()
config = Config(descr)
assert config.getpaths() == ['gc.name', 'gc.dummy', 'gc.float', 'bool',
'objspace', 'wantref', 'str', 'wantframework',
'int', 'boolop']
assert config.getpaths() == descr.getpaths()
assert config.gc.getpaths() == ['name', 'dummy', 'float']
assert config.gc.getpaths() == descr.gc.getpaths()
assert config.getpaths(include_groups=True) == [
'gc', 'gc.name', 'gc.dummy', 'gc.float',
'bool', 'objspace', 'wantref', 'str', 'wantframework', 'int', 'boolop']
assert config.getpaths(True) == descr.getpaths(True)
def test_getpaths_with_hidden():
objspaceoption = ChoiceOption('objspace', 'Object space',
['std', 'thunk'], 'std')
booloption = BoolOption('bool', 'Test boolean option', default=True)
booloption.hide()
intoption = IntOption('int', 'Test int option', default=0)
stroption = StrOption('str', 'Test string option', default="abc")
boolop = BoolOption('boolop', 'Test boolean option op', default=True)
wantref_option = BoolOption('wantref', 'Test requires', default=False)
wantframework_option = BoolOption('wantframework', 'Test requires',
default=False)
descr = OptionDescription('tiramisu', '', [booloption, objspaceoption,
wantref_option, stroption,
wantframework_option,
intoption, boolop])
config = Config(descr)
result = ['objspace', 'wantref', 'str', 'wantframework', 'int', 'boolop']
assert config.getpaths() == result
r2 = ['bool', 'objspace', 'wantref', 'str', 'wantframework', 'int', 'boolop']
assert config.getpaths(allpaths=True) == r2
def test_str():
descr = make_description()
c = Config(descr)
print c # does not crash
def test_dir():
descr = make_description()
c = Config(descr)
print dir(c)
def test_make_dict():
"serialization of the whole config to a dict"
descr = OptionDescription("opt", "", [
OptionDescription("s1", "", [
BoolOption("a", "", default=False)]),
IntOption("int", "", default=42)])
config = Config(descr)
d = make_dict(config)
assert d == {"s1.a": False, "int": 42}
config.int = 43
config.s1.a = True
d = make_dict(config)
assert d == {"s1.a": True, "int": 43}
d2 = make_dict(config, flatten=True)
assert d2 == {'a': True, 'int': 43}
def test_delattr():
"delattr, means suppression of an option in a config"
descr = OptionDescription("opt", "", [
OptionDescription("s1", "", [
BoolOption("a", "", default=False)]),
IntOption("int", "", default=42)])
c = Config(descr)
c.int = 45
assert c.int == 45
del c.int
assert c.int == 42
c.int = 45
assert c.int == 45
#def test_validator():
# "validates the integrity of an option towards a whole configuration"
# def my_validator_1(config):
# assert config is c
# def my_validator_2(config):
# assert config is c
# raise ConflictConfigError
# descr = OptionDescription("opt", "", [
# BoolOption('booloption1', 'option test1', default=False,
# validator=my_validator_1),
# BoolOption('booloption2', 'option test2', default=False,
# validator=my_validator_2),
# BoolOption('booloption4', 'option test4', default=False,
# ),
# ])
# c = Config(descr)
# c.booloption1 = True
## raises(ConfigError, "c.booloption2 = True")
## assert c.booloption2 is False
## raises(ConfigError, "c.booloption3 = True")
# assert c.booloption2 is False
# c.booloption4 = True
# assert c.booloption2 is False
# c.booloption2 = False
# assert c.booloption2 is False
#

View file

@ -0,0 +1,258 @@
#just a proof of concept with a lot of options and option groups
import autopath
from config import *
from option import *
all_modules = ['amon', 'sphynx', 'zephir']
example__optiondescription = OptionDescription("objspace", "Object Space Options", [
ChoiceOption("name", "Object Space name",
["std", "flow", "thunk", "dump", "taint"],
"std"),
OptionDescription("opcodes", "opcodes to enable in the interpreter", [
BoolOption("CALL_LIKELY_BUILTIN", "emit a special bytecode for likely calls to builtin functions",
default=False,
requires=[("translation.stackless", False)]),
BoolOption("CALL_METHOD", "emit a special bytecode for expr.name()",
default=False),
]),
BoolOption("nofaking", "disallow faking in the object space",
default=False,
requires=[
("objspace.usemodules.posix", True),
("objspace.usemodules.time", True),
("objspace.usemodules.errno", True)],
),
OptionDescription("usemodules", "Which Modules should be used", [
BoolOption(modname, "use module %s" % (modname, ),
default=True,
requires= ['amon'],
)
for modname in all_modules]),
BoolOption("allworkingmodules", "use as many working modules as possible",
default=True,
),
BoolOption("translationmodules",
"use only those modules that are needed to run translate.py on pypy",
default=False,
),
BoolOption("geninterp", "specify whether geninterp should be used",
default=True),
BoolOption("logbytecodes",
"keep track of bytecode usage",
default=False),
BoolOption("usepycfiles", "Write and read pyc files when importing",
default=True),
BoolOption("lonepycfiles", "Import pyc files with no matching py file",
default=False,
requires=[("objspace.usepycfiles", True)]),
StrOption("soabi",
"Tag to differentiate extension modules built for different Python interpreters",
default=None),
BoolOption("honor__builtins__",
"Honor the __builtins__ key of a module dictionary",
default=False),
BoolOption("disable_call_speedhacks",
"make sure that all calls go through space.call_args",
default=False),
BoolOption("timing",
"timing of various parts of the interpreter (simple profiling)",
default=False),
OptionDescription("std", "Standard Object Space Options", [
BoolOption("withtproxy", "support transparent proxies",
default=True),
BoolOption("withsmallint", "use tagged integers",
default=False,
requires=[("objspace.std.withprebuiltint", False),
("translation.taggedpointers", True)]),
BoolOption("withprebuiltint", "prebuild commonly used int objects",
default=False),
IntOption("prebuiltintfrom", "lowest integer which is prebuilt",
default=-5),
IntOption("prebuiltintto", "highest integer which is prebuilt",
default=100),
BoolOption("withstrjoin", "use strings optimized for addition",
default=False),
BoolOption("withstrslice", "use strings optimized for slicing",
default=False),
BoolOption("withstrbuf", "use strings optimized for addition (ver 2)",
default=False),
BoolOption("withprebuiltchar",
"use prebuilt single-character string objects",
default=False),
BoolOption("sharesmallstr",
"always reuse the prebuilt string objects "
"(the empty string and potentially single-char strings)",
default=False),
BoolOption("withrope", "use ropes as the string implementation",
default=False,
requires=[("objspace.std.withstrslice", False),
("objspace.std.withstrjoin", False),
("objspace.std.withstrbuf", False)],
),
BoolOption("withropeunicode", "use ropes for the unicode implementation",
default=False,
requires=[("objspace.std.withrope", True)]),
BoolOption("withcelldict",
"use dictionaries that are optimized for being used as module dicts",
default=False,
requires=[("objspace.opcodes.CALL_LIKELY_BUILTIN", False),
("objspace.honor__builtins__", False)]),
BoolOption("withdictmeasurement",
"create huge files with masses of information "
"about dictionaries",
default=False),
BoolOption("withmapdict",
"make instances really small but slow without the JIT",
default=False,
requires=[("objspace.std.getattributeshortcut", True),
("objspace.std.withtypeversion", True),
]),
BoolOption("withrangelist",
"enable special range list implementation that does not "
"actually create the full list until the resulting "
"list is mutated",
default=False),
BoolOption("withtypeversion",
"version type objects when changing them",
default=False,
# weakrefs needed, because of get_subclasses()
requires=[("translation.rweakref", True)]),
BoolOption("withmethodcache",
"try to cache method lookups",
default=False,
requires=[("objspace.std.withtypeversion", True),
("translation.rweakref", True)]),
BoolOption("withmethodcachecounter",
"try to cache methods and provide a counter in __pypy__. "
"for testing purposes only.",
default=False,
requires=[("objspace.std.withmethodcache", True)]),
IntOption("methodcachesizeexp",
" 2 ** methodcachesizeexp is the size of the of the method cache ",
default=11),
BoolOption("optimized_int_add",
"special case the addition of two integers in BINARY_ADD",
default=False),
BoolOption("optimized_comparison_op",
"special case the comparison of integers",
default=False),
BoolOption("optimized_list_getitem",
"special case the 'list[integer]' expressions",
default=False),
BoolOption("builtinshortcut",
"a shortcut for operations between built-in types",
default=False),
BoolOption("getattributeshortcut",
"track types that override __getattribute__",
default=False),
BoolOption("newshortcut",
"cache and shortcut calling __new__ from builtin types",
default=False),
BoolOption("logspaceoptypes",
"a instrumentation option: before exit, print the types seen by "
"certain simpler bytecodes",
default=False),
ChoiceOption("multimethods", "the multimethod implementation to use",
["doubledispatch", "mrd"],
default="mrd"),
BoolOption("immutable_builtintypes",
"Forbid the changing of builtin types", default=True),
]),
])
# ____________________________________________________________
def get_combined_translation_config(other_optdescr=None,
existing_config=None,
overrides=None,
translating=False):
if overrides is None:
overrides = {}
d = BoolOption("translating",
"indicates whether we are translating currently",
default=False)
if other_optdescr is None:
children = []
newname = ""
else:
children = [other_optdescr]
newname = other_optdescr._name
descr = OptionDescription("eole", "all options", children)
config = Config(descr, **overrides)
if translating:
config.translating = True
if existing_config is not None:
for child in existing_config._cfgimpl_descr._children:
if child._name == newname:
continue
value = getattr(existing_config, child._name)
config._cfgimpl_values[child._name] = value
return config
def get_example_config(overrides=None, translating=False):
return get_combined_translation_config(
example__optiondescription, overrides=overrides,
translating=translating)
# ____________________________________________________________
def test_example_option():
config = get_example_config()
result = ['objspace.name', 'objspace.opcodes.CALL_LIKELY_BUILTIN',
'objspace.opcodes.CALL_METHOD', 'objspace.nofaking',
'objspace.usemodules.amon', 'objspace.usemodules.sphynx',
'objspace.usemodules.zephir', 'objspace.allworkingmodules',
'objspace.translationmodules', 'objspace.geninterp',
'objspace.logbytecodes', 'objspace.usepycfiles', 'objspace.lonepycfiles',
'objspace.soabi', 'objspace.honor__builtins__',
'objspace.disable_call_speedhacks', 'objspace.timing',
'objspace.std.withtproxy', 'objspace.std.withsmallint',
'objspace.std.withprebuiltint', 'objspace.std.prebuiltintfrom',
'objspace.std.prebuiltintto', 'objspace.std.withstrjoin',
'objspace.std.withstrslice', 'objspace.std.withstrbuf',
'objspace.std.withprebuiltchar', 'objspace.std.sharesmallstr',
'objspace.std.withrope', 'objspace.std.withropeunicode',
'objspace.std.withcelldict', 'objspace.std.withdictmeasurement',
'objspace.std.withmapdict', 'objspace.std.withrangelist',
'objspace.std.withtypeversion', 'objspace.std.withmethodcache',
'objspace.std.withmethodcachecounter', 'objspace.std.methodcachesizeexp',
'objspace.std.optimized_int_add', 'objspace.std.optimized_comparison_op',
'objspace.std.optimized_list_getitem', 'objspace.std.builtinshortcut',
'objspace.std.getattributeshortcut', 'objspace.std.newshortcut',
'objspace.std.logspaceoptypes', 'objspace.std.multimethods',
'objspace.std.immutable_builtintypes']
assert config.getpaths(allpaths=True) == result

View file

@ -0,0 +1,207 @@
import autopath
from py.test import raises
from config import *
from option import *
def make_description():
gcoption = ChoiceOption('name', 'GC name', ['ref', 'framework'], 'ref')
gcdummy = BoolOption('dummy', 'dummy', default=False)
objspaceoption = ChoiceOption('objspace', 'Object space',
['std', 'thunk'], 'std')
booloption = BoolOption('bool', 'Test boolean option', default=True)
intoption = IntOption('int', 'Test int option', default=0)
floatoption = FloatOption('float', 'Test float option', default=2.3)
stroption = StrOption('str', 'Test string option', default="abc")
boolop = BoolOption('boolop', 'Test boolean option op', default=True)
wantref_option = BoolOption('wantref', 'Test requires', default=False,
requires=['boolop'])
wantframework_option = BoolOption('wantframework', 'Test requires',
default=False,
requires=['boolop'])
gcgroup = OptionDescription('gc', '', [gcoption, gcdummy, floatoption])
descr = OptionDescription('constraints', '', [gcgroup, booloption, objspaceoption,
wantref_option, stroption,
wantframework_option,
intoption, boolop])
return descr
def make_description_duplicates():
gcoption = ChoiceOption('name', 'GC name', ['ref', 'framework'], 'ref')
## dummy 1
gcdummy = BoolOption('dummy', 'dummy', default=False)
objspaceoption = ChoiceOption('objspace', 'Object space',
['std', 'thunk'], 'std')
booloption = BoolOption('bool', 'Test boolean option', default=True)
intoption = IntOption('int', 'Test int option', default=0)
floatoption = FloatOption('float', 'Test float option', default=2.3)
stroption = StrOption('str', 'Test string option', default="abc")
boolop = BoolOption('boolop', 'Test boolean option op', default=True)
wantref_option = BoolOption('wantref', 'Test requires', default=False,
requires=['boolop'])
wantframework_option = BoolOption('wantframework', 'Test requires',
default=False,
requires=['boolop'])
# dummy2 (same name)
gcdummy2 = BoolOption('dummy', 'dummy2', default=True)
gcgroup = OptionDescription('gc', '', [gcoption, gcdummy, gcdummy2, floatoption])
descr = OptionDescription('constraints', '', [gcgroup, booloption, objspaceoption,
wantref_option, stroption,
wantframework_option,
intoption, boolop])
return descr
def test_identical_names():
"""If in the schema (the option description) there is something that
have the same name, an exection is raised
"""
descr = make_description_duplicates()
raises(ConflictConfigError, "cfg = Config(descr)")
def make_description2():
gcoption = ChoiceOption('name', 'GC name', ['ref', 'framework'], 'ref')
gcdummy = BoolOption('dummy', 'dummy', default=False)
floatoption = FloatOption('float', 'Test float option', default=2.3)
objspaceoption = ChoiceOption('objspace', 'Object space',
['std', 'thunk'], 'std')
booloption = BoolOption('bool', 'Test boolean option', default=True)
intoption = IntOption('int', 'Test int option', default=0)
stroption = StrOption('str', 'Test string option', default="abc")
# first multi
boolop = BoolOption('boolop', 'Test boolean option op', default=True)
boolop.enable_multi()
wantref_option = BoolOption('wantref', 'Test requires', default=False,
requires=['boolop'])
# second multi
wantframework_option = BoolOption('wantframework', 'Test requires',
default=False,
requires=['boolop'])
wantframework_option.enable_multi()
gcgroup = OptionDescription('gc', '', [gcoption, gcdummy, floatoption])
descr = OptionDescription('constraints', '', [gcgroup, booloption, objspaceoption,
wantref_option, stroption,
wantframework_option,
intoption, boolop])
return descr
# FIXME: XXX would you mind putting the multi validations anywhere else
# than in the requires !!!
#def test_multi_constraints():
# "a multi in a constraint has to have the same length"
# descr = make_description2()
# cfg = Config(descr)
# cfg.boolop = [True, True, False]
# cfg.wantframework = [False, False, True]
#
#def test_multi_raise():
# "a multi in a constraint has to have the same length"
# # FIXME fusionner les deux tests, MAIS PROBLEME :
# # il ne devrait pas etre necessaire de refaire une config
# # si la valeur est modifiee une deuxieme fois ->
# #raises(ConflictConfigError, "cfg.wantframework = [False, False, True]")
# # ExceptionFailure: 'DID NOT RAISE'
# descr = make_description2()
# cfg = Config(descr)
# cfg.boolop = [True]
# raises(ConflictConfigError, "cfg.wantframework = [False, False, True]")
# ____________________________________________________________
# adding dynamically new options description schema
def test_newoption_add_in_descr():
descr = make_description()
newoption = BoolOption('newoption', 'dummy twoo', default=False)
descr.add_child(newoption)
config = Config(descr)
assert config.newoption == False
def test_newoption_add_in_subdescr():
descr = make_description()
newoption = BoolOption('newoption', 'dummy twoo', default=False)
descr.gc.add_child(newoption)
config = Config(descr, bool=False)
assert config.gc.newoption == False
def test_newoption_add_in_config():
descr = make_description()
config = Config(descr, bool=False)
newoption = BoolOption('newoption', 'dummy twoo', default=False)
descr.add_child(newoption)
config.cfgimpl_update()
assert config.newoption == False
# ____________________________________________________________
def make_description_requires():
gcoption = ChoiceOption('name', 'GC name', ['ref', 'framework'], 'ref')
gcdummy = BoolOption('dummy', 'dummy', default=False)
floatoption = FloatOption('float', 'Test float option', default=2.3)
objspaceoption = ChoiceOption('objspace', 'Object space',
['std', 'thunk'], 'std')
booloption = BoolOption('bool', 'Test boolean option', default=True)
intoption = IntOption('int', 'Test int option', default=0)
stroption = StrOption('str', 'Test string option', default="abc",
requires=[('int', 1, 'hide')])
gcgroup = OptionDescription('gc', '', [gcoption, gcdummy, floatoption])
descr = OptionDescription('constraints', '', [gcgroup, booloption, objspaceoption,
stroption, intoption])
return descr
def test_hidden_if_in():
descr = make_description_requires()
cfg = Config(descr)
intoption = cfg.unwrap_from_path('int')
stroption = cfg.unwrap_from_path('str')
assert not stroption._is_hidden()
cfg.int = 1
raises(HiddenOptionError, "cfg.str")
raises(HiddenOptionError, 'cfg.str= "uvw"')
assert stroption._is_hidden()
def test_hidden_if_in_with_group():
gcoption = ChoiceOption('name', 'GC name', ['ref', 'framework'], 'ref')
gcdummy = BoolOption('dummy', 'dummy', default=False)
floatoption = FloatOption('float', 'Test float option', default=2.3)
objspaceoption = ChoiceOption('objspace', 'Object space',
['std', 'thunk'], 'std')
booloption = BoolOption('bool', 'Test boolean option', default=True)
intoption = IntOption('int', 'Test int option', default=0)
stroption = StrOption('str', 'Test string option', default="abc")
gcgroup = OptionDescription('gc', '', [gcoption, gcdummy, floatoption],
requires=[('int', 1, 'hide')])
descr = OptionDescription('constraints', '', [gcgroup, booloption,
objspaceoption, stroption, intoption])
cfg = Config(descr)
assert not gcgroup._is_hidden()
cfg.int = 1
raises(HiddenOptionError, "cfg.gc.name")
# raises(HiddenOptionError, 'cfg.gc= "uvw"')
assert gcgroup._is_hidden()
def test_disabled_with_group():
gcoption = ChoiceOption('name', 'GC name', ['ref', 'framework'], 'ref')
gcdummy = BoolOption('dummy', 'dummy', default=False)
floatoption = FloatOption('float', 'Test float option', default=2.3)
objspaceoption = ChoiceOption('objspace', 'Object space',
['std', 'thunk'], 'std')
booloption = BoolOption('bool', 'Test boolean option', default=True)
intoption = IntOption('int', 'Test int option', default=0)
stroption = StrOption('str', 'Test string option', default="abc")
gcgroup = OptionDescription('gc', '', [gcoption, gcdummy, floatoption],
requires=[('int', 1, 'disable')])
descr = OptionDescription('constraints', '', [gcgroup, booloption,
objspaceoption, stroption, intoption])
cfg = Config(descr)
assert not gcgroup._is_disabled()
cfg.int = 1
raises(DisabledOptionError, "cfg.gc.name")
# raises(HiddenOptionError, 'cfg.gc= "uvw"')
assert gcgroup._is_disabled()

121
test/test_option_default.py Normal file
View file

@ -0,0 +1,121 @@
"test all types of option default values for options, add new option in a descr"
import autopath
from py.test import raises
from config import *
from option import *
from error import MandatoryError
def make_description():
gcoption = ChoiceOption('name', 'GC name', ['ref', 'framework'], 'ref')
gcdummy = BoolOption('dummy', 'dummy', default=False)
objspaceoption = ChoiceOption('objspace', 'Object space',
['std', 'thunk'], 'std')
booloption = BoolOption('bool', 'Test boolean option', default=True)
intoption = IntOption('int', 'Test int option', default=0)
floatoption = FloatOption('float', 'Test float option', default=2.3)
stroption = StrOption('str', 'Test string option', default="abc")
boolop = BoolOption('boolop', 'Test boolean option op', default=True)
wantref_option = BoolOption('wantref', 'Test requires', default=False,
requires=['boolop'])
wantframework_option = BoolOption('wantframework', 'Test requires',
default=False,
requires=['boolop'])
gcgroup = OptionDescription('gc', '', [gcoption, gcdummy, floatoption])
descr = OptionDescription('tiramisu', '', [gcgroup, booloption, objspaceoption,
wantref_option, stroption,
wantframework_option,
intoption, boolop])
return descr
#____________________________________________________________
# default values
def test_default_is_none():
"""
Most constructors take a ``default`` argument that specifies the default
value of the option. If this argument is not supplied the default value is
assumed to be ``None``.
"""
dummy1 = BoolOption('dummy1', 'doc dummy')
dummy2 = BoolOption('dummy2', 'doc dummy')
group = OptionDescription('group', '', [dummy1, dummy2])
config = Config(group)
# so when the default value is not set, there is actually a default value
assert config.dummy1 == None
assert config.dummy2 == None
def test_set_defaut_value_from_option_object():
"""Options have an available default setting and can give it back"""
b = BoolOption("boolean", "", default=False)
assert b.getdefault() == False
def test_mandatory():
dummy1 = BoolOption('dummy1', 'doc dummy', mandatory=True)
dummy2 = BoolOption('dummy2', 'doc dummy', mandatory=True)
group = OptionDescription('group', '', [dummy1, dummy2])
config = Config(group)
# config.setoption('dummy1', True)
raises(MandatoryError, 'config.dummy1')
config.dummy1 = True
assert config.dummy1 == True
raises(MandatoryError, 'config.dummy2 == None')
raises(MandatoryError, "config.override({'dummy2':None})")
config.set(dummy2=True)
config.dummy2 = False
assert config.dummy2 == False
def test_override_are_defaults():
descr = make_description()
config = Config(descr, bool=False)
config.gc.dummy = True
assert config._cfgimpl_values['gc']._cfgimpl_value_owners['dummy'] == 'user'
#Options have an available default setting and can give it back
assert config._cfgimpl_descr._children[0]._children[1].getdefault() == False
config.override({'gc.dummy':True})
#assert config.gc.dummy == True
#assert config._cfgimpl_descr._children[0]._children[1].getdefault() == True
#assert config._cfgimpl_values['gc']._cfgimpl_value_owners['dummy'] == 'default'
def test_overrides_changes_option_value():
"with config.override(), the default is changed and the value is changed"
descr = OptionDescription("test", "", [
BoolOption("b", "", default=False)])
config = Config(descr)
config.b = True
config.override({'b': False})
assert config.b == False
#____________________________________________________________
# test various option types
def test_choice_with_no_default():
descr = OptionDescription("test", "", [
ChoiceOption("backend", "", ["c", "cli"])])
config = Config(descr)
assert config.backend is None
config.backend = "c"
def test_choice_with_default():
descr = OptionDescription("test", "", [
ChoiceOption("backend", "", ["c", "cli"], default="cli")])
config = Config(descr)
assert config.backend == "cli"
def test_arbitrary_option():
descr = OptionDescription("top", "", [
ArbitraryOption("a", "no help", default=None)
])
config = Config(descr)
config.a = []
config.a.append(1)
assert config.a == [1]
descr = OptionDescription("top", "", [
ArbitraryOption("a", "no help", defaultfactory=list)
])
c1 = Config(descr)
c2 = Config(descr)
c1.a.append(1)
assert c2.a == []
assert c1.a == [1]

109
test/test_option_owner.py Normal file
View file

@ -0,0 +1,109 @@
import autopath
from py.test import raises
from config import *
from option import *
from error import SpecialOwnersError
def make_description():
gcoption = ChoiceOption('name', 'GC name', ['ref', 'framework'], 'ref')
## dummy 1
gcdummy = BoolOption('dummy', 'dummy', default=False)
objspaceoption = ChoiceOption('objspace', 'Object space',
['std', 'thunk'], 'std')
booloption = BoolOption('bool', 'Test boolean option', default=True)
intoption = IntOption('int', 'Test int option', default=0)
floatoption = FloatOption('float', 'Test float option', default=2.3)
stroption = StrOption('str', 'Test string option', default="abc")
boolop = BoolOption('boolop', 'Test boolean option op', default=True)
wantref_option = BoolOption('wantref', 'Test requires', default=False,
requires=['boolop'])
wantframework_option = BoolOption('wantframework', 'Test requires',
default=False,
requires=['boolop'])
gcgroup = OptionDescription('gc', '', [gcoption, gcdummy, floatoption])
descr = OptionDescription('constraints', '', [gcgroup, booloption, objspaceoption,
wantref_option, stroption,
wantframework_option,
intoption, boolop])
return descr
def test_override_are_default_owner():
"config.override() implies that the owner is 'default' again"
descr = make_description()
config = Config(descr, bool=False)
# defaut
assert config.gc._cfgimpl_value_owners['dummy'] == 'default'
# user
config.gc.dummy = True
assert config.gc._cfgimpl_value_owners['dummy'] == 'user'
assert config._cfgimpl_values['gc']._cfgimpl_value_owners['dummy'] == 'user'
#Options have an available default setting and can give it back
assert config._cfgimpl_descr._children[0]._children[1].getdefault() == False
config.override({'gc.dummy':True})
assert config.gc._cfgimpl_value_owners['dummy'] == 'default'
# user again
config.gc.dummy = False
assert config.gc._cfgimpl_value_owners['dummy'] == 'user'
def test_change_owner():
descr = make_description()
# here the owner is 'default'
config = Config(descr, bool=False)
# the default owner is 'user' (which is not 'default')
# Still not getting it ? read the docs
config.gc.dummy = True
assert config.gc._cfgimpl_value_owners['dummy'] == 'user'
config.cfgimpl_set_owner('eggs')
config.set(dummy=False)
assert config.gc._cfgimpl_value_owners['dummy'] == 'eggs'
config.cfgimpl_set_owner('spam')
gcdummy = config.unwrap_from_path('gc.dummy')
gcdummy.setowner(config.gc, 'blabla')
assert config.gc._cfgimpl_value_owners['dummy'] == 'blabla'
config.gc.dummy = True
assert config.gc._cfgimpl_value_owners['dummy'] == 'spam'
#____________________________________________________________
# special owners
def test_auto_owner():
descr = make_description()
config = Config(descr, bool=False)
config.gc.setoption('dummy', True, 'auto')
raises(HiddenOptionError, "config.gc.dummy")
raises(ConflictConfigError, "config.gc.setoption('dummy', False, 'auto')")
# shall return an auto value...
#assert config.gc.dummy == 'auto_dummy_value'
def test_cannot_override_special_owners():
descr = make_description()
config = Config(descr, bool=False)
config.gc.setoption('dummy', True, 'auto')
raises(SpecialOwnersError, "config.override({'gc.dummy': True})")
def test_fill_owner():
"fill option"
descr = make_description()
config = Config(descr, bool=False)
assert config.bool == False
assert config.gc.dummy == False
# 'fill' special values
config.gc.setoption('dummy', True, 'fill')
assert config.gc.dummy == False
def test_auto_fill_and_override():
descr = make_description()
config = Config(descr, bool=False)
booloption = config.unwrap_from_path('bool')
booloption.callback = 'identical'
booloption.setowner(config, 'auto')
assert config.bool == 'identicalbool'
gcdummy = config.unwrap_from_path('gc.dummy')
gcdummy.callback = 'identical'
gcdummy.setowner(config.gc, 'fill')
raises(SpecialOwnersError, "config.override({'gc.dummy':True})")
config.gc.setoption('dummy', False, 'fill')
# value is returned
assert config.gc.dummy == False

356
test/test_option_setting.py Normal file
View file

@ -0,0 +1,356 @@
"config.set() or config.setoption() or option.setoption()"
import autopath
from py.test import raises
from config import *
from option import *
from error import *
def make_description():
gcoption = ChoiceOption('name', 'GC name', ['ref', 'framework'], 'ref')
gcdummy = BoolOption('dummy', 'dummy', default=False)
objspaceoption = ChoiceOption('objspace', 'Object space',
['std', 'thunk'], 'std')
booloption = BoolOption('bool', 'Test boolean option', default=True)
intoption = IntOption('int', 'Test int option', default=0)
floatoption = FloatOption('float', 'Test float option', default=2.3)
stroption = StrOption('str', 'Test string option', default="abc")
boolop = BoolOption('boolop', 'Test boolean option op', default=True)
wantref_option = BoolOption('wantref', 'Test requires', default=False)
wantframework_option = BoolOption('wantframework', 'Test requires',
default=False)
gcgroup = OptionDescription('gc', '', [gcoption, gcdummy, floatoption])
descr = OptionDescription('tiramisu', '', [gcgroup, booloption, objspaceoption,
wantref_option, stroption,
wantframework_option,
intoption, boolop])
return descr
#____________________________________________________________
# change with __setattr__
def test_attribute_access():
"Once set, option values can't be changed again by attribute access"
s = StrOption("string", "", default="string")
descr = OptionDescription("options", "", [s])
config = Config(descr)
# let's try to change it again
config.string = "foo"
assert config.string == "foo"
# raises(ConflictConfigError, 'config.string = "bar"')
def test_idontexist():
descr = make_description()
cfg = Config(descr)
raises(AttributeError, "cfg.idontexist")
# ____________________________________________________________
def test_attribute_access_with_multi():
s = StrOption("string", "", default="string", multi=True)
descr = OptionDescription("options", "", [s])
config = Config(descr)
config.string = ["foo", "bar"]
assert config.string == ["foo", "bar"]
def test_attribute_access_with_multi():
s = StrOption("string", "", default="string", multi=True)
descr = OptionDescription("options", "", [s])
config = Config(descr)
config.string = ["foo", "bar"]
assert config.string == ["foo", "bar"]
def test_multi_with_requires():
s = StrOption("string", "", default="string", multi=True)
intoption = IntOption('int', 'Test int option', default=0)
stroption = StrOption('str', 'Test string option', default="abc",
requires=[('int', 1, 'hide')], multi=True)
descr = OptionDescription("options", "", [s, intoption, stroption])
config = Config(descr)
assert stroption._is_hidden() == False
config.int = 1
raises(HiddenOptionError, "config.str = ['a', 'b']")
assert stroption._is_hidden()
def test__requires_with_inverted():
s = StrOption("string", "", default="string", multi=True)
intoption = IntOption('int', 'Test int option', default=0)
stroption = StrOption('str', 'Test string option', default="abc",
requires=[('int', 1, 'hide', 'inverted')], multi=True)
descr = OptionDescription("options", "", [s, intoption, stroption])
config = Config(descr)
assert stroption._is_hidden() == False
config.int = 1
assert stroption._is_hidden() == False
def test_multi_with_requires_in_another_group():
s = StrOption("string", "", default="string", multi=True)
intoption = IntOption('int', 'Test int option', default=0)
descr = OptionDescription("options", "", [intoption])
stroption = StrOption('str', 'Test string option', default="abc",
requires=[('int', 1, 'hide')], multi=True)
descr = OptionDescription("opt", "", [stroption])
descr2 = OptionDescription("opt2", "", [intoption, s, descr])
config = Config(descr2)
assert stroption._is_hidden() == False
config.int = 1
raises(HiddenOptionError, "config.opt.str = ['a', 'b']")
assert stroption._is_hidden()
def test_apply_requires_from_config():
s = StrOption("string", "", default="string", multi=True)
intoption = IntOption('int', 'Test int option', default=0)
descr = OptionDescription("options", "", [intoption])
stroption = StrOption('str', 'Test string option', default="abc",
requires=[('int', 1, 'hide')], multi=True)
descr = OptionDescription("opt", "", [stroption])
descr2 = OptionDescription("opt2", "", [intoption, s, descr])
config = Config(descr2)
assert stroption._is_hidden() == False
config.int = 1
try:
config.opt.str
except:
pass
assert stroption._is_hidden()
def test_apply_requires_with_disabled():
s = StrOption("string", "", default="string", multi=True)
intoption = IntOption('int', 'Test int option', default=0)
descr = OptionDescription("options", "", [intoption])
stroption = StrOption('str', 'Test string option', default="abc",
requires=[('int', 1, 'disable')], multi=True)
descr = OptionDescription("opt", "", [stroption])
descr2 = OptionDescription("opt2", "", [intoption, s, descr])
config = Config(descr2)
assert stroption._is_disabled() == False
config.int = 1
try:
config.opt.str
except:
pass
assert stroption._is_disabled()
def test_multi_with_requires_with_disabled_in_another_group():
s = StrOption("string", "", default="string", multi=True)
intoption = IntOption('int', 'Test int option', default=0)
descr = OptionDescription("options", "", [intoption])
stroption = StrOption('str', 'Test string option', default="abc",
requires=[('int', 1, 'disable')], multi=True)
descr = OptionDescription("opt", "", [stroption])
descr2 = OptionDescription("opt2", "", [intoption, s, descr])
config = Config(descr2)
assert stroption._is_disabled() == False
config.int = 1
raises(DisabledOptionError, "config.opt.str = ['a', 'b']")
assert stroption._is_disabled()
def test_multi_with_requires_that_is_multi():
s = StrOption("string", "", default="string", multi=True)
intoption = IntOption('int', 'Test int option', default=[0, 0], multi=True)
stroption = StrOption('str', 'Test string option', default="abc",
requires=[('int', [1, 1], 'hide')], multi=True)
descr = OptionDescription("options", "", [s, intoption, stroption])
config = Config(descr)
assert stroption._is_hidden() == False
config.int = [1, 1]
raises(HiddenOptionError, "config.str = ['a', 'b']")
assert stroption._is_hidden()
def test_multi_with_bool():
s = BoolOption("bool", "", default=[False], multi=True)
descr = OptionDescription("options", "", [s])
config = Config(descr)
assert descr.bool.multi == True
config.bool = [True, False]
assert config._cfgimpl_values['bool'] == [True, False]
assert config.bool == [True, False]
def test_multi_with_bool_two():
s = BoolOption("bool", "", default=[False], multi=True)
descr = OptionDescription("options", "", [s])
config = Config(descr)
assert descr.bool.multi == True
raises(ConfigError, "config.bool = True")
def test_choice_access_with_multi():
ch = ChoiceOption("t1", "", ["a", "b"], default=["a", "a", "a"], multi=True)
descr = OptionDescription("options", "", [ch])
config = Config(descr)
config.t1 = ["a", "b", "a", "b"]
assert config.t1 == ["a", "b", "a", "b"]
# ____________________________________________________________
def test_setoption_from_option():
"a setoption directly from the option is **not** a good practice"
booloption = BoolOption('bool', 'Test boolean option', default=True)
descr = OptionDescription('descr', '', [booloption])
cfg = Config(descr)
booloption.setoption(cfg, False, 'owner')
assert cfg.bool == False
# ____________________________________________________________
def test_set_mode_in_config():
booloption = BoolOption('bool', 'Test boolean option', default=True,
mode='expert')
descr = OptionDescription('descr', '', [booloption])
cfg = Config(descr)
cfg.cfgimpl_set_mode('expert')
raises(ModeOptionError, "cfg.bool")
cfg.cfgimpl_set_mode('normal')
assert cfg.bool == True
#____________________________________________________________
def test_dwim_set():
descr = OptionDescription("opt", "", [
OptionDescription("sub", "", [
BoolOption("b1", ""),
ChoiceOption("c1", "", ['a', 'b', 'c'], 'a'),
BoolOption("d1", ""),
]),
BoolOption("b2", ""),
BoolOption("d1", ""),
])
c = Config(descr)
c.set(b1=False, c1='b')
assert not c.sub.b1
assert c.sub.c1 == 'b'
# new config, because you cannot change values once they are set
c = Config(descr)
c.set(b2=False, **{'sub.c1': 'c'})
assert not c.b2
assert c.sub.c1 == 'c'
raises(AmbigousOptionError, "c.set(d1=True)")
raises(NoMatchingOptionFound, "c.set(unknown='foo')")
def test_more_set():
descr = OptionDescription("opt", "", [
OptionDescription("s1", "", [
BoolOption("a", "", default=False)]),
IntOption("int", "", default=42)])
d = {'s1.a': True, 'int': 23}
config = Config(descr)
config.set(**d)
assert config.s1.a
assert config.int == 23
def test_set_with_hidden_option():
boolopt = BoolOption("a", "", default=False)
boolopt.hide()
descr = OptionDescription("opt", "", [
OptionDescription("s1", "", [boolopt]),
IntOption("int", "", default=42)])
d = {'s1.a': True, 'int': 23}
config = Config(descr)
raises(HiddenOptionError, "config.set(**d)")
def test_set_with_unknown_option():
boolopt = BoolOption("b", "", default=False)
descr = OptionDescription("opt", "", [
OptionDescription("s1", "", [boolopt]),
IntOption("int", "", default=42)])
d = {'s1.a': True, 'int': 23}
config = Config(descr)
raises(NoMatchingOptionFound, "config.set(**d)")
def test_set_symlink_option():
boolopt = BoolOption("b", "", default=False)
linkopt = SymLinkOption("c", "s1.b")
descr = OptionDescription("opt", "",
[linkopt, OptionDescription("s1", "", [boolopt])])
config = Config(descr)
setattr(config, "s1.b", True)
setattr(config, "s1.b", False)
assert config.s1.b == False
assert config.c == False
config.c = True
assert config.s1.b == True
assert config.c == True
config.c = False
assert config.s1.b == False
assert config.c == False
#____________________________________________________________
def test_config_impl_values():
descr = make_description()
config = Config(descr, bool=False)
# gcdummy.setoption(config, True, "user")
# config.setoption("gc.dummy", True, "user")
#config.gc.dummy = True
# config.setoption("bool", False, "user")
config.set(dummy=False)
assert config.gc._cfgimpl_values == {'dummy': False, 'float': 2.3, 'name': 'ref'}
## acces to the option object
# config.gc._cfgimpl_descr.dummy.setoption(config, True, "user")
assert config.gc.dummy == False
# config.set(dummy=True)
# assert config.gc.dummy == True
#____________________________________________________________
def test_accepts_multiple_changes_from_option():
s = StrOption("string", "", default="string")
descr = OptionDescription("options", "", [s])
config = Config(descr)
config.string = "egg"
assert s.getdefault() == "string"
assert config.string == "egg"
s.setoption(config, 'blah', "default")
assert s.getdefault() == "blah"
assert config.string == "blah"
s.setoption(config, 'bol', "user")
assert config.string == 'bol'
config.override({'string': "blurp"})
assert config.string == 'blurp'
assert s.getdefault() == 'blurp'
def test_allow_multiple_changes_from_config():
"""
a `setoption` from the config object is much like the attribute access,
except the fact that value owner can bet set
"""
s = StrOption("string", "", default="string")
s2 = StrOption("string2", "", default="string")
suboption = OptionDescription("bip", "", [s2])
descr = OptionDescription("options", "", [s, suboption])
config = Config(descr)
config.setoption("string", 'blah', "user")
config.setoption("string", "oh", "user")
assert config.string == "oh"
config.set(string2= 'blah')
assert config.bip.string2 == 'blah'
# ____________________________________________________________
def test_overrides_are_defaults():
descr = OptionDescription("test", "", [
BoolOption("b1", "", default=False),
BoolOption("b2", "", default=False),
])
# overrides here
config = Config(descr, b2=True)
assert config.b2
# test with a require
config.b1 = True
assert config.b2
# ____________________________________________________________
# accessing a value by the get method
def test_access_by_get():
descr = make_description()
cfg = Config(descr)
raises(NotFoundError, "cfg.get('idontexist')" )
assert cfg.get('wantref') == False
assert cfg.gc.dummy == False
assert cfg.get('dummy') == False
def test_access_by_get_whith_hide():
b1 = BoolOption("b1", "")
b1.hide()
descr = OptionDescription("opt", "", [
OptionDescription("sub", "", [
b1,
ChoiceOption("c1", "", ['a', 'b', 'c'], 'a'),
BoolOption("d1", ""),
]),
BoolOption("b2", ""),
BoolOption("d1", ""),
])
c = Config(descr)
raises(HiddenOptionError, "c.get('b1')")

141
test/test_option_type.py Normal file
View file

@ -0,0 +1,141 @@
# coding: utf-8
"frozen and hidden values"
import autopath
from py.test import raises
from config import *
from option import *
def make_description():
gcoption = ChoiceOption('name', 'GC name', ['ref', 'framework'], 'ref')
gcoption.set_mode("expert")
gcdummy = BoolOption('dummy', 'dummy', default=False)
# hidding dummy here
gcdummy.hide()
objspaceoption = ChoiceOption('objspace', 'Object space',
['std', 'thunk'], 'std')
booloption = BoolOption('bool', 'Test boolean option', default=True)
intoption = IntOption('int', 'Test int option', default=0)
floatoption = FloatOption('float', 'Test float option', default=2.3)
stroption = StrOption('str', 'Test string option', default="abc")
wantref_option = BoolOption('wantref', 'Test requires', default=False,
requires=[('gc.name', 'ref')])
wantframework_option = BoolOption('wantframework', 'Test requires',
default=False,
requires=[('gc.name', 'framework')])
# ____________________________________________________________
booloptiontwo = BoolOption('booltwo', 'Test boolean option two', default=False)
subgroup = OptionDescription('subgroup', '', [booloptiontwo])
# ____________________________________________________________
gcgroup = OptionDescription('gc', '', [subgroup, gcoption, gcdummy, floatoption])
gcgroup.set_mode("expert")
descr = OptionDescription('trs', '', [gcgroup, booloption, objspaceoption,
wantref_option, stroption,
wantframework_option,
intoption])
return descr
#____________________________________________________________
#freeze
def make_description_freeze():
gcoption = ChoiceOption('name', 'GC name', ['ref', 'framework'], 'ref')
gcdummy = BoolOption('dummy', 'dummy', default=False)
objspaceoption = ChoiceOption('objspace', 'Object space',
['std', 'thunk'], 'std')
booloption = BoolOption('bool', 'Test boolean option', default=True)
intoption = IntOption('int', 'Test int option', default=0)
floatoption = FloatOption('float', 'Test float option', default=2.3)
stroption = StrOption('str', 'Test string option', default="abc")
boolop = BoolOption('boolop', 'Test boolean option op', default=True)
wantref_option = BoolOption('wantref', 'Test requires', default=False,
requires=['boolop'])
wantframework_option = BoolOption('wantframework', 'Test requires',
default=False,
requires=['boolop'])
gcgroup = OptionDescription('gc', '', [gcoption, gcdummy, floatoption])
descr = OptionDescription('tiramisu', '', [gcgroup, booloption, objspaceoption,
wantref_option, stroption,
wantframework_option,
intoption, boolop])
return descr
def test_freeze_one_option():
"freeze an option "
descr = make_description_freeze()
conf = Config(descr)
#freeze only one option
conf.gc._cfgimpl_descr.dummy.freeze()
assert conf.gc.dummy == False
raises(TypeError, "conf.gc.dummy = True")
def test_frozen_value():
"setattr a frozen value at the config level"
s = StrOption("string", "", default="string")
descr = OptionDescription("options", "", [s])
config = Config(descr)
s.freeze()
raises(ConfigError, 'config.string = "egg"')
def test_freeze():
"freeze a whole configuration object"
descr = make_description()
conf = Config(descr)
conf.cfgimpl_freeze()
raises(ConfigError, "conf.gc.name = 'try to modify'")
# ____________________________________________________________
def test_is_hidden():
descr = make_description()
config = Config(descr)
assert config.gc._cfgimpl_descr.dummy._is_hidden() == True
# setattr
raises(HiddenOptionError, "config.gc.dummy == False")
# getattr
raises(HiddenOptionError, "config.gc.dummy")
# I want to access to this option anyway
path = 'gc.dummy'
homeconfig, name = config._cfgimpl_get_home_by_path(path)
assert homeconfig._cfgimpl_values[name] == False
def test_group_is_hidden():
descr = make_description()
config = Config(descr)
gc = config.unwrap_from_path('gc')
gc.hide()
dummy = config.unwrap_from_path('gc.dummy')
raises(HiddenOptionError, "config.gc.dummy")
assert gc._is_hidden()
raises(HiddenOptionError, "config.gc.float")
# manually set the subconfigs to "show"
gc.show()
assert gc._is_hidden() == False
assert config.gc.float == 2.3
#dummy est en hide
raises(HiddenOptionError, "config.gc.dummy == False")
def test_global_show():
descr = make_description()
config = Config(descr)
assert config.gc._cfgimpl_descr.dummy._is_hidden() == True
raises(HiddenOptionError, "config.gc.dummy == False")
def test_with_many_subgroups():
descr = make_description()
config = Config(descr)
assert config.gc.subgroup._cfgimpl_descr.booltwo._is_hidden() == False
assert config.gc.subgroup.booltwo == False
config.gc.subgroup._cfgimpl_descr.booltwo.hide()
path = 'gc.subgroup.booltwo'
homeconfig, name = config._cfgimpl_get_home_by_path(path)
assert name == "booltwo"
option = getattr(homeconfig._cfgimpl_descr, name)
assert option._is_hidden()
def test_option_mode():
descr = make_description()
config = Config(descr)
assert config.gc._cfgimpl_descr.name.get_mode() == 'expert'
assert config._cfgimpl_descr.gc.get_mode() == 'expert'

View file

@ -0,0 +1,47 @@
#this test is much more to test that **it's there** and answers attribute access
import autopath
from py.test import raises
from config import *
from option import *
def make_description():
gcoption = ChoiceOption('name', 'GC name', ['ref', 'framework'], 'ref')
gcdummy = BoolOption('dummy', 'dummy', default=False)
gcdummy2 = BoolOption('hide', 'dummy', default=True)
objspaceoption = ChoiceOption('objspace', 'Object space',
['std', 'thunk'], 'std')
booloption = BoolOption('bool', 'Test boolean option', default=True)
intoption = IntOption('int', 'Test int option', default=0)
floatoption = FloatOption('float', 'Test float option', default=2.3)
stroption = StrOption('str', 'Test string option', default="abc")
boolop = BoolOption('boolop', 'Test boolean option op', default=True)
wantref_option = BoolOption('wantref', 'Test requires', default=False)
wantframework_option = BoolOption('wantframework', 'Test requires',
default=False)
gcgroup = OptionDescription('gc', '', [gcoption, gcdummy, floatoption, gcdummy2])
descr = OptionDescription('tiram', '', [gcgroup, booloption, objspaceoption,
wantref_option, stroption,
wantframework_option,
intoption, boolop])
return descr
def test_base_config_and_groups():
descr = make_description()
# overrides the booloption default value
config = Config(descr, bool=False)
assert config.gc.hide == True
def test_root_config_answers_ok():
"if you hide the root config, the options in this namespace behave normally"
gcdummy = BoolOption('dummy', 'dummy', default=False)
boolop = BoolOption('boolop', 'Test boolean option op', default=True)
descr = OptionDescription('tiramisu', '', [gcdummy, boolop])
cfg = Config(descr)
cfg.cfgimpl_hide()
assert cfg.dummy == False
assert cfg.boolop == True

View file

@ -0,0 +1,70 @@
# coding: utf-8
import autopath
from config import *
from option import *
def make_description():
numero_etab = StrOption('numero_etab', "identifiant de l'établissement")
nom_machine = StrOption('nom_machine', "nom de la machine", default="eoleng")
nombre_interfaces = IntOption('nombre_interfaces', "nombre d'interfaces à activer",
default=1)
activer_proxy_client = BoolOption('activer_proxy_client', "utiliser un proxy",
default=False)
mode_conteneur_actif = BoolOption('mode_conteneur_actif', "le serveur est en mode conteneur",
default=False)
# hidden (variable cachée)
# mode_conteneur_actif.taint()
adresse_serveur_ntp = StrOption('serveur_ntp', "adresse serveur ntp", multi=True)
time_zone = ChoiceOption('time_zone', 'fuseau horaire du serveur',
['Paris', 'Londres'], 'Paris')
ip_admin_eth0 = StrOption('ip_admin_eth0', "ip réseau autorisé")
netmask_admin_eth0 = StrOption('netmask_admin_eth0', "masque du sous-réseau")
master = OptionDescription('ip_admin_eth0', '', [ip_admin_eth0, netmask_admin_eth0])
interface1 = OptionDescription('interface1', '', [master])
interface1.set_group_type('group')
general = OptionDescription('general', '', [numero_etab, nom_machine,
nombre_interfaces, activer_proxy_client,
mode_conteneur_actif, adresse_serveur_ntp,
time_zone])
general.set_group_type('family')
creole = OptionDescription('creole', 'first tiramisu configuration', [general, interface1])
descr = OptionDescription('baseconfig', 'baseconifgdescr', [creole] )
return descr
def test_base_config():
descr = make_description()
config = Config(descr)
assert config.creole.general.activer_proxy_client == False
assert config.creole.general.nom_machine == "eoleng"
assert config.get('nom_machine') == "eoleng"
result = {'general.numero_etab': None, 'general.nombre_interfaces': 1,
'general.serveur_ntp': None, 'interface1.ip_admin_eth0.ip_admin_eth0': None,
'general.mode_conteneur_actif': False, 'general.time_zone': 'Paris',
'interface1.ip_admin_eth0.netmask_admin_eth0': None, 'general.nom_machine':
'eoleng', 'general.activer_proxy_client': False}
assert make_dict(config.creole) == result
result = {'serveur_ntp': None, 'mode_conteneur_actif': False,
'ip_admin_eth0': None, 'time_zone': 'Paris', 'numero_etab': None,
'netmask_admin_eth0': None, 'nom_machine': 'eoleng', 'activer_proxy_client':
False, 'nombre_interfaces': 1}
assert make_dict(config.creole, flatten=True) == result
def test_get_group_type():
descr = make_description()
config = Config(descr)
grp = config.unwrap_from_path('creole.general')
assert grp.get_group_type() == "family"
def test_iter_on_groups():
descr = make_description()
config = Config(descr)
result = list(config.creole.iter_groups(group_type= "family"))
group_names = [res[0] for res in result]
assert group_names == ['general']
result = list(config.creole.iter_groups())
group_names = [res[0] for res in result]
assert group_names == ['general', 'interface1']

View file

@ -0,0 +1,42 @@
import autopath
from py.test import raises
from tool import reverse_from_paths
#def make_description():
# gcoption = ChoiceOption('name', 'GC name', ['ref', 'framework'], 'ref')
# gcdummy = BoolOption('dummy', 'dummy', default=False)
# objspaceoption = ChoiceOption('objspace', 'Object space',
# ['std', 'thunk'], 'std')
# booloption = BoolOption('bool', 'Test boolean option', default=True)
# intoption = IntOption('int', 'Test int option', default=0)
# floatoption = FloatOption('float', 'Test float option', default=2.3)
# stroption = StrOption('str', 'Test string option', default="abc")
# boolop = BoolOption('boolop', 'Test boolean option op', default=True)
# wantref_option = BoolOption('wantref', 'Test requires', default=False)
# wantframework_option = BoolOption('wantframework', 'Test requires',
# default=False)
#
# gcgroup = OptionDescription('gc', '', [gcoption, gcdummy, floatoption])
# descr = OptionDescription('tiram', '', [gcgroup, booloption, objspaceoption,
# wantref_option, stroption,
# wantframework_option,
# intoption, boolop])
# return descr
def test_rebuild():
# pouvoir faire une comparaison avec equal
d = {"s1.s2.s3.s4.a": True, "int": 43, "s2.b":True, "s3.c": True, "s3.d":[1,2,3]}
cfg = reverse_from_paths(d)
assert cfg.s1.s2.s3.s4.a == True
assert cfg.int == 43
assert cfg.s2.b == True
assert cfg.s3.c == True
assert cfg.s3.d == [1,2,3]
# assert config.getpaths() == ['gc.name', 'gc.dummy', 'gc.float', 'bool',
# 'objspace', 'wantref', 'str', 'wantframework',
# 'int', 'boolop']
# assert config.getpaths(include_groups=False) == ['gc.name', 'gc.dummy', 'gc.float', 'bool', 'objspace', 'wantref', 'str', 'wantframework', 'int', 'boolop']
# assert config.getpaths(include_groups=True) == ['gc', 'gc.name', 'gc.dummy', 'gc.float', 'bool', 'objspace', 'wantref', 'str', 'wantframework', 'int', 'boolop']

26
test/test_tool.py Normal file
View file

@ -0,0 +1,26 @@
#this test is much more to test that **it's there** and answers attribute access
import autopath
from py.test import raises
from tool import extend
class A:
a = 'titi'
def tarte(self):
return "tart"
class B:
__metaclass__ = extend
def to_rst(self):
return "hello"
B.extend(A)
a = B()
def test_extendable():
assert a.a == 'titi'
assert a.tarte() == 'tart'
assert a.to_rst() == "hello"

110
tool.py Normal file
View file

@ -0,0 +1,110 @@
from config import Config
from option import (OptionDescription, Option, ChoiceOption, BoolOption,
FloatOption, StrOption, IntOption, IPOption, NetmaskOption,
ArbitraryOption, group_types, apply_requires)
# ____________________________________________________________
# reverse factory
# XXX HAAAAAAAAAAAACK (but possibly a good one)
def reverse_from_paths(data):
"rebuilds a (fake) data structure from an unflatten `make_dict()` result"
# ____________________________________________________________
_build_map = {
bool: BoolOption,
int: IntOption,
float: FloatOption,
str: StrOption,
}
def option_factory(name, value):
"dummy -> Option('dummy')"
if type(value) == list:
return _build_map[type(value[0])](name, '', multi=True, default=value)
else:
return _build_map[type(value)](name, '', default=value)
def build_options(data):
"config.gc.dummy -> Option('dummy')"
for key, value in data.items():
name = key.split('.')[-1]
yield (key, option_factory(name, value))
# ____________________________________________________________
def parent(pathname):
"config.gc.dummy -> config.gc"
if "." in pathname:
return ".".join(pathname.split('.')[:-1])
# no parent except rootconfig, naturally returns None
def subgroups(pathname):
"config.gc.dummy.bool -> [config.gc, config.gc.dummy]"
group = parent(pathname)
parents =[]
while group is not None:
parents.append(group)
group = parent(group)
return parents
def build_option_descriptions(data):
all_groups = []
for key in data.keys():
for group in subgroups(key):
# so group is unique in the list
if group not in all_groups:
all_groups.append(group)
for group in all_groups:
name = group.split('.')[-1]
yield (group, OptionDescription(name, '', []))
# ____________________________________________________________
descr = OptionDescription('tiramisu', 'fake rebuild structure', [])
cfg = Config(descr)
# add descrs in cfg
def compare(a, b):
l1 = a.split(".")
l2 = b.split(".")
if len(l1) < len(l2):
return -1
elif len(l1) > len(l2):
return 1
else:
return 0
grps = list(build_option_descriptions(data))
groups = dict(grps)
grp_paths = [pathname for pathname, opt_descr in grps]
grp_paths.sort(compare)
for grp in grp_paths:
if not "." in grp:
cfg._cfgimpl_descr.add_child(groups[grp])
cfg.cfgimpl_update()
else:
parentdescr = cfg.unwrap_from_path(parent(grp))
parentdescr.add_child(groups[grp])
getattr(cfg, parent(grp)).cfgimpl_update()
# add options in descrs
for pathname, opt in build_options(data):
current_group_name = parent(pathname)
if current_group_name == None:
cfg._cfgimpl_descr.add_child(opt)
cfg.cfgimpl_update()
else:
curr_grp = groups[current_group_name]
curr_grp.add_child(opt)
getattr(cfg, current_group_name).cfgimpl_update()
return cfg
# ____________________________________________________________
# extendable type
class extend(type):
"""
A magic trick for classes, which lets you add methods or attributes to a
class
"""
def extend(cls, extclass):
bases = list(extclass.__bases__)
bases.append(extclass)
for cl in bases:
for key, value in cl.__dict__.items():
if key == '__module__':
continue
setattr(cls, key, value)
# ____________________________________________________________