Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/certbot/certbot.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'certbot-nginx/certbot_nginx/_internal/parser_obj.py')
-rw-r--r--certbot-nginx/certbot_nginx/_internal/parser_obj.py396
1 files changed, 396 insertions, 0 deletions
diff --git a/certbot-nginx/certbot_nginx/_internal/parser_obj.py b/certbot-nginx/certbot_nginx/_internal/parser_obj.py
new file mode 100644
index 000000000..e03913887
--- /dev/null
+++ b/certbot-nginx/certbot_nginx/_internal/parser_obj.py
@@ -0,0 +1,396 @@
+""" This file contains parsing routines and object classes to help derive meaning from
+raw lists of tokens from pyparsing. """
+
+import abc
+import logging
+
+import six
+
+from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module
+from certbot import errors
+
+logger = logging.getLogger(__name__)
+COMMENT = " managed by Certbot"
+COMMENT_BLOCK = ["#", COMMENT]
+
+
+class Parsable(object):
+ """ Abstract base class for "Parsable" objects whose underlying representation
+ is a tree of lists.
+
+ :param .Parsable parent: This object's parsed parent in the tree
+ """
+
+ __metaclass__ = abc.ABCMeta
+
+ def __init__(self, parent=None):
+ self._data = [] # type: List[object]
+ self._tabs = None
+ self.parent = parent
+
+ @classmethod
+ def parsing_hooks(cls):
+ """Returns object types that this class should be able to `parse` recusrively.
+ The order of the objects indicates the order in which the parser should
+ try to parse each subitem.
+ :returns: A list of Parsable classes.
+ :rtype list:
+ """
+ return (Block, Sentence, Statements)
+
+ @staticmethod
+ @abc.abstractmethod
+ def should_parse(lists):
+ """ Returns whether the contents of `lists` can be parsed into this object.
+
+ :returns: Whether `lists` can be parsed as this object.
+ :rtype bool:
+ """
+ raise NotImplementedError()
+
+ @abc.abstractmethod
+ def parse(self, raw_list, add_spaces=False):
+ """ Loads information into this object from underlying raw_list structure.
+ Each Parsable object might make different assumptions about the structure of
+ raw_list.
+
+ :param list raw_list: A list or sublist of tokens from pyparsing, containing whitespace
+ as separate tokens.
+ :param bool add_spaces: If set, the method can and should manipulate and insert spacing
+ between non-whitespace tokens and lists to delimit them.
+ :raises .errors.MisconfigurationError: when the assumptions about the structure of
+ raw_list are not met.
+ """
+ raise NotImplementedError()
+
+ @abc.abstractmethod
+ def iterate(self, expanded=False, match=None):
+ """ Iterates across this object. If this object is a leaf object, only yields
+ itself. If it contains references other parsing objects, and `expanded` is set,
+ this function should first yield itself, then recursively iterate across all of them.
+ :param bool expanded: Whether to recursively iterate on possible children.
+ :param callable match: If provided, an object is only iterated if this callable
+ returns True when called on that object.
+
+ :returns: Iterator over desired objects.
+ """
+ raise NotImplementedError()
+
+ @abc.abstractmethod
+ def get_tabs(self):
+ """ Guess at the tabbing style of this parsed object, based on whitespace.
+
+ If this object is a leaf, it deducts the tabbing based on its own contents.
+ Other objects may guess by calling `get_tabs` recursively on child objects.
+
+ :returns: Guess at tabbing for this object. Should only return whitespace strings
+ that does not contain newlines.
+ :rtype str:
+ """
+ raise NotImplementedError()
+
+ @abc.abstractmethod
+ def set_tabs(self, tabs=" "):
+ """This tries to set and alter the tabbing of the current object to a desired
+ whitespace string. Primarily meant for objects that were constructed, so they
+ can conform to surrounding whitespace.
+
+ :param str tabs: A whitespace string (not containing newlines).
+ """
+ raise NotImplementedError()
+
+ def dump(self, include_spaces=False):
+ """ Dumps back to pyparsing-like list tree. The opposite of `parse`.
+
+ Note: if this object has not been modified, `dump` with `include_spaces=True`
+ should always return the original input of `parse`.
+
+ :param bool include_spaces: If set to False, magically hides whitespace tokens from
+ dumped output.
+
+ :returns: Pyparsing-like list tree.
+ :rtype list:
+ """
+ return [elem.dump(include_spaces) for elem in self._data]
+
+
+class Statements(Parsable):
+ """ A group or list of "Statements". A Statement is either a Block or a Sentence.
+
+ The underlying representation is simply a list of these Statement objects, with
+ an extra `_trailing_whitespace` string to keep track of the whitespace that does not
+ precede any more statements.
+ """
+ def __init__(self, parent=None):
+ super(Statements, self).__init__(parent)
+ self._trailing_whitespace = None
+
+ # ======== Begin overridden functions
+
+ @staticmethod
+ def should_parse(lists):
+ return isinstance(lists, list)
+
+ def set_tabs(self, tabs=" "):
+ """ Sets the tabbing for this set of statements. Does this by calling `set_tabs`
+ on each of the child statements.
+
+ Then, if a parent is present, sets trailing whitespace to parent tabbing. This
+ is so that the trailing } of any Block that contains Statements lines up
+ with parent tabbing.
+ """
+ for statement in self._data:
+ statement.set_tabs(tabs)
+ if self.parent is not None:
+ self._trailing_whitespace = "\n" + self.parent.get_tabs()
+
+ def parse(self, raw_list, add_spaces=False):
+ """ Parses a list of statements.
+ Expects all elements in `raw_list` to be parseable by `type(self).parsing_hooks`,
+ with an optional whitespace string at the last index of `raw_list`.
+ """
+ if not isinstance(raw_list, list):
+ raise errors.MisconfigurationError("Statements parsing expects a list!")
+ # If there's a trailing whitespace in the list of statements, keep track of it.
+ if raw_list and isinstance(raw_list[-1], six.string_types) and raw_list[-1].isspace():
+ self._trailing_whitespace = raw_list[-1]
+ raw_list = raw_list[:-1]
+ self._data = [parse_raw(elem, self, add_spaces) for elem in raw_list]
+
+ def get_tabs(self):
+ """ Takes a guess at the tabbing of all contained Statements by retrieving the
+ tabbing of the first Statement."""
+ if self._data:
+ return self._data[0].get_tabs()
+ return ""
+
+ def dump(self, include_spaces=False):
+ """ Dumps this object by first dumping each statement, then appending its
+ trailing whitespace (if `include_spaces` is set) """
+ data = super(Statements, self).dump(include_spaces)
+ if include_spaces and self._trailing_whitespace is not None:
+ return data + [self._trailing_whitespace]
+ return data
+
+ def iterate(self, expanded=False, match=None):
+ """ Combines each statement's iterator. """
+ for elem in self._data:
+ for sub_elem in elem.iterate(expanded, match):
+ yield sub_elem
+
+ # ======== End overridden functions
+
+
+def _space_list(list_):
+ """ Inserts whitespace between adjacent non-whitespace tokens. """
+ spaced_statement = [] # type: List[str]
+ for i in reversed(six.moves.xrange(len(list_))):
+ spaced_statement.insert(0, list_[i])
+ if i > 0 and not list_[i].isspace() and not list_[i-1].isspace():
+ spaced_statement.insert(0, " ")
+ return spaced_statement
+
+
+class Sentence(Parsable):
+ """ A list of words. Non-whitespace words are typically separated with whitespace tokens. """
+
+ # ======== Begin overridden functions
+
+ @staticmethod
+ def should_parse(lists):
+ """ Returns True if `lists` can be parseable as a `Sentence`-- that is,
+ every element is a string type.
+
+ :param list lists: The raw unparsed list to check.
+
+ :returns: whether this lists is parseable by `Sentence`.
+ """
+ return isinstance(lists, list) and len(lists) > 0 and \
+ all([isinstance(elem, six.string_types) for elem in lists])
+
+ def parse(self, raw_list, add_spaces=False):
+ """ Parses a list of string types into this object.
+ If add_spaces is set, adds whitespace tokens between adjacent non-whitespace tokens."""
+ if add_spaces:
+ raw_list = _space_list(raw_list)
+ if not isinstance(raw_list, list) or \
+ any([not isinstance(elem, six.string_types) for elem in raw_list]):
+ raise errors.MisconfigurationError("Sentence parsing expects a list of string types.")
+ self._data = raw_list
+
+ def iterate(self, expanded=False, match=None):
+ """ Simply yields itself. """
+ if match is None or match(self):
+ yield self
+
+ def set_tabs(self, tabs=" "):
+ """ Sets the tabbing on this sentence. Inserts a newline and `tabs` at the
+ beginning of `self._data`. """
+ if self._data[0].isspace():
+ return
+ self._data.insert(0, "\n" + tabs)
+
+ def dump(self, include_spaces=False):
+ """ Dumps this sentence. If include_spaces is set, includes whitespace tokens."""
+ if not include_spaces:
+ return self.words
+ return self._data
+
+ def get_tabs(self):
+ """ Guesses at the tabbing of this sentence. If the first element is whitespace,
+ returns the whitespace after the rightmost newline in the string. """
+ first = self._data[0]
+ if not first.isspace():
+ return ""
+ rindex = first.rfind("\n")
+ return first[rindex+1:]
+
+ # ======== End overridden functions
+
+ @property
+ def words(self):
+ """ Iterates over words, but without spaces. Like Unspaced List. """
+ return [word.strip("\"\'") for word in self._data if not word.isspace()]
+
+ def __getitem__(self, index):
+ return self.words[index]
+
+ def __contains__(self, word):
+ return word in self.words
+
+
+class Block(Parsable):
+ """ Any sort of bloc, denoted by a block name and curly braces, like so:
+ The parsed block:
+ block name {
+ content 1;
+ content 2;
+ }
+ might be represented with the list [names, contents], where
+ names = ["block", " ", "name", " "]
+ contents = [["\n ", "content", " ", "1"], ["\n ", "content", " ", "2"], "\n"]
+ """
+ def __init__(self, parent=None):
+ super(Block, self).__init__(parent)
+ self.names = None # type: Sentence
+ self.contents = None # type: Block
+
+ @staticmethod
+ def should_parse(lists):
+ """ Returns True if `lists` can be parseable as a `Block`-- that is,
+ it's got a length of 2, the first element is a `Sentence` and the second can be
+ a `Statements`.
+
+ :param list lists: The raw unparsed list to check.
+
+ :returns: whether this lists is parseable by `Block`. """
+ return isinstance(lists, list) and len(lists) == 2 and \
+ Sentence.should_parse(lists[0]) and isinstance(lists[1], list)
+
+ def set_tabs(self, tabs=" "):
+ """ Sets tabs by setting equivalent tabbing on names, then adding tabbing
+ to contents."""
+ self.names.set_tabs(tabs)
+ self.contents.set_tabs(tabs + " ")
+
+ def iterate(self, expanded=False, match=None):
+ """ Iterator over self, and if expanded is set, over its contents. """
+ if match is None or match(self):
+ yield self
+ if expanded:
+ for elem in self.contents.iterate(expanded, match):
+ yield elem
+
+ def parse(self, raw_list, add_spaces=False):
+ """ Parses a list that resembles a block.
+
+ The assumptions that this routine makes are:
+ 1. the first element of `raw_list` is a valid Sentence.
+ 2. the second element of `raw_list` is a valid Statement.
+ If add_spaces is set, we call it recursively on `names` and `contents`, and
+ add an extra trailing space to `names` (to separate the block's opening bracket
+ and the block name).
+ """
+ if not Block.should_parse(raw_list):
+ raise errors.MisconfigurationError("Block parsing expects a list of length 2. "
+ "First element should be a list of string types (the bloc names), "
+ "and second should be another list of statements (the bloc content).")
+ self.names = Sentence(self)
+ if add_spaces:
+ raw_list[0].append(" ")
+ self.names.parse(raw_list[0], add_spaces)
+ self.contents = Statements(self)
+ self.contents.parse(raw_list[1], add_spaces)
+ self._data = [self.names, self.contents]
+
+ def get_tabs(self):
+ """ Guesses tabbing by retrieving tabbing guess of self.names. """
+ return self.names.get_tabs()
+
+def _is_comment(parsed_obj):
+ """ Checks whether parsed_obj is a comment.
+
+ :param .Parsable parsed_obj:
+
+ :returns: whether parsed_obj represents a comment sentence.
+ :rtype bool:
+ """
+ if not isinstance(parsed_obj, Sentence):
+ return False
+ return parsed_obj.words[0] == "#"
+
+def _is_certbot_comment(parsed_obj):
+ """ Checks whether parsed_obj is a "managed by Certbot" comment.
+
+ :param .Parsable parsed_obj:
+
+ :returns: whether parsed_obj is a "managed by Certbot" comment.
+ :rtype bool:
+ """
+ if not _is_comment(parsed_obj):
+ return False
+ if len(parsed_obj.words) != len(COMMENT_BLOCK):
+ return False
+ for i, word in enumerate(parsed_obj.words):
+ if word != COMMENT_BLOCK[i]:
+ return False
+ return True
+
+def _certbot_comment(parent, preceding_spaces=4):
+ """ A "Managed by Certbot" comment.
+ :param int preceding_spaces: Number of spaces between the end of the previous
+ statement and the comment.
+ :returns: Sentence containing the comment.
+ :rtype: .Sentence
+ """
+ result = Sentence(parent)
+ result.parse([" " * preceding_spaces] + COMMENT_BLOCK)
+ return result
+
+def _choose_parser(parent, list_):
+ """ Choose a parser from type(parent).parsing_hooks, depending on whichever hook
+ returns True first. """
+ hooks = Parsable.parsing_hooks()
+ if parent:
+ hooks = type(parent).parsing_hooks()
+ for type_ in hooks:
+ if type_.should_parse(list_):
+ return type_(parent)
+ raise errors.MisconfigurationError(
+ "None of the parsing hooks succeeded, so we don't know how to parse this set of lists.")
+
+def parse_raw(lists_, parent=None, add_spaces=False):
+ """ Primary parsing factory function.
+
+ :param list lists_: raw lists from pyparsing to parse.
+ :param .Parent parent: The parent containing this object.
+ :param bool add_spaces: Whether to pass add_spaces to the parser.
+
+ :returns .Parsable: The parsed object.
+
+ :raises errors.MisconfigurationError: If no parsing hook passes, and we can't
+ determine which type to parse the raw lists into.
+ """
+ parser = _choose_parser(parent, lists_)
+ parser.parse(lists_, add_spaces)
+ return parser