Skip to content

Commit 59250e6

Browse files
committed
Implement sending formatted code to stdout. #19
1 parent da3d974 commit 59250e6

File tree

4 files changed

+132
-37
lines changed

4 files changed

+132
-37
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,3 +136,5 @@ dmypy.json
136136

137137
# pytype static type analyzer
138138
.pytype/
139+
140+
examples/

nginxfmt.py

Lines changed: 78 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -9,24 +9,26 @@
99

1010
import argparse
1111
import codecs
12+
import contextlib
1213
import dataclasses
1314
import logging
1415
import pathlib
1516
import re
17+
import sys
1618

1719
__author__ = "Michał Słomkowski"
1820
__license__ = "Apache 2.0"
1921
__version__ = "1.2.0-SNAPSHOT"
2022

21-
"""Class holds the formatting options. For now, only indentation supported."""
22-
2323

2424
@dataclasses.dataclass
2525
class FormatterOptions:
26+
"""Class holds the formatting options. For now, only indentation supported."""
2627
indentation: int = 4
2728

2829

2930
class Formatter:
31+
"""nginx formatter. Can format config loaded from file or string."""
3032
_TEMPLATE_VARIABLE_OPENING_TAG = '___TEMPLATE_VARIABLE_OPENING_TAG___'
3133
_TEMPLATE_VARIABLE_CLOSING_TAG = '___TEMPLATE_VARIABLE_CLOSING_TAG___'
3234

@@ -56,16 +58,39 @@ def format_string(self,
5658

5759
return text + '\n'
5860

61+
def get_formatted_string_from_file(self,
62+
file_path: pathlib.Path) -> str:
63+
"""Loads nginx config from file, performs formatting and returns contents as string.
64+
:param file_path: path to original nginx configuration file."""
65+
66+
_, original_file_content = self._load_file_content(file_path)
67+
return self.format_string(original_file_content)
68+
5969
def format_file(self,
6070
file_path: pathlib.Path,
6171
original_backup_file_path: pathlib.Path = None):
62-
"""
63-
Performs the formatting on the given file. The function tries to detect file encoding first.
72+
"""Performs the formatting on the given file. The function tries to detect file encoding first.
6473
:param file_path: path to original nginx configuration file. This file will be overridden.
65-
:param original_backup_file_path: optional path, where original file will be backed up.
66-
"""
67-
encodings = ('utf-8', 'latin1')
74+
:param original_backup_file_path: optional path, where original file will be backed up."""
75+
76+
chosen_encoding, original_file_content = self._load_file_content(file_path)
77+
78+
with codecs.open(file_path, 'w', encoding=chosen_encoding) as wfp:
79+
wfp.write(self.format_string(original_file_content))
80+
81+
self.logger.info("Formatted content written to original file.")
6882

83+
if original_backup_file_path:
84+
with codecs.open(original_backup_file_path, 'w', encoding=chosen_encoding) as wfp:
85+
wfp.write(original_file_content)
86+
self.logger.info("Previous content saved to '%s'.", original_backup_file_path)
87+
88+
def _load_file_content(self,
89+
file_path: pathlib.Path) -> (str, str):
90+
"""Determines the encoding of the input file and loads its content to string.
91+
:param file_path: path to original nginx configuration file."""
92+
93+
encodings = ('utf-8', 'latin1')
6994
encoding_failures = []
7095
chosen_encoding = None
7196
original_file_content = None
@@ -83,17 +108,10 @@ def format_file(self,
83108
raise Exception('none of encodings %s are valid for file %s. Errors: %s'
84109
% (encodings, file_path, [e.message for e in encoding_failures]))
85110

86-
assert original_file_content is not None
111+
self.logger.info("Loaded file '%s' (detected encoding %s).", file_path, chosen_encoding)
87112

88-
with codecs.open(file_path, 'w', encoding=chosen_encoding) as wfp:
89-
wfp.write(self.format_string(original_file_content))
90-
91-
self.logger.info("Formatted file '%s' (detected encoding %s).", file_path, chosen_encoding)
92-
93-
if original_backup_file_path:
94-
with codecs.open(original_backup_file_path, 'w', encoding=chosen_encoding) as wfp:
95-
wfp.write(original_file_content)
96-
self.logger.info("Original saved to '%s'.", original_backup_file_path)
113+
assert original_file_content is not None
114+
return chosen_encoding, original_file_content
97115

98116
@staticmethod
99117
def _strip_line(single_line):
@@ -267,24 +285,57 @@ def _perform_indentation(self, lines):
267285
return indented_lines
268286

269287

270-
if __name__ == "__main__":
271-
arg_parser = argparse.ArgumentParser(description=__doc__)
288+
@contextlib.contextmanager
289+
def _redirect_stdout_to_stderr():
290+
"""Redirects stdout to stderr for argument parsing. This is to don't pollute the stdout
291+
when --print-result is used."""
292+
old_stdout = sys.stdout
293+
sys.stdout = sys.stderr
294+
try:
295+
yield
296+
finally:
297+
sys.stdout = old_stdout
272298

299+
300+
def _standalone_run(program_arguments):
273301
# todo add logger: logs to stderr
274-
# option to format from stdin
302+
303+
arg_parser = argparse.ArgumentParser(description="Formats nginx configuration files in consistent way.")
275304

276305
arg_parser.add_argument("-v", "--verbose", action="store_true", help="show formatted file names")
277-
arg_parser.add_argument("-b", "--backup-original", action="store_true", help="backup original config file")
278-
arg_parser.add_argument("-i", "--indent", action="store", default=4, type=int,
279-
help="specify number of spaces for indentation")
306+
307+
backup_xor_print_group = arg_parser.add_mutually_exclusive_group()
308+
print_result_action = backup_xor_print_group.add_argument("-p", "--print-result", action="store_true",
309+
help="prints result to stdout, original file is not changed")
310+
backup_xor_print_group.add_argument("-b", "--backup-original", action="store_true",
311+
help="backup original config file")
280312
arg_parser.add_argument("config_files", nargs='+', help="configuration files to format")
281313

282-
args = arg_parser.parse_args()
314+
formatter_options_group = arg_parser.add_argument_group("formatting options")
315+
formatter_options_group.add_argument("-i", "--indent", action="store", default=4, type=int,
316+
help="specify number of spaces for indentation")
317+
318+
with _redirect_stdout_to_stderr():
319+
args = arg_parser.parse_args(program_arguments)
320+
321+
try:
322+
if args.print_result and len(args.config_files) != 1:
323+
raise Exception("if %s is enabled, only one file can be passed as input" % argparse._get_action_name(
324+
print_result_action))
325+
except Exception as e:
326+
arg_parser.error(str(e))
283327

284328
format_options = FormatterOptions()
285329
format_options.indentation = args.indent
286330
formatter = Formatter(format_options)
287331

288-
for config_file_path in args.config_files:
289-
backup_file_path = config_file_path + '~' if args.backup_original else None
290-
formatter.format_file(config_file_path, backup_file_path)
332+
if args.print_result:
333+
print(formatter.get_formatted_string_from_file(args.config_files[0]))
334+
else:
335+
for config_file_path in args.config_files:
336+
backup_file_path = config_file_path + '~' if args.backup_original else None
337+
formatter.format_file(config_file_path, backup_file_path)
338+
339+
340+
if __name__ == "__main__":
341+
_standalone_run(sys.argv[1:])

test-files/not-formatted-1.conf

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
server {
2+
listen 80;
3+
listen [::]:80;
4+
server_name example.com;
5+
}
6+

test_nginxfmt.py

Lines changed: 46 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,21 @@
22
# -*- coding: utf-8 -*-
33

44
"""Unit tests for nginxfmt module."""
5+
import contextlib
6+
import logging
7+
import pathlib
58
import shutil
69
import tempfile
710
import unittest
811

9-
from nginxfmt import *
12+
import nginxfmt
1013

1114
__author__ = "Michał Słomkowski"
1215
__license__ = "Apache 2.0"
1316

1417

1518
class TestFormatter(unittest.TestCase):
16-
fmt = Formatter()
19+
fmt = nginxfmt.Formatter()
1720

1821
def __init__(self, method_name: str = ...) -> None:
1922
super().__init__(method_name)
@@ -238,17 +241,21 @@ def test_multi_semicolon(self):
238241

239242
def test_loading_utf8_file(self):
240243
tmp_file = pathlib.Path(tempfile.mkstemp('utf-8')[1])
241-
shutil.copy('test-files/umlaut-utf8.conf', tmp_file)
242-
self.fmt.format_file(tmp_file)
243-
# todo perform some tests on result file
244-
tmp_file.unlink()
244+
try:
245+
shutil.copy('test-files/umlaut-utf8.conf', tmp_file)
246+
self.fmt.format_file(tmp_file)
247+
# todo perform some tests on result file
248+
finally:
249+
tmp_file.unlink()
245250

246251
def test_loading_latin1_file(self):
247252
tmp_file = pathlib.Path(tempfile.mkstemp('latin1')[1])
248-
shutil.copy('test-files/umlaut-latin1.conf', tmp_file)
249-
self.fmt.format_file(tmp_file)
250-
# todo perform some tests on result file
251-
tmp_file.unlink()
253+
try:
254+
shutil.copy('test-files/umlaut-latin1.conf', tmp_file)
255+
self.fmt.format_file(tmp_file)
256+
# todo perform some tests on result file
257+
finally:
258+
tmp_file.unlink()
252259

253260
def test_issue_15(self):
254261
self.check_formatting(
@@ -345,6 +352,35 @@ def test_issue_9(self):
345352
),
346353
)
347354

355+
def test_custom_indentation(self):
356+
fo = nginxfmt.FormatterOptions(indentation=2)
357+
fmt2 = nginxfmt.Formatter(fo)
358+
self.assertMultiLineEqual("{\n"
359+
" foo bar;\n"
360+
"}\n",
361+
fmt2.format_string(
362+
" { \n"
363+
" foo bar;\n"
364+
"}\n"))
365+
366+
367+
class TestStandaloneRun(unittest.TestCase):
368+
369+
@contextlib.contextmanager
370+
def input_test_file(self, file_name):
371+
tmp_file = pathlib.Path(tempfile.mkstemp('utf-8')[1])
372+
try:
373+
shutil.copy('test-files/' + file_name, tmp_file)
374+
yield str(tmp_file)
375+
# todo perform some tests on result file
376+
finally:
377+
tmp_file.unlink()
378+
379+
# todo better tests of standalone mode?
380+
def test_print_result(self):
381+
with self.input_test_file('not-formatted-1.conf') as input:
382+
nginxfmt._standalone_run(['-p', input])
383+
348384

349385
if __name__ == '__main__':
350386
unittest.main()

0 commit comments

Comments
 (0)