├── .gitignore ├── images ├── 0.jpg ├── 1.jpg └── 2.jpg ├── README.md └── ngxfmt.py /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | *.pyc 3 | venv/ 4 | core.* 5 | *.fmt 6 | *.conf 7 | -------------------------------------------------------------------------------- /images/0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fangpsh/ngxfmt/HEAD/images/0.jpg -------------------------------------------------------------------------------- /images/1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fangpsh/ngxfmt/HEAD/images/1.jpg -------------------------------------------------------------------------------- /images/2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fangpsh/ngxfmt/HEAD/images/2.jpg -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Ngxfmt 2 | 3 | ``` 4 | usage: ngxfmt [-h] [-f nginx.conf] [-d conf.d] 5 | 6 | nginx conf fmt tool 7 | 8 | optional arguments: 9 | -h, --help show this help message and exit 10 | -f nginx.conf conf file 11 | -d conf.d conf directory 12 | ``` 13 | 14 | DEMO: 15 | 16 | ![demo0](./images/0.jpg) 17 | 18 | ![demo1](./images/1.jpg) 19 | 20 | ![demo2](./images/2.jpg) 21 | -------------------------------------------------------------------------------- /ngxfmt.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | __author__ = 'FPs' 5 | 6 | import argparse 7 | import shutil 8 | import os 9 | import sys 10 | 11 | sys.setrecursionlimit(10000) 12 | 13 | COLS = 120 14 | INDENT_W = 4 15 | SPACE_W = 1 16 | NGX_BLOCKS = ["charset_map", "events", "geo", "http", "if", 17 | "limit_except", "location", "mail", "map", 18 | "match", "split_clients", "stream", "tcp", 19 | "types", "upstream"] 20 | DUP_ITEM = ['server'] 21 | 22 | 23 | class Conf(object): 24 | def __init__(self, import_path, export_path): 25 | self.import_path = import_path 26 | self.export_path = export_path 27 | self.export_content = '' 28 | self.level = 0 29 | self.simple_buffer = [] 30 | self.status = 'init' 31 | 32 | def _indent(self): 33 | return self.level * ' ' * INDENT_W 34 | 35 | @staticmethod 36 | def _split_line(line, words): 37 | if line == "": 38 | words.append('\n') 39 | return words 40 | w = "" 41 | i = 0 42 | while i < len(line): 43 | if line[i] == '#': 44 | words.append(line[i:-1]) 45 | words.append('\n') 46 | break 47 | elif line[i] in ['\'', '\"']: 48 | end = line.find(line[i], i+1) 49 | if end == -1: 50 | words.append(line[i]) 51 | i += 1 52 | else: 53 | words.append(line[i:end+1]) 54 | i = end + 1 55 | elif line[i] in ['{', '}', ';']: 56 | if w != "": 57 | words.append(w) 58 | w = "" 59 | words.append(line[i]) 60 | i += 1 61 | elif line[i] == ' ': 62 | if w != "": 63 | words.append(w) 64 | w = "" 65 | i += 1 66 | elif line[i] == '\n': 67 | if w != "": 68 | words.append(w) 69 | w = "" 70 | words.append('\n') 71 | i += 1 72 | elif line[i] in ['\t']: 73 | i += 1 74 | else: 75 | w += line[i] 76 | i += 1 77 | return words 78 | 79 | def _clear_simple_buffer(self): 80 | if len(self.simple_buffer) == 0: 81 | return 82 | if self.status == 'simple_end': 83 | self.export_content += '\n' 84 | 85 | max_w = max([len(w[0]) for w in self.simple_buffer]) 86 | 87 | items_max_w = {} 88 | for w in self.simple_buffer: 89 | if w[0] not in items_max_w: 90 | items_max_w[w[0]] = [len(w[0])] 91 | for i in xrange(1, w.index(';')): 92 | if len(items_max_w[w[0]]) <= i: 93 | items_max_w[w[0]].append(len(w[i])) 94 | else: 95 | items_max_w[w[0]][i] = max(items_max_w[w[0]][i], len(w[i])) 96 | 97 | for index, w in enumerate(self.simple_buffer): 98 | num = w.index(';') 99 | # line feed 100 | if w[0] in ['log_format'] and num > 3: 101 | self.export_content += self._indent() + w[0].ljust(max_w) + SPACE_W*' ' + w[1] + SPACE_W*' ' + w[2] 102 | _w = self._indent() + ' '*max_w + SPACE_W*' ' + ' '*len(w[1]) + ' '*SPACE_W 103 | for i in xrange(3, num): 104 | self.export_content += '\n' + _w + w[i] 105 | elif w[0] in ['server_name', 'charset_types', 'gzip_types'] and num > 4: 106 | self.export_content += self._indent() + w[0].ljust(max_w) + SPACE_W*' ' + w[1] 107 | _w = self._indent() + ' '*max_w + SPACE_W*' ' 108 | for i in xrange(2, num): 109 | self.export_content += '\n' + _w + w[i] 110 | else: 111 | justify = False 112 | if index > 0 and self.simple_buffer[index-1][0] == w[0]: 113 | justify = True 114 | if index+1 < len(self.simple_buffer) and self.simple_buffer[index+1][0] == w[0]: 115 | justify = True 116 | 117 | if justify: 118 | self.export_content += self._indent() + w[0].ljust(max_w) 119 | for i in xrange(1, num): 120 | self.export_content += SPACE_W*' ' + w[i].ljust(items_max_w[w[0]][i]) 121 | else: 122 | self.export_content += self._indent() + w[0].ljust(max_w) 123 | for i in xrange(1, num): 124 | self.export_content += SPACE_W*' ' + w[i] 125 | 126 | self.export_content += ';' 127 | if w[-1][0] == '#': 128 | self.export_content += ' ' + w[-1] 129 | self.export_content += '\n' 130 | 131 | del self.simple_buffer[:] 132 | self.status = 'simple_end' 133 | 134 | def _parser_comment(self, comment): 135 | content = comment.lstrip('#').strip() 136 | i = 0 137 | while i < len(content): 138 | self.export_content += self._indent() + ('# ' + content[i:][0:(COLS-2)] + '\n') 139 | i += (COLS-2) 140 | self.status = "comment_end" 141 | 142 | def _parser_beg_block(self, words, comment): 143 | self.status = 'block_beg' 144 | self.export_content += self._indent() + words[0] 145 | for w in words[1:]: 146 | self.export_content += SPACE_W * ' ' + w 147 | if comment != "": 148 | self.export_content += SPACE_W * ' ' + comment 149 | 150 | self.export_content += '\n' 151 | self.level += 1 152 | 153 | def _parser_end_block(self): 154 | self.level -= 1 155 | self.export_content += self._indent() + '}' + '\n' 156 | self.status = 'block_end' 157 | 158 | def _parser_simple(self, words): 159 | self.simple_buffer.append(filter(lambda w: w != '\n', words)) 160 | 161 | def _parser(self, words): 162 | if len(words) == 0: 163 | self._clear_simple_buffer() 164 | return 165 | elem = words[0] 166 | 167 | if 'by_lua' in elem and 'by_lua_file' not in elem: 168 | print 'NOT SUPPORT LUA BLOCK!!!' 169 | sys.exit() 170 | elif elem[0] == '#': 171 | if self.status == 'simple_end': 172 | self.export_content += '\n' 173 | self._clear_simple_buffer() 174 | self._parser_comment(words[0]) 175 | words = words[2:] 176 | elif elem in ['\n', ';']: 177 | words.remove(elem) 178 | self._clear_simple_buffer() 179 | elif (elem in DUP_ITEM and words[1] == '{') or (elem in NGX_BLOCKS): 180 | if self.status in ['block_end', 'simple_end']: 181 | self.export_content += '\n' 182 | self._clear_simple_buffer() 183 | i = words.index('{') 184 | comment = "" 185 | if i+1 < len(words) and words[i+1][0] == '#': 186 | comment = words[i+1] 187 | words.remove(comment) 188 | self._parser_beg_block(words[0:i+1], comment) 189 | words = words[i+1:] 190 | elif elem == '}': 191 | self._clear_simple_buffer() 192 | words.remove(elem) 193 | self._parser_end_block() 194 | else: 195 | i = words.index(';') 196 | if i+1 < len(words) and words[i+1][0] == '#': 197 | i += 1 198 | if i+1 < len(words) and words[i+1][0] == '\n': 199 | i += 1 200 | self._parser_simple(words[0:i+1]) 201 | words = words[i+1:] 202 | 203 | self._parser(words) 204 | 205 | def fmt(self): 206 | with open(self.import_path) as f: 207 | words = [] 208 | while True: 209 | line = f.readline() 210 | if not line: 211 | break 212 | words = self._split_line(line, words) 213 | 214 | self._parser(words) 215 | with open(self.export_path, 'w') as f: 216 | f.writelines(self.export_content) 217 | 218 | 219 | def main(): 220 | parser = argparse.ArgumentParser(prog='ngxfmt', description='nginx conf fmt tool') 221 | parser.add_argument('-f', action='store', metavar='nginx.conf', dest='conf_file', help='conf file') 222 | parser.add_argument('-d', action='store', metavar='conf.d', dest='conf_dir', help='conf directory') 223 | 224 | args = parser.parse_args() 225 | if args.conf_file is None and args.conf_dir is None: 226 | parser.print_help() 227 | if args.conf_file: 228 | fmt_file = args.conf_file+'.fmt' 229 | conf = Conf(args.conf_file, fmt_file) 230 | conf.fmt() 231 | print args.conf_file + ' -> ' + fmt_file 232 | if args.conf_dir: 233 | fmt_dir = args.conf_dir + '.fmt' 234 | shutil.copytree(args.conf_dir, fmt_dir) 235 | print args.conf_dir + ' -> ' + fmt_dir 236 | for root, dirs, files in os.walk(fmt_dir): 237 | for f in files: 238 | if os.path.splitext(f)[-1] != '.conf': 239 | continue 240 | path = os.path.join(root, f) 241 | conf = Conf(path, path) 242 | conf.fmt() 243 | 244 | 245 | if __name__ == "__main__": 246 | main() 247 | --------------------------------------------------------------------------------