99
1010import argparse
1111import codecs
12+ import contextlib
1213import dataclasses
1314import logging
1415import pathlib
1516import 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
2525class FormatterOptions :
26+ """Class holds the formatting options. For now, only indentation supported."""
2627 indentation : int = 4
2728
2829
2930class 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 :])
0 commit comments