We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
nginx plugin destroys config
I have a config file called webp.conf in /etc/nginx/conf.d/ which works great.
After running letsencrypt -d example.org run the webp.conf is broken because it's missing a closing }
https://pastebin.mozilla.org/8837365
Line 18 gets removed.


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `letsencrypt-nginx/letsencrypt_nginx/nginxparser.py`
Content:
```
1 """Very low-level nginx config parser based on pyparsing."""
2 import string
3 
4 from pyparsing import (
5     Literal, White, Word, alphanums, CharsNotIn, Forward, Group,
6     Optional, OneOrMore, Regex, ZeroOrMore)
7 from pyparsing import stringEnd
8 from pyparsing import restOfLine
9 
10 class RawNginxParser(object):
11     # pylint: disable=expression-not-assigned
12     """A class that parses nginx configuration with pyparsing."""
13 
14     # constants
15     left_bracket = Literal("{").suppress()
16     right_bracket = Literal("}").suppress()
17     semicolon = Literal(";").suppress()
18     space = White().suppress()
19     key = Word(alphanums + "_/")
20     # Matches anything that is not a special character AND any chars in single
21     # or double quotes
22     value = Regex(r"((\".*\")?(\'.*\')?[^\{\};,]?)+")
23     location = CharsNotIn("{};," + string.whitespace)
24     # modifier for location uri [ = | ~ | ~* | ^~ ]
25     modifier = Literal("=") | Literal("~*") | Literal("~") | Literal("^~")
26 
27     # rules
28     comment = Literal('#') + restOfLine()
29     assignment = (key + Optional(space + value, default=None) + semicolon)
30     location_statement = Optional(space + modifier) + Optional(space + location)
31     if_statement = Literal("if") + space + Regex(r"\(.+\)") + space
32     block = Forward()
33 
34     block << Group(
35         (Group(key + location_statement) ^ Group(if_statement))
36         + left_bracket
37         + Group(ZeroOrMore(Group(comment | assignment) | block))
38         + right_bracket)
39 
40     script = OneOrMore(Group(comment | assignment) | block) + stringEnd
41 
42     def __init__(self, source):
43         self.source = source
44 
45     def parse(self):
46         """Returns the parsed tree."""
47         return self.script.parseString(self.source)
48 
49     def as_list(self):
50         """Returns the parsed tree as a list."""
51         return self.parse().asList()
52 
53 
54 class RawNginxDumper(object):
55     # pylint: disable=too-few-public-methods
56     """A class that dumps nginx configuration from the provided tree."""
57     def __init__(self, blocks, indentation=4):
58         self.blocks = blocks
59         self.indentation = indentation
60 
61     def __iter__(self, blocks=None, current_indent=0, spacer=' '):
62         """Iterates the dumped nginx content."""
63         blocks = blocks or self.blocks
64         for key, values in blocks:
65             indentation = spacer * current_indent
66             if isinstance(key, list):
67                 if current_indent:
68                     yield ''
69                 yield indentation + spacer.join(key) + ' {'
70 
71                 for parameter in values:
72                     dumped = self.__iter__([parameter], current_indent + self.indentation)
73                     for line in dumped:
74                         yield line
75 
76                 yield indentation + '}'
77             else:
78                 if key == '#':
79                     yield spacer * current_indent + key + values
80                 else:
81                     if values is None:
82                         yield spacer * current_indent + key + ';'
83                     else:
84                         yield spacer * current_indent + key + spacer + values + ';'
85 
86     def as_string(self):
87         """Return the parsed block as a string."""
88         return '\n'.join(self) + '\n'
89 
90 
91 # Shortcut functions to respect Python's serialization interface
92 # (like pyyaml, picker or json)
93 
94 def loads(source):
95     """Parses from a string.
96 
97     :param str souce: The string to parse
98     :returns: The parsed tree
99     :rtype: list
100 
101     """
102     return RawNginxParser(source).as_list()
103 
104 
105 def load(_file):
106     """Parses from a file.
107 
108     :param file _file: The file to parse
109     :returns: The parsed tree
110     :rtype: list
111 
112     """
113     return loads(_file.read())
114 
115 
116 def dumps(blocks, indentation=4):
117     """Dump to a string.
118 
119     :param list block: The parsed tree
120     :param int indentation: The number of spaces to indent
121     :rtype: str
122 
123     """
124     return RawNginxDumper(blocks, indentation).as_string()
125 
126 
127 def dump(blocks, _file, indentation=4):
128     """Dump to a file.
129 
130     :param list block: The parsed tree
131     :param file _file: The file to dump to
132     :param int indentation: The number of spaces to indent
133     :rtype: NoneType
134 
135     """
136     return _file.write(dumps(blocks, indentation))
137
```
--- 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/letsencrypt-nginx/letsencrypt_nginx/nginxparser.py b/letsencrypt-nginx/letsencrypt_nginx/nginxparser.py
--- a/letsencrypt-nginx/letsencrypt_nginx/nginxparser.py
+++ b/letsencrypt-nginx/letsencrypt_nginx/nginxparser.py
@@ -37,7 +37,7 @@
         + Group(ZeroOrMore(Group(comment | assignment) | block))
         + right_bracket)
 
-    script = OneOrMore(Group(comment | assignment) | block) + stringEnd
+    script = OneOrMore(Group(comment | assignment) ^ block) + stringEnd
 
     def __init__(self, source):
         self.source = source

{"golden_diff": "diff --git a/letsencrypt-nginx/letsencrypt_nginx/nginxparser.py b/letsencrypt-nginx/letsencrypt_nginx/nginxparser.py\n--- a/letsencrypt-nginx/letsencrypt_nginx/nginxparser.py\n+++ b/letsencrypt-nginx/letsencrypt_nginx/nginxparser.py\n@@ -37,7 +37,7 @@\n         + Group(ZeroOrMore(Group(comment | assignment) | block))\n         + right_bracket)\n \n-    script = OneOrMore(Group(comment | assignment) | block) + stringEnd\n+    script = OneOrMore(Group(comment | assignment) ^ block) + stringEnd\n \n     def __init__(self, source):\n         self.source = source\n", "issue": "nginx plugin destroys config\nI have a config file called webp.conf in /etc/nginx/conf.d/ which works great.\nAfter running letsencrypt -d example.org run the webp.conf is broken because it's missing a closing }\nhttps://pastebin.mozilla.org/8837365\nLine 18 gets removed.\n\n", "before_files": [{"content": "\"\"\"Very low-level nginx config parser based on pyparsing.\"\"\"\nimport string\n\nfrom pyparsing import (\n    Literal, White, Word, alphanums, CharsNotIn, Forward, Group,\n    Optional, OneOrMore, Regex, ZeroOrMore)\nfrom pyparsing import stringEnd\nfrom pyparsing import restOfLine\n\nclass RawNginxParser(object):\n    # pylint: disable=expression-not-assigned\n    \"\"\"A class that parses nginx configuration with pyparsing.\"\"\"\n\n    # constants\n    left_bracket = Literal(\"{\").suppress()\n    right_bracket = Literal(\"}\").suppress()\n    semicolon = Literal(\";\").suppress()\n    space = White().suppress()\n    key = Word(alphanums + \"_/\")\n    # Matches anything that is not a special character AND any chars in single\n    # or double quotes\n    value = Regex(r\"((\\\".*\\\")?(\\'.*\\')?[^\\{\\};,]?)+\")\n    location = CharsNotIn(\"{};,\" + string.whitespace)\n    # modifier for location uri [ = | ~ | ~* | ^~ ]\n    modifier = Literal(\"=\") | Literal(\"~*\") | Literal(\"~\") | Literal(\"^~\")\n\n    # rules\n    comment = Literal('#') + restOfLine()\n    assignment = (key + Optional(space + value, default=None) + semicolon)\n    location_statement = Optional(space + modifier) + Optional(space + location)\n    if_statement = Literal(\"if\") + space + Regex(r\"\\(.+\\)\") + space\n    block = Forward()\n\n    block << Group(\n        (Group(key + location_statement) ^ Group(if_statement))\n        + left_bracket\n        + Group(ZeroOrMore(Group(comment | assignment) | block))\n        + right_bracket)\n\n    script = OneOrMore(Group(comment | assignment) | block) + stringEnd\n\n    def __init__(self, source):\n        self.source = source\n\n    def parse(self):\n        \"\"\"Returns the parsed tree.\"\"\"\n        return self.script.parseString(self.source)\n\n    def as_list(self):\n        \"\"\"Returns the parsed tree as a list.\"\"\"\n        return self.parse().asList()\n\n\nclass RawNginxDumper(object):\n    # pylint: disable=too-few-public-methods\n    \"\"\"A class that dumps nginx configuration from the provided tree.\"\"\"\n    def __init__(self, blocks, indentation=4):\n        self.blocks = blocks\n        self.indentation = indentation\n\n    def __iter__(self, blocks=None, current_indent=0, spacer=' '):\n        \"\"\"Iterates the dumped nginx content.\"\"\"\n        blocks = blocks or self.blocks\n        for key, values in blocks:\n            indentation = spacer * current_indent\n            if isinstance(key, list):\n                if current_indent:\n                    yield ''\n                yield indentation + spacer.join(key) + ' {'\n\n                for parameter in values:\n                    dumped = self.__iter__([parameter], current_indent + self.indentation)\n                    for line in dumped:\n                        yield line\n\n                yield indentation + '}'\n            else:\n                if key == '#':\n                    yield spacer * current_indent + key + values\n                else:\n                    if values is None:\n                        yield spacer * current_indent + key + ';'\n                    else:\n                        yield spacer * current_indent + key + spacer + values + ';'\n\n    def as_string(self):\n        \"\"\"Return the parsed block as a string.\"\"\"\n        return '\\n'.join(self) + '\\n'\n\n\n# Shortcut functions to respect Python's serialization interface\n# (like pyyaml, picker or json)\n\ndef loads(source):\n    \"\"\"Parses from a string.\n\n    :param str souce: The string to parse\n    :returns: The parsed tree\n    :rtype: list\n\n    \"\"\"\n    return RawNginxParser(source).as_list()\n\n\ndef load(_file):\n    \"\"\"Parses from a file.\n\n    :param file _file: The file to parse\n    :returns: The parsed tree\n    :rtype: list\n\n    \"\"\"\n    return loads(_file.read())\n\n\ndef dumps(blocks, indentation=4):\n    \"\"\"Dump to a string.\n\n    :param list block: The parsed tree\n    :param int indentation: The number of spaces to indent\n    :rtype: str\n\n    \"\"\"\n    return RawNginxDumper(blocks, indentation).as_string()\n\n\ndef dump(blocks, _file, indentation=4):\n    \"\"\"Dump to a file.\n\n    :param list block: The parsed tree\n    :param file _file: The file to dump to\n    :param int indentation: The number of spaces to indent\n    :rtype: NoneType\n\n    \"\"\"\n    return _file.write(dumps(blocks, indentation))\n", "path": "letsencrypt-nginx/letsencrypt_nginx/nginxparser.py"}], "after_files": [{"content": "\"\"\"Very low-level nginx config parser based on pyparsing.\"\"\"\nimport string\n\nfrom pyparsing import (\n    Literal, White, Word, alphanums, CharsNotIn, Forward, Group,\n    Optional, OneOrMore, Regex, ZeroOrMore)\nfrom pyparsing import stringEnd\nfrom pyparsing import restOfLine\n\nclass RawNginxParser(object):\n    # pylint: disable=expression-not-assigned\n    \"\"\"A class that parses nginx configuration with pyparsing.\"\"\"\n\n    # constants\n    left_bracket = Literal(\"{\").suppress()\n    right_bracket = Literal(\"}\").suppress()\n    semicolon = Literal(\";\").suppress()\n    space = White().suppress()\n    key = Word(alphanums + \"_/\")\n    # Matches anything that is not a special character AND any chars in single\n    # or double quotes\n    value = Regex(r\"((\\\".*\\\")?(\\'.*\\')?[^\\{\\};,]?)+\")\n    location = CharsNotIn(\"{};,\" + string.whitespace)\n    # modifier for location uri [ = | ~ | ~* | ^~ ]\n    modifier = Literal(\"=\") | Literal(\"~*\") | Literal(\"~\") | Literal(\"^~\")\n\n    # rules\n    comment = Literal('#') + restOfLine()\n    assignment = (key + Optional(space + value, default=None) + semicolon)\n    location_statement = Optional(space + modifier) + Optional(space + location)\n    if_statement = Literal(\"if\") + space + Regex(r\"\\(.+\\)\") + space\n    block = Forward()\n\n    block << Group(\n        (Group(key + location_statement) ^ Group(if_statement))\n        + left_bracket\n        + Group(ZeroOrMore(Group(comment | assignment) | block))\n        + right_bracket)\n\n    script = OneOrMore(Group(comment | assignment) ^ block) + stringEnd\n\n    def __init__(self, source):\n        self.source = source\n\n    def parse(self):\n        \"\"\"Returns the parsed tree.\"\"\"\n        return self.script.parseString(self.source)\n\n    def as_list(self):\n        \"\"\"Returns the parsed tree as a list.\"\"\"\n        return self.parse().asList()\n\n\nclass RawNginxDumper(object):\n    # pylint: disable=too-few-public-methods\n    \"\"\"A class that dumps nginx configuration from the provided tree.\"\"\"\n    def __init__(self, blocks, indentation=4):\n        self.blocks = blocks\n        self.indentation = indentation\n\n    def __iter__(self, blocks=None, current_indent=0, spacer=' '):\n        \"\"\"Iterates the dumped nginx content.\"\"\"\n        blocks = blocks or self.blocks\n        for key, values in blocks:\n            indentation = spacer * current_indent\n            if isinstance(key, list):\n                if current_indent:\n                    yield ''\n                yield indentation + spacer.join(key) + ' {'\n\n                for parameter in values:\n                    dumped = self.__iter__([parameter], current_indent + self.indentation)\n                    for line in dumped:\n                        yield line\n\n                yield indentation + '}'\n            else:\n                if key == '#':\n                    yield spacer * current_indent + key + values\n                else:\n                    if values is None:\n                        yield spacer * current_indent + key + ';'\n                    else:\n                        yield spacer * current_indent + key + spacer + values + ';'\n\n    def as_string(self):\n        \"\"\"Return the parsed block as a string.\"\"\"\n        return '\\n'.join(self) + '\\n'\n\n\n# Shortcut functions to respect Python's serialization interface\n# (like pyyaml, picker or json)\n\ndef loads(source):\n    \"\"\"Parses from a string.\n\n    :param str souce: The string to parse\n    :returns: The parsed tree\n    :rtype: list\n\n    \"\"\"\n    return RawNginxParser(source).as_list()\n\n\ndef load(_file):\n    \"\"\"Parses from a file.\n\n    :param file _file: The file to parse\n    :returns: The parsed tree\n    :rtype: list\n\n    \"\"\"\n    return loads(_file.read())\n\n\ndef dumps(blocks, indentation=4):\n    \"\"\"Dump to a string.\n\n    :param list block: The parsed tree\n    :param int indentation: The number of spaces to indent\n    :rtype: str\n\n    \"\"\"\n    return RawNginxDumper(blocks, indentation).as_string()\n\n\ndef dump(blocks, _file, indentation=4):\n    \"\"\"Dump to a file.\n\n    :param list block: The parsed tree\n    :param file _file: The file to dump to\n    :param int indentation: The number of spaces to indent\n    :rtype: NoneType\n\n    \"\"\"\n    return _file.write(dumps(blocks, indentation))\n", "path": "letsencrypt-nginx/letsencrypt_nginx/nginxparser.py"}]}