def _unquote_tag(tag): if tag[0] == '"' and tag[-1] == '"': tag = tag[1:-1] if tag[0] == "'" and tag[-1] == "'": tag = tag[1:-1] if not tag: raise error.ApacheConfigError('Empty block tag not allowed') return tag
def __init__(self, raw, parser): if len(raw) < 4: raise error.ApacheConfigError( "Expected properly-formatted data returned from " "``apacheconfig.parser``. Got a list that is too short.") self._whitespace = "" self._type = raw[0] if self._type != "block": raise error.ApacheConfigError( "Expected properly-formatted data returned from " "``apacheconfig.parser``. First element of data is not " "\"block\" typestring.") start = 1 if isinstance(raw[start], six.string_types) and raw[start].isspace(): self._whitespace = raw[start] start += 1 self._full_tag = LeafNode(('statement', ) + raw[start]) self._close_tag = raw[-1] self._contents = None if raw[start + 1]: # If we have a list of elements to process. super(BlockNode, self).__init__(raw[start + 1], parser) self._type = raw[0]
def _walkast(self, ast): if not ast: return node_type = ast[0] try: handler = getattr(self, 'g_' + node_type) except AttributeError: raise error.ApacheConfigError('Unsupported AST node type %s' % node_type) return handler(ast[1:])
def _dumpdict(self, obj, indent=0, continue_tag=False): if not isinstance(obj, dict): raise error.ApacheConfigError('Unknown object type "%r" to dump' % obj) text = '' spacing = ' ' * indent for key, val in obj.items(): if isinstance(val, six.text_type): if val.isalnum(): text += '%s%s %s\n' % (spacing, key, val) else: text += '%s%s "%s"\n' % (spacing, key, val) elif isinstance(val, list): for dup in val: if isinstance(dup, six.text_type): if dup.isalnum(): text += '%s%s %s\n' % (spacing, key, dup) else: text += '%s%s "%s"\n' % (spacing, key, dup) else: if self._options.get('namedblocks', True): text += ('%s<%s>\n%s%s</%s>\n' % (spacing, key, self._dumpdict(dup, indent + 2), spacing, key)) else: text += ('%s<%s %s%s</%s>\n' % (spacing, key, self._dumpdict(dup, indent + 2, continue_tag=True), spacing, key)) else: if self._options.get('namedblocks', True): text += ('%s<%s>\n%s%s</%s>\n' % (spacing, key, self._dumpdict(val, indent + 2), spacing, key)) else: if continue_tag: text += ('%s>\n%s' % (key, self._dumpdict(val, indent + 2))) else: text += ('%s<%s %s%s</%s>\n' % (spacing, key, self._dumpdict(val, indent + 2, continue_tag=True), spacing, key)) return text
def lookup(match): option = match.groups()[0] for frame in self._stack: if option in frame: return interpolate(frame[option]) if self._options.get('interpolateenv', False): if option in self._reader.environ: return interpolate(self._reader.environ[option]) if self._options.get('strictvars', True): raise error.ApacheConfigError( 'Undefined variable "${%s}" referenced' % option) return interpolate(match.string)
def dump(self, filepath, dct): tmpf = tempfile.NamedTemporaryFile(dir=os.path.dirname(filepath), delete=False) try: with open(tmpf.name, 'w') as f: f.write(self.dumps(dct)) os.rename(tmpf.name, filepath) except IOError as ex: try: os.unlink(tmpf.name) except Exception: pass raise error.ApacheConfigError('File %s can\'t be written: %s' % (filepath, ex))
def add(self, index, raw_str): """Parses and adds child element at given index. Parses given string into an :class:`apacheconfig.AbstractASTNode` object, then adds to list at specified index. Args: index (int): index of list at which to insert the node. raw_str (str): string to parse. The parser will automatically determine whether it's a :class:`apacheconfig.BlockNode` or :class:`apacheconfig.LeafNode`. Returns: The :class:`apacheconfig.AbstractASTNode` created from parsing ``raw_str``. Raises: ApacheConfigError: If `raw_str` cannot be parsed into a :class:`apacheconfig.BlockNode` or :class:`apacheconfig.LeafNode`. IndexError: If `index` is not within bounds [0, len(self)]. """ if index < 0 or index > len(self): raise IndexError("supplied index is out of range") raw = self._parser.parse(raw_str) if len(raw) != 2: raise error.ApacheConfigError("Given raw_str should be " "parsable into a single node.") raw = self._parser.parse(raw_str)[1] if raw[0] == "block": node = BlockNode(raw, self._parser) else: node = LeafNode(raw) # If we're adding an element to the beginning of list, the first # item may not have a preceding newline-- so we add one in case. # For instance, something like: # Contents("line1\nline2").add(0, "\nline0") # should end up as "\nline0\nline1\nline2" if (index == 0 and self._contents and '\n' not in self._contents[0].whitespace): whitespace_after = self._contents[0].whitespace self._contents[0].whitespace = '\n' + whitespace_after self._contents.insert(index, node) return node
def _merge_dicts(self, dict1, dict2, path=[]): "merges dict2 into dict1" for key in dict2: if key in dict1: if isinstance(dict1[key], dict) and isinstance(dict2[key], dict): self._merge_dicts(dict1[key], dict2[key], path + [str(key)]) elif dict1[key] != dict2[key]: if self._options.get('allowmultioptions', True): if not isinstance(dict1[key], list): dict1[key] = [dict1[key]] if not isinstance(dict2[key], list): dict2[key] = [dict2[key]] dict1[key] = self._merge_lists(dict1[key], dict2[key]) else: if self._options.get('mergeduplicateoptions', False): dict1[key] = dict2[key] else: raise error.ApacheConfigError('Duplicate option "%s" prohibited' % '.'.join(path + [str(key)])) else: dict1[key] = dict2[key] return dict1
def dump(self, filepath, dct): """Dumps the configuration in `dct` to a file. Args: filepath (Text): Filepath to write config to, in UTF-8 encoding. dct (dict): Configuration represented as a dictionary. """ tmpf = tempfile.NamedTemporaryFile(dir=os.path.dirname(filepath), delete=False) try: with io.open(tmpf.name, mode='w', encoding='utf-8') as f: f.write(self.dumps(dct)) os.rename(tmpf.name, filepath) except IOError as ex: if os.path.exists(tmpf.name): os.unlink(tmpf.name) raise error.ApacheConfigError('File %s can\'t be written: %s' % (filepath, ex))
def _merge_item(self, contents, key, value, path=[]): """Merges a single "key, value" item into contents dictionary, and returns new contents. Merging rules differ depending on flags set, and whether `value` is a dictionary (block). """ if key not in contents: contents[key] = value return contents # In case of duplicate keys, AST can contain a list of values. # Here all values forced into being a list to unify further processing. if isinstance(value, list): vector = value else: vector = [value] if isinstance(value, dict): # Merge block into contents if self._options.get('mergeduplicateblocks'): contents = self._merge_dicts(contents[key], value) else: if not isinstance(contents[key], list): contents[key] = [contents[key]] contents[key] += vector else: # Merge statement/option into contents if not self._options.get('allowmultioptions', True) \ and not self._options.get('mergeduplicateoptions', False): raise error.ApacheConfigError( 'Duplicate option "%s" prohibited' % '.'.join(path + [six.text_type(key)])) if self._options.get('mergeduplicateoptions', False): contents[key] = value else: if not isinstance(contents[key], list): contents[key] = [contents[key]] contents[key] += vector return contents
def g_statements(self, ast): statements = {} for subtree in ast: items = self._walkast(subtree) for item in items: if item in statements: if (self._options.get('allowmultioptions', True) and not self._options.get('mergeduplicateoptions', False)): if not isinstance(statements[item], list): statements[item] = [statements[item]] statements[item].append(items[item]) elif self._options.get('mergeduplicateoptions', False): statements[item] = items[item] else: raise error.ApacheConfigError( 'Duplicate option "%s" prohibited' % item) else: statements[item] = items[item] if (self._options.get('interpolateenv', False) or self._options.get('allowsinglequoteinterpolation', False)): self._options['interpolatevars'] = True if self._options.get('interpolatevars', False): def lookup(match): option = match.groups()[0] if option in statements: return interpolate(statements[option]) for frame in self._stack: if option in frame: return interpolate(frame[option]) if self._options.get('interpolateenv', False): if option in self._reader.environ: return interpolate(self._reader.environ[option]) if self._options.get('strictvars', True): raise error.ApacheConfigError( 'Undefined variable "${%s}" referenced' % option) return interpolate(match.string) def interpolate(value): expanded = re.sub(r'(?<!\\)\${([^\n\r]+?)}', lookup, value) if expanded != value: return expanded return re.sub(r'(?<!\\)\$([^\n\r $]+?)', lookup, value) for option, value in tuple(statements.items()): if (not getattr(value, 'is_single_quoted', False) or self._options.get('allowsinglequoteinterpolation', False)): if isinstance(value, list): statements[option] = [interpolate(x) for x in value] else: statements[option] = interpolate(value) def remove_escapes(value): if self._options.get('noescape'): return value if not isinstance(value, str): return value return re.sub(r'\\([$\\"#])', lambda x: x.groups()[0], value) for option, value in tuple(statements.items()): if isinstance(value, list): statements[option] = [remove_escapes(x) for x in value] else: statements[option] = remove_escapes(value) self._stack.insert(0, statements) return statements