diff --git a/README.md b/README.md index dd889b4..be2c1d6 100644 --- a/README.md +++ b/README.md @@ -431,16 +431,19 @@ formatting tool. If that is what you are looking for, then you may want to look writing your own using crossplane's Python API. ``` -usage: crossplane format [-h] [-o OUT] [-i NUM | -t] filename +usage: crossplane format [-h] [--align] [--spacious] [-o OUT | -w] [-i NUM | -t] filename formats an nginx config file positional arguments: filename the nginx config file -optional arguments: +options: -h, --help show this help message and exit + --align align directives within blocks + --spacious add line breaks after blocks -o OUT, --out OUT write output to a file + -w, --write write output to a source file -i NUM, --indent NUM number of spaces to indent output -t, --tabs indent with tabs instead of spaces ``` diff --git a/crossplane/__main__.py b/crossplane/__main__.py index 0ac64f6..0c408f5 100644 --- a/crossplane/__main__.py +++ b/crossplane/__main__.py @@ -152,9 +152,15 @@ def write_block(block): o.close() -def format(filename, out, indent=4, tabs=False): - output = format_file(filename, indent=indent, tabs=tabs) - o = sys.stdout if out is None else io.open(out, 'w', encoding='utf-8') +def format(filename, out, indent=4, tabs=False, align=False, spacious=False, write=False): + output = format_file(filename, indent=indent, tabs=tabs, align=align, spacious=spacious) + + o = sys.stdout + if write: + o = io.open(filename, 'w', encoding='utf-8') + elif out is not None: + o = io.open(out, 'w', encoding='utf-8') + try: o.write(output + u'\n') finally: @@ -226,7 +232,11 @@ def create_subparser(function, help): p = create_subparser(format, 'formats an nginx config file') p.add_argument('filename', help='the nginx config file') - p.add_argument('-o', '--out', type=str, help='write output to a file') + p.add_argument('--align', action='store_true', help='align directives within blocks') + p.add_argument('--spacious', action='store_true', help="add line breaks after blocks") + g = p.add_mutually_exclusive_group() + g.add_argument('-o', '--out', type=str, help='write output to a file') + g.add_argument('-w', '--write', action='store_true', help='write output to a source file') g = p.add_mutually_exclusive_group() g.add_argument('-i', '--indent', type=int, metavar='NUM', help='number of spaces to indent output', default=4) g.add_argument('-t', '--tabs', action='store_true', help='indent with tabs instead of spaces') diff --git a/crossplane/builder.py b/crossplane/builder.py index 049d224..f9a9196 100644 --- a/crossplane/builder.py +++ b/crossplane/builder.py @@ -71,7 +71,7 @@ def _enquote(arg): return arg -def build(payload, indent=4, tabs=False, header=False): +def build(payload, indent=4, tabs=False, header=False, align=False, spacious=False): padding = '\t' if tabs else ' ' * indent head = '' @@ -84,10 +84,19 @@ def build(payload, indent=4, tabs=False, header=False): def _build_block(output, block, depth, last_line): margin = padding * depth + if len(block) > 0: + max_directive_length = max(len(stmt['directive']) for stmt in block) + for stmt in block: directive = _enquote(stmt['directive']) line = stmt.get('line', 0) + directive_padding = ( + (max_directive_length + 1 - len(directive)) * ' ' + if align and stmt.get('block') is None + else ' ' + ) + if directive == '#' and line == last_line: output += ' #' + stmt['comment'] continue @@ -102,7 +111,7 @@ def _build_block(output, block, depth, last_line): if directive == 'if': built = 'if (' + ' '.join(args) + ')' elif args: - built = directive + ' ' + ' '.join(args) + built = directive + directive_padding + ' '.join(args) else: built = directive @@ -113,6 +122,9 @@ def _build_block(output, block, depth, last_line): built = _build_block(built, stmt['block'], depth+1, line) built += '\n' + margin + '}' + if spacious and stmt.get('block') is not None and line - 1 != last_line: + output += '\n' + output += ('\n' if output else '') + margin + built last_line = line diff --git a/crossplane/formatter.py b/crossplane/formatter.py index 80c3b21..8428861 100644 --- a/crossplane/formatter.py +++ b/crossplane/formatter.py @@ -4,7 +4,7 @@ from .parser import parse -def format(filename, indent=4, tabs=False): +def format(filename, indent=4, tabs=False, align=False, spacious=False): payload = parse( filename, comments=True, @@ -18,5 +18,6 @@ def format(filename, indent=4, tabs=False): raise NgxParserBaseException(e['error'], e['file'], e['line']) parsed = payload['config'][0]['parsed'] - output = build(parsed, indent=indent, tabs=tabs) + output = build(parsed, indent=indent, tabs=tabs, + align=align, spacious=spacious) return output diff --git a/tests/configs/many-directives/nginx.conf b/tests/configs/many-directives/nginx.conf new file mode 100644 index 0000000..4170184 --- /dev/null +++ b/tests/configs/many-directives/nginx.conf @@ -0,0 +1,10 @@ +server { + listen 443 ssl; + ssl_certificate fullchain.pem; + ssl_certificate_key privite.pem; + server_name _; + index index.html; + root /public; + charset utf-8; + expires $expires; +} \ No newline at end of file diff --git a/tests/configs/many-directives/with-blocks.conf b/tests/configs/many-directives/with-blocks.conf new file mode 100644 index 0000000..a2dc934 --- /dev/null +++ b/tests/configs/many-directives/with-blocks.conf @@ -0,0 +1,11 @@ +server { + location ~ \d { + image_filter_buffer 5M; + image_filter_interlace on; + image_filter_jpeg_quality 75; + } location ~ \d { + image_filter_buffer 5M; + image_filter_interlace on; + image_filter_jpeg_quality 75; + } +} \ No newline at end of file diff --git a/tests/test_format.py b/tests/test_format.py index d116572..f84e0e9 100644 --- a/tests/test_format.py +++ b/tests/test_format.py @@ -66,6 +66,66 @@ def test_format_not_main_file(): ]) +def test_format_align_option(): + dirname = os.path.join(here, 'configs', 'many-directives') + config = os.path.join(dirname, 'nginx.conf') + output = crossplane.format(config, align=True) + assert output == '\n'.join([ + 'server {', + ' listen 443 ssl;', + ' ssl_certificate fullchain.pem;', + ' ssl_certificate_key privite.pem;', + ' server_name _;', + ' index index.html;', + ' root /public;', + ' charset utf-8;', + ' expires $expires;', + '}', + ]) + + +def test_format_just_spacious_option(): + dirname = os.path.join(here, 'configs', 'many-directives') + config = os.path.join(dirname, 'with-blocks.conf') + output = crossplane.format(config, spacious=True) + assert output == '\n'.join([ + 'server {', + ' location ~ \d {', + ' image_filter_buffer 5M;', + ' image_filter_interlace on;', + ' image_filter_jpeg_quality 75;', + ' }', + '', + ' location ~ \d {', + ' image_filter_buffer 5M;', + ' image_filter_interlace on;', + ' image_filter_jpeg_quality 75;', + ' }', + '}', + ]) + + +def test_format_spacious_option_with_align(): + dirname = os.path.join(here, 'configs', 'many-directives') + config = os.path.join(dirname, 'with-blocks.conf') + output = crossplane.format(config, align=True, spacious=True) + assert output == '\n'.join([ + 'server {', + ' location ~ \d {', + ' image_filter_buffer 5M;', + ' image_filter_interlace on;', + ' image_filter_jpeg_quality 75;', + ' }', + '', + ' location ~ \d {', + ' image_filter_buffer 5M;', + ' image_filter_interlace on;', + ' image_filter_jpeg_quality 75;', + ' }', + '}', + ]) + + def test_format_args_not_analyzed(): dirname = os.path.join(here, 'configs', 'bad-args') config = os.path.join(dirname, 'nginx.conf')