Skip to content

Commit

Permalink
Merge pull request #60 from nginxinc/add-better-formatter
Browse files Browse the repository at this point in the history
Added crossplane.format
  • Loading branch information
aluttik authored Jan 8, 2019
2 parents de68daf + a76d633 commit d8fd35f
Show file tree
Hide file tree
Showing 7 changed files with 122 additions and 20 deletions.
3 changes: 2 additions & 1 deletion crossplane/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@
from .parser import parse
from .lexer import lex
from .builder import build
from .formatter import format
from .ext.lua import LuaBlockPlugin

__all__ = ['parse', 'lex', 'build']
__all__ = ['parse', 'lex', 'build', 'format']

__title__ = 'crossplane'
__summary__ = 'Reliable and fast NGINX configuration file parser.'
Expand Down
14 changes: 4 additions & 10 deletions crossplane/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from .lexer import lex as lex_file
from .parser import parse as parse_file
from .builder import build as build_string, build_files, _enquote, DELIMITERS
from .errors import NgxParserBaseException
from .formatter import format as format_file
from .compat import json, input


Expand Down Expand Up @@ -123,15 +123,9 @@ def minify(filename, out):
out.write('\n')


def format(filename, out, indent=None, tabs=False):
payload = parse_file(filename)
parsed = payload['config'][0]['parsed']
if payload['status'] == 'ok':
output = build_string(parsed, indent=indent, tabs=tabs) + '\n'
out.write(output)
else:
e = payload['errors'][0]
raise NgxParserBaseException(e['error'], e['file'], e['line'])
def format(filename, out, indent=4, tabs=False):
output = format_file(filename, indent=indent, tabs=tabs)
out.write(output + '\n')


class _SubparserHelpFormatter(RawDescriptionHelpFormatter):
Expand Down
17 changes: 11 additions & 6 deletions crossplane/analyzer.py
Original file line number Diff line number Diff line change
Expand Up @@ -1902,7 +1902,9 @@ def enter_block_ctx(stmt, ctx):
return ctx + (stmt['directive'],)


def analyze(fname, stmt, term, ctx=(), strict=False):
def analyze(fname, stmt, term, ctx=(), strict=False, check_ctx=True,
check_args=True):

directive = stmt['directive']
line = stmt['line']

Expand All @@ -1920,13 +1922,16 @@ def analyze(fname, stmt, term, ctx=(), strict=False):
n_args = len(args)

masks = DIRECTIVES[directive]
ctx_mask = CONTEXTS[ctx]

# if this directive can't be used in this context then throw an error
masks = [mask for mask in masks if mask & ctx_mask]
if not masks:
reason = '"%s" directive is not allowed here' % directive
raise NgxParserDirectiveContextError(reason, fname, line)
if check_ctx:
masks = [mask for mask in masks if mask & CONTEXTS[ctx]]
if not masks:
reason = '"%s" directive is not allowed here' % directive
raise NgxParserDirectiveContextError(reason, fname, line)

if not check_args:
return

valid_flag = lambda x: x.lower() in ('on', 'off')

Expand Down
16 changes: 16 additions & 0 deletions crossplane/formatter.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# -*- coding: utf-8 -*-
from .errors import NgxParserBaseException
from .builder import build
from .parser import parse


def format(filename, indent=4, tabs=False):
payload = parse(filename, single=True, check_ctx=False, check_args=False)

if payload['status'] != 'ok':
e = payload['errors'][0]
raise NgxParserBaseException(e['error'], e['file'], e['line'])

parsed = payload['config'][0]['parsed']
output = build(parsed, indent=indent, tabs=tabs)
return output
12 changes: 9 additions & 3 deletions crossplane/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,11 @@ def _prepare_if_args(stmt):


def parse(filename, onerror=None, catch_errors=True, ignore=(), single=False,
comments=False, strict=False, combine=False):
comments=False, strict=False, combine=False, check_ctx=True,
check_args=True):
"""
Parses an nginx config file and returns a nested dict payload
:param filename: string contianing the name of the config file to parse
:param onerror: function that determines what's saved in "callback"
:param catch_errors: bool; if False, parse stops after first error
Expand All @@ -35,6 +36,8 @@ def parse(filename, onerror=None, catch_errors=True, ignore=(), single=False,
:param single: bool; if True, including from other files doesn't happen
:param comments: bool; if True, including comments to json payload
:param strict: bool; if True, unrecognized directives raise errors
:param check_ctx: bool; if True, runs context analysis on directives
:param check_args: bool; if True, runs arg count analysis on directives
:returns: a payload that describes the parsed nginx config
"""
config_dir = os.path.dirname(filename)
Expand Down Expand Up @@ -131,7 +134,10 @@ def _parse(parsing, tokens, ctx=(), consume=False):

try:
# raise errors if this statement is invalid
analyze(fname, stmt, token, ctx, strict=strict)
analyze(
fname=fname, stmt=stmt, term=token, ctx=ctx, strict=strict,
check_ctx=check_ctx, check_args=check_args
)
except NgxParserDirectiveError as e:
if catch_errors:
_handle_error(parsing, e)
Expand Down
3 changes: 3 additions & 0 deletions tests/configs/bad-args/nginx.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
user;
events {}
http {}
77 changes: 77 additions & 0 deletions tests/test_format.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
# -*- coding: utf-8 -*-
import os

import crossplane
from . import here


def test_format_messy_config():
dirname = os.path.join(here, 'configs', 'messy')
config = os.path.join(dirname, 'nginx.conf')
output = crossplane.format(config)
assert output == '\n'.join([
'user nobody;',
'events {',
' worker_connections 2048;',
'}',
'http {',
' access_log off;',
' default_type text/plain;',
' error_log off;',
' server {',
' listen 8083;',
r""" return 200 'Ser" \' \' ver\\ \ $server_addr:\$server_port\n\nTime: $time_local\n\n';""",
' }',
' server {',
' listen 8080;',
' root /usr/share/nginx/html;',
" location ~ '/hello/world;' {",
' return 301 /status.html;',
' }',
' location /foo {',
' }',
' location /bar {',
' }',
' location /\{\;\}\ #\ ab {',
' }',
' if ($request_method = P\{O\)\###\;ST) {',
' }',
' location /status.html {',
" try_files '/abc/${uri} /abc/${uri}.html' =404;",
' }',
r" location '/sta;\n tus' {",
' return 302 /status.html;',
' }',
' location /upstream_conf {',
' return 200 /status.html;',
' }',
' }',
' server {',
' }',
'}'
])


def test_format_not_main_file():
dirname = os.path.join(here, 'configs', 'includes-globbed', 'servers')
config = os.path.join(dirname, 'server1.conf')
output = crossplane.format(config)
assert output == '\n'.join([
'server {',
' listen 8080;',
' include locations/*.conf;',
'}'
])


def test_format_args_not_analyzed():
dirname = os.path.join(here, 'configs', 'bad-args')
config = os.path.join(dirname, 'nginx.conf')
output = crossplane.format(config)
assert output == '\n'.join([
'user;',
'events {',
'}',
'http {',
'}'
])

0 comments on commit d8fd35f

Please sign in to comment.