import os, os.path import re import abc import logging import yaml log = logging.getLogger(__name__) class Item(object): __metaclass__ = abc.ABCMeta @abc.abstractproperty def name(self): pass def isuptodate(self, oldmeta, catalog): return False def render(self, catalog): """Render the item, return re-generated metadata.""" pass class Text(Item): def __init__(self, source, dest, filters, default_meta={}): self.source = source self.dest = dest self.filters = filters self.default_meta = default_meta def __repr__(self): return '<{0}({1}, {2}, ...)>'.format( self.__class__.__name__, self.source, self.dest) @property def name(self): return self.dest def render(self, catalog): meta = self.default_meta.copy() # Initialize data and metadata from source file. with open(self.source) as f: data = f.read().decode('utf-8') if data.startswith('---\n'): meta_yaml, data = data.split('\n---\n', 1) meta.update(yaml.safe_load(meta_yaml[4:])) # Appply filter. try: filter_name = meta['__filter__'] except KeyError: log.error('{0}: No __filter__ defined.'.format(self.source)) else: try: filter = self.filters[filter_name] except KeyError: log.error('{0}: No filter {0}.'.format(filter_name)) else: data, meta = filter(data, meta, self.dest, catalog) # Write filtered data to destination file. with open(self.dest, 'w') as f: f.write(data.encode('utf-8')) return meta def build(items, old_catalog={}): catalog = {} for item in items: name = item.name meta = old_catalog.get(name) if meta is None or not item.isuptodate(meta, catalog): meta = item.render(catalog) if name in catalog: log.warning('{0} already present.'.format(name)) catalog[name] = meta return catalog def chain_filters(*filters): def chained(data, meta, name, catalog): for filter in filters: data, meta = filter(data, meta, name, catalog) return data, meta return chained def expand_rules(rules): rules = [(rexp if hasattr(rexp, 'match') else re.compile(rexp), repl, priority, func) for rexp, repl, priority, func in rules] priority_item_seq = [] for root, dirs, files in os.walk('.'): for file in files: # For security, do not follow symlinks. source = os.path.join(root, file) if os.path.islink(os.path.join(root, file)): log.warn('Ignoring symlink: {0}'.format(source)) continue for rexp, repl, priority, func in rules: if not rexp.match(source): continue dest = rexp.sub(repl, source) priority_item_seq.append((priority, func(source, dest))) priority_item_seq.sort(key=lambda p_i: p_i[0]) return [i for p, i in priority_item_seq]