Now handles some conf files in whitespace-preserving mode

(but not all of them)
This commit is contained in:
Peter Eckersley 2016-06-08 17:52:35 -07:00
parent 44edbecba0
commit 8147c671e4
3 changed files with 53 additions and 114 deletions

View file

@ -18,8 +18,7 @@ class RawNginxParser(object):
left_bracket = Literal("{").suppress()
right_bracket = Literal("}").suppress()
semicolon = Literal(";").suppress()
space = White().suppress()
keepSpace = Optional(White())
space = Optional(White())
key = Word(alphanums + "_/+-.")
# Matches anything that is not a special character AND any chars in single
# or double quotes
@ -29,21 +28,23 @@ class RawNginxParser(object):
modifier = Literal("=") | Literal("~*") | Literal("~") | Literal("^~")
# rules
comment = Literal('#') + restOfLine()
comment = space + Literal('#') + restOfLine()
assignment = keepSpace + key + Optional(space + value, default=None) + semicolon
location_statement = Optional(space + modifier) + Optional(space + location)
if_statement = Literal("if") + space + Regex(r"\(.+\)") + space
map_statement = Literal("map") + space + Regex(r"\S+") + space + Regex(r"\$\S+") + space
assignment = space + key + Optional(space + value, default=None) + semicolon
location_statement = space + Optional(modifier) + Optional(space + location)
if_statement = space + Literal("if") + space + Regex(r"\(.+\)") + space
map_statement = space + Literal("map") + space + Regex(r"\S+") + space + Regex(r"\$\S+") + space
block = Forward()
block << Group(
# XXX could this "key" be Literal("location")?
(Group(key + location_statement) ^ Group(if_statement) ^ Group(map_statement)) +
left_bracket +
Group(ZeroOrMore(Group(comment | assignment) | block)) +
right_bracket)
script = OneOrMore(Group(comment | assignment) ^ block) + stringEnd
script.parseWithTabs()
def __init__(self, source):
self.source = source
@ -56,51 +57,6 @@ class RawNginxParser(object):
"""Returns the parsed tree as a list."""
return self.parse().asList()
class OldRawNginxParser(object):
# pylint: disable=expression-not-assigned
"""A class that parses nginx configuration with pyparsing."""
# constants
left_bracket = Literal("{").suppress()
right_bracket = Literal("}").suppress()
semicolon = Literal(";").suppress()
space = White().suppress()
key = Word(alphanums + "_/+-.")
# Matches anything that is not a special character AND any chars in single
# or double quotes
value = Regex(r"((\".*\")?(\'.*\')?[^\{\};,]?)+")
location = CharsNotIn("{};," + string.whitespace)
# modifier for location uri [ = | ~ | ~* | ^~ ]
modifier = Literal("=") | Literal("~*") | Literal("~") | Literal("^~")
# rules
comment = Literal("#") + restOfLine()
assignment = (key + Optional(space + value, default=None) + semicolon)
location_statement = Optional(space + modifier) + Optional(space + location)
if_statement = Literal("if") + space + Regex(r"\(.+\)") + space
map_statement = Literal("map") + space + Regex(r"\S+") + space + Regex(r"\$\S+") + space
block = Forward()
block << Group(
(Group(key + location_statement) ^ Group(if_statement) ^ Group(map_statement)) +
left_bracket +
Group(ZeroOrMore(Group(comment | assignment) | block)) +
right_bracket)
script = OneOrMore(Group(comment | assignment) ^ block) + stringEnd
def __init__(self, source):
self.source = source
def parse(self):
"""Returns the parsed tree."""
return self.script.parseString(self.source)
def as_list(self):
"""Returns the parsed tree as a list."""
return self.parse().asList()
class RawNginxDumper(object):
# pylint: disable=too-few-public-methods
"""A class that dumps nginx configuration from the provided tree."""
@ -108,35 +64,41 @@ class RawNginxDumper(object):
self.blocks = blocks
self.indentation = indentation
def __iter__(self, blocks=None, spacer=' '):
def __iter__(self, blocks=None):
"""Iterates the dumped nginx content."""
blocks = blocks or self.blocks
print "iterating", blocks
for b in blocks:
b0 = b
b = copy.deepcopy(b)
indentation = ""
if spacey(b[0]):
indentation = b.pop(0)
indentation = indentation.replace("\n", "", 1)
key = b.pop(0)
values = b.pop(0)
values = b
if isinstance(key, list):
yield indentation + spacer.join(key) + ' {'
yield indentation + "".join(key) + '{'
for parameter in values:
dumped = self.__iter__([parameter])
for line in dumped:
yield line
yield '}'
else:
#yield b
if isinstance(key, str) and key.strip() == '#':
yield indentation + key + values
else:
gap = ""
# Sometimes the parser has stuck some gap whitespace in here;
# if so rotate it into gap
if spacey(values):
gap = values
values = b.pop(0)
if values is None:
yield indentation + key + ';'
yield indentation + key + gap + ';'
else:
yield indentation + key + spacer + values + ';'
yield indentation + key + gap + values + ';'
def __str__(self):
"""Return the parsed block as a string."""
@ -146,42 +108,6 @@ class RawNginxDumper(object):
class OldRawNginxDumper(object):
# pylint: disable=too-few-public-methods
"""A class that dumps nginx configuration from the provided tree."""
def __init__(self, blocks, indentation=4):
self.blocks = blocks
self.indentation = indentation
def __iter__(self, blocks=None, current_indent=0, spacer=' '):
"""Iterates the dumped nginx content."""
blocks = blocks or self.blocks
for key, values in blocks:
indentation = spacer * current_indent
if isinstance(key, list):
if current_indent:
yield ''
yield indentation + spacer.join(key) + ' {'
for parameter in values:
dumped = self.__iter__([parameter], current_indent + self.indentation)
for line in dumped:
yield line
yield indentation + '}'
else:
if key == '#':
yield spacer * current_indent + key + values
else:
if values is None:
yield spacer * current_indent + key + ';'
else:
yield spacer * current_indent + key + spacer + values + ';'
def __str__(self):
"""Return the parsed block as a string."""
return '\n'.join(self) + '\n'
# Shortcut functions to respect Python's serialization interface
# (like pyyaml, picker or json)
@ -194,10 +120,6 @@ def loads(source):
:rtype: list
"""
old = OldRawNginxParser(source).as_list()
print "Old:"
for entry in old:
print len(entry), " ",
new = UnspacedList(RawNginxParser(source).as_list())
print "\nNew:"
print new
@ -207,14 +129,6 @@ def loads(source):
for entry in new.spaced:
print str(len(entry))+ " ",
print "\ngo"
if old != new:
print "NON-MATCH"
for a, b in zip(old, new):
if a != b:
print "Entry", a, "!=", b
import sys
else:
print "Parallel"
return new
@ -229,7 +143,7 @@ def load(_file):
return loads(_file.read())
def dumps(blocks, indentation=4):
def dumps(blocks):
"""Dump to a string.
:param UnspacedList block: The parsed tree
@ -237,10 +151,10 @@ def dumps(blocks, indentation=4):
:rtype: str
"""
return str(RawNginxDumper(blocks, indentation))
return str(RawNginxDumper(blocks))
def dump(blocks, _file, indentation=4):
def dump(blocks, _file):
"""Dump to a file.
:param UnspacedList block: The parsed tree
@ -249,7 +163,7 @@ def dump(blocks, _file, indentation=4):
:rtype: NoneType
"""
return _file.write(dumps(blocks, indentation))
return _file.write(dumps(blocks))
spacey = lambda x: isinstance(x, str) and x.isspace()

View file

@ -4,6 +4,7 @@ import logging
import os
import pyparsing
import re
import sys
from certbot import errors
@ -213,9 +214,26 @@ class NginxParser(object):
if ext:
filename = filename + os.path.extsep + ext
try:
logger.debug('Dumping to %s:\n%s', filename, nginxparser.dumps(tree))
out = nginxparser.dumps(tree)
logger.debug('Dumping to %s:\n%s', filename, out)
with open(filename, 'w') as _file:
nginxparser.dump(tree, _file)
_file.write(out)
if "owncloud" in filename:
print "Outputting", filename
print out
a = open("/tmp/nginx/sites-enabled/owncloud.conf").read()
b = open(filename).read()
for linea, lineb in zip(a.split('\n'), b.split('\n')):
if linea != lineb:
print "a", repr(linea)
print "b", repr(lineb)
if a != b:
print "Mismatch!"
if a != out:
print "Double mismatch", len(a), len(out)
else:
print "Match!"
except IOError:
logger.error("Could not open file for writing: %s", filename)

View file

@ -3,6 +3,7 @@
import itertools
import logging
import os
import sys
from certbot import errors
from certbot.plugins import common
@ -123,7 +124,13 @@ class NginxTlsSni01(common.TLSSNI01):
True, self.challenge_conf)
with open(self.challenge_conf, "w") as new_conf:
nginxparser.dump(config, new_conf)
if "mime" in self.challenge_conf:
print "Weird"
out = nginxparser.dumps(config)
print out
#sys.exit(1)
#nginxparser.dump(config, new_conf)
new_conf.write(out)
def _make_server_block(self, achall, addrs):
"""Creates a server block for a challenge.