Skip to content

Commit 5d292d9

Browse files
author
Jacob Cook
committed
Permit parsing of general nginx conf files
1 parent 075aff5 commit 5d292d9

File tree

1 file changed

+90
-104
lines changed

1 file changed

+90
-104
lines changed

nginx.py

Lines changed: 90 additions & 104 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,13 @@
1111
INDENT = ' '
1212

1313

14+
def bump_child_depth(obj, depth):
15+
children = getattr(obj, 'children', [])
16+
for child in children:
17+
child._depth = depth + 1
18+
bump_child_depth(child, child._depth)
19+
20+
1421
class Conf(object):
1522
"""
1623
Represents an nginx configuration.
@@ -97,41 +104,48 @@ def as_strings(self):
97104
else:
98105
for y in x.as_strings:
99106
ret.append(y)
107+
ret[-1] = re.sub('}\n+$', '}\n', ret[-1])
100108
return ret
101109

102110

103-
class Server(object):
111+
class Container(object):
104112
"""
105-
Represents an nginx server block.
113+
Represents a type of child block found in an nginx config.
106114
107-
A `Server` contains a list of key-values used to set up the web server
108-
for a particular site. Can also contain other objects like Location blocks.
115+
Intended to be subclassed by various types of child blocks, like
116+
Locations or Geo blocks.
109117
"""
110118

111-
def __init__(self, *args):
119+
def __init__(self, value, *args):
112120
"""
113121
Initialize object.
114122
115-
:param *args: Any objects to include in this Server block.
123+
:param str value: Value to be used in name (e.g. regex for Location)
124+
:param *args: Any objects to include in this Conf.
116125
"""
126+
self.name = ''
127+
self.value = value
128+
self._depth = 0
117129
self.children = list(args)
130+
bump_child_depth(self, self._depth)
118131

119132
def add(self, *args):
120133
"""
121-
Add object(s) to the Server block.
134+
Add object(s) to the Container.
122135
123-
:param *args: Any objects to add to the Server block.
124-
:returns: full list of Server block's child objects
136+
:param *args: Any objects to add to the Container.
137+
:returns: full list of Container's child objects
125138
"""
126139
self.children.extend(args)
140+
bump_child_depth(self, self._depth)
127141
return self.children
128142

129143
def remove(self, *args):
130144
"""
131-
Remove object(s) from the Server block.
145+
Remove object(s) from the Container.
132146
133-
:param *args: Any objects to remove from the Server block.
134-
:returns: full list of Server block's child objects
147+
:param *args: Any objects to remove from the Container.
148+
:returns: full list of Container's child objects
135149
"""
136150
for x in args:
137151
self.children.remove(x)
@@ -171,88 +185,6 @@ def keys(self):
171185
"""Return a list of child Key objects."""
172186
return [x for x in self.children if isinstance(x, Key)]
173187

174-
@property
175-
def as_list(self):
176-
"""Return all child objects in nested lists of strings."""
177-
return ['server', '', [x.as_list for x in self.children]]
178-
179-
@property
180-
def as_dict(self):
181-
"""Return all child objects in nested dict."""
182-
return {'server': [x.as_dict for x in self.children]}
183-
184-
@property
185-
def as_strings(self):
186-
"""Return the entire Server block as nginx config strings."""
187-
ret = []
188-
ret.append('\nserver {\n')
189-
for x in self.children:
190-
if isinstance(x, Key):
191-
ret.append(INDENT + x.as_strings)
192-
elif isinstance(x, Comment):
193-
if x.inline and len(ret) >= 1:
194-
ret[-1] = ret[-1].rstrip('\n') + ' ' + x.as_strings
195-
else:
196-
ret.append(INDENT + x.as_strings)
197-
elif isinstance(x, Container):
198-
y = x.as_strings
199-
ret.append('\n' + INDENT + y[0])
200-
for z in y[1:]:
201-
ret.append(INDENT+z)
202-
ret.append('}\n')
203-
return ret
204-
205-
206-
class Container(object):
207-
"""
208-
Represents a type of child block found in an nginx config.
209-
210-
Intended to be subclassed by various types of child blocks, like
211-
Locations or Geo blocks.
212-
"""
213-
214-
def __init__(self, value, *args):
215-
"""
216-
Initialize object.
217-
218-
:param str value: Value to be used in name (e.g. regex for Location)
219-
:param *args: Any objects to include in this Conf.
220-
"""
221-
self.name = ''
222-
self.value = value
223-
self.children = list(args)
224-
225-
def add(self, *args):
226-
"""
227-
Add object(s) to the Container.
228-
229-
:param *args: Any objects to add to the Container.
230-
:returns: full list of Container's child objects
231-
"""
232-
self.children.extend(args)
233-
return self.children
234-
235-
def remove(self, *args):
236-
"""
237-
Remove object(s) from the Container.
238-
239-
:param *args: Any objects to remove from the Container.
240-
:returns: full list of Container's child objects
241-
"""
242-
for x in args:
243-
self.children.remove(x)
244-
return self.children
245-
246-
@property
247-
def comments(self):
248-
"""Return a list of child Comment objects."""
249-
return [x for x in self.children if isinstance(x, Comment)]
250-
251-
@property
252-
def keys(self):
253-
"""Return a list of child Key objects."""
254-
return [x for x in self.children if isinstance(x, Key)]
255-
256188
@property
257189
def as_list(self):
258190
"""Return all child objects in nested lists of strings."""
@@ -268,7 +200,11 @@ def as_dict(self):
268200
def as_strings(self):
269201
"""Return the entire Container as nginx config strings."""
270202
ret = []
271-
ret.append('{0} {1} {{\n'.format(self.name, self.value))
203+
container_title = (INDENT * self._depth)
204+
container_title += '{0}{1} {{\n'.format(
205+
self.name, (' {0}'.format(self.value) if self.value else '')
206+
)
207+
ret.append(container_title)
272208
for x in self.children:
273209
if isinstance(x, Key):
274210
ret.append(INDENT + x.as_strings)
@@ -279,13 +215,14 @@ def as_strings(self):
279215
ret.append(INDENT + x.as_strings)
280216
elif isinstance(x, Container):
281217
y = x.as_strings
282-
ret.append('\n' + INDENT + INDENT + y[0])
218+
ret.append('\n' + y[0])
283219
for z in y[1:]:
284220
ret.append(INDENT + z)
285221
else:
286222
y = x.as_strings
287223
ret.append(INDENT + y)
288-
ret.append('}\n')
224+
ret[-1] = re.sub('}\n+$', '}\n', ret[-1])
225+
ret.append('}\n\n')
289226
return ret
290227

291228

@@ -318,6 +255,29 @@ def as_strings(self):
318255
return '# {0}\n'.format(self.comment)
319256

320257

258+
class Http(Container):
259+
"""Container for HTTP sections in the main NGINX conf file."""
260+
261+
def __init__(self, *args):
262+
"""Initialize."""
263+
super(Http, self).__init__('', *args)
264+
self.name = 'http'
265+
266+
267+
class Server(Container):
268+
"""Container for server block configurations."""
269+
270+
def __init__(self, *args):
271+
"""Initialize."""
272+
super(Server, self).__init__('', *args)
273+
self.name = 'server'
274+
275+
@property
276+
def as_dict(self):
277+
"""Return all child objects in nested dict."""
278+
return {'server': [x.as_dict for x in self.children]}
279+
280+
321281
class Location(Container):
322282
"""Container for Location-based options."""
323283

@@ -327,6 +287,15 @@ def __init__(self, value, *args):
327287
self.name = 'location'
328288

329289

290+
class Events(Container):
291+
"""Container for Event-based options."""
292+
293+
def __init__(self, *args):
294+
"""Initialize."""
295+
super(Events, self).__init__('', *args)
296+
self.name = 'events'
297+
298+
330299
class LimitExcept(Container):
331300
"""Container for specifying HTTP method restrictions."""
332301

@@ -420,6 +389,20 @@ def loads(data, conf=True):
420389
index = 0
421390

422391
while True:
392+
m = re.compile(r'^\s*events\s*{', re.S).search(data[index:])
393+
if m:
394+
e = Events()
395+
lopen.insert(0, e)
396+
index += m.end()
397+
continue
398+
399+
m = re.compile(r'^\s*http\s*{', re.S).search(data[index:])
400+
if m:
401+
h = Http()
402+
lopen.insert(0, h)
403+
index += m.end()
404+
continue
405+
423406
m = re.compile(r'^\s*server\s*{', re.S).search(data[index:])
424407
if m:
425408
s = Server()
@@ -458,7 +441,7 @@ def loads(data, conf=True):
458441
m = re.compile(r'^(\s*)#\s*(.*?)\n', re.S).search(data[index:])
459442
if m:
460443
c = Comment(m.group(2), inline='\n' not in m.group(1))
461-
if lopen and isinstance(lopen[0], (Container, Server)):
444+
if lopen and isinstance(lopen[0], Container):
462445
lopen[0].add(c)
463446
else:
464447
f.add(c) if conf else f.append(c)
@@ -467,13 +450,10 @@ def loads(data, conf=True):
467450

468451
m = re.compile(r'^\s*}', re.S).search(data[index:])
469452
if m:
470-
if isinstance(lopen[0], Server):
471-
f.add(lopen[0]) if conf else f.append(lopen[0])
472-
lopen.pop(0)
473-
elif isinstance(lopen[0], Container):
453+
if isinstance(lopen[0], Container):
474454
c = lopen[0]
475455
lopen.pop(0)
476-
if lopen and isinstance(lopen[0], (Container, Server)):
456+
if lopen and isinstance(lopen[0], Container):
477457
lopen[0].add(c)
478458
else:
479459
f.add(c) if conf else f.append(c)
@@ -484,7 +464,13 @@ def loads(data, conf=True):
484464
key_wo_quoted = r'^\s*([a-zA-Z0-9-_]+?)\s+(.+?);'
485465
m1 = re.compile(key_with_quoted, re.S).search(data[index:])
486466
m2 = re.compile(key_wo_quoted, re.S).search(data[index:])
487-
m = m1 or m2
467+
if m1 and m2:
468+
if m1.start() <= m2.start():
469+
m = m1
470+
else:
471+
m = m2
472+
else:
473+
m = m1 or m2
488474
if m:
489475
k = Key(m.group(1), m.group(2))
490476
if lopen and isinstance(lopen[0], (Container, Server)):

0 commit comments

Comments
 (0)