From 1e7f49b4087847d4497cf1c2b9580b29596245c7 Mon Sep 17 00:00:00 2001 From: Fabio Erculiani Date: Thu, 20 May 2010 15:42:10 +0200 Subject: [PATCH] [molecule] add preliminary support for template files preprocessor Implement "%import /path" preprocessor statement support inside .spec files --- .../specs/5-x86-g-remaster-add-games.spec | 43 +----- molecule/settings.py | 127 +++++++++++++++--- 2 files changed, 112 insertions(+), 58 deletions(-) diff --git a/examples/specs/5-x86-g-remaster-add-games.spec b/examples/specs/5-x86-g-remaster-add-games.spec index 5623f42..59d9880 100644 --- a/examples/specs/5-x86-g-remaster-add-games.spec +++ b/examples/specs/5-x86-g-remaster-add-games.spec @@ -1,45 +1,4 @@ -# Sabayon Linux 5 x86 GNOME Molecule remaster spec file -# The aim of this spec file is to add arbitrary applications & misc stuff -# to an already built ISO image via scripting (providing hooks that call -# user-defined scripts). -# squashfs, mkisofs needed - -# Define an alternative execution strategy, in this case, the value must be -# "iso_remaster" -execution_strategy: iso_remaster - -# pre chroot command, example, for 32bit chroots on 64bit system, you always -# have to append "linux32" this is useful for inner_chroot_script -prechroot: linux32 - -# Path to source ISO file (MANDATORY) -source_iso: /sabayon/iso_images/Sabayon_5.0_G.iso - -# Error script command, executed when something went wrong and molecule has to terminate the execution -# environment variables exported: -# - CHROOT_DIR: path to chroot directory, if any -# - CDROOT_DIR: path to livecd root directory, if any -# - SOURCE_CHROOT_DIR: path from where chroot is copied for final handling -# error_script: /path/to/script/to/be/executed/outside/after - -# Outer chroot script command, to be executed outside destination chroot before -# before entering it (and before inner_chroot_script) -# outer_chroot_script: /path/to/script/to/be/executed/outside - -# Inner chroot script command, to be executed inside destination chroot before packing it -# - kmerge.sh - setup kernel bins -# inner_chroot_script: /sabayon/scripts/inner_chroot_script.sh - -# Inner chroot script command, to be executed inside destination chroot after -# packages installation and removal -# inner_chroot_script_after: /sabayon/scripts/inner_chroot_script_after.sh - -# Outer chroot script command, to be executed outside destination chroot before -# before entering it (and AFTER inner_chroot_script) -# outer_chroot_script_after: /path/to/script/to/be/executed/outside/after - -# Extra mkisofs parameters, perhaps something to include/use your bootloader -extra_mkisofs_parameters: -b isolinux/isolinux.bin -c isolinux/boot.cat +%import half.inc # Pre-ISO building script. Hook to be able to copy kernel images in place, for example # pre_iso_script: /sabayon/scripts/cdroot.py diff --git a/molecule/settings.py b/molecule/settings.py index de20b36..dcf110a 100644 --- a/molecule/settings.py +++ b/molecule/settings.py @@ -77,12 +77,110 @@ class Configuration(dict): molecule.utils.valid_exec_check(self.get(key)) +class SpecPreprocessor: + + PREFIX = "%" + class PreprocessorError(Exception): + """ Error while preprocessing file """ + + def __init__(self, spec_path): + self.__expanders = {} + self.__builtin_expanders = {} + self._spec_path = spec_path + self._add_builtin_expanders() + + def add_expander(self, statement, expander_callback): + """ + Add Preprocessor expander. + + @param statement: statement to expand + @type statement: string + @param expand_callback: one argument callback that is used to expand + given line (line is raw format). Line is already pre-parsed and + contains a valid preprocessor statement that callback can handle. + Preprocessor callback should raise SpecPreprocessor.PreprocessorError + if line is malformed. + @type expander_callback: callable + @raise KeyError: if expander is already available + @return: a raw string (containing \n and whatever) + @rtype: string + """ + return self._add_expander(statement, expander_callback, builtin = False) + + def _add_expander(self, statement, expander_callback, builtin = False): + obj = self.__expanders + if builtin: + obj = self.__builtin_expanders + if statement in obj: + raise KeyError("expander %s already provided" % (statement,)) + obj[SpecPreprocessor.PREFIX + statement] = \ + expander_callback + + def _add_builtin_expanders(self): + # import statement + self._add_expander("import", self._import_expander, builtin = True) + + def _import_expander(self, line): + + rest_line = line.split(" ", 1)[1].strip() + if not rest_line: + return line + + if rest_line.startswith(os.path.sep): + # absolute path + path = rest_line + else: + path = os.path.join(os.path.dirname(self._spec_path), + rest_line) + if not (os.path.isfile(path) and os.access(path, os.R_OK)): + raise SpecPreprocessor.PreprocessorError( + "invalid preprocessor line: %s" % (line,)) + + with open(path, "r") as spec_f: + lines = '' + for line in spec_f.xreadlines(): + # call recursively + split_line = line.split(" ", 1) + if split_line: + expander = self.__builtin_expanders.get(split_line[0]) + if expander is not None: + line = expander(line) + lines += line + + return lines + + def parse(self): + + content = [] + with open(self._spec_path, "r") as spec_f: + for line in spec_f.xreadlines(): + split_line = line.split(" ", 1) + if split_line: + expander = self.__builtin_expanders.get(split_line[0]) + if expander is not None: + line = expander(line) + content.append(line) + + final_content = [] + for line in content: + split_line = line.split(" ", 1) + if split_line: + expander = self.__expanders.get(split_line[0]) + if expander is not None: + line = expander(line) + final_content.append(line) + + final_content = (''.join(final_content)).split("\n") + + return final_content + class SpecParser: def __init__(self, filepath): self.filepath = filepath[:] + self._preprocessor = SpecPreprocessor(self.filepath) # FIXME: kept for backward .spec files compatibility where # execution_strategy argument is not set @@ -95,7 +193,6 @@ class SpecParser: from molecule.specs.factory import PluginFactory spec_plugins = PluginFactory.get_spec_plugins() - print spec_plugins plugin = spec_plugins.get(execution_strategy) if plugin is None: @@ -109,7 +206,7 @@ class SpecParser: def parse_execution_strategy(self): - data = self.__generic_parser(self.filepath) + data = self._generic_parser() exc_str = GenericSpec.EXECUTION_STRATEGY_KEY for line in data: if not line.startswith(exc_str): @@ -132,7 +229,7 @@ class SpecParser: def parse(self): mydict = {} - data = self.__generic_parser(self.filepath) + data = self._generic_parser() # compact lines properly old_key = None for line in data: @@ -175,16 +272,14 @@ class SpecParser: " file is incomplete!" % (self.filepath,param,) ) - def __generic_parser(self, filepath): - with open(filepath,"r") as f: - data = [] - content = f.readlines() - f.close() - # filter comments and white lines - content = [x.strip().rsplit("#", 1)[0].strip() for x in content if \ - not x.startswith("#") and x.strip()] - for line in content: - if line in data: - continue - data.append(line) - return data + def _generic_parser(self): + data = [] + content = self._preprocessor.parse() + # filter comments and white lines + content = [x.strip().rsplit("#", 1)[0].strip() for x in content if \ + not x.startswith("#") and x.strip()] + for line in content: + if line in data: + continue + data.append(line) + return data