Skip to content

Commit 6f48ec8

Browse files
authored
Merge pull request #21 from nginxinc/more-parse-options
added --single-file and --ignore to parse
2 parents 641124a + 36e0788 commit 6f48ec8

File tree

3 files changed

+148
-6
lines changed

3 files changed

+148
-6
lines changed

crossplane/__main__.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from argparse import ArgumentParser, FileType, RawDescriptionHelpFormatter
66
from traceback import format_exception
77

8+
from . import __version__
89
from .lexer import lex as lex_file
910
from .parser import parse as parse_file
1011
from .errors import NgxParserBaseException
@@ -70,12 +71,14 @@ def _dump_payload(obj, fp, indent):
7071
fp.write(json.dumps(obj, **kwargs) + '\n')
7172

7273

73-
def parse(filename, out, indent=None, catch=None, tb_onerror=None):
74+
def parse(filename, out, indent=None, catch=None, tb_onerror=None, ignore='', single=False):
75+
ignore = ignore.split(',') if ignore else []
76+
7477
def callback(e):
7578
exc = sys.exc_info() + (10,)
7679
return ''.join(format_exception(*exc)).rstrip()
7780

78-
kwargs = {'catch_errors': catch}
81+
kwargs = {'catch_errors': catch, 'ignore': ignore, 'single': single}
7982
if tb_onerror:
8083
kwargs['onerror'] = callback
8184

@@ -157,6 +160,7 @@ def parse_args(args=None):
157160
description='various operations for nginx config files',
158161
usage='%(prog)s <command> [options]'
159162
)
163+
parser.add_argument('-V', '--version', action='version', version='%(prog)s ' + __version__)
160164
subparsers = parser.add_subparsers(title='commands')
161165

162166
def create_subparser(function, help):
@@ -170,8 +174,10 @@ def create_subparser(function, help):
170174
p.add_argument('filename', help='the nginx config file')
171175
p.add_argument('-o', '--out', type=FileType('w'), default='-', help='write output to a file')
172176
p.add_argument('-i', '--indent', type=int, metavar='NUM', help='number of spaces to indent output')
177+
p.add_argument('--ignore', metavar='DIRECTIVES', default='', help='ignore directives (comma-separated)')
173178
p.add_argument('--no-catch', action='store_false', dest='catch', help='only collect first error in file')
174179
p.add_argument('--tb-onerror', action='store_true', help='include tracebacks in config errors')
180+
p.add_argument('--single-file', action='store_true', dest='single', help='do not include other config files')
175181

176182
p = create_subparser(lex, 'lexes tokens from an nginx config file')
177183
p.add_argument('filename', help='the nginx config file')
@@ -201,7 +207,7 @@ def help(command):
201207

202208
parsed = parser.parse_args(args=args)
203209

204-
# this addresses a bug that was added to argparse in Python 3.3
210+
# this addresses a bug that was added to argparse in Python 3.3
205211
if not parsed.__dict__:
206212
parser.error('too few arguments')
207213

crossplane/parser.py

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,17 @@ def _prepare_if_args(stmt):
1919
args[:] = args[start:end]
2020

2121

22-
def parse(filename, onerror=None, catch_errors=True):
23-
"""Parses an nginx config file and returns a nested dict payload"""
22+
def parse(filename, onerror=None, catch_errors=True, ignore=(), single=False):
23+
"""
24+
Parses an nginx config file and returns a nested dict payload
25+
26+
:param filename: string contianing the name of the config file to parse
27+
:param onerror: function that determines what's saved in "callback"
28+
:param catch_errors: bool; if False, parse stops after first error
29+
:param ignore: list or tuple of directives to exclude from the payload
30+
:param single: bool; if True, including from other files doesn't happen
31+
:returns: a payload that describes the parsed nginx config
32+
"""
2433
config_dir = os.path.dirname(filename)
2534

2635
payload = {
@@ -82,6 +91,13 @@ def _parse(parsing, tokens, ctx=(), consume=False):
8291
stmt['args'].append(token)
8392
token, __ = next(tokens)
8493

94+
# consume the directive if it is ignored and move on
95+
if stmt['directive'] in ignore:
96+
# if this directive was a block consume it too
97+
if token == '{':
98+
_parse(parsing, tokens, consume=True)
99+
continue
100+
85101
# prepare arguments
86102
if stmt['directive'] == 'if':
87103
_prepare_if_args(stmt)
@@ -103,7 +119,7 @@ def _parse(parsing, tokens, ctx=(), consume=False):
103119
raise e
104120

105121
# add "includes" to the payload if this is an include statement
106-
if stmt['directive'] == 'include':
122+
if not single and stmt['directive'] == 'include':
107123
pattern = args[0]
108124
if not os.path.isabs(args[0]):
109125
pattern = os.path.join(config_dir, args[0])

tests/test_parse.py

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -247,3 +247,123 @@ def test_includes_globbed():
247247
}
248248
]
249249
}
250+
251+
252+
def test_includes_single():
253+
dirname = os.path.join(here, 'configs', 'includes-regular')
254+
config = os.path.join(dirname, 'nginx.conf')
255+
payload = crossplane.parse(config, single=True)
256+
assert payload == {
257+
'status': 'ok',
258+
'errors': [],
259+
'config': [
260+
{
261+
'file': os.path.join(dirname, 'nginx.conf'),
262+
'status': 'ok',
263+
'errors': [],
264+
'parsed': [
265+
{
266+
'directive': 'events',
267+
'line': 1,
268+
'args': [],
269+
'block': []
270+
},
271+
{
272+
'directive': 'http',
273+
'line': 2,
274+
'args': [],
275+
'block': [
276+
{
277+
'directive': 'include',
278+
'line': 3,
279+
'args': ['conf.d/server.conf']
280+
# no 'includes' key
281+
}
282+
]
283+
}
284+
]
285+
}
286+
# single config parsed
287+
]
288+
}
289+
290+
291+
def test_ignore_directives():
292+
dirname = os.path.join(here, 'configs', 'simple')
293+
config = os.path.join(dirname, 'nginx.conf')
294+
295+
# check that you can ignore multiple directives
296+
payload = crossplane.parse(config, ignore=['listen', 'server_name'])
297+
assert payload == {
298+
"status": "ok",
299+
"errors": [],
300+
"config": [
301+
{
302+
"file": os.path.join(dirname, 'nginx.conf'),
303+
"status": "ok",
304+
"errors": [],
305+
"parsed": [
306+
{
307+
"directive": "events",
308+
"line": 1,
309+
"args": [],
310+
"block": [
311+
{
312+
"directive": "worker_connections",
313+
"line": 2,
314+
"args": ["1024"]
315+
}
316+
]
317+
},
318+
{
319+
"directive": "http",
320+
"line": 5,
321+
"args": [],
322+
"block": [
323+
{
324+
"directive": "server",
325+
"line": 6,
326+
"args": [],
327+
"block": [
328+
{
329+
"directive": "location",
330+
"line": 9,
331+
"args": ["/"],
332+
"block": [
333+
{
334+
"directive": "return",
335+
"line": 10,
336+
"args": ["200", "foo bar baz"]
337+
}
338+
]
339+
}
340+
]
341+
}
342+
]
343+
}
344+
]
345+
}
346+
]
347+
}
348+
349+
# check that you can also ignore block directives
350+
payload = crossplane.parse(config, ignore=['events', 'server'])
351+
assert payload == {
352+
"status": "ok",
353+
"errors": [],
354+
"config": [
355+
{
356+
"file": os.path.join(dirname, 'nginx.conf'),
357+
"status": "ok",
358+
"errors": [],
359+
"parsed": [
360+
{
361+
"directive": "http",
362+
"line": 5,
363+
"args": [],
364+
"block": []
365+
}
366+
]
367+
}
368+
]
369+
}

0 commit comments

Comments
 (0)