commit 8b16814ab41070efd86c8bb1b2d99486dbfdfe98 Author: gwen Date: Sun May 13 20:48:51 2012 +0200 first revision diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/autolib.py b/autolib.py new file mode 100644 index 0000000..c4ea2fb --- /dev/null +++ b/autolib.py @@ -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 + + diff --git a/config.py b/config.py new file mode 100644 index 0000000..871d6d1 --- /dev/null +++ b/config.py @@ -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 +# ____________________________________________________________ + diff --git a/doc/Changelog b/doc/Changelog new file mode 100644 index 0000000..77a7916 --- /dev/null +++ b/doc/Changelog @@ -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 + + diff --git a/doc/Makefile b/doc/Makefile new file mode 100644 index 0000000..7cac92a --- /dev/null +++ b/doc/Makefile @@ -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 + diff --git a/doc/build/Makefile b/doc/build/Makefile new file mode 100644 index 0000000..cc5f93b --- /dev/null +++ b/doc/build/Makefile @@ -0,0 +1,6 @@ +.PHONY: clean +.SUFFIXES: + +clean: + rm -f *.html + rm -f api/*.html diff --git a/doc/build/api/Readme b/doc/build/api/Readme new file mode 100644 index 0000000..8623c0e --- /dev/null +++ b/doc/build/api/Readme @@ -0,0 +1 @@ +API's directory diff --git a/doc/build/architecture.dia b/doc/build/architecture.dia new file mode 100644 index 0000000..35adb7e Binary files /dev/null and b/doc/build/architecture.dia differ diff --git a/doc/build/architecture.png b/doc/build/architecture.png new file mode 100644 index 0000000..fecefa0 Binary files /dev/null and b/doc/build/architecture.png differ diff --git a/doc/build/default.css b/doc/build/default.css new file mode 100644 index 0000000..8625c0e --- /dev/null +++ b/doc/build/default.css @@ -0,0 +1,1080 @@ +body,body.editor,body.body { + font: 110% "Times New Roman", Arial, Verdana, Helvetica, serif; + background: White; + color: Black; +} + +a, a.reference { + text-decoration: none; +} +a[href]:hover { text-decoration: underline; } + +img { + border: none; + vertical-align: middle; +} + +p, div.text { + text-align: left; + line-height: 1.5em; + margin: 0.5em 0em 0em 0em; +} + + + +p a:active { + color: Red; + background-color: transparent; +} + +p img { + border: 0; + margin: 0; +} + +img.inlinephoto { + padding: 0; + padding-right: 1em; + padding-top: 0.7em; + float: left; +} + +hr { + clear: both; + height: 1px; + color: #8CACBB; + background-color: transparent; +} + + +ul { + line-height: 1.5em; + /*list-style-image: url("bullet.gif"); */ + margin-left: 1.5em; + padding:0; +} + +ol { + line-height: 1.5em; + margin-left: 1.5em; + padding:0; +} + +ul a, ol a { + text-decoration: underline; +} + +dl { +} + +dt { + font-weight: bold; +} + +dd { + line-height: 1.5em; + margin-bottom: 1em; +} + +blockquote { + font-family: Times, "Times New Roman", serif; + font-style: italic; + font-size: 120%; +} + +code { + color: Black; + /*background-color: #dee7ec;*/ + background-color: #cccccc; +} + +pre { + padding: 1em; + border: 1px solid #8cacbb; + color: Black; + background-color: #dee7ec; + background-color: #cccccc; + overflow: auto; +} + + +.netscape4 { + display: none; +} + +/* main page styles */ + +/*a[href]:hover { color: black; text-decoration: underline; } +a[href]:link { color: black; text-decoration: underline; } +a[href] { color: black; text-decoration: underline; } +*/ + +span.menu_selected { + color: black; + font: 140% Verdana, Helvetica, Arial, sans-serif; + text-decoration: none; + padding-right: 0.3em; + background-color: #cccccc; +} + + +a.menu { + /*color: #3ba6ec; */ + font: 140% Verdana, Helvetica, Arial, sans-serif; + text-decoration: none; + padding-right: 0.3em; +} + +a.menu[href]:visited, a.menu[href]:link{ + /*color: #3ba6ec; */ + font: 140% Verdana, Helvetica, Arial, sans-serif; + text-decoration: none; +} + +a.menu[href]:hover { + /*color: black;*/ +} + +div.project_title{ + /*border-spacing: 20px;*/ + font: 160% Verdana, Helvetica, Arial, sans-serif; + color: #3ba6ec; + vertical-align: middle; + padding-bottom: 0.3em; +} + +a.wikicurrent { + font: 100% Verdana, Helvetica, Arial, sans-serif; + color: #3ba6ec; + vertical-align: middle; +} + + +table.body { + border: 0; + /*padding: 0; + border-spacing: 0px; + border-collapse: separate; + */ +} + +td.page-header-left { + padding: 5px; + /*border-bottom: 1px solid #444444;*/ +} + +td.page-header-top { + padding: 0; + + /*border-bottom: 1px solid #444444;*/ +} + +td.sidebar { + padding: 1 0 0 1; +} + +td.sidebar p.classblock { + padding: 0 5 0 5; + margin: 1 1 1 1; + border: 1px solid #444444; + background-color: #eeeeee; +} + +td.sidebar p.userblock { + padding: 0 5 0 5; + margin: 1 1 1 1; + border: 1px solid #444444; + background-color: #eeeeff; +} + +td.content { + padding: 1 5 1 5; + vertical-align: top; + width: 100%; +} + +p.ok-message { + background-color: #22bb22; + padding: 5 5 5 5; + color: white; + font-weight: bold; +} +p.error-message { + background-color: #bb2222; + padding: 5 5 5 5; + color: white; + font-weight: bold; +} + +p:first-child { + margin: 0 ; + padding: 0; +} + +/* style for forms */ +table.form { + padding: 2; + border-spacing: 0px; + border-collapse: separate; +} + +table.form th { + color: #333388; + text-align: right; + vertical-align: top; + font-weight: normal; +} +table.form th.header { + font-weight: bold; + background-color: #eeeeff; + text-align: left; +} + +table.form th.required { + font-weight: bold; +} + +table.form td { + color: #333333; + empty-cells: show; + vertical-align: top; +} + +table.form td.optional { + font-weight: bold; + font-style: italic; +} + +table.form td.html { + color: #777777; +} + +/* style for lists */ +table.list { + border-spacing: 0px; + border-collapse: separate; + vertical-align: top; + padding-top: 0; + width: 100%; +} + +table.list th { + padding: 0 4 0 4; + color: #404070; + background-color: #eeeeff; + border-right: 1px solid #404070; + border-top: 1px solid #404070; + border-bottom: 1px solid #404070; + vertical-align: top; + empty-cells: show; +} +table.list th a[href]:hover { color: #404070 } +table.list th a[href]:link { color: #404070 } +table.list th a[href] { color: #404070 } +table.list th.group { + background-color: #f4f4ff; + text-align: center; + font-size: 120%; +} + +table.list td { + padding: 0 4 0 4; + border: 0 2 0 2; + border-right: 1px solid #404070; + color: #404070; + background-color: white; + vertical-align: top; + empty-cells: show; +} + +table.list tr.normal td { + background-color: white; + white-space: nowrap; +} + +table.list tr.alt td { + background-color: #efefef; + white-space: nowrap; +} + +table.list td:first-child { + border-left: 1px solid #404070; + border-right: 1px solid #404070; +} + +table.list th:first-child { + border-left: 1px solid #404070; + border-right: 1px solid #404070; +} + +table.list tr.navigation th { + text-align: right; +} +table.list tr.navigation th:first-child { + border-right: none; + text-align: left; +} + + +/* style for message displays */ +table.messages { + border-spacing: 0px; + border-collapse: separate; + width: 100%; +} + +table.messages th.header{ + padding-top: 10px; + border-bottom: 1px solid gray; + font-weight: bold; + background-color: white; + color: #707040; +} + +table.messages th { + font-weight: bold; + color: black; + text-align: left; + border-bottom: 1px solid #afafaf; +} + +table.messages td { + font-family: monospace; + background-color: #efefef; + border-bottom: 1px solid #afafaf; + color: black; + empty-cells: show; + border-right: 1px solid #afafaf; + vertical-align: top; + padding: 2 5 2 5; +} + +table.messages td:first-child { + border-left: 1px solid #afafaf; + border-right: 1px solid #afafaf; +} + +/* style for file displays */ +table.files { + border-spacing: 0px; + border-collapse: separate; + width: 100%; +} + +table.files th.header{ + padding-top: 10px; + border-bottom: 1px solid gray; + font-weight: bold; + background-color: white; + color: #707040; +} + +table.files th { + border-bottom: 1px solid #afafaf; + font-weight: bold; + text-align: left; +} + +table.files td { + font-family: monospace; + empty-cells: show; +} + +/* style for history displays */ +table.history { + border-spacing: 0px; + border-collapse: separate; + width: 100%; +} + +table.history th.header{ + padding-top: 10px; + border-bottom: 1px solid gray; + font-weight: bold; + background-color: white; + color: #707040; + font-size: 100%; +} + +table.history th { + border-bottom: 1px solid #afafaf; + font-weight: bold; + text-align: left; + font-size: 90%; +} + +table.history td { + font-size: 90%; + vertical-align: top; + empty-cells: show; +} + + +/* style for class list */ +table.classlist { + border-spacing: 0px; + border-collapse: separate; + width: 100%; +} + +table.classlist th.header{ + padding-top: 10px; + border-bottom: 1px solid gray; + font-weight: bold; + background-color: white; + color: #707040; +} + +table.classlist th { + font-weight: bold; + text-align: left; +} + + +/* style for class help display */ +table.classhelp { + border-spacing: 0px; + border-collapse: separate; + width: 100%; +} + +table.classhelp th { + font-weight: bold; + text-align: left; + color: #707040; +} + +table.classhelp td { + padding: 2 2 2 2; + border: 1px solid black; + text-align: left; + vertical-align: top; + empty-cells: show; +} + + +/* style for "other" displays */ +table.otherinfo { + border-spacing: 0px; + border-collapse: separate; + width: 100%; +} + +table.otherinfo th.header{ + padding-top: 10px; + border-bottom: 1px solid gray; + font-weight: bold; + background-color: white; + color: #707040; +} + +table.otherinfo th { + border-bottom: 1px solid #afafaf; + font-weight: bold; + text-align: left; +} + +input { + border: 1px solid #8cacbb; + color: Black; + background-color: white; + vertical-align: middle; + margin-bottom: 1px; /* IE bug fix */ + padding: 0.1em; +} + +select { + border: 1px solid #8cacbb; + color: Black; + background-color: white; + vertical-align: middle; + margin-bottom: 1px; /* IE bug fix */ + padding: 0.1em; +} + + +a.nonexistent { + color: #FF2222; +} +a.nonexistent:visited { + color: #FF2222; +} +a.external { + color: #AA6600; +} + +/* +dl,ul,ol { + margin-top: 1pt; +} +tt,pre { + font-family: Lucida Console,Courier New,Courier,monotype; + font-size: 12pt; +} +pre.code { + margin-top: 8pt; + margin-bottom: 8pt; + background-color: #FFFFEE; + white-space:pre; + border-style:solid; + border-width:1pt; + border-color:#999999; + color:#111111; + padding:5px; + width:100%; +} +*/ +div.diffold { + background-color: #FFFF80; + border-style:none; + border-width:thin; + width:100%; +} +div.diffnew { + background-color: #80FF80; + border-style:none; + border-width:thin; + width:100%; +} +div.message { + margin-top: 6pt; + background-color: #E8FFE8; + border-style:solid; + border-width:1pt; + border-color:#999999; + color:#440000; + padding:5px; + width:100%; +} +strong.highlight { + background-color: #FFBBBB; +/* as usual, NetScape fucks up with innocent CSS + border-color: #FFAAAA; + border-style: solid; + border-width: 1pt; +*/ +} + +table.navibar { + background-color: #C8C8C8; + border-spacing: 3px; +} +td.navibar { + background-color: #E8E8E8; + vertical-align: top; + text-align: right; + padding: 0px; +} + +div.pagename { + font-size: 140%; + color: blue; + text-align: center; + font-weight: bold; + background-color: white; + padding: 0 ; +} + +a.wikiaction, input.wikiaction { + color: black; + text-decoration: None; + text-align: center; + color: black; + /*border: 1px solid #3ba6ec; */ + margin: 4px; + padding: 5; + padding-bottom: 0; + white-space: nowrap; +} + +a.wikiaction[href]:hover { + color: black; + text-decoration: none; + /*background-color: #dddddd; */ +} + +span.wikiuserpref { + padding-top: 1em; + font-size: 120%; +} + +div.wikitrail { + vertical-align: bottom; + /*font-size: -1;*/ + padding-top: 1em; + display: none; +} + +div.wikiaction { + vertical-align: middle; + /*border-bottom: 1px solid #8cacbb;*/ + padding-bottom:1em; + text-align: left; + width: 100%; +} + +div.wikieditmenu { + text-align: right; +} + +form.wikiedit { + border: 1px solid #8cacbb; + background-color: #f0f0f0; + background-color: #fabf00; + padding: 1em; + padding-right: 0em; +} + +div.legenditem { + padding-top: 0.5em; + padding-left: 0.3em; +} + +span.wikitoken { + background-color: #eeeeee; +} + + +div#contentspace h1:first-child, div.heading:first-child { + padding-top: 0; + margin-top: 0; +} +div#contentspace h2:first-child { + padding-top: 0; + margin-top: 0; +} + +/* heading and paragraph text */ + +div.heading, h1 { + font-family: Verdana, Helvetica, Arial, sans-serif; + background-color: #58b3ef; + background-color: #FFFFFF; + /*color: #4893cf;*/ + color: black; + padding-top: 1.0em; + padding-bottom:0.2em; + text-align: left; + margin-top: 0em; + /*margin-bottom:8pt;*/ + font-weight: bold; + font-size: 115%; + border-bottom: 1px solid #8CACBB; +} + + +h1, h2, h3, h4, h5, h6 { + color: orange; + clear: left; + font: 100% Verdana, Helvetica, Arial, sans-serif; + margin: 0; + padding-left: 0em; + padding-top: 1em; + padding-bottom: 0.2em; + /*border-bottom: 1px solid #8CACBB;*/ +} +/* h1,h2 { padding-top: 0; }*/ + + +h1 { font-size: 145%; } +h2 { font-size: 135%; } +h3 { font-size: 125%; } +h4 { font-size: 120%; } +h5 { font-size: 110%; } +h6 { font-size: 80%; } + +h1 a { text-decoration: None;} + +div.exception { + background-color: #bb2222; + padding: 5 5 5 5; + color: white; + font-weight: bold; +} +pre.exception { + font-size: 110%; + padding: 1em; + border: 1px solid #8cacbb; + color: Black; + background-color: #dee7ec; + background-color: #cccccc; +} + +/* defines for navgiation bar (documentation) */ + + +div.direntry { + padding-top: 0.3em; + padding-bottom: 0.3em; + margin-right: 1em; + font-weight: bold; + background-color: #dee7ec; + font-size: 110%; +} + +div.fileentry { + font-family: Verdana, Helvetica, Arial, sans-serif; + padding-bottom: 0.3em; + white-space: nowrap; + line-height: 150%; +} + +a.fileentry { + white-space: nowrap; +} + + +span.left { + text-align: left; +} +span.right { + text-align: right; +} + +div.navbar { + /*margin: 0;*/ + font-size: 80% /*smaller*/; + font-weight: bold; + text-align: left; + /* position: fixed; */ + top: 100pt; + left: 0pt; /* auto; */ + width: 120pt; + /* right: auto; + right: 0pt; 2em; */ +} + + +div.history a { + /* font-size: 70%; */ +} + +div.wikiactiontitle { + font-weight: bold; +} + +/* REST defines */ + +div.document { + margin: 0; +} + +h1.title { + margin: 0; + margin-bottom: 0.5em; +} + +td.toplist { + vertical-align: top; +} + +img#pyimg { + position: absolute; + top: 4px; + left: 4px; +} + +div#navspace { + position: absolute; + top: 130px; + left: 11px; + font-size: 100%; + width: 150px; + overflow: hidden; /* scroll; */ +} + +div#metaspace { + position: absolute; + top: 40px; + left: 170px; +} + +div#errorline { + position: relative; + top: 5px; + float: right; +} + +div#contentspace { + position: absolute; + /* font: 120% "Times New Roman", serif;*/ + font: 110% Verdana, Helvetica, Arial, sans-serif; + top: 130px; + left: 170px; + margin-right: 5px; +} + +div#menubar { +/* width: 400px; */ + float: left; +} + +/* for the documentation page */ +div#docinfoline { + position: relative; + top: 5px; + left: 0px; + + /*background-color: #dee7ec; */ + padding: 5pt; + padding-bottom: 1em; + color: black; + /*border-width: 1pt; + border-style: solid;*/ + +} + +div#docnavlist { + /*background-color: #dee7ec; */ + padding: 5pt; + padding-bottom: 2em; + color: black; + border-width: 1pt; + /*border-style: solid;*/ +} + + +/* text markup */ + +div.listtitle { + color: Black; + clear: left; + font: 120% Verdana, Helvetica, Arial, sans-serif; + margin: 0; + padding-left: 0em; + padding-top: 0em; + padding-bottom: 0.2em; + margin-right: 0.5em; + border-bottom: 1px solid #8CACBB; +} + +div.actionbox h3 { + padding-top: 0; + padding-right: 0.5em; + padding-left: 0.5em; + background-color: #fabf00; + text-align: center; + border: 1px solid black; /* 8cacbb; */ +} + +div.actionbox a { + display: block; + padding-bottom: 0.5em; + padding-top: 0.5em; + margin-left: 0.5em; +} + +div.actionbox a.history { + display: block; + padding-bottom: 0.5em; + padding-top: 0.5em; + margin-left: 0.5em; + font-size: 90%; +} + +div.actionbox { + margin-bottom: 2em; + padding-bottom: 1em; + overflow: hidden; /* scroll; */ +} + +/* taken from docutils (oh dear, a bit senseless) */ +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 } + + +/* +:Author: David Goodger +:Contact: goodger@users.sourceforge.net +:date: $Date: 2003/01/22 22:26:48 $ +:version: $Revision: 1.29 $ +:copyright: This stylesheet has been placed in the public domain. + +Default cascading style sheet for the HTML output of Docutils. +*/ +/* +.first { + margin-top: 0 } + +.last { + margin-bottom: 0 } + +a.toc-backref { + text-decoration: none ; + color: black } + +dd { + margin-bottom: 0.5em } + +div.abstract { + margin: 2em 5em } + +div.abstract p.topic-title { + font-weight: bold ; + text-align: center } + +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.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.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.dedication { + margin: 2em 5em ; + text-align: center ; + font-style: italic } + +div.dedication p.topic-title { + font-weight: bold ; + font-style: normal } + +div.figure { + margin-left: 2em } + +div.footer, div.header { + font-size: smaller } + +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.title { + text-align: center ; + color: orange} + +h2.subtitle { + color: orange; + text-align: center } + +hr { + width: 75% } + +p.caption { + font-style: italic } + +p.credits { + font-style: italic ; + font-size: smaller } + +p.label { + white-space: nowrap } + +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 ; + 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 { + margin-top: 0.5em ; + margin-bottom: 0.5em } + +table.citation { + border-left: solid thin gray ; + padding-left: 0.5ex } + +table.docinfo { + margin: 2em 4em } + +table.footnote { + border-left: solid thin black ; + padding-left: 0.5ex } + +td, th { + padding-left: 0.5em ; + padding-right: 0.5em ; + vertical-align: top } + +th.docinfo-name, th.field-name { + font-weight: bold ; + text-align: left ; + white-space: nowrap } + +h1 tt, h2 tt, h3 tt, h4 tt, h5 tt, h6 tt { + font-size: 100% } + +tt { + background-color: #eeeeee } + +ul.auto-toc { + list-style-type: none } +*/ + +div.section { + margin-top: 1.0em ; +} diff --git a/doc/build/docutils.css b/doc/build/docutils.css new file mode 100644 index 0000000..f03e03d --- /dev/null +++ b/doc/build/docutils.css @@ -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 } + diff --git a/doc/build/style.css b/doc/build/style.css new file mode 100644 index 0000000..28c256e --- /dev/null +++ b/doc/build/style.css @@ -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%; +} + + diff --git a/doc/build/tiramisu.jpeg b/doc/build/tiramisu.jpeg new file mode 100644 index 0000000..84b391d Binary files /dev/null and b/doc/build/tiramisu.jpeg differ diff --git a/doc/build/tiramisu.tar.gz b/doc/build/tiramisu.tar.gz new file mode 100644 index 0000000..1820be3 Binary files /dev/null and b/doc/build/tiramisu.tar.gz differ diff --git a/doc/code2html b/doc/code2html new file mode 100755 index 0000000..be62c7c --- /dev/null +++ b/doc/code2html @@ -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 = """ + + + +{title} + + + + + + +
+{content}
+
+ + +""" + +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() + diff --git a/doc/config.txt b/doc/config.txt new file mode 100644 index 0000000..9b967d7 --- /dev/null +++ b/doc/config.txt @@ -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 + diff --git a/doc/configapi.txt b/doc/configapi.txt new file mode 100644 index 0000000..d6fe570 --- /dev/null +++ b/doc/configapi.txt @@ -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': , '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). + diff --git a/doc/consistency.txt b/doc/consistency.txt new file mode 100644 index 0000000..873b0f2 --- /dev/null +++ b/doc/consistency.txt @@ -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()` + diff --git a/doc/eole-report/eolreport/D01AccesVariables.txt b/doc/eole-report/eolreport/D01AccesVariables.txt new file mode 100644 index 0000000..af6f19f --- /dev/null +++ b/doc/eole-report/eolreport/D01AccesVariables.txt @@ -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` + +:: + + + + + +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. + + + diff --git a/doc/eole-report/eolreport/D02CoherenceVariables.txt b/doc/eole-report/eolreport/D02CoherenceVariables.txt new file mode 100644 index 0000000..fa37aaf --- /dev/null +++ b/doc/eole-report/eolreport/D02CoherenceVariables.txt @@ -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:: + + + + + +:: + + 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 + +:: + + + + toto + + + + + 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 + + diff --git a/doc/eole-report/eolreport/D03ReglesEtats.txt b/doc/eole-report/eolreport/D03ReglesEtats.txt new file mode 100644 index 0000000..70705f5 --- /dev/null +++ b/doc/eole-report/eolreport/D03ReglesEtats.txt @@ -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 + +:: + + + + + + non + + + oui + + + non + activer_clam + + + + non + activer_clam + + +: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 "", line 1, in + HiddenOptionError("trying to access to a hidden option:activer_clam") + >>> + diff --git a/doc/eole-report/eolreport/Makefile b/doc/eole-report/eolreport/Makefile new file mode 100644 index 0000000..fbf5816 --- /dev/null +++ b/doc/eole-report/eolreport/Makefile @@ -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/$@ + diff --git a/doc/eole-report/eolreport/build/Makefile b/doc/eole-report/eolreport/build/Makefile new file mode 100644 index 0000000..cc5f93b --- /dev/null +++ b/doc/eole-report/eolreport/build/Makefile @@ -0,0 +1,6 @@ +.PHONY: clean +.SUFFIXES: + +clean: + rm -f *.html + rm -f api/*.html diff --git a/doc/eole-report/eolreport/build/default.css b/doc/eole-report/eolreport/build/default.css new file mode 100644 index 0000000..8625c0e --- /dev/null +++ b/doc/eole-report/eolreport/build/default.css @@ -0,0 +1,1080 @@ +body,body.editor,body.body { + font: 110% "Times New Roman", Arial, Verdana, Helvetica, serif; + background: White; + color: Black; +} + +a, a.reference { + text-decoration: none; +} +a[href]:hover { text-decoration: underline; } + +img { + border: none; + vertical-align: middle; +} + +p, div.text { + text-align: left; + line-height: 1.5em; + margin: 0.5em 0em 0em 0em; +} + + + +p a:active { + color: Red; + background-color: transparent; +} + +p img { + border: 0; + margin: 0; +} + +img.inlinephoto { + padding: 0; + padding-right: 1em; + padding-top: 0.7em; + float: left; +} + +hr { + clear: both; + height: 1px; + color: #8CACBB; + background-color: transparent; +} + + +ul { + line-height: 1.5em; + /*list-style-image: url("bullet.gif"); */ + margin-left: 1.5em; + padding:0; +} + +ol { + line-height: 1.5em; + margin-left: 1.5em; + padding:0; +} + +ul a, ol a { + text-decoration: underline; +} + +dl { +} + +dt { + font-weight: bold; +} + +dd { + line-height: 1.5em; + margin-bottom: 1em; +} + +blockquote { + font-family: Times, "Times New Roman", serif; + font-style: italic; + font-size: 120%; +} + +code { + color: Black; + /*background-color: #dee7ec;*/ + background-color: #cccccc; +} + +pre { + padding: 1em; + border: 1px solid #8cacbb; + color: Black; + background-color: #dee7ec; + background-color: #cccccc; + overflow: auto; +} + + +.netscape4 { + display: none; +} + +/* main page styles */ + +/*a[href]:hover { color: black; text-decoration: underline; } +a[href]:link { color: black; text-decoration: underline; } +a[href] { color: black; text-decoration: underline; } +*/ + +span.menu_selected { + color: black; + font: 140% Verdana, Helvetica, Arial, sans-serif; + text-decoration: none; + padding-right: 0.3em; + background-color: #cccccc; +} + + +a.menu { + /*color: #3ba6ec; */ + font: 140% Verdana, Helvetica, Arial, sans-serif; + text-decoration: none; + padding-right: 0.3em; +} + +a.menu[href]:visited, a.menu[href]:link{ + /*color: #3ba6ec; */ + font: 140% Verdana, Helvetica, Arial, sans-serif; + text-decoration: none; +} + +a.menu[href]:hover { + /*color: black;*/ +} + +div.project_title{ + /*border-spacing: 20px;*/ + font: 160% Verdana, Helvetica, Arial, sans-serif; + color: #3ba6ec; + vertical-align: middle; + padding-bottom: 0.3em; +} + +a.wikicurrent { + font: 100% Verdana, Helvetica, Arial, sans-serif; + color: #3ba6ec; + vertical-align: middle; +} + + +table.body { + border: 0; + /*padding: 0; + border-spacing: 0px; + border-collapse: separate; + */ +} + +td.page-header-left { + padding: 5px; + /*border-bottom: 1px solid #444444;*/ +} + +td.page-header-top { + padding: 0; + + /*border-bottom: 1px solid #444444;*/ +} + +td.sidebar { + padding: 1 0 0 1; +} + +td.sidebar p.classblock { + padding: 0 5 0 5; + margin: 1 1 1 1; + border: 1px solid #444444; + background-color: #eeeeee; +} + +td.sidebar p.userblock { + padding: 0 5 0 5; + margin: 1 1 1 1; + border: 1px solid #444444; + background-color: #eeeeff; +} + +td.content { + padding: 1 5 1 5; + vertical-align: top; + width: 100%; +} + +p.ok-message { + background-color: #22bb22; + padding: 5 5 5 5; + color: white; + font-weight: bold; +} +p.error-message { + background-color: #bb2222; + padding: 5 5 5 5; + color: white; + font-weight: bold; +} + +p:first-child { + margin: 0 ; + padding: 0; +} + +/* style for forms */ +table.form { + padding: 2; + border-spacing: 0px; + border-collapse: separate; +} + +table.form th { + color: #333388; + text-align: right; + vertical-align: top; + font-weight: normal; +} +table.form th.header { + font-weight: bold; + background-color: #eeeeff; + text-align: left; +} + +table.form th.required { + font-weight: bold; +} + +table.form td { + color: #333333; + empty-cells: show; + vertical-align: top; +} + +table.form td.optional { + font-weight: bold; + font-style: italic; +} + +table.form td.html { + color: #777777; +} + +/* style for lists */ +table.list { + border-spacing: 0px; + border-collapse: separate; + vertical-align: top; + padding-top: 0; + width: 100%; +} + +table.list th { + padding: 0 4 0 4; + color: #404070; + background-color: #eeeeff; + border-right: 1px solid #404070; + border-top: 1px solid #404070; + border-bottom: 1px solid #404070; + vertical-align: top; + empty-cells: show; +} +table.list th a[href]:hover { color: #404070 } +table.list th a[href]:link { color: #404070 } +table.list th a[href] { color: #404070 } +table.list th.group { + background-color: #f4f4ff; + text-align: center; + font-size: 120%; +} + +table.list td { + padding: 0 4 0 4; + border: 0 2 0 2; + border-right: 1px solid #404070; + color: #404070; + background-color: white; + vertical-align: top; + empty-cells: show; +} + +table.list tr.normal td { + background-color: white; + white-space: nowrap; +} + +table.list tr.alt td { + background-color: #efefef; + white-space: nowrap; +} + +table.list td:first-child { + border-left: 1px solid #404070; + border-right: 1px solid #404070; +} + +table.list th:first-child { + border-left: 1px solid #404070; + border-right: 1px solid #404070; +} + +table.list tr.navigation th { + text-align: right; +} +table.list tr.navigation th:first-child { + border-right: none; + text-align: left; +} + + +/* style for message displays */ +table.messages { + border-spacing: 0px; + border-collapse: separate; + width: 100%; +} + +table.messages th.header{ + padding-top: 10px; + border-bottom: 1px solid gray; + font-weight: bold; + background-color: white; + color: #707040; +} + +table.messages th { + font-weight: bold; + color: black; + text-align: left; + border-bottom: 1px solid #afafaf; +} + +table.messages td { + font-family: monospace; + background-color: #efefef; + border-bottom: 1px solid #afafaf; + color: black; + empty-cells: show; + border-right: 1px solid #afafaf; + vertical-align: top; + padding: 2 5 2 5; +} + +table.messages td:first-child { + border-left: 1px solid #afafaf; + border-right: 1px solid #afafaf; +} + +/* style for file displays */ +table.files { + border-spacing: 0px; + border-collapse: separate; + width: 100%; +} + +table.files th.header{ + padding-top: 10px; + border-bottom: 1px solid gray; + font-weight: bold; + background-color: white; + color: #707040; +} + +table.files th { + border-bottom: 1px solid #afafaf; + font-weight: bold; + text-align: left; +} + +table.files td { + font-family: monospace; + empty-cells: show; +} + +/* style for history displays */ +table.history { + border-spacing: 0px; + border-collapse: separate; + width: 100%; +} + +table.history th.header{ + padding-top: 10px; + border-bottom: 1px solid gray; + font-weight: bold; + background-color: white; + color: #707040; + font-size: 100%; +} + +table.history th { + border-bottom: 1px solid #afafaf; + font-weight: bold; + text-align: left; + font-size: 90%; +} + +table.history td { + font-size: 90%; + vertical-align: top; + empty-cells: show; +} + + +/* style for class list */ +table.classlist { + border-spacing: 0px; + border-collapse: separate; + width: 100%; +} + +table.classlist th.header{ + padding-top: 10px; + border-bottom: 1px solid gray; + font-weight: bold; + background-color: white; + color: #707040; +} + +table.classlist th { + font-weight: bold; + text-align: left; +} + + +/* style for class help display */ +table.classhelp { + border-spacing: 0px; + border-collapse: separate; + width: 100%; +} + +table.classhelp th { + font-weight: bold; + text-align: left; + color: #707040; +} + +table.classhelp td { + padding: 2 2 2 2; + border: 1px solid black; + text-align: left; + vertical-align: top; + empty-cells: show; +} + + +/* style for "other" displays */ +table.otherinfo { + border-spacing: 0px; + border-collapse: separate; + width: 100%; +} + +table.otherinfo th.header{ + padding-top: 10px; + border-bottom: 1px solid gray; + font-weight: bold; + background-color: white; + color: #707040; +} + +table.otherinfo th { + border-bottom: 1px solid #afafaf; + font-weight: bold; + text-align: left; +} + +input { + border: 1px solid #8cacbb; + color: Black; + background-color: white; + vertical-align: middle; + margin-bottom: 1px; /* IE bug fix */ + padding: 0.1em; +} + +select { + border: 1px solid #8cacbb; + color: Black; + background-color: white; + vertical-align: middle; + margin-bottom: 1px; /* IE bug fix */ + padding: 0.1em; +} + + +a.nonexistent { + color: #FF2222; +} +a.nonexistent:visited { + color: #FF2222; +} +a.external { + color: #AA6600; +} + +/* +dl,ul,ol { + margin-top: 1pt; +} +tt,pre { + font-family: Lucida Console,Courier New,Courier,monotype; + font-size: 12pt; +} +pre.code { + margin-top: 8pt; + margin-bottom: 8pt; + background-color: #FFFFEE; + white-space:pre; + border-style:solid; + border-width:1pt; + border-color:#999999; + color:#111111; + padding:5px; + width:100%; +} +*/ +div.diffold { + background-color: #FFFF80; + border-style:none; + border-width:thin; + width:100%; +} +div.diffnew { + background-color: #80FF80; + border-style:none; + border-width:thin; + width:100%; +} +div.message { + margin-top: 6pt; + background-color: #E8FFE8; + border-style:solid; + border-width:1pt; + border-color:#999999; + color:#440000; + padding:5px; + width:100%; +} +strong.highlight { + background-color: #FFBBBB; +/* as usual, NetScape fucks up with innocent CSS + border-color: #FFAAAA; + border-style: solid; + border-width: 1pt; +*/ +} + +table.navibar { + background-color: #C8C8C8; + border-spacing: 3px; +} +td.navibar { + background-color: #E8E8E8; + vertical-align: top; + text-align: right; + padding: 0px; +} + +div.pagename { + font-size: 140%; + color: blue; + text-align: center; + font-weight: bold; + background-color: white; + padding: 0 ; +} + +a.wikiaction, input.wikiaction { + color: black; + text-decoration: None; + text-align: center; + color: black; + /*border: 1px solid #3ba6ec; */ + margin: 4px; + padding: 5; + padding-bottom: 0; + white-space: nowrap; +} + +a.wikiaction[href]:hover { + color: black; + text-decoration: none; + /*background-color: #dddddd; */ +} + +span.wikiuserpref { + padding-top: 1em; + font-size: 120%; +} + +div.wikitrail { + vertical-align: bottom; + /*font-size: -1;*/ + padding-top: 1em; + display: none; +} + +div.wikiaction { + vertical-align: middle; + /*border-bottom: 1px solid #8cacbb;*/ + padding-bottom:1em; + text-align: left; + width: 100%; +} + +div.wikieditmenu { + text-align: right; +} + +form.wikiedit { + border: 1px solid #8cacbb; + background-color: #f0f0f0; + background-color: #fabf00; + padding: 1em; + padding-right: 0em; +} + +div.legenditem { + padding-top: 0.5em; + padding-left: 0.3em; +} + +span.wikitoken { + background-color: #eeeeee; +} + + +div#contentspace h1:first-child, div.heading:first-child { + padding-top: 0; + margin-top: 0; +} +div#contentspace h2:first-child { + padding-top: 0; + margin-top: 0; +} + +/* heading and paragraph text */ + +div.heading, h1 { + font-family: Verdana, Helvetica, Arial, sans-serif; + background-color: #58b3ef; + background-color: #FFFFFF; + /*color: #4893cf;*/ + color: black; + padding-top: 1.0em; + padding-bottom:0.2em; + text-align: left; + margin-top: 0em; + /*margin-bottom:8pt;*/ + font-weight: bold; + font-size: 115%; + border-bottom: 1px solid #8CACBB; +} + + +h1, h2, h3, h4, h5, h6 { + color: orange; + clear: left; + font: 100% Verdana, Helvetica, Arial, sans-serif; + margin: 0; + padding-left: 0em; + padding-top: 1em; + padding-bottom: 0.2em; + /*border-bottom: 1px solid #8CACBB;*/ +} +/* h1,h2 { padding-top: 0; }*/ + + +h1 { font-size: 145%; } +h2 { font-size: 135%; } +h3 { font-size: 125%; } +h4 { font-size: 120%; } +h5 { font-size: 110%; } +h6 { font-size: 80%; } + +h1 a { text-decoration: None;} + +div.exception { + background-color: #bb2222; + padding: 5 5 5 5; + color: white; + font-weight: bold; +} +pre.exception { + font-size: 110%; + padding: 1em; + border: 1px solid #8cacbb; + color: Black; + background-color: #dee7ec; + background-color: #cccccc; +} + +/* defines for navgiation bar (documentation) */ + + +div.direntry { + padding-top: 0.3em; + padding-bottom: 0.3em; + margin-right: 1em; + font-weight: bold; + background-color: #dee7ec; + font-size: 110%; +} + +div.fileentry { + font-family: Verdana, Helvetica, Arial, sans-serif; + padding-bottom: 0.3em; + white-space: nowrap; + line-height: 150%; +} + +a.fileentry { + white-space: nowrap; +} + + +span.left { + text-align: left; +} +span.right { + text-align: right; +} + +div.navbar { + /*margin: 0;*/ + font-size: 80% /*smaller*/; + font-weight: bold; + text-align: left; + /* position: fixed; */ + top: 100pt; + left: 0pt; /* auto; */ + width: 120pt; + /* right: auto; + right: 0pt; 2em; */ +} + + +div.history a { + /* font-size: 70%; */ +} + +div.wikiactiontitle { + font-weight: bold; +} + +/* REST defines */ + +div.document { + margin: 0; +} + +h1.title { + margin: 0; + margin-bottom: 0.5em; +} + +td.toplist { + vertical-align: top; +} + +img#pyimg { + position: absolute; + top: 4px; + left: 4px; +} + +div#navspace { + position: absolute; + top: 130px; + left: 11px; + font-size: 100%; + width: 150px; + overflow: hidden; /* scroll; */ +} + +div#metaspace { + position: absolute; + top: 40px; + left: 170px; +} + +div#errorline { + position: relative; + top: 5px; + float: right; +} + +div#contentspace { + position: absolute; + /* font: 120% "Times New Roman", serif;*/ + font: 110% Verdana, Helvetica, Arial, sans-serif; + top: 130px; + left: 170px; + margin-right: 5px; +} + +div#menubar { +/* width: 400px; */ + float: left; +} + +/* for the documentation page */ +div#docinfoline { + position: relative; + top: 5px; + left: 0px; + + /*background-color: #dee7ec; */ + padding: 5pt; + padding-bottom: 1em; + color: black; + /*border-width: 1pt; + border-style: solid;*/ + +} + +div#docnavlist { + /*background-color: #dee7ec; */ + padding: 5pt; + padding-bottom: 2em; + color: black; + border-width: 1pt; + /*border-style: solid;*/ +} + + +/* text markup */ + +div.listtitle { + color: Black; + clear: left; + font: 120% Verdana, Helvetica, Arial, sans-serif; + margin: 0; + padding-left: 0em; + padding-top: 0em; + padding-bottom: 0.2em; + margin-right: 0.5em; + border-bottom: 1px solid #8CACBB; +} + +div.actionbox h3 { + padding-top: 0; + padding-right: 0.5em; + padding-left: 0.5em; + background-color: #fabf00; + text-align: center; + border: 1px solid black; /* 8cacbb; */ +} + +div.actionbox a { + display: block; + padding-bottom: 0.5em; + padding-top: 0.5em; + margin-left: 0.5em; +} + +div.actionbox a.history { + display: block; + padding-bottom: 0.5em; + padding-top: 0.5em; + margin-left: 0.5em; + font-size: 90%; +} + +div.actionbox { + margin-bottom: 2em; + padding-bottom: 1em; + overflow: hidden; /* scroll; */ +} + +/* taken from docutils (oh dear, a bit senseless) */ +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 } + + +/* +:Author: David Goodger +:Contact: goodger@users.sourceforge.net +:date: $Date: 2003/01/22 22:26:48 $ +:version: $Revision: 1.29 $ +:copyright: This stylesheet has been placed in the public domain. + +Default cascading style sheet for the HTML output of Docutils. +*/ +/* +.first { + margin-top: 0 } + +.last { + margin-bottom: 0 } + +a.toc-backref { + text-decoration: none ; + color: black } + +dd { + margin-bottom: 0.5em } + +div.abstract { + margin: 2em 5em } + +div.abstract p.topic-title { + font-weight: bold ; + text-align: center } + +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.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.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.dedication { + margin: 2em 5em ; + text-align: center ; + font-style: italic } + +div.dedication p.topic-title { + font-weight: bold ; + font-style: normal } + +div.figure { + margin-left: 2em } + +div.footer, div.header { + font-size: smaller } + +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.title { + text-align: center ; + color: orange} + +h2.subtitle { + color: orange; + text-align: center } + +hr { + width: 75% } + +p.caption { + font-style: italic } + +p.credits { + font-style: italic ; + font-size: smaller } + +p.label { + white-space: nowrap } + +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 ; + 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 { + margin-top: 0.5em ; + margin-bottom: 0.5em } + +table.citation { + border-left: solid thin gray ; + padding-left: 0.5ex } + +table.docinfo { + margin: 2em 4em } + +table.footnote { + border-left: solid thin black ; + padding-left: 0.5ex } + +td, th { + padding-left: 0.5em ; + padding-right: 0.5em ; + vertical-align: top } + +th.docinfo-name, th.field-name { + font-weight: bold ; + text-align: left ; + white-space: nowrap } + +h1 tt, h2 tt, h3 tt, h4 tt, h5 tt, h6 tt { + font-size: 100% } + +tt { + background-color: #eeeeee } + +ul.auto-toc { + list-style-type: none } +*/ + +div.section { + margin-top: 1.0em ; +} diff --git a/doc/eole-report/eolreport/build/docutils.css b/doc/eole-report/eolreport/build/docutils.css new file mode 100644 index 0000000..f03e03d --- /dev/null +++ b/doc/eole-report/eolreport/build/docutils.css @@ -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 } + diff --git a/doc/eole-report/eolreport/build/imgs/eol.png b/doc/eole-report/eolreport/build/imgs/eol.png new file mode 100644 index 0000000..5b23138 Binary files /dev/null and b/doc/eole-report/eolreport/build/imgs/eol.png differ diff --git a/doc/eole-report/eolreport/build/imgs/logo.png b/doc/eole-report/eolreport/build/imgs/logo.png new file mode 100644 index 0000000..9c554f4 Binary files /dev/null and b/doc/eole-report/eolreport/build/imgs/logo.png differ diff --git a/doc/eole-report/eolreport/build/index-report.html b/doc/eole-report/eolreport/build/index-report.html new file mode 100644 index 0000000..098c9c7 --- /dev/null +++ b/doc/eole-report/eolreport/build/index-report.html @@ -0,0 +1,76 @@ + + + + + + +rapports eole + + + +
+ + +imgs/eol.png + +++ + + + + + +
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.

+ +
+
+ + diff --git a/doc/eole-report/eolreport/build/pdfreport/D01AccesVariables.pdf b/doc/eole-report/eolreport/build/pdfreport/D01AccesVariables.pdf new file mode 100644 index 0000000..471a375 Binary files /dev/null and b/doc/eole-report/eolreport/build/pdfreport/D01AccesVariables.pdf differ diff --git a/doc/eole-report/eolreport/build/pdfreport/D02CoherenceVariables.pdf b/doc/eole-report/eolreport/build/pdfreport/D02CoherenceVariables.pdf new file mode 100644 index 0000000..d8c37fe Binary files /dev/null and b/doc/eole-report/eolreport/build/pdfreport/D02CoherenceVariables.pdf differ diff --git a/doc/eole-report/eolreport/build/pdfreport/D03ReglesEtats.pdf b/doc/eole-report/eolreport/build/pdfreport/D03ReglesEtats.pdf new file mode 100644 index 0000000..7ec977c Binary files /dev/null and b/doc/eole-report/eolreport/build/pdfreport/D03ReglesEtats.pdf differ diff --git a/doc/eole-report/eolreport/build/pdfreport/make_index b/doc/eole-report/eolreport/build/pdfreport/make_index new file mode 100755 index 0000000..ca207c8 --- /dev/null +++ b/doc/eole-report/eolreport/build/pdfreport/make_index @@ -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()) + + diff --git a/doc/eole-report/eolreport/build/pdfreport/rst.py b/doc/eole-report/eolreport/build/pdfreport/rst.py new file mode 100644 index 0000000..7548cdd --- /dev/null +++ b/doc/eole-report/eolreport/build/pdfreport/rst.py @@ -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 + diff --git a/doc/eole-report/eolreport/build/style.css b/doc/eole-report/eolreport/build/style.css new file mode 100644 index 0000000..28c256e --- /dev/null +++ b/doc/eole-report/eolreport/build/style.css @@ -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%; +} + + diff --git a/doc/eole-report/eolreport/inc/00-Redacteur.txt b/doc/eole-report/eolreport/inc/00-Redacteur.txt new file mode 100644 index 0000000..554e81b --- /dev/null +++ b/doc/eole-report/eolreport/inc/00-Redacteur.txt @@ -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`` + diff --git a/doc/eole-report/eolreport/inc/eol.png b/doc/eole-report/eolreport/inc/eol.png new file mode 100644 index 0000000..5b23138 Binary files /dev/null and b/doc/eole-report/eolreport/inc/eol.png differ diff --git a/doc/eole-report/eolreport/inc/logo.png b/doc/eole-report/eolreport/inc/logo.png new file mode 100644 index 0000000..9c554f4 Binary files /dev/null and b/doc/eole-report/eolreport/inc/logo.png differ diff --git a/doc/eole-report/eolreport/inc/menjva.gif b/doc/eole-report/eolreport/inc/menjva.gif new file mode 100644 index 0000000..49d17a6 Binary files /dev/null and b/doc/eole-report/eolreport/inc/menjva.gif differ diff --git a/doc/eole-report/eolreport/inc/preambule.txt b/doc/eole-report/eolreport/inc/preambule.txt new file mode 100644 index 0000000..c25bb90 --- /dev/null +++ b/doc/eole-report/eolreport/inc/preambule.txt @@ -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 + + + diff --git a/doc/eole-report/eolreport/index-report.txt b/doc/eole-report/eolreport/index-report.txt new file mode 100644 index 0000000..4670b94 --- /dev/null +++ b/doc/eole-report/eolreport/index-report.txt @@ -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 + diff --git a/doc/eole-report/eolreport/styles.odt b/doc/eole-report/eolreport/styles.odt new file mode 100644 index 0000000..2121251 Binary files /dev/null and b/doc/eole-report/eolreport/styles.odt differ diff --git a/doc/eole-report/proposal/Makefile b/doc/eole-report/proposal/Makefile new file mode 100644 index 0000000..7b32a56 --- /dev/null +++ b/doc/eole-report/proposal/Makefile @@ -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 + diff --git a/doc/eole-report/proposal/comparaison.tex b/doc/eole-report/proposal/comparaison.tex new file mode 100644 index 0000000..53e168a --- /dev/null +++ b/doc/eole-report/proposal/comparaison.tex @@ -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} + + diff --git a/doc/eole-report/proposal/definition.tex b/doc/eole-report/proposal/definition.tex new file mode 100644 index 0000000..5b10ac1 --- /dev/null +++ b/doc/eole-report/proposal/definition.tex @@ -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} + diff --git a/doc/eole-report/proposal/statut.tex b/doc/eole-report/proposal/statut.tex new file mode 100644 index 0000000..1f665b6 --- /dev/null +++ b/doc/eole-report/proposal/statut.tex @@ -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} + + diff --git a/doc/eole-report/proposal/tiramisu.tex b/doc/eole-report/proposal/tiramisu.tex new file mode 100644 index 0000000..eb01619 --- /dev/null +++ b/doc/eole-report/proposal/tiramisu.tex @@ -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} + diff --git a/doc/epydoc.sh b/doc/epydoc.sh new file mode 100755 index 0000000..a5bcf22 --- /dev/null +++ b/doc/epydoc.sh @@ -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 + diff --git a/doc/gaspacho.txt b/doc/gaspacho.txt new file mode 100644 index 0000000..457e3e9 --- /dev/null +++ b/doc/gaspacho.txt @@ -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 n’attend + 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 s’agit 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). + + + + + + + diff --git a/doc/getting-started.txt b/doc/getting-started.txt new file mode 100644 index 0000000..c72a671 --- /dev/null +++ b/doc/getting-started.txt @@ -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. + diff --git a/doc/glossary.txt b/doc/glossary.txt new file mode 100644 index 0000000..06f0616 --- /dev/null +++ b/doc/glossary.txt @@ -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. diff --git a/doc/index.txt b/doc/index.txt new file mode 100644 index 0000000..a6e812a --- /dev/null +++ b/doc/index.txt @@ -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. + + + + diff --git a/doc/optionapi.txt b/doc/optionapi.txt new file mode 100644 index 0000000..c3c534a --- /dev/null +++ b/doc/optionapi.txt @@ -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. + diff --git a/doc/pydoc/Makefile b/doc/pydoc/Makefile new file mode 100644 index 0000000..612cb8f --- /dev/null +++ b/doc/pydoc/Makefile @@ -0,0 +1,5 @@ +.PHONY: clean +.SUFFIXES: + +clean: + rm -f *.html diff --git a/doc/pydoc/crarr.png b/doc/pydoc/crarr.png new file mode 100644 index 0000000..26b43c5 Binary files /dev/null and b/doc/pydoc/crarr.png differ diff --git a/doc/pydoc/epydoc.css b/doc/pydoc/epydoc.css new file mode 100644 index 0000000..1c00695 --- /dev/null +++ b/doc/pydoc/epydoc.css @@ -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 + *

). 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; } diff --git a/doc/pydoc/epydoc.js b/doc/pydoc/epydoc.js new file mode 100644 index 0000000..e787dbc --- /dev/null +++ b/doc/pydoc/epydoc.js @@ -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...
"; + 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 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[0] + ""; + } + + // Put it all together. + elt.insertBefore(box1, elt.childNodes.item(0)); + //box1.appendChild(box2); + box1.appendChild(shadow); + shadow.appendChild(box2); + box2.innerHTML = + "Which "+name+" do you want to see documentation for?" + + ""; + } + 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-m" or "-c"; + // extract the 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; + } + } + } diff --git a/doc/rst2html.py b/doc/rst2html.py new file mode 100755 index 0000000..3e356f3 --- /dev/null +++ b/doc/rst2html.py @@ -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) + diff --git a/doc/status.txt b/doc/status.txt new file mode 100644 index 0000000..67e015e --- /dev/null +++ b/doc/status.txt @@ -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`. + diff --git a/doc/todo.txt b/doc/todo.txt new file mode 100644 index 0000000..9891062 --- /dev/null +++ b/doc/todo.txt @@ -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() + diff --git a/error.py b/error.py new file mode 100644 index 0000000..4588f71 --- /dev/null +++ b/error.py @@ -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 + diff --git a/option.py b/option.py new file mode 100644 index 0000000..3af1c45 --- /dev/null +++ b/option.py @@ -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])() + diff --git a/report/Makefile b/report/Makefile new file mode 100644 index 0000000..760f9bb --- /dev/null +++ b/report/Makefile @@ -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 + diff --git a/report/__init__.py b/report/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/report/build/Makefile b/report/build/Makefile new file mode 100644 index 0000000..e6b01ad --- /dev/null +++ b/report/build/Makefile @@ -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 + diff --git a/report/build/basic.css b/report/build/basic.css new file mode 100644 index 0000000..f0379f3 --- /dev/null +++ b/report/build/basic.css @@ -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; + } +} \ No newline at end of file diff --git a/report/build/rst2html.py b/report/build/rst2html.py new file mode 100755 index 0000000..cfa6769 --- /dev/null +++ b/report/build/rst2html.py @@ -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) + diff --git a/report/build/style.css b/report/build/style.css new file mode 100644 index 0000000..1126f9d --- /dev/null +++ b/report/build/style.css @@ -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; + } + +} diff --git a/report/generate.py b/report/generate.py new file mode 100644 index 0000000..b4112a1 --- /dev/null +++ b/report/generate.py @@ -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()) +# ____________________________________________________________ + diff --git a/report/makerestdoc.py b/report/makerestdoc.py new file mode 100644 index 0000000..3ca042d --- /dev/null +++ b/report/makerestdoc.py @@ -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 + +# ____________________________________________________________ + + diff --git a/report/rst.py b/report/rst.py new file mode 100644 index 0000000..7548cdd --- /dev/null +++ b/report/rst.py @@ -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 + diff --git a/report/test_config_big_example.py b/report/test_config_big_example.py new file mode 100644 index 0000000..3a93273 --- /dev/null +++ b/report/test_config_big_example.py @@ -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) diff --git a/test/__init__.py b/test/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/autopath.py b/test/autopath.py new file mode 100644 index 0000000..bc2ddff --- /dev/null +++ b/test/autopath.py @@ -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) diff --git a/test/test_config.py b/test/test_config.py new file mode 100644 index 0000000..a18cf60 --- /dev/null +++ b/test/test_config.py @@ -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'] diff --git a/test/test_config_api.py b/test/test_config_api.py new file mode 100644 index 0000000..9c6578c --- /dev/null +++ b/test/test_config_api.py @@ -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 +# diff --git a/test/test_config_big_example.py b/test/test_config_big_example.py new file mode 100644 index 0000000..3ffbd80 --- /dev/null +++ b/test/test_config_big_example.py @@ -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 diff --git a/test/test_option_consistency.py b/test/test_option_consistency.py new file mode 100644 index 0000000..d59e378 --- /dev/null +++ b/test/test_option_consistency.py @@ -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() + + diff --git a/test/test_option_default.py b/test/test_option_default.py new file mode 100644 index 0000000..3d7baf7 --- /dev/null +++ b/test/test_option_default.py @@ -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] + diff --git a/test/test_option_owner.py b/test/test_option_owner.py new file mode 100644 index 0000000..3043353 --- /dev/null +++ b/test/test_option_owner.py @@ -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 + + diff --git a/test/test_option_setting.py b/test/test_option_setting.py new file mode 100644 index 0000000..dad5e20 --- /dev/null +++ b/test/test_option_setting.py @@ -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')") + diff --git a/test/test_option_type.py b/test/test_option_type.py new file mode 100644 index 0000000..5421ca4 --- /dev/null +++ b/test/test_option_type.py @@ -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' + diff --git a/test/test_option_with_special_name.py b/test/test_option_with_special_name.py new file mode 100644 index 0000000..acc1683 --- /dev/null +++ b/test/test_option_with_special_name.py @@ -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 + + + diff --git a/test/test_parsing_group.py b/test/test_parsing_group.py new file mode 100644 index 0000000..b496a21 --- /dev/null +++ b/test/test_parsing_group.py @@ -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'] + diff --git a/test/test_reverse_from_path.py b/test/test_reverse_from_path.py new file mode 100644 index 0000000..9bf885b --- /dev/null +++ b/test/test_reverse_from_path.py @@ -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'] diff --git a/test/test_tool.py b/test/test_tool.py new file mode 100644 index 0000000..8620bc4 --- /dev/null +++ b/test/test_tool.py @@ -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" + + diff --git a/tool.py b/tool.py new file mode 100644 index 0000000..9e5db1f --- /dev/null +++ b/tool.py @@ -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) + +# ____________________________________________________________ +