We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Missing endscript in logrotate_conf parser causes infinite loop
If a script tag in a logrotate config file doesn't have a corresponding endscript tag, the parser loops forever and eventually consumes all system memory.

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `insights/combiners/logrotate_conf.py`
Content:
```
1 """
2 LogrotateConfAll - Combiner for logrotate configuration
3 =======================================================
4 Combiner for accessing all the logrotate configuration files.  It collects all
5 LogrotateConf generated from each single logrotate configuration file.
6 
7 There may be multiple logrotate configuration, and the main configuration file is
8 ``/etc/logrotate.conf``.  Only the options defined in this file are global
9 options, and all other options (if there are) will be discarded.
10 
11 """
12 
13 import operator
14 import os
15 import string
16 from fnmatch import fnmatch
17 from insights.core import ConfigCombiner, ConfigParser
18 from insights.core.plugins import combiner, parser
19 from insights.parsers.logrotate_conf import LogrotateConf
20 from insights.parsr.query import eq
21 from insights.specs import Specs
22 from insights.parsr import (AnyChar, Choice, EOF, EOL, Forward, LeftCurly,
23         LineEnd, Literal, Many, Number, OneLineComment, Opt, PosMarker,
24         QuotedString, RightCurly, skip_none, String, WS, WSChar)
25 from insights.parsr.query import Directive, Entry, Section
26 
27 
28 @combiner(LogrotateConf)
29 class LogrotateConfAll(object):
30     """
31     Class for combining all the logrotate configuration files.
32 
33     Sample files::
34 
35         # /etc/logrotate.conf:
36             compress
37             rotate 7
38 
39             /var/log/messages {
40                 rotate 5
41                 weekly
42                 postrotate
43                             /sbin/killall -HUP syslogd
44                 endscript
45             }
46 
47         # /etc/logrotate.d/httpd
48             "/var/log/httpd/access.log" /var/log/httpd/error.log {
49                 rotate 5
50                 mail www@my.org
51                 size=100k
52                 sharedscripts
53                 postrotate
54                             /sbin/killall -HUP httpd
55                 endscript
56             }
57 
58         # /etc/logrotate.d/newscrit
59             /var/log/news/*.crit  {
60                 monthly
61                 rotate 2
62                 olddir /var/log/news/old
63                 missingok
64                 postrotate
65                             kill -HUP `cat /var/run/inn.pid`
66                 endscript
67                 nocompress
68             }
69 
70     Examples:
71         >>> all_lrt.global_options
72         ['compress', 'rotate']
73         >>> all_lrt['rotate']
74         '7'
75         >>> '/var/log/httpd/access.log' in all_lrt.log_files
76         True
77         >>> all_lrt['/var/log/httpd/access.log']['rotate']
78         '5'
79         >>> all_lrt.configfile_of_logfile('/var/log/news/olds.crit')
80         '/etc/logrotate.d/newscrit'
81         >>> all_lrt.options_of_logfile('/var/log/httpd/access.log')['mail']
82         'www@my.org'
83 
84 
85     Attributes:
86         data(dict): All parsed options and log files are stored in this
87             dictionary
88         global_options(list): List of global options defined in
89             ``/etc/logrotate.conf``
90         log_files(list): List of log files in all logrotate configuration files
91     """
92     def __init__(self, lrt_conf):
93         self.data = {}
94         self.global_options = []
95         self.log_files = []
96         self._file_map = {}
97 
98         for lrt in lrt_conf:
99             self.data.update(lrt.data)
100             if lrt.file_path == "/etc/logrotate.conf":
101                 self.global_options = lrt.options
102             self.log_files.extend(lrt.log_files)
103             self._file_map[lrt.file_path] = lrt.log_files
104 
105     def __contains__(self, item):
106         return (item in self.global_options or
107                 any(fnmatch(item, f) for f in self.log_files))
108 
109     def __getitem__(self, item):
110         if item in self.global_options:
111             return self.data[item]
112         return self.data.get(self._get_match_logfile(item))
113 
114     def _get_match_logfile(self, log_file, file_list=[]):
115         file_list = self.log_files if not file_list else file_list
116         for f in file_list:
117             if fnmatch(log_file, f):
118                 return f
119 
120     def options_of_logfile(self, log_file):
121         """
122         Get the options of ``log_file``.
123 
124         Parameters:
125             log_file(str): The log files need to check.
126 
127         Returns:
128             dict: Dictionary contains the options of ``log_file``.  None when no
129             such ``log_file``.
130         """
131         return self.data.get(self._get_match_logfile(log_file))
132 
133     def configfile_of_logfile(self, log_file):
134         """
135         Get the configuration file path in which the ``log_file`` is configured.
136 
137         Parameters:
138             log_file(str): The log files need to check.
139 
140         Returns:
141             dict: The configuration file path of ``log_file``.  None when no
142             such ``log_file``.
143         """
144         for f, lfs in self._file_map.items():
145             if self._get_match_logfile(log_file, lfs):
146                 return f
147 
148 
149 class DocParser(object):
150     def __init__(self, ctx):
151         self.ctx = ctx
152 
153         scripts = set("postrotate prerotate firstaction lastaction preremove".split())
154         Stanza = Forward()
155         Spaces = Many(WSChar)
156         Bare = String(set(string.printable) - (set(string.whitespace) | set("#{}'\"")))
157         Num = Number & (WSChar | LineEnd)
158         Comment = OneLineComment("#").map(lambda x: None)
159         ScriptStart = WS >> PosMarker(Choice([Literal(s) for s in scripts])) << WS
160         ScriptEnd = Literal("endscript")
161         Line = (WS >> AnyChar.until(EOL) << WS).map(lambda x: "".join(x))
162         Lines = Line.until(ScriptEnd).map(lambda x: "\n".join(x))
163         Script = ScriptStart + Lines << ScriptEnd
164         Script = Script.map(lambda x: [x[0], [x[1]], None])
165         BeginBlock = WS >> LeftCurly << WS
166         EndBlock = WS >> RightCurly
167         First = PosMarker((Bare | QuotedString)) << Spaces
168         Attr = Spaces >> (Num | Bare | QuotedString) << Spaces
169         Rest = Many(Attr)
170         Block = BeginBlock >> Many(Stanza).map(skip_none).map(self.to_entries) << EndBlock
171         Stmt = WS >> (Script | (First + Rest + Opt(Block))) << WS
172         Stanza <= WS >> (Stmt | Comment) << WS
173         Doc = Many(Stanza).map(skip_none).map(self.to_entries)
174 
175         self.Top = Doc + EOF
176 
177     def to_entries(self, x):
178         ret = []
179         for i in x:
180             name, attrs, body = i
181             if body:
182                 for n in [name.value] + attrs:
183                     ret.append(Section(name=n, children=body, lineno=name.lineno))
184             else:
185                 ret.append(Directive(name=name.value, attrs=attrs, lineno=name.lineno))
186         return ret
187 
188     def __call__(self, content):
189         return self.Top(content)
190 
191 
192 def parse_doc(content, ctx=None):
193     """ Parse a configuration document into a tree that can be queried. """
194     if isinstance(content, list):
195         content = "\n".join(content)
196     parse = DocParser(ctx)
197     result = parse(content)[0]
198     return Entry(children=result, src=ctx)
199 
200 
201 @parser(Specs.logrotate_conf, continue_on_error=False)
202 class _LogRotateConf(ConfigParser):
203     def parse_doc(self, content):
204         return parse_doc("\n".join(content), ctx=self)
205 
206 
207 @combiner(_LogRotateConf)
208 class LogRotateConfTree(ConfigCombiner):
209     """
210     Exposes logrotate configuration through the parsr query interface.
211 
212     See the :py:class:`insights.core.ConfigComponent` class for example usage.
213     """
214     def __init__(self, confs):
215         include = eq("include")
216         main_file = "logrotate.conf"
217         super(LogRotateConfTree, self).__init__(confs, main_file, include)
218 
219     @property
220     def conf_path(self):
221         return os.path.dirname(self.main.file_path)
222 
223     def find_matches(self, confs, pattern):
224         results = []
225         for c in confs:
226             if os.path.isdir(pattern) and c.file_path.startswith(pattern):
227                 results.append(c)
228             elif fnmatch(c.file_path, pattern):
229                 results.append(c)
230         return sorted(results, key=operator.attrgetter("file_name"))
231 
232 
233 def get_tree(root=None):
234     """
235     This is a helper function to get a logrotate configuration component for
236     your local machine or an archive. It's for use in interactive sessions.
237     """
238     from insights import run
239     return run(LogRotateConfTree, root=root).get(LogRotateConfTree)
240
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/insights/combiners/logrotate_conf.py b/insights/combiners/logrotate_conf.py
--- a/insights/combiners/logrotate_conf.py
+++ b/insights/combiners/logrotate_conf.py
@@ -159,8 +159,8 @@
         ScriptStart = WS >> PosMarker(Choice([Literal(s) for s in scripts])) << WS
         ScriptEnd = Literal("endscript")
         Line = (WS >> AnyChar.until(EOL) << WS).map(lambda x: "".join(x))
-        Lines = Line.until(ScriptEnd).map(lambda x: "\n".join(x))
-        Script = ScriptStart + Lines << ScriptEnd
+        Lines = Line.until(ScriptEnd | EOF).map(lambda x: "\n".join(x))
+        Script = ScriptStart + Lines << Opt(ScriptEnd)
         Script = Script.map(lambda x: [x[0], [x[1]], None])
         BeginBlock = WS >> LeftCurly << WS
         EndBlock = WS >> RightCurly
@@ -172,7 +172,7 @@
         Stanza <= WS >> (Stmt | Comment) << WS
         Doc = Many(Stanza).map(skip_none).map(self.to_entries)
 
-        self.Top = Doc + EOF
+        self.Top = Doc << EOF
 
     def to_entries(self, x):
         ret = []
@@ -194,7 +194,7 @@
     if isinstance(content, list):
         content = "\n".join(content)
     parse = DocParser(ctx)
-    result = parse(content)[0]
+    result = parse(content)
     return Entry(children=result, src=ctx)

{"golden_diff": "diff --git a/insights/combiners/logrotate_conf.py b/insights/combiners/logrotate_conf.py\n--- a/insights/combiners/logrotate_conf.py\n+++ b/insights/combiners/logrotate_conf.py\n@@ -159,8 +159,8 @@\n         ScriptStart = WS >> PosMarker(Choice([Literal(s) for s in scripts])) << WS\n         ScriptEnd = Literal(\"endscript\")\n         Line = (WS >> AnyChar.until(EOL) << WS).map(lambda x: \"\".join(x))\n-        Lines = Line.until(ScriptEnd).map(lambda x: \"\\n\".join(x))\n-        Script = ScriptStart + Lines << ScriptEnd\n+        Lines = Line.until(ScriptEnd | EOF).map(lambda x: \"\\n\".join(x))\n+        Script = ScriptStart + Lines << Opt(ScriptEnd)\n         Script = Script.map(lambda x: [x[0], [x[1]], None])\n         BeginBlock = WS >> LeftCurly << WS\n         EndBlock = WS >> RightCurly\n@@ -172,7 +172,7 @@\n         Stanza <= WS >> (Stmt | Comment) << WS\n         Doc = Many(Stanza).map(skip_none).map(self.to_entries)\n \n-        self.Top = Doc + EOF\n+        self.Top = Doc << EOF\n \n     def to_entries(self, x):\n         ret = []\n@@ -194,7 +194,7 @@\n     if isinstance(content, list):\n         content = \"\\n\".join(content)\n     parse = DocParser(ctx)\n-    result = parse(content)[0]\n+    result = parse(content)\n     return Entry(children=result, src=ctx)\n", "issue": "Missing endscript in logrotate_conf parser causes infinite loop\nIf a script tag in a logrotate config file doesn't have a corresponding endscript tag, the parser loops forever and eventually consumes all system memory.\n", "before_files": [{"content": "\"\"\"\nLogrotateConfAll - Combiner for logrotate configuration\n=======================================================\nCombiner for accessing all the logrotate configuration files.  It collects all\nLogrotateConf generated from each single logrotate configuration file.\n\nThere may be multiple logrotate configuration, and the main configuration file is\n``/etc/logrotate.conf``.  Only the options defined in this file are global\noptions, and all other options (if there are) will be discarded.\n\n\"\"\"\n\nimport operator\nimport os\nimport string\nfrom fnmatch import fnmatch\nfrom insights.core import ConfigCombiner, ConfigParser\nfrom insights.core.plugins import combiner, parser\nfrom insights.parsers.logrotate_conf import LogrotateConf\nfrom insights.parsr.query import eq\nfrom insights.specs import Specs\nfrom insights.parsr import (AnyChar, Choice, EOF, EOL, Forward, LeftCurly,\n        LineEnd, Literal, Many, Number, OneLineComment, Opt, PosMarker,\n        QuotedString, RightCurly, skip_none, String, WS, WSChar)\nfrom insights.parsr.query import Directive, Entry, Section\n\n\n@combiner(LogrotateConf)\nclass LogrotateConfAll(object):\n    \"\"\"\n    Class for combining all the logrotate configuration files.\n\n    Sample files::\n\n        # /etc/logrotate.conf:\n            compress\n            rotate 7\n\n            /var/log/messages {\n                rotate 5\n                weekly\n                postrotate\n                            /sbin/killall -HUP syslogd\n                endscript\n            }\n\n        # /etc/logrotate.d/httpd\n            \"/var/log/httpd/access.log\" /var/log/httpd/error.log {\n                rotate 5\n                mail www@my.org\n                size=100k\n                sharedscripts\n                postrotate\n                            /sbin/killall -HUP httpd\n                endscript\n            }\n\n        # /etc/logrotate.d/newscrit\n            /var/log/news/*.crit  {\n                monthly\n                rotate 2\n                olddir /var/log/news/old\n                missingok\n                postrotate\n                            kill -HUP `cat /var/run/inn.pid`\n                endscript\n                nocompress\n            }\n\n    Examples:\n        >>> all_lrt.global_options\n        ['compress', 'rotate']\n        >>> all_lrt['rotate']\n        '7'\n        >>> '/var/log/httpd/access.log' in all_lrt.log_files\n        True\n        >>> all_lrt['/var/log/httpd/access.log']['rotate']\n        '5'\n        >>> all_lrt.configfile_of_logfile('/var/log/news/olds.crit')\n        '/etc/logrotate.d/newscrit'\n        >>> all_lrt.options_of_logfile('/var/log/httpd/access.log')['mail']\n        'www@my.org'\n\n\n    Attributes:\n        data(dict): All parsed options and log files are stored in this\n            dictionary\n        global_options(list): List of global options defined in\n            ``/etc/logrotate.conf``\n        log_files(list): List of log files in all logrotate configuration files\n    \"\"\"\n    def __init__(self, lrt_conf):\n        self.data = {}\n        self.global_options = []\n        self.log_files = []\n        self._file_map = {}\n\n        for lrt in lrt_conf:\n            self.data.update(lrt.data)\n            if lrt.file_path == \"/etc/logrotate.conf\":\n                self.global_options = lrt.options\n            self.log_files.extend(lrt.log_files)\n            self._file_map[lrt.file_path] = lrt.log_files\n\n    def __contains__(self, item):\n        return (item in self.global_options or\n                any(fnmatch(item, f) for f in self.log_files))\n\n    def __getitem__(self, item):\n        if item in self.global_options:\n            return self.data[item]\n        return self.data.get(self._get_match_logfile(item))\n\n    def _get_match_logfile(self, log_file, file_list=[]):\n        file_list = self.log_files if not file_list else file_list\n        for f in file_list:\n            if fnmatch(log_file, f):\n                return f\n\n    def options_of_logfile(self, log_file):\n        \"\"\"\n        Get the options of ``log_file``.\n\n        Parameters:\n            log_file(str): The log files need to check.\n\n        Returns:\n            dict: Dictionary contains the options of ``log_file``.  None when no\n            such ``log_file``.\n        \"\"\"\n        return self.data.get(self._get_match_logfile(log_file))\n\n    def configfile_of_logfile(self, log_file):\n        \"\"\"\n        Get the configuration file path in which the ``log_file`` is configured.\n\n        Parameters:\n            log_file(str): The log files need to check.\n\n        Returns:\n            dict: The configuration file path of ``log_file``.  None when no\n            such ``log_file``.\n        \"\"\"\n        for f, lfs in self._file_map.items():\n            if self._get_match_logfile(log_file, lfs):\n                return f\n\n\nclass DocParser(object):\n    def __init__(self, ctx):\n        self.ctx = ctx\n\n        scripts = set(\"postrotate prerotate firstaction lastaction preremove\".split())\n        Stanza = Forward()\n        Spaces = Many(WSChar)\n        Bare = String(set(string.printable) - (set(string.whitespace) | set(\"#{}'\\\"\")))\n        Num = Number & (WSChar | LineEnd)\n        Comment = OneLineComment(\"#\").map(lambda x: None)\n        ScriptStart = WS >> PosMarker(Choice([Literal(s) for s in scripts])) << WS\n        ScriptEnd = Literal(\"endscript\")\n        Line = (WS >> AnyChar.until(EOL) << WS).map(lambda x: \"\".join(x))\n        Lines = Line.until(ScriptEnd).map(lambda x: \"\\n\".join(x))\n        Script = ScriptStart + Lines << ScriptEnd\n        Script = Script.map(lambda x: [x[0], [x[1]], None])\n        BeginBlock = WS >> LeftCurly << WS\n        EndBlock = WS >> RightCurly\n        First = PosMarker((Bare | QuotedString)) << Spaces\n        Attr = Spaces >> (Num | Bare | QuotedString) << Spaces\n        Rest = Many(Attr)\n        Block = BeginBlock >> Many(Stanza).map(skip_none).map(self.to_entries) << EndBlock\n        Stmt = WS >> (Script | (First + Rest + Opt(Block))) << WS\n        Stanza <= WS >> (Stmt | Comment) << WS\n        Doc = Many(Stanza).map(skip_none).map(self.to_entries)\n\n        self.Top = Doc + EOF\n\n    def to_entries(self, x):\n        ret = []\n        for i in x:\n            name, attrs, body = i\n            if body:\n                for n in [name.value] + attrs:\n                    ret.append(Section(name=n, children=body, lineno=name.lineno))\n            else:\n                ret.append(Directive(name=name.value, attrs=attrs, lineno=name.lineno))\n        return ret\n\n    def __call__(self, content):\n        return self.Top(content)\n\n\ndef parse_doc(content, ctx=None):\n    \"\"\" Parse a configuration document into a tree that can be queried. \"\"\"\n    if isinstance(content, list):\n        content = \"\\n\".join(content)\n    parse = DocParser(ctx)\n    result = parse(content)[0]\n    return Entry(children=result, src=ctx)\n\n\n@parser(Specs.logrotate_conf, continue_on_error=False)\nclass _LogRotateConf(ConfigParser):\n    def parse_doc(self, content):\n        return parse_doc(\"\\n\".join(content), ctx=self)\n\n\n@combiner(_LogRotateConf)\nclass LogRotateConfTree(ConfigCombiner):\n    \"\"\"\n    Exposes logrotate configuration through the parsr query interface.\n\n    See the :py:class:`insights.core.ConfigComponent` class for example usage.\n    \"\"\"\n    def __init__(self, confs):\n        include = eq(\"include\")\n        main_file = \"logrotate.conf\"\n        super(LogRotateConfTree, self).__init__(confs, main_file, include)\n\n    @property\n    def conf_path(self):\n        return os.path.dirname(self.main.file_path)\n\n    def find_matches(self, confs, pattern):\n        results = []\n        for c in confs:\n            if os.path.isdir(pattern) and c.file_path.startswith(pattern):\n                results.append(c)\n            elif fnmatch(c.file_path, pattern):\n                results.append(c)\n        return sorted(results, key=operator.attrgetter(\"file_name\"))\n\n\ndef get_tree(root=None):\n    \"\"\"\n    This is a helper function to get a logrotate configuration component for\n    your local machine or an archive. It's for use in interactive sessions.\n    \"\"\"\n    from insights import run\n    return run(LogRotateConfTree, root=root).get(LogRotateConfTree)\n", "path": "insights/combiners/logrotate_conf.py"}], "after_files": [{"content": "\"\"\"\nLogrotateConfAll - Combiner for logrotate configuration\n=======================================================\nCombiner for accessing all the logrotate configuration files.  It collects all\nLogrotateConf generated from each single logrotate configuration file.\n\nThere may be multiple logrotate configuration, and the main configuration file is\n``/etc/logrotate.conf``.  Only the options defined in this file are global\noptions, and all other options (if there are) will be discarded.\n\n\"\"\"\n\nimport operator\nimport os\nimport string\nfrom fnmatch import fnmatch\nfrom insights.core import ConfigCombiner, ConfigParser\nfrom insights.core.plugins import combiner, parser\nfrom insights.parsers.logrotate_conf import LogrotateConf\nfrom insights.parsr.query import eq\nfrom insights.specs import Specs\nfrom insights.parsr import (AnyChar, Choice, EOF, EOL, Forward, LeftCurly,\n        LineEnd, Literal, Many, Number, OneLineComment, Opt, PosMarker,\n        QuotedString, RightCurly, skip_none, String, WS, WSChar)\nfrom insights.parsr.query import Directive, Entry, Section\n\n\n@combiner(LogrotateConf)\nclass LogrotateConfAll(object):\n    \"\"\"\n    Class for combining all the logrotate configuration files.\n\n    Sample files::\n\n        # /etc/logrotate.conf:\n            compress\n            rotate 7\n\n            /var/log/messages {\n                rotate 5\n                weekly\n                postrotate\n                            /sbin/killall -HUP syslogd\n                endscript\n            }\n\n        # /etc/logrotate.d/httpd\n            \"/var/log/httpd/access.log\" /var/log/httpd/error.log {\n                rotate 5\n                mail www@my.org\n                size=100k\n                sharedscripts\n                postrotate\n                            /sbin/killall -HUP httpd\n                endscript\n            }\n\n        # /etc/logrotate.d/newscrit\n            /var/log/news/*.crit  {\n                monthly\n                rotate 2\n                olddir /var/log/news/old\n                missingok\n                postrotate\n                            kill -HUP `cat /var/run/inn.pid`\n                endscript\n                nocompress\n            }\n\n    Examples:\n        >>> all_lrt.global_options\n        ['compress', 'rotate']\n        >>> all_lrt['rotate']\n        '7'\n        >>> '/var/log/httpd/access.log' in all_lrt.log_files\n        True\n        >>> all_lrt['/var/log/httpd/access.log']['rotate']\n        '5'\n        >>> all_lrt.configfile_of_logfile('/var/log/news/olds.crit')\n        '/etc/logrotate.d/newscrit'\n        >>> all_lrt.options_of_logfile('/var/log/httpd/access.log')['mail']\n        'www@my.org'\n\n\n    Attributes:\n        data(dict): All parsed options and log files are stored in this\n            dictionary\n        global_options(list): List of global options defined in\n            ``/etc/logrotate.conf``\n        log_files(list): List of log files in all logrotate configuration files\n    \"\"\"\n    def __init__(self, lrt_conf):\n        self.data = {}\n        self.global_options = []\n        self.log_files = []\n        self._file_map = {}\n\n        for lrt in lrt_conf:\n            self.data.update(lrt.data)\n            if lrt.file_path == \"/etc/logrotate.conf\":\n                self.global_options = lrt.options\n            self.log_files.extend(lrt.log_files)\n            self._file_map[lrt.file_path] = lrt.log_files\n\n    def __contains__(self, item):\n        return (item in self.global_options or\n                any(fnmatch(item, f) for f in self.log_files))\n\n    def __getitem__(self, item):\n        if item in self.global_options:\n            return self.data[item]\n        return self.data.get(self._get_match_logfile(item))\n\n    def _get_match_logfile(self, log_file, file_list=[]):\n        file_list = self.log_files if not file_list else file_list\n        for f in file_list:\n            if fnmatch(log_file, f):\n                return f\n\n    def options_of_logfile(self, log_file):\n        \"\"\"\n        Get the options of ``log_file``.\n\n        Parameters:\n            log_file(str): The log files need to check.\n\n        Returns:\n            dict: Dictionary contains the options of ``log_file``.  None when no\n            such ``log_file``.\n        \"\"\"\n        return self.data.get(self._get_match_logfile(log_file))\n\n    def configfile_of_logfile(self, log_file):\n        \"\"\"\n        Get the configuration file path in which the ``log_file`` is configured.\n\n        Parameters:\n            log_file(str): The log files need to check.\n\n        Returns:\n            dict: The configuration file path of ``log_file``.  None when no\n            such ``log_file``.\n        \"\"\"\n        for f, lfs in self._file_map.items():\n            if self._get_match_logfile(log_file, lfs):\n                return f\n\n\nclass DocParser(object):\n    def __init__(self, ctx):\n        self.ctx = ctx\n\n        scripts = set(\"postrotate prerotate firstaction lastaction preremove\".split())\n        Stanza = Forward()\n        Spaces = Many(WSChar)\n        Bare = String(set(string.printable) - (set(string.whitespace) | set(\"#{}'\\\"\")))\n        Num = Number & (WSChar | LineEnd)\n        Comment = OneLineComment(\"#\").map(lambda x: None)\n        ScriptStart = WS >> PosMarker(Choice([Literal(s) for s in scripts])) << WS\n        ScriptEnd = Literal(\"endscript\")\n        Line = (WS >> AnyChar.until(EOL) << WS).map(lambda x: \"\".join(x))\n        Lines = Line.until(ScriptEnd | EOF).map(lambda x: \"\\n\".join(x))\n        Script = ScriptStart + Lines << Opt(ScriptEnd)\n        Script = Script.map(lambda x: [x[0], [x[1]], None])\n        BeginBlock = WS >> LeftCurly << WS\n        EndBlock = WS >> RightCurly\n        First = PosMarker((Bare | QuotedString)) << Spaces\n        Attr = Spaces >> (Num | Bare | QuotedString) << Spaces\n        Rest = Many(Attr)\n        Block = BeginBlock >> Many(Stanza).map(skip_none).map(self.to_entries) << EndBlock\n        Stmt = WS >> (Script | (First + Rest + Opt(Block))) << WS\n        Stanza <= WS >> (Stmt | Comment) << WS\n        Doc = Many(Stanza).map(skip_none).map(self.to_entries)\n\n        self.Top = Doc << EOF\n\n    def to_entries(self, x):\n        ret = []\n        for i in x:\n            name, attrs, body = i\n            if body:\n                for n in [name.value] + attrs:\n                    ret.append(Section(name=n, children=body, lineno=name.lineno))\n            else:\n                ret.append(Directive(name=name.value, attrs=attrs, lineno=name.lineno))\n        return ret\n\n    def __call__(self, content):\n        return self.Top(content)\n\n\ndef parse_doc(content, ctx=None):\n    \"\"\" Parse a configuration document into a tree that can be queried. \"\"\"\n    if isinstance(content, list):\n        content = \"\\n\".join(content)\n    parse = DocParser(ctx)\n    result = parse(content)\n    return Entry(children=result, src=ctx)\n\n\n@parser(Specs.logrotate_conf, continue_on_error=False)\nclass _LogRotateConf(ConfigParser):\n    def parse_doc(self, content):\n        return parse_doc(\"\\n\".join(content), ctx=self)\n\n\n@combiner(_LogRotateConf)\nclass LogRotateConfTree(ConfigCombiner):\n    \"\"\"\n    Exposes logrotate configuration through the parsr query interface.\n\n    See the :py:class:`insights.core.ConfigComponent` class for example usage.\n    \"\"\"\n    def __init__(self, confs):\n        include = eq(\"include\")\n        main_file = \"logrotate.conf\"\n        super(LogRotateConfTree, self).__init__(confs, main_file, include)\n\n    @property\n    def conf_path(self):\n        return os.path.dirname(self.main.file_path)\n\n    def find_matches(self, confs, pattern):\n        results = []\n        for c in confs:\n            if os.path.isdir(pattern) and c.file_path.startswith(pattern):\n                results.append(c)\n            elif fnmatch(c.file_path, pattern):\n                results.append(c)\n        return sorted(results, key=operator.attrgetter(\"file_name\"))\n\n\ndef get_tree(root=None):\n    \"\"\"\n    This is a helper function to get a logrotate configuration component for\n    your local machine or an archive. It's for use in interactive sessions.\n    \"\"\"\n    from insights import run\n    return run(LogRotateConfTree, root=root).get(LogRotateConfTree)\n", "path": "insights/combiners/logrotate_conf.py"}]}