You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
636 lines
22 KiB
636 lines
22 KiB
# |
|
# Copyright 2006 sense.lab e.V. |
|
# |
|
# This file is part of the CryptoBox. |
|
# |
|
# The CryptoBox is free software; you can redistribute it and/or modify |
|
# it under the terms of the GNU General Public License as published by |
|
# the Free Software Foundation; either version 2 of the License, or |
|
# (at your option) any later version. |
|
# |
|
# The CryptoBox is distributed in the hope that it will be useful, |
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
# GNU General Public License for more details. |
|
# |
|
# You should have received a copy of the GNU General Public License |
|
# along with the CryptoBox; if not, write to the Free Software |
|
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
|
# |
|
""" this module handles all http requests and renders a website """ |
|
|
|
__revision__ = "$Id$" |
|
|
|
import cryptobox.core.main |
|
import cryptobox.web.dataset |
|
import cryptobox.plugins.manage |
|
import cryptobox.core.exceptions |
|
import re |
|
import cherrypy |
|
import os |
|
import sys |
|
|
|
try: |
|
import neo_cgi, neo_util, neo_cs |
|
except ImportError: |
|
_ERRMSG = "Could not import clearsilver module. \ |
|
Try 'apt-get install python-clearsilver'." |
|
sys.stderr.write(_ERRMSG) |
|
raise ImportError, _ERRMSG |
|
|
|
|
|
GETTEXT_DOMAIN = 'cryptobox-server' |
|
|
|
|
|
class PluginIconHandler: |
|
"""deliver the icons of available plugins via cherrypy""" |
|
|
|
def __init__(self, plugins): |
|
for plugin in plugins.get_plugins(): |
|
if not plugin: |
|
continue |
|
plname = plugin.get_name() |
|
## expose the get_icon function of this plugin |
|
setattr(self, plname, plugin.get_icon) |
|
|
|
|
|
|
|
class WebInterfaceSites: |
|
"""handle all http requests and render pages |
|
|
|
this includes: |
|
- filtering common arguments |
|
- calling feature actions |
|
- translating content |
|
|
|
all available features are dynamically exposed |
|
""" |
|
|
|
## this template is used under strange circumstances |
|
defaultTemplate = "empty" |
|
|
|
|
|
def __init__(self, conf_file=None): |
|
## we should only use variables preceded by "__" to avoid name conflicts |
|
## when loading features |
|
self.cbox = cryptobox.core.main.CryptoBox(conf_file) |
|
self.__cached_language_data = None |
|
self.__dataset = None |
|
self.icons = None |
|
self.__plugin_manager = None |
|
self.__reset_dataset() |
|
## store the original http error handler |
|
self._cp_on_http_error = self.new_http_error_handler |
|
## set initial language order |
|
self.lang_order = self.cbox.prefs["WebSettings"]["Languages"][:] |
|
self.setup() |
|
|
|
|
|
def setup(self): |
|
"""Prepare the webinterface. |
|
""" |
|
self.cbox.setup() |
|
for plugin in self.__plugin_manager.get_plugins(): |
|
if plugin: |
|
plugin.setup() |
|
|
|
|
|
def cleanup(self): |
|
"""Shutdown the webinterface safely. |
|
""" |
|
for plugin in self.__plugin_manager.get_plugins(): |
|
if plugin: |
|
plugin.cleanup() |
|
self.cbox.cleanup() |
|
|
|
|
|
def __reset_dataset(self): |
|
"""this method has to be called at the beginning of every "site" action |
|
important: only at the beginning of an action (to not loose information) |
|
important: for _every_ "site" action (cherrypy is stateful) |
|
also take care for the plugins, as they also contain datasets |
|
""" |
|
self.__load_plugins() |
|
self.__dataset = cryptobox.web.dataset.WebInterfaceDataset( |
|
self.cbox, self.cbox.prefs, self.__plugin_manager.get_plugins()) |
|
## publish plugin icons |
|
self.icons = PluginIconHandler(self.__plugin_manager) |
|
self.icons.exposed = True |
|
## check, if a configuration partition has become available |
|
self.cbox.prefs.prepare_partition() |
|
|
|
|
|
def __load_plugins(self): |
|
"""reinitialize the list of available plugins |
|
|
|
this includes the following: |
|
- reload all plugins and check their state (disabled or not) |
|
- reinitilize the datasets of all plugins |
|
""" |
|
self.__plugin_manager = cryptobox.plugins.manage.PluginManager( |
|
self.cbox, self.cbox.prefs["Locations"]["PluginDir"], self) |
|
for plugin in self.__plugin_manager.get_plugins(): |
|
if not plugin: |
|
continue |
|
plname = plugin.get_name() |
|
## check if there are name conflicts: e.g. a local variable has the |
|
## same name as a plugin to be loaded -> skip these plugins |
|
## if we do not check this here, nasty side effects may occour ... |
|
try: |
|
prev_obj = getattr(self, plname) |
|
if not callable(prev_obj) \ |
|
or not prev_obj.exposed: |
|
self.cbox.log.error("Skipped feature (%s) as its name" |
|
+ " conflicts with a local variable - see" |
|
+ " module cryptobox.web.sites" % plname) |
|
## skip this plugin |
|
continue |
|
except (NameError, AttributeError): |
|
## an attribute with the same name does not exist -> ok |
|
if plugin.is_enabled(): |
|
self.cbox.log.info("Plugin '%s' loaded" % plname) |
|
## expose all features as URLs |
|
setattr(self, plname, self.return_plugin_action(plugin)) |
|
getattr(self, plname).exposed = True |
|
#TODO: check, if this really works |
|
#for now the "stream_response" feature seems to be broken |
|
#setattr(getattr(self, plname), "stream_respones", True) |
|
else: |
|
self.cbox.log.info("Plugin '%s' is disabled" % plname) |
|
## remove the plugin, if it was active before |
|
setattr(self, plname, None) |
|
|
|
|
|
## sub pages requiring authentication may not be defined above |
|
def __request_auth(self=None): |
|
""" this is a function decorator to check authentication |
|
""" |
|
def check_credentials(site): |
|
""" see description of _inner_wrapper - please simplify this! |
|
""" |
|
def _inner_wrapper(self, *args, **kargs): |
|
"""this function was necessary while trying around with the |
|
function decorator - if someone can implement the decorator |
|
with less effort, then any suggestions are welcome! |
|
""" |
|
import base64 |
|
## define a "non-allowed" function |
|
user, password = None, None |
|
try: |
|
## ignore the "Basic " (first six letters) part |
|
resp = cherrypy.request.headers["Authorization"][6:] |
|
(user, password) = base64.b64decode(resp).split(":", 1) |
|
except KeyError: |
|
## no "authorization" header was sent |
|
pass |
|
except TypeError: |
|
## invalid base64 string |
|
pass |
|
except AttributeError: |
|
## no cherrypy response header defined |
|
pass |
|
auth_dict = self.cbox.prefs.user_db["admins"] |
|
if user in auth_dict.keys(): |
|
if self.cbox.prefs.user_db.get_digest(password) == auth_dict[user]: |
|
## ok: return the choosen page |
|
self.cbox.log.info("access granted for: %s" % user) |
|
return site(self, *args, **kargs) |
|
else: |
|
self.cbox.log.info( |
|
"wrong password supplied for: %s" % user) |
|
else: |
|
self.cbox.log.info("unknown user: %s" % str(user)) |
|
## wrong credentials: return "access denied" |
|
cherrypy.response.headers["WWW-Authenticate"] = \ |
|
'''Basic realm="CryptoBox"''' |
|
cherrypy.response.status = 401 |
|
return self.__render("access_denied") |
|
return _inner_wrapper |
|
return check_credentials |
|
|
|
|
|
###################################################################### |
|
## put real sites down here and don't forget to expose them at the end |
|
|
|
|
|
@cherrypy.expose |
|
def index(self, weblang="", help="0", device=None): |
|
"""the default page on startup - we show the list of available disks |
|
""" |
|
self.__reset_dataset() |
|
self.__set_web_lang(weblang) |
|
self.__check_environment() |
|
## do not forget the language! |
|
param_dict = {"weblang":weblang} |
|
## render "disks" plugin by default |
|
return self.return_plugin_action( |
|
self.__plugin_manager.get_plugin("disks"))(**param_dict) |
|
|
|
def new_http_error_handler(self, error_code, message): |
|
"""handle http errors gracefully |
|
|
|
404 - not found errors: ignored if url is below /cryptobox-misc/ |
|
other 404 errors: send the error code and return a nice informative page |
|
500 - runtime errors: return "ok" exit code and show a polite excuse |
|
others: are there any other possible http errors? |
|
""" |
|
import traceback |
|
## we ignore uninteresting not-found errors |
|
if (error_code == 404) and \ |
|
(cherrypy.request.path.startswith("/cryptobox-misc/") or \ |
|
cherrypy.request.path in ['/robots.txt','/favicon.ico']): |
|
cherrypy.response.status = error_code |
|
return |
|
## an invalid action was requested |
|
if error_code == 404: |
|
## we send a not-found error (with the usual interface) |
|
cherrypy.response.status = error_code |
|
self.__dataset["Data.Warning"] = "InvalidAction" |
|
cherrypy.response.body = self.__render("empty") |
|
return |
|
## are there still bugs in the code? |
|
if error_code == 500: |
|
## we fix the error code (200 is "OK") |
|
cherrypy.response.status = 200 |
|
self.cbox.log.error( |
|
"HTTP-ERROR[500] - runtime error: %s" % str(message)) |
|
## add a traceback and exception information to the lo |
|
for log_line in traceback.format_exception(*sys.exc_info()): |
|
self.cbox.log.error("\t%s" % log_line) |
|
self.__dataset["Data.Warning"] = "RuntimeError" |
|
cherrypy.response.body = self.__render("empty") |
|
return |
|
## unknown error type |
|
cherrypy.response.status = error_code |
|
self.cbox.log.warn("HTTP-ERROR[%d] - an unknown error occoured: %s" \ |
|
% (error_code, message)) |
|
cherrypy.response.body = self.__render("empty") |
|
|
|
|
|
def return_plugin_action(self, plugin): |
|
""" returns a function that is suitable for handling a cherrypy |
|
page request |
|
""" |
|
def handler(self, weblang="", device=None, help="0", redirect=None, message_keep=None, **args): |
|
"""this function handles a cherrypy page request |
|
""" |
|
plugin.reset() |
|
self.__reset_dataset() |
|
self.__check_environment() |
|
self.__set_web_lang(weblang) |
|
## we always read the "device" setting - otherwise volume-plugin |
|
## links would not work easily |
|
## (see "volume_props" linking to "volume_format_fs") |
|
## it will get ignored for non-volume plugins |
|
plugin.device = None |
|
if device and self.__set_device(device): |
|
plugin.device = device |
|
## check the device argument of volume plugins |
|
if "volume" in plugin.plugin_capabilities: |
|
## initialize the dataset of the selected device if necessary |
|
if plugin.device: |
|
self.__dataset.set_current_disk_state(plugin.device) |
|
else: |
|
## invalid (or missing) device setting |
|
return self.__render(self.defaultTemplate) |
|
## check if there is a "redirect" setting - this will override |
|
## the return value of the do_action function |
|
## (e.g. useful for umount-before-format) |
|
override_next_template = None |
|
if redirect: |
|
override_next_template = { "plugin": redirect } |
|
if "volume" in plugin.plugin_capabilities: |
|
override_next_template["values"] = {"device":plugin.device} |
|
## check for information to be kept after the last call |
|
if message_keep: |
|
for (key, value) in message_keep["dataset"].items(): |
|
self.__dataset[key] = value |
|
## check if the device is busy |
|
if plugin.device and self.cbox.get_container(plugin.device).is_busy(): |
|
return self.__render("volume_busy") |
|
## call the plugin handler |
|
next_template = plugin.do_action(**args) |
|
## for 'volume' plugins: reread the dataset of the current disk |
|
## additionally: set the default template for plugins |
|
if "volume" in plugin.plugin_capabilities: |
|
## maybe the state of the current volume was changed? |
|
self.__dataset.set_current_disk_state(plugin.device) |
|
if not next_template: |
|
next_template = { "plugin":"volume_mount", |
|
"values":{"device":plugin.device}} |
|
else: |
|
## some non-volume plugins change the internal state of other |
|
## plugins - e.g.: plugin_manager |
|
self.__dataset.set_plugin_data() |
|
## default page for non-volume plugins is the disk selection |
|
if not next_template: |
|
next_template = { "plugin":"disks", "values":{} } |
|
## some non-volume plugins may change the state of containers |
|
## the mount plugin may change the number of active disks - for the logo |
|
self.__dataset.set_containers_state() |
|
## was a redirect requested? |
|
if override_next_template: |
|
next_template = override_next_template |
|
## if another plugins was choosen for 'next_template', then do it! |
|
if isinstance(next_template, dict) \ |
|
and "plugin" in next_template.keys() \ |
|
and "values" in next_template.keys() \ |
|
and self.__plugin_manager.get_plugin(next_template["plugin"]): |
|
value_dict = dict(next_template["values"]) |
|
## force the current weblang attribute - otherwise it gets lost |
|
value_dict["weblang"] = self.lang_order[0] |
|
## check for warnings/success messages, that should be kept |
|
if "Data.Success" in plugin.hdf.keys() \ |
|
or "Data.Warning" in plugin.hdf.keys(): |
|
value_dict["message_keep"] = {"plugin":plugin, "dataset":{}} |
|
for keep_key in ("Data.Warning", "Data.Success"): |
|
if keep_key in plugin.hdf.keys(): |
|
self.cbox.log.info("keeping message: %s" % \ |
|
plugin.hdf[keep_key]) |
|
value_dict["message_keep"]["dataset"][keep_key] = \ |
|
plugin.hdf[keep_key] |
|
new_plugin = self.__plugin_manager.get_plugin(next_template["plugin"]) |
|
return self.return_plugin_action(new_plugin)(**value_dict) |
|
## save the currently active plugin name |
|
self.__dataset["Data.ActivePlugin"] = plugin.get_name() |
|
return self.__render(next_template, plugin) |
|
## apply authentication? |
|
if plugin.is_auth_required(): |
|
return lambda **args: self.__request_auth()(handler)(self, **args) |
|
else: |
|
return lambda **args: handler(self, **args) |
|
|
|
|
|
@cherrypy.expose |
|
def test(self, weblang="", help="0", device=None): |
|
"""test authentication - this function may be safely removed |
|
""" |
|
self.__reset_dataset() |
|
self.__set_web_lang(weblang) |
|
self.__check_environment() |
|
return "test passed" |
|
|
|
|
|
@cherrypy.expose |
|
def test_stream(self, weblang="", help="0", device=None): |
|
"""just for testing purposes - to check if the "stream_response" feature |
|
actually works - for now (September 02006) it does not seem to be ok |
|
""" |
|
import time |
|
yield "<html><head><title>neu</title></head><body><p><ul>" |
|
for num in range(10): |
|
yield "<li>yes: %d - %s</li>" % (num, str(time.time())) |
|
time.sleep(1) |
|
yield "</ul></p></html>" |
|
|
|
|
|
|
|
##################### input checker ########################## |
|
|
|
def __check_environment(self): |
|
"""inform the user of suspicious environmental problems |
|
|
|
examples are: non-https, readonly-config, ... |
|
""" |
|
## this check is done _after_ "reset_dataset" -> if there is |
|
## a config partition, then it was loaded before |
|
if self.cbox.prefs.requires_partition() \ |
|
and not self.cbox.prefs.get_active_partition(): |
|
self.__dataset["Data.EnvironmentWarning"] = "ReadOnlyConfig" |
|
#TODO: turn this on soon (add "not") - for now it is annoying |
|
if not self.__check_https(): |
|
self.__dataset["Data.EnvironmentWarning"] = "NoSSL" |
|
|
|
|
|
def __check_https(self): |
|
"""check the request scheme |
|
""" |
|
if cherrypy.request.scheme == "https": |
|
return True |
|
## check an environment setting - this is quite common behind proxies |
|
if os.environ.has_key("HTTPS"): |
|
return True |
|
## this arbitrarily chosen header must be documented in README.proxy |
|
#TODO: check http://jamesthornton.com/writing/openacs-pound.html for this |
|
if cherrypy.request.headers.has_key("X-SSL-Request") \ |
|
and (cherrypy.request.headers["X-SSL-Request"] == "1"): |
|
return True |
|
## plaintext connection |
|
return False |
|
|
|
|
|
def __set_web_lang(self, value): |
|
"""set the preferred priority of languages according to this order: |
|
1. language selected via web interface |
|
2. preferred browser language setting |
|
3. languages defined in the config file |
|
""" |
|
## start with the configured language order |
|
lang_order = self.cbox.prefs["WebSettings"]["Languages"][:] |
|
self.cbox.log.debug( |
|
"updating language preferences (default: %s)" % str(lang_order)) |
|
## put the preferred browser language in front |
|
guess = self.__get_browser_language(lang_order) |
|
if guess: |
|
lang_order.remove(guess) |
|
lang_order.insert(0, guess) |
|
self.cbox.log.debug( |
|
"raised priority of preferred browser language: %s" % guess) |
|
## is the chosen language (via web interface) valid? - put it in front |
|
if value and (value in lang_order) and (not re.search(r'\W', value)): |
|
lang_order.remove(value) |
|
lang_order.insert(0, value) |
|
self.cbox.log.debug( |
|
"raised priority of selected language: %s" % value) |
|
elif value: |
|
self.cbox.log.info("invalid language selected: %s" % value) |
|
## store current language setting |
|
self.cbox.log.debug( |
|
"current language preference: %s" % str(lang_order)) |
|
self.lang_order = lang_order |
|
self.__dataset["Settings.Language"] = lang_order[0] |
|
self.__dataset["Settings.LinkAttrs.weblang"] = lang_order[0] |
|
|
|
|
|
def __get_browser_language(self, avail_langs): |
|
"""guess the preferred language of the user (as sent by the browser) |
|
take the first language, that is part of 'avail_langs' |
|
""" |
|
try: |
|
pref_lang_header = cherrypy.request.headers["Accept-Language"] |
|
except KeyError: |
|
## no language header was specified |
|
return None |
|
## this could be a typical 'Accept-Language' header: |
|
## de-de,de;q=0.8,en-us;q=0.5,en;q=0.3 |
|
regex = re.compile(r"\w+(-\w+)?(;q=[\d\.]+)?$") |
|
pref_langs = [e.split(";", 1)[0] |
|
for e in pref_lang_header.split(",") |
|
if regex.match(e)] |
|
## is one of these preferred languages available? |
|
for lang in pref_langs: |
|
if lang in avail_langs: |
|
return lang |
|
## we try to be nice: also look for "de" if "de-de" was specified ... |
|
for lang in pref_langs: |
|
## use only the first part of the language |
|
short_lang = lang.split("-", 1)[0] |
|
if short_lang in avail_langs: |
|
return short_lang |
|
## we give up |
|
return None |
|
|
|
|
|
def __set_device(self, device): |
|
"""check a device name that was chosen via the web interface |
|
issue a warning if the device is invalid""" |
|
if device and re.match(r'[\w /\-]+$', device) \ |
|
and self.cbox.get_container(device): |
|
self.cbox.log.debug("select device: %s" % device) |
|
return True |
|
else: |
|
self.cbox.log.warn("invalid device: %s" % device) |
|
self.__dataset["Data.Warning"] = "InvalidDevice" |
|
return False |
|
|
|
|
|
def __substitute_gettext(self, languages, text_domain, hdf): |
|
"""substitute all texts in the hdf dataset with their translated |
|
counterparts as returned by gettext |
|
""" |
|
import gettext |
|
try: |
|
translator = gettext.translation(text_domain, languages=languages) |
|
except IOError, err_msg: |
|
## no translation found |
|
self.cbox.log.warn("unable to load language file: %s" % err_msg) |
|
return hdf |
|
def walk_tree(parent_name, hdf_node): |
|
"""iterate through all nodes""" |
|
def translate_node(node): |
|
"""turn one single string into unicode""" |
|
if not node.value(): |
|
return |
|
for (key, value) in node.attrs(): |
|
## ignore all nodes with the 'LINK' attribute |
|
## for now clearsilver is buggy regarding attributes |
|
## buggy -> parsing of a hdf file fails silently |
|
if key == "LINK": |
|
return |
|
## as long as the attributes do not work, we have to rely on |
|
## some magic names to ignore translations |
|
if (parent_name == "Link") and \ |
|
(node.name() in ["Rel", "Prot", "Abs"]): |
|
return |
|
try: |
|
#TODO: we should use unicode - or not? - turn it on later |
|
#node.setValue("", translator.ugettext(node.value())) |
|
## quite obscure: ugettext can handle None - gettext breaks instead |
|
node.setValue("", translator.gettext(node.value())) |
|
except UnicodeEncodeError, err_msg: |
|
self.cbox.log.info( |
|
"Failed unicode encoding for gettext: %s - %s" \ |
|
% (node.value(),err_msg)) |
|
## fallback to default encoding |
|
node.setValue("", translator.gettext(node.value())) |
|
while hdf_node: |
|
translate_node(hdf_node) |
|
walk_tree(hdf_node.name(), hdf_node.child()) |
|
hdf_node = hdf_node.next() |
|
walk_tree("", hdf) |
|
|
|
|
|
def __get_language_data(self): |
|
"""return the hdf dataset of the main interface and all plugins |
|
translations are done according to self.lang_order |
|
""" |
|
## check if the language setting has changed - use cache if possible |
|
if self.__cached_language_data and \ |
|
self.__cached_language_data["lang_order"] == self.lang_order: |
|
self.cbox.log.debug( |
|
"using cached language data: %s" % str(self.lang_order)) |
|
return self.__cached_language_data["hdf"] |
|
self.cbox.log.debug("generating language data") |
|
hdf = neo_util.HDF() |
|
hdf.readFile(os.path.join( |
|
self.cbox.prefs["Locations"]["TemplateDir"],"language.hdf")) |
|
self.__substitute_gettext(self.lang_order, GETTEXT_DOMAIN, hdf) |
|
## load the language data of all plugins |
|
for plugin in self.__plugin_manager.get_plugins(): |
|
pl_lang = plugin.get_language_data() |
|
self.__substitute_gettext(self.lang_order, "%s-feature-%s" % \ |
|
(GETTEXT_DOMAIN, plugin.get_name()), pl_lang) |
|
hdf.copy("Plugins.%s" % plugin.get_name(), pl_lang) |
|
self.cbox.log.debug( |
|
"language data for plugin loaded: %s" % plugin.get_name()) |
|
## cache result for later retrieval |
|
self.__cached_language_data = \ |
|
{"lang_order": self.lang_order, "hdf": hdf} |
|
return hdf |
|
|
|
|
|
def __render(self, render_info, plugin=None): |
|
'''renders from clearsilver templates and returns the resulting html |
|
''' |
|
## is render_info a string (filename of the template) or a dictionary? |
|
if isinstance(render_info, dict): |
|
template = render_info["template"] |
|
if render_info.has_key("generator"): |
|
generator = render_info["generator"] |
|
else: |
|
generator = None |
|
else: |
|
(template, generator) = (render_info, None) |
|
|
|
## load the language data |
|
hdf = neo_util.HDF() |
|
hdf.copy("Lang", self.__get_language_data()) |
|
|
|
## first: assume, that the template file is in the global |
|
## template directory |
|
self.__dataset["Settings.TemplateFile"] = os.path.abspath(os.path.join( |
|
self.cbox.prefs["Locations"]["TemplateDir"], |
|
template + ".cs")) |
|
|
|
if plugin: |
|
## check, if the plugin provides the template file -> overriding |
|
plugin_cs_file = plugin.get_template_filename(template) |
|
if plugin_cs_file: |
|
self.__dataset["Settings.TemplateFile"] = plugin_cs_file |
|
|
|
## add the current state of the plugins to the hdf dataset |
|
self.__dataset["Data.Status.Plugins.%s" % plugin.get_name()] = \ |
|
plugin.get_status() |
|
## load the dataset of the plugin |
|
plugin.load_dataset(hdf) |
|
|
|
self.cbox.log.info("rendering site: " + template) |
|
|
|
cs_path = os.path.abspath(os.path.join( |
|
self.cbox.prefs["Locations"]["TemplateDir"], "main.cs")) |
|
if not os.access(cs_path, os.R_OK): |
|
self.cbox.log.error( |
|
"Couldn't read clearsilver file: %s" % cs_path) |
|
yield "Couldn't read clearsilver file: %s" % cs_path |
|
return |
|
|
|
self.cbox.log.debug(self.__dataset) |
|
for key in self.__dataset.keys(): |
|
hdf.setValue(key, str(self.__dataset[key])) |
|
cs_data = neo_cs.CS(hdf) |
|
cs_data.parseFile(cs_path) |
|
|
|
## is there a generator containing additional information? |
|
if not generator: |
|
## all content in one flush |
|
yield cs_data.render() |
|
else: |
|
content_generate = generator() |
|
dummy_line = """<!-- CONTENT_DUMMY -->""" |
|
## now we do it linewise - checking for the content marker |
|
for line in cs_data.render().splitlines(): |
|
if line.find(dummy_line) != -1: |
|
yield line.replace(dummy_line, content_generate.next()) |
|
else: |
|
yield line + "\n" |
|
|
|
|