From e81d3f9bd7a2fdb52588202cc5c9b21c8eecbb82 Mon Sep 17 00:00:00 2001 From: Ruben Tsirunyan Date: Wed, 6 Jun 2018 17:53:04 +0400 Subject: [PATCH 01/10] Adding some common characters to the components --- parse_apache_configs/parse_config.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/parse_apache_configs/parse_config.py b/parse_apache_configs/parse_config.py index 99452b7..2d11b66 100644 --- a/parse_apache_configs/parse_config.py +++ b/parse_apache_configs/parse_config.py @@ -5,16 +5,16 @@ # a conditional expression. The reason this is done # is so that a tag with the ">" operator in the # arguments will parse correctly. -OPERAND = Word(alphanums + "." + '"' + '/-' + "*:^_![]?$%@)(#=`" + '\\') +OPERAND = Word(alphanums + "." + '"' + '/-' + "*:^_|![]?$%@)(#=`'}{" + '\\') OPERATOR = oneOf(["<=", ">=", "==", "!=", "<", ">", "~"], useRegex=False) -EXPRESSION_TAG = OPERAND + White() + OPERATOR + White() + OPERAND +EXPRESSION_TAG = Word(alphanums) + White() + OPERAND + White() + OPERATOR + White() + OPERAND # LITERAL_TAG will match tags that do not have # a conditional expression. So any other tag # with arguments that don't contain OPERATORs LITERAL_TAG = OneOrMore(Word( alphanums + '*:' + '/' + '"-' + '.' + " " + "^" + "_" + "!" + "[]?$" - + "'" + '\\' + + "'" + '\\' + "*:^_|![]?$%@)(#=`'}{" )) # Will match the start of any tag TAG_START_GRAMMAR = Group(Literal("<") + (EXPRESSION_TAG | LITERAL_TAG) From 155f152f936d0fef5544f8df763f2cffad2f0769 Mon Sep 17 00:00:00 2001 From: Ruben Tsirunyan Date: Thu, 7 Jun 2018 14:23:36 +0400 Subject: [PATCH 02/10] Adding some new characters to be parsed --- parse_apache_configs/parse_config.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/parse_apache_configs/parse_config.py b/parse_apache_configs/parse_config.py index 2d11b66..3d922ce 100644 --- a/parse_apache_configs/parse_config.py +++ b/parse_apache_configs/parse_config.py @@ -5,7 +5,7 @@ # a conditional expression. The reason this is done # is so that a tag with the ">" operator in the # arguments will parse correctly. -OPERAND = Word(alphanums + "." + '"' + '/-' + "*:^_|![]?$%@)(#=`'}{" + '\\') +OPERAND = Word(alphanums + "." + '"' + '/-' + "*:^_|![]?$%@)(#=`'}{&+~" + '\\') OPERATOR = oneOf(["<=", ">=", "==", "!=", "<", ">", "~"], useRegex=False) EXPRESSION_TAG = Word(alphanums) + White() + OPERAND + White() + OPERATOR + White() + OPERAND @@ -14,7 +14,7 @@ # with arguments that don't contain OPERATORs LITERAL_TAG = OneOrMore(Word( alphanums + '*:' + '/' + '"-' + '.' + " " + "^" + "_" + "!" + "[]?$" - + "'" + '\\' + "*:^_|![]?$%@)(#=`'}{" + + "'" + '\\' + "*:^_|![]?$%@)(#=`'}{&+~" )) # Will match the start of any tag TAG_START_GRAMMAR = Group(Literal("<") + (EXPRESSION_TAG | LITERAL_TAG) From 9f0e3fa0326d9b90a004e31cda68d0a386eef2db Mon Sep 17 00:00:00 2001 From: Rick Hornsby Date: Sat, 16 Jun 2018 19:20:09 -0500 Subject: [PATCH 03/10] Allow for directives like "php_value" --- parse_apache_configs/parse_config.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/parse_apache_configs/parse_config.py b/parse_apache_configs/parse_config.py index 3d922ce..6dd7f50 100644 --- a/parse_apache_configs/parse_config.py +++ b/parse_apache_configs/parse_config.py @@ -27,10 +27,9 @@ # Will match any directive. We are performing # a simple parse by matching the directive on # the left, and everything else on the right. -ANY_DIRECTIVE = Group(Word(alphanums) + Suppress(White()) +ANY_DIRECTIVE = Group(Word(alphanums, alphanums+'_-@.') + Suppress(White()) + Word(printables + " ") + LineEnd()) - COMMENT = Group( (Literal("#") + LineEnd()) ^ (Literal("#") From 8d61321bfc846b11cb07b542ca3ea035f988527a Mon Sep 17 00:00:00 2001 From: Rick Hornsby Date: Sun, 17 Jun 2018 16:02:38 -0500 Subject: [PATCH 04/10] Simplify call to `Word` constructor. --- parse_apache_configs/parse_config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/parse_apache_configs/parse_config.py b/parse_apache_configs/parse_config.py index 6dd7f50..b6adfd9 100644 --- a/parse_apache_configs/parse_config.py +++ b/parse_apache_configs/parse_config.py @@ -27,7 +27,7 @@ # Will match any directive. We are performing # a simple parse by matching the directive on # the left, and everything else on the right. -ANY_DIRECTIVE = Group(Word(alphanums, alphanums+'_-@.') + Suppress(White()) +ANY_DIRECTIVE = Group(Word(alphanums+'_-@.') + Suppress(White()) + Word(printables + " ") + LineEnd()) COMMENT = Group( From 9e4e95e360b05a0277f2037954e8fcc191631f67 Mon Sep 17 00:00:00 2001 From: daladim Date: Thu, 30 Apr 2020 00:00:46 +0200 Subject: [PATCH 05/10] Parsed objects inherit from Node --- parse_apache_configs/parse_config.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/parse_apache_configs/parse_config.py b/parse_apache_configs/parse_config.py index b6adfd9..3f22c3a 100644 --- a/parse_apache_configs/parse_config.py +++ b/parse_apache_configs/parse_config.py @@ -46,10 +46,8 @@ CONFIG_FILE = OneOrMore(LINE) -class Node(): - def __init__(self, index): - self.index = index - +class Node(list): + pass class Directive(Node): def __init__(self, name, args): @@ -62,17 +60,17 @@ def __init__(self, comment_string): self.comment_string = comment_string -class BlankLine(): +class BlankLine(Node): pass -class NestedTags(list): +class NestedTags(Node): def __init__(self, open_tag, close_tag): self.open_tag = open_tag self.close_tag = close_tag -class RootNode(list): +class RootNode(Node): pass From 9b03c4e878abb21fc1e6c09209e1a8a71519ddca Mon Sep 17 00:00:00 2001 From: daladim Date: Thu, 30 Apr 2020 00:13:29 +0200 Subject: [PATCH 06/10] Replaced add_directive with node.add_or_update_directive It is * object-oriented * less circumvoluted * fixed in some cashes where it was broken (e.g. when searching/ading keys at the root of the tree) --- README.rst | 14 ++--- parse_apache_configs/parse_config.py | 93 ++++++++++++---------------- 2 files changed, 48 insertions(+), 59 deletions(-) diff --git a/README.rst b/README.rst index d01553b..2276db9 100644 --- a/README.rst +++ b/README.rst @@ -15,7 +15,7 @@ Main Functions To use: .. code-block:: python - + from parse_apache_configs import parse_config Parse the apache config via file path, and return a python object representation: @@ -38,13 +38,13 @@ To add or override an existing directive and return the result: .. code-block:: python - apache_config = apache_parse_obj.add_directive(apache_config, "SomeDirectiveName", "SomeDirectiveArguments", "") - + apache_config.add_or_update_directive([""], "SomeDirectiveName", "SomeDirectiveArguments"): The code above will add the line "SomeDirectiveName SomeDirectiveArguments" under . If the directive is already there, then it's arguments will be overridden. -Keep in mind that directives in nested tags can also be added/overridden, but their full "path" must be fed into -add_directive. For example, given the following apache file: +To add/override directives at the root of the config, pass None (or []) for the "path". +To add/override directives in nested tags, pass their full "path" into add_or_update_directive. +For example, given the following apache file: .. code-block:: apache @@ -59,11 +59,11 @@ add_directive. For example, given the following apache file: -To override the "Order" directive under , the invocation to add_directive would look like this: +To override the "Order" directive under , the invocation to add_or_update_directive would look like this: .. code-block:: python - apache_config = apache_parse_obj.add_directive(apache_config, "Order", "deny,allow", "", "") + apache_config.add_or_update_directive(["", ""], "Order", "deny,allow") To convert the apache_config object into a printable string: diff --git a/parse_apache_configs/parse_config.py b/parse_apache_configs/parse_config.py index 3f22c3a..4e45caf 100644 --- a/parse_apache_configs/parse_config.py +++ b/parse_apache_configs/parse_config.py @@ -47,7 +47,47 @@ class Node(list): - pass + def get_from_path(self, path): + """ + Return a sub-node from a path of nested tags + """ + if path is None or len(path) == 0: + return self + + tag = path[0] + for item in self: + if isinstance(item, NestedTags): + if item.open_tag.rstrip() == tag: + return item.get_from_path(path[1:]) + + raise KeyError("Invalid tag path") + + + def add_or_update_directive(self, path, directive_name, directive_arguments): + """ + Add/override a directive in the apache config file. + path must be a list of tags, e.g. ["", ""], + or an empty list/None in case you want to change something at the root of your file + Returns whether something was changed + Throws in case the path is invalid + """ + node = self.get_from_path(path) + + for item in node: + if not isinstance(item, Directive): + continue + else: + if item.name == directive_name: + # We have found our item, let's update it + if item.args == directive_arguments: + return False # Nothing was changed + item.args = directive_arguments + return True + + # The item is not present at the given path. Let's add it + node.append(Directive(directive_name, directive_arguments)) + return True + class Directive(Node): def __init__(self, name, args): @@ -180,58 +220,7 @@ def get_apache_config(self, nested_list_conf): return config_string - def add_directive(self, nested_list_conf, directive_name, - directive_arguments, *path): - """ - This method adds/overrides a directivie in the apache config file. - """ - # Variables - if len(path) == 0: - tag_path = [] - else: - tag_path = [] - for string in path: - tag_path.append(string) - - stack = [] - stack.append(nested_list_conf) - # A dummy nested list so we don't modify the orignal - # as we are iterating through the list - dummy_nested_list_conf = list(nested_list_conf) - - # Iterating through the list - while(len(tag_path) > 0): - - # If we iterate through the entire list and tag_path is still - # greater than zero, then the tag is not there. - if len(dummy_nested_list_conf) == 0: - raise Exception("Y U GIVE INCORRECT PATH!?") - - # Pop the first element off the stack - current = dummy_nested_list_conf.pop(0) - if isinstance(current, NestedTags): - if current.open_tag.rstrip() == tag_path[0]: - # We are only conerned with the current block of the config - dummy_nested_list_conf = current - stack.append(current) - tag_path.pop(0) - - directive = Directive(directive_name, directive_arguments) - # Checking to see if directive is already there. - # If it is, override it. - for directive_object in stack[-1]: - if not isinstance(directive_object, Directive): - continue - else: - if directive_object.name == directive_name: - directive_object.args = directive_arguments - return nested_list_conf - # If we have reached this point, the directive is not in the - # config file and we can add it. - stack[-1].append(directive) - - return nested_list_conf def _is_open_tag(self, tokenized_line): """ From 1396ab071e2fe3d061fefdbf6dab22fc906232c5 Mon Sep 17 00:00:00 2001 From: daladim Date: Thu, 30 Apr 2020 00:21:03 +0200 Subject: [PATCH 07/10] Added the add_nested_tag method --- README.rst | 7 +++++++ parse_apache_configs/parse_config.py | 16 ++++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/README.rst b/README.rst index 2276db9..c84391b 100644 --- a/README.rst +++ b/README.rst @@ -65,6 +65,13 @@ To override the "Order" directive under , the apache_config.add_or_update_directive(["", ""], "Order", "deny,allow") +To add a tag into the config, you can use add_nested_tags. +Here as well, use [] or None for the path in case you want to change a tag at the root of your config + +.. code-block:: python + + apache_config.add_nested_tags([], "", "") + To convert the apache_config object into a printable string: diff --git a/parse_apache_configs/parse_config.py b/parse_apache_configs/parse_config.py index 4e45caf..58102d5 100644 --- a/parse_apache_configs/parse_config.py +++ b/parse_apache_configs/parse_config.py @@ -88,6 +88,22 @@ def add_or_update_directive(self, path, directive_name, directive_arguments): node.append(Directive(directive_name, directive_arguments)) return True + def add_nested_tags(self, path, open_tag, close_tag): + """ + Add a nested tag into the config + Returns true on success, false in case a tag already existed at this path + Throws in case of an invalid path + """ + node = self.get_from_path(path) + for item in node: + if not isinstance(item, NestedTags): + continue + else: + if item.open_tag == open_tag: + return False + # The item is not present at the given path. Let's add it + node.append(NestedTags(open_tag, close_tag)) + return True class Directive(Node): def __init__(self, name, args): From da822b720323773a08ce648ea5430dde1c44ffdf Mon Sep 17 00:00:00 2001 From: daladim Date: Thu, 30 Apr 2020 00:53:41 +0200 Subject: [PATCH 08/10] get_apache_config() is now a method of Node --- README.rst | 3 +- parse_apache_configs/parse_config.py | 81 ++++++++++++---------------- 2 files changed, 34 insertions(+), 50 deletions(-) diff --git a/README.rst b/README.rst index c84391b..1cb263b 100644 --- a/README.rst +++ b/README.rst @@ -77,6 +77,5 @@ To convert the apache_config object into a printable string: .. code-block:: python - apache_config_string = apache_parse_obj.get_apache_config(apache_config) - print apache_config_string + print apache_config.get_apache_config() diff --git a/parse_apache_configs/parse_config.py b/parse_apache_configs/parse_config.py index 58102d5..9e6c819 100644 --- a/parse_apache_configs/parse_config.py +++ b/parse_apache_configs/parse_config.py @@ -105,6 +105,39 @@ def add_nested_tags(self, path, open_tag, close_tag): node.append(NestedTags(open_tag, close_tag)) return True + def get_apache_config(self, indentation=0): + """ + This method returns the apache config contents as a string + given the nested list returned by parse_config + """ + config_string = "" + + if isinstance(self, Directive): + config_string += ( + "\t"*indentation + self.name + " " + + self.args + "\n" + ) + if isinstance(self, Comment): + config_string += ( + "\t"*indentation + "#" + self.comment_string + + "\n" + ) + if isinstance(self, BlankLine): + config_string += "\n" + + if isinstance(self, NestedTags): + config_string += "\t" * indentation + self.open_tag + "\n" + + for item in self: + new_indent = indentation if isinstance(self, RootNode) else indentation+1 + config_string += item.get_apache_config(new_indent) + + if isinstance(self, NestedTags): + config_string += "\t" * indentation + self.close_tag + "\n" + + return config_string + + class Directive(Node): def __init__(self, name, args): self.name = name @@ -190,54 +223,6 @@ def parse_config(self): return config_stack[-1] - def get_apache_config(self, nested_list_conf): - """ - This method returns the apache config contents as a string - given the nested list returned by parse_config - """ - stack = [] - stack.append(nested_list_conf) - depth = -1 - config_string = "" - while(len(stack) > 0): - current = stack[-1] - if isinstance(current, Directive): - config_string += ( - "\t"*depth + current.name + " " - + current.args + "\n" - ) - stack.pop() - continue - if isinstance(current, Comment): - config_string += ( - "\t"*depth + "#" + current.comment_string - + "\n" - ) - stack.pop() - continue - if isinstance(current, BlankLine): - config_string += "\n" - stack.pop() - continue - - if hasattr(current, 'should_close'): - depth -= 1 - if isinstance(current, NestedTags): - config_string += "\t" * depth + current.close_tag + "\n" - stack.pop() - continue - - if isinstance(current, NestedTags): - config_string += "\t" * depth + current.open_tag + "\n" - - current.should_close = True - depth += 1 - stack.extend(reversed(current)) - - return config_string - - - def _is_open_tag(self, tokenized_line): """ Returns true if tozenized_line is an apache start tag. From 67a39ed255d6fab7839f1e4aefd26dd873d8753a Mon Sep 17 00:00:00 2001 From: daladim Date: Thu, 30 Apr 2020 07:57:42 +0200 Subject: [PATCH 09/10] Added the remove_node method --- parse_apache_configs/parse_config.py | 30 ++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/parse_apache_configs/parse_config.py b/parse_apache_configs/parse_config.py index 9e6c819..1f0c1a7 100644 --- a/parse_apache_configs/parse_config.py +++ b/parse_apache_configs/parse_config.py @@ -105,6 +105,36 @@ def add_nested_tags(self, path, open_tag, close_tag): node.append(NestedTags(open_tag, close_tag)) return True + def remove_node(self, path, directive_name=None, nested_tag_open_tag=None): + """ + This method removes a node under path. + Pass a directive_name in case you want to remove a directive + Pass nested_tag_open_tag in case you want to remove a nested tag + Returns true on success + Throws in case no such node is found + """ + if(directive_name is None and nested_tag_open_tag is None): + raise KeyError("No argument was passed") + if(not directive_name is None and not nested_tag_open_tag is None): + raise KeyError("Pass only one argument") + + parent = self.get_from_path(path) + for item in parent: + if directive_name is not None: + if isinstance(item, Directive): + if item.name == directive_name: + parent.remove(item) + return True + + if nested_tag_open_tag is not None: + if isinstance(item, NestedTags): + if item.open_tag == nested_tag_open_tag: + parent.remove(item) + return True + + raise KeyError("No such node") + + def get_apache_config(self, indentation=0): """ This method returns the apache config contents as a string From 0ceef0d3be563167d85b2fb453f2cebe83141187 Mon Sep 17 00:00:00 2001 From: daladim Date: Thu, 30 Apr 2020 08:00:33 +0200 Subject: [PATCH 10/10] v1.0 (breaks the API) --- setup.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/setup.py b/setup.py index ae90396..e67f8f1 100644 --- a/setup.py +++ b/setup.py @@ -1,12 +1,12 @@ from setuptools import setup setup(name='parse_apache_configs', - version="0.0.2", + version="1.0", description="A python module to parse apache config files.", - url='http://github.com/alextricity25/parse_apache_configs', - download_url = 'https://github.com/alextricity25/parse_apache_configs/tarball/0.0.2', - author='Miguel Alex Cantu', - author_email='miguel.cantu@rackspace.com', + url='https://github.com/daladim/parse_apache_configs', + download_url = 'https://github.com/daladim/parse_apache_configs/tarball/1.0', + author='Miguel Alex Cantu and Jérôme Froissart', + author_email='software@froissart.eu', packages=['parse_apache_configs'], install_requires=[ 'pyparsing',