├── .gitignore ├── LICENSE ├── README.md ├── README_EN.md ├── cmd └── ngixn.go ├── config └── main.go ├── example.jpg ├── go.mod ├── go.sum ├── main.go ├── nginx-config-reader.iml ├── output ├── error.go └── nginx.go └── parser ├── ast.go ├── parser.go ├── token.go └── visit.go /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | conf.d 3 | nginx.conf 4 | build 5 | build.sh -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 ICU-Coders 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 |

3 | ICU-Coders 4 |

5 | 6 | ``` 7 | _| _| _|_|_|_|_|_| _|_|_|_|_|_| 8 | _| _| _| _| _| | 9 | _| _| _| _| _|_|_|_|_|_| 10 | _| _| _| _| _| _| 11 | _| _| _| _| _| _| 12 | _| _| _|_|_|_|_|_| _| __| 13 | ``` 14 | 15 | # nginx-config-reader(ncr) 16 | 17 | nginx 配置文件读取工具,用于快速预览当前服务下所有nginx配置文件 18 | 19 | ![MIT](https://img.shields.io/badge/License-MIT-blue.svg?style=flat) 20 | 21 | [English](./README_EN.md) 22 | 23 | 效果图 24 | ![example](./example.jpg) 25 | 26 | ### 如何使用 27 | 有以下两种方案可供选择: 28 | 29 | 1. 下载已经编译好的可执行文件 [Release](https://github.com/ICU-Coders/nginx-config-reader/releases) 30 | 31 | ```shell 32 | chmod o+x ./ncr-linux 33 | ./ncr-linux 34 | ``` 35 | 36 | 2. 本地编译运行 37 | ```shell 38 | go run ./main.go 39 | ``` 40 | 41 | ### 可选参数 42 | ``` 43 | -i input 指定解析文件地址 44 | -l log 输出nginx配置log的位置 45 | -s sort 排序server/listen/uri/dir 默认listen 46 | -m match 匹配字符筛选 47 | -h help 输出帮助内容 48 | ``` 49 | 50 | ## MIT License 51 | 52 | Copyright (c) 2022 ICU-Coders 53 | 54 | Permission is hereby granted, free of charge, to any person obtaining a copy 55 | of this software and associated documentation files (the "Software"), to deal 56 | in the Software without restriction, including without limitation the rights 57 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 58 | copies of the Software, and to permit persons to whom the Software is 59 | furnished to do so, subject to the following conditions: 60 | 61 | The above copyright notice and this permission notice shall be included in all 62 | copies or substantial portions of the Software. 63 | 64 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 65 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 66 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 67 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 68 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 69 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 70 | SOFTWARE. -------------------------------------------------------------------------------- /README_EN.md: -------------------------------------------------------------------------------- 1 | 2 |

3 | ICU-Coders 4 |

5 | 6 | # nginx-config-reader(ncr) 7 | 8 | nginx configuration file reader tool, used to quickly preview all nginx configuration files under the current service 9 | 10 | ![MIT](https://img.shields.io/badge/License-MIT-blue.svg?style=flat) 11 | 12 | Results Show 13 | ![example](./example.jpg) 14 | 15 | ### How To Use 16 | The following two options are available: 17 | 18 | 1. Download the compiled executable file [Release](https://github.com/ICU-Coders/nginx-config-reader/releases) 19 | 20 | ```shell 21 | chmod o+x ./ncr-linux 22 | ./ncr-linux 23 | ``` 24 | 25 | 2. Local operation 26 | ```shell 27 | go run ./main.go 28 | ``` 29 | ### Optional parameters 30 | ``` 31 | -i input Specify the address of the parsed file 32 | -l log Output the location of the nginx configuration log 33 | -s sort server/listen/uri/dir, default `listen` 34 | -m match Matching character filter 35 | -h help Help content 36 | ``` 37 | 38 | ## MIT License 39 | 40 | Copyright (c) 2022 ICU-Coders 41 | 42 | Permission is hereby granted, free of charge, to any person obtaining a copy 43 | of this software and associated documentation files (the "Software"), to deal 44 | in the Software without restriction, including without limitation the rights 45 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 46 | copies of the Software, and to permit persons to whom the Software is 47 | furnished to do so, subject to the following conditions: 48 | 49 | The above copyright notice and this permission notice shall be included in all 50 | copies or substantial portions of the Software. 51 | 52 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 53 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 54 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 55 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 56 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 57 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 58 | SOFTWARE. -------------------------------------------------------------------------------- /cmd/ngixn.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | output2 "nginx-config-reader/output" 5 | "os/exec" 6 | "regexp" 7 | ) 8 | 9 | func CheckNginx() (string, error) { 10 | command := exec.Command("nginx", "-t") 11 | output, err := command.CombinedOutput() 12 | if err != nil { 13 | output2.ErrorFatal(1001, err) 14 | } 15 | result := string(output) 16 | compile, err := regexp.Compile("the configuration file(.*?)syntax is ok") 17 | if err != nil { 18 | output2.ErrorFatal(1002, err) 19 | } 20 | 21 | submatch := compile.FindStringSubmatch(result) 22 | if len(submatch) < 2 { 23 | output2.ErrorFatal(1003, result) 24 | } 25 | return submatch[1], nil 26 | } 27 | -------------------------------------------------------------------------------- /config/main.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import "flag" 4 | 5 | var RootPath string 6 | var ShowLog bool 7 | var SortType string // port/host/uri/dir 8 | var Match string 9 | 10 | func init() { 11 | flag.StringVar(&RootPath, "i", "", "请输入待解析文件路径(Please enter the path of the file to be parsed)") 12 | flag.StringVar(&SortType, "s", "port", "输出结果排序依据(Output results are sorted based on)") 13 | flag.BoolVar(&ShowLog, "l", false, "是否显示日志路径(Whether to display the log path)") 14 | flag.StringVar(&Match, "m", "", "高亮匹配到的字符(Matching character filter)") 15 | flag.Parse() 16 | } 17 | -------------------------------------------------------------------------------- /example.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ICU-Coders/nginx-config-reader/4beb258691b07f65f7b835eb117a5e5dcd07420b/example.jpg -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module nginx-config-reader 2 | 3 | go 1.17 4 | 5 | require github.com/fatih/color v1.13.0 6 | 7 | require ( 8 | github.com/ICU-Coders/table v0.0.2 9 | github.com/mattn/go-colorable v0.1.9 // indirect 10 | github.com/mattn/go-isatty v0.0.14 // indirect 11 | golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c // indirect 12 | ) 13 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/ICU-Coders/table v0.0.2 h1:4MQfPhlg/tDzxX7ODsT8wlJdZN0LNsl8Fa2xz0XWgWQ= 2 | github.com/ICU-Coders/table v0.0.2/go.mod h1:Ft1PPw8EOW9HUC45e/00Ki0guql6u3taP2uEssf6WnY= 3 | github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= 4 | github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= 5 | github.com/mattn/go-colorable v0.1.9 h1:sqDoxXbdeALODt0DAeJCVp38ps9ZogZEAXjus69YV3U= 6 | github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= 7 | github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= 8 | github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= 9 | github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= 10 | golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 11 | golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 12 | golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c h1:F1jZWGFhYfh0Ci55sIpILtKKK8p3i2/krTr0H1rg74I= 13 | golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 14 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/fatih/color" 5 | "log" 6 | "nginx-config-reader/cmd" 7 | "nginx-config-reader/config" 8 | "nginx-config-reader/output" 9 | "nginx-config-reader/parser" 10 | ) 11 | 12 | func main() { 13 | 14 | color.New(color.FgGreen).Println(` 15 | *********************Welcome to use*********************** 16 | * * 17 | * _| _| _|_|_|_|_|_| _|_|_|_|_|_| * 18 | * _| _| _| _| _| | * 19 | * _| _| _| _| _|_|_|_|_|_| * 20 | * _| _| _| _| _| _| * 21 | * _| _| _| _| _| _| * 22 | * _| _| _|_|_|_|_|_| _| __| * 23 | * * 24 | ********************nginx config reader******************* 25 | `) 26 | 27 | if len(config.RootPath) == 0 { 28 | var err error 29 | config.RootPath, err = cmd.CheckNginx() 30 | if err != nil { 31 | output.ErrorFatal(1004, err) 32 | } 33 | } 34 | // 从根配置开始解析,如果用户输入文件,解析用户文件,否则按照nginx -t解析输出文件 35 | content, err := parser.ConfigParser(config.RootPath) 36 | if err != nil { 37 | log.Fatal(err) 38 | } 39 | 40 | color.New(color.FgCyan).Println("> Generate results...") 41 | output.Nginx(content) 42 | 43 | } 44 | -------------------------------------------------------------------------------- /nginx-config-reader.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /output/error.go: -------------------------------------------------------------------------------- 1 | package output 2 | 3 | import ( 4 | "github.com/fatih/color" 5 | "os" 6 | ) 7 | 8 | var ErrorCode = map[int]string{ 9 | 1001: "Reading nginx root file error", 10 | 1002: "Reading nginx root file error", 11 | 1003: "Reading nginx root file error", 12 | 1004: "Reading nginx root file error", 13 | } 14 | var errorColor = color.New(color.FgRed) 15 | 16 | func ErrorFatal(code int, msg interface{}) { 17 | errorColor.Println(code, ErrorCode[code], msg) 18 | os.Exit(1) 19 | } 20 | -------------------------------------------------------------------------------- /output/nginx.go: -------------------------------------------------------------------------------- 1 | package output 2 | 3 | import ( 4 | "fmt" 5 | "github.com/ICU-Coders/table" 6 | "nginx-config-reader/config" 7 | "nginx-config-reader/parser" 8 | "sort" 9 | "strconv" 10 | "strings" 11 | ) 12 | 13 | type Location struct { 14 | URI string 15 | Dir string 16 | Listen string 17 | ServerName string 18 | Method string 19 | } 20 | 21 | type NginxLogPath struct { 22 | Type string 23 | Path string 24 | } 25 | 26 | type NginxLog struct { 27 | Module string 28 | LogPaths []NginxLogPath 29 | } 30 | 31 | func Nginx(nginx *parser.ObjectExpression) { 32 | var paths []*Location 33 | var nginxLogs []NginxLog 34 | // https -> servers -> locations 35 | nginxLogs = append(nginxLogs, NginxLog{ 36 | Module: "default", 37 | LogPaths: makeNginxLog(nginx), 38 | }) 39 | https := nginx.GetMustObject("http") 40 | for _, http := range https { 41 | 42 | nginxLogs = append(nginxLogs, NginxLog{ 43 | Module: "http", 44 | LogPaths: makeNginxLog(http), 45 | }) 46 | servers := http.GetMustObject("server") 47 | for _, server := range servers { 48 | listen := server.GetFirst("listen").(*parser.MultiValue).GetValueWithSep(" ") 49 | defaultRoot := server.GetFirstMustString("root") 50 | locations := server.GetMustObject("location") 51 | serverName := server.GetFirstMustString("server_name") 52 | 53 | nginxLogs = append(nginxLogs, NginxLog{ 54 | Module: serverName + ":" + listen, 55 | LogPaths: makeNginxLog(http), 56 | }) 57 | 58 | for _, location := range locations { 59 | lo := new(Location) 60 | uri := location.GetFirst("path").(*parser.MultiValue).GetValue() 61 | body := location.GetFirstMustObject("body") 62 | dir := body.GetFirstMustString("root") 63 | lo.Method = "root" 64 | alias := body.GetFirstMustString("alias") 65 | if len(dir) == 0 { 66 | dir = alias 67 | lo.Method = "alias" 68 | } 69 | proxyPass := body.GetFirstMustString("proxy_pass") 70 | if len(dir) == 0 { 71 | dir = proxyPass 72 | lo.Method = "proxy" 73 | } 74 | if len(dir) == 0 { 75 | dir = defaultRoot 76 | lo.Method = "default" 77 | } 78 | lo.URI = uri 79 | lo.Dir = dir 80 | lo.Listen = listen 81 | lo.ServerName = serverName 82 | paths = append(paths, lo) 83 | } 84 | } 85 | } 86 | 87 | if config.ShowLog { 88 | fmt.Println() 89 | fmt.Println() 90 | 91 | fmt.Println("Log Table:") 92 | var content [][]string 93 | for _, nginxLog := range nginxLogs { 94 | for _, path := range nginxLog.LogPaths { 95 | var sub []string 96 | sub = append(sub, nginxLog.Module, path.Type, path.Path) 97 | content = append(content, sub) 98 | } 99 | 100 | } 101 | table.Show([]string{"Module", "Type", "Path"}, content) 102 | 103 | fmt.Println() 104 | fmt.Println() 105 | } 106 | 107 | switch strings.ToLower(config.SortType) { 108 | case "host", "server", "server_name": 109 | sort.Slice(paths, func(i, j int) bool { 110 | return paths[i].ServerName < paths[j].ServerName 111 | }) 112 | case "uri": 113 | sort.Slice(paths, func(i, j int) bool { 114 | return paths[i].URI < paths[j].URI 115 | }) 116 | case "dir": 117 | sort.Slice(paths, func(i, j int) bool { 118 | return paths[i].Dir < paths[j].Dir 119 | }) 120 | default: 121 | sort.Slice(paths, func(i, j int) bool { 122 | ii, err := strconv.ParseInt(paths[i].Listen, 10, 64) 123 | if err != nil { 124 | return paths[i].Listen < paths[j].Listen 125 | } 126 | jj, err := strconv.ParseInt(paths[j].Listen, 10, 64) 127 | if err != nil { 128 | return paths[i].Listen < paths[j].Listen 129 | } 130 | return ii < jj 131 | }) 132 | } 133 | 134 | var content [][]string 135 | for _, path := range paths { 136 | var sub []string 137 | if len(config.Match) > 0 { 138 | if strings.Contains(path.ServerName, config.Match) || 139 | strings.Contains(path.Listen, config.Match) || 140 | strings.Contains(path.URI, config.Match) || 141 | strings.Contains(path.Method, config.Match) || 142 | strings.Contains(path.Dir, config.Match) { 143 | sub = append(sub, path.ServerName, path.Listen, path.URI, path.Method, path.Dir) 144 | } 145 | } else { 146 | sub = append(sub, path.ServerName, path.Listen, path.URI, path.Method, path.Dir) 147 | } 148 | content = append(content, sub) 149 | } 150 | 151 | fmt.Println("Index Table:") 152 | 153 | table.Show([]string{"ServerName", "Listen", "URI", "Method", "Dir"}, content) 154 | 155 | } 156 | 157 | func makeNginxLog(nginx *parser.ObjectExpression) []NginxLogPath { 158 | if !config.ShowLog { 159 | return nil 160 | } 161 | var logs []NginxLogPath 162 | errorLogs := nginx.GetMustString("error_log") 163 | for _, errorLog := range errorLogs { 164 | logs = append(logs, NginxLogPath{ 165 | Type: "error", 166 | Path: errorLog, 167 | }) 168 | } 169 | accessLogs := nginx.GetMustString("access_log") 170 | for _, accessLog := range accessLogs { 171 | logs = append(logs, NginxLogPath{ 172 | Type: "access", 173 | Path: accessLog, 174 | }) 175 | } 176 | return logs 177 | } 178 | -------------------------------------------------------------------------------- /parser/ast.go: -------------------------------------------------------------------------------- 1 | package parser 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | ) 7 | 8 | type Node interface { 9 | Transfer() string 10 | } 11 | type BaseAst struct { 12 | } 13 | type MultiValue struct { 14 | BaseAst 15 | Values []string 16 | } 17 | type PropertyExpression struct { 18 | BaseAst 19 | Key string 20 | Value Node 21 | } 22 | type ObjectExpression struct { 23 | BaseAst 24 | Properties []*PropertyExpression 25 | } 26 | 27 | func (receiver MultiValue) GetValue() string { 28 | return fmt.Sprintf("%s", strings.Join(receiver.Values, "")) 29 | } 30 | func (receiver MultiValue) GetValueWithSep(sep string) string { 31 | return fmt.Sprintf("%s", strings.Join(receiver.Values, sep)) 32 | } 33 | func (receiver MultiValue) Transfer() string { 34 | return fmt.Sprintf("%s", strings.Join(receiver.Values, " ")) 35 | } 36 | func (receiver *BaseAst) Transfer() string { 37 | return "" 38 | } 39 | func (receiver PropertyExpression) Transfer() string { 40 | return fmt.Sprintf("%s : %s", receiver.Key, receiver.Value.Transfer()) 41 | } 42 | 43 | func (receiver ObjectExpression) Get(key string) []Node { 44 | var objs []Node 45 | for _, property := range receiver.Properties { 46 | if property.Key == key { 47 | objs = append(objs, property.Value) 48 | } 49 | } 50 | return objs 51 | } 52 | func (receiver *ObjectExpression) GetMustObject(key string) []*ObjectExpression { 53 | var objs []*ObjectExpression 54 | for _, property := range receiver.Properties { 55 | if property.Key == key { 56 | objs = append(objs, property.Value.(*ObjectExpression)) 57 | } 58 | } 59 | return objs 60 | } 61 | func (receiver *ObjectExpression) GetMustString(key string) []string { 62 | var objs []string 63 | for _, property := range receiver.Properties { 64 | if property.Key == key { 65 | objs = append(objs, property.Value.(*MultiValue).GetValue()) 66 | } 67 | } 68 | return objs 69 | } 70 | func (receiver *ObjectExpression) GetFirst(key string) Node { 71 | for _, property := range receiver.Properties { 72 | if property.Key == key { 73 | return property.Value 74 | } 75 | } 76 | return nil 77 | } 78 | func (receiver *ObjectExpression) GetFirstMustObject(key string) *ObjectExpression { 79 | for _, property := range receiver.Properties { 80 | if property.Key == key { 81 | obj, ok := property.Value.(*ObjectExpression) 82 | if ok { 83 | return obj 84 | } 85 | return nil 86 | } 87 | } 88 | return nil 89 | } 90 | func (receiver *ObjectExpression) GetFirstMustString(key string) string { 91 | for _, property := range receiver.Properties { 92 | if property.Key == key { 93 | obj, ok := property.Value.(*MultiValue) 94 | if ok { 95 | return obj.GetValue() 96 | } 97 | return "" 98 | } 99 | } 100 | return "" 101 | } 102 | func (receiver ObjectExpression) Transfer() string { 103 | var maker []string 104 | maker = append(maker, "{") 105 | for _, i2 := range receiver.Properties { 106 | if len(i2.Key) == 0 { 107 | continue 108 | } 109 | maker = append(maker, i2.Transfer()) 110 | } 111 | maker = append(maker, "}") 112 | return strings.Join(maker, "\n") 113 | } 114 | 115 | func (receiver *ObjectExpression) Append(node Node) { 116 | switch node.(type) { 117 | case *PropertyExpression: 118 | receiver.Properties = append(receiver.Properties, node.(*PropertyExpression)) 119 | case *ObjectExpression: 120 | receiver.Properties = append(receiver.Properties, node.(*ObjectExpression).Properties...) 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /parser/parser.go: -------------------------------------------------------------------------------- 1 | package parser 2 | 3 | import ( 4 | "github.com/fatih/color" 5 | "io/ioutil" 6 | "path/filepath" 7 | "strings" 8 | "unicode" 9 | ) 10 | 11 | var ObjTokens = [...]string{"server", "http", "location", "events", "types"} 12 | 13 | var PropertyTokens = [...]string{"root", "index", "alias", "proxy_pass", "listen", "error_page", "log_format", 14 | "access_log", "include", "user", "worker_processes", "error_log", "pid", "sendfile", 15 | "tcp_nopush", "tcp_nodelay", "keepalive_timeout", "types_hash_max_size", "worker_connections", "server_name"} 16 | 17 | func tokenizer(input []byte) []Token { 18 | current := 0 19 | tokens := make([]Token, 0, 500) 20 | for current < len(input) { 21 | char := input[current] 22 | if char == '{' { 23 | tokens = append(tokens, Token{ 24 | name: TokenNameBrace, 25 | value: "{", 26 | }) 27 | current++ 28 | continue 29 | } 30 | 31 | if char == '}' { 32 | tokens = append(tokens, Token{ 33 | name: TokenNameBrace, 34 | value: "}", 35 | }) 36 | current++ 37 | continue 38 | } 39 | 40 | if char == ';' { 41 | tokens = append(tokens, Token{ 42 | name: TokenNameSemicolon, 43 | value: ";", 44 | }) 45 | current++ 46 | continue 47 | } 48 | 49 | if char == '#' { 50 | tokens = append(tokens, Token{ 51 | name: TokenNameComment, 52 | value: "#", 53 | }) 54 | current++ 55 | continue 56 | } 57 | 58 | if char == '\n' { 59 | tokens = append(tokens, Token{ 60 | name: TokenNameNewLine, 61 | value: "\n", 62 | }) 63 | current++ 64 | continue 65 | } 66 | 67 | if unicode.IsSpace(rune(char)) { 68 | current++ 69 | continue 70 | } 71 | 72 | { 73 | var value []byte 74 | for !unicode.IsSpace(rune(char)) && char != '#' && char != ';' { 75 | value = append(value, char) 76 | current++ 77 | char = input[current] 78 | } 79 | str := string(value) 80 | find := false 81 | for _, token := range ObjTokens { 82 | if token == str { 83 | find = true 84 | break 85 | } 86 | } 87 | if !find { 88 | for _, token := range PropertyTokens { 89 | if token == str { 90 | find = true 91 | break 92 | } 93 | } 94 | } 95 | if !find { 96 | tokens = append(tokens, Token{ 97 | name: TokenNameString, 98 | value: str, 99 | }) 100 | } else { 101 | tokens = append(tokens, Token{ 102 | name: TokenNameToken, 103 | value: str, 104 | }) 105 | } 106 | } 107 | } 108 | return tokens 109 | } 110 | 111 | func parser(tokens []Token) *ObjectExpression { 112 | current := 0 113 | var walk func(end TokenName) Node 114 | walk = func(end TokenName) Node { 115 | token := tokens[current] 116 | switch end { 117 | case TokenNameNewLine, TokenNameSemicolon, TokenNameBrace: 118 | m := new(MultiValue) 119 | for token.name != end { 120 | m.Values = append(m.Values, token.value) 121 | current++ 122 | token = tokens[current] 123 | } 124 | return m 125 | } 126 | 127 | if token.name == TokenNameComment { 128 | current++ 129 | return &PropertyExpression{Key: "common", Value: walk(TokenNameNewLine)} 130 | } 131 | if token.name == TokenNameToken { 132 | p := new(PropertyExpression) 133 | p.Key = token.value 134 | 135 | find := false 136 | for _, objToken := range ObjTokens { 137 | if objToken == token.value { 138 | find = true 139 | break 140 | } 141 | } 142 | current++ 143 | if !find { 144 | p.Value = walk(TokenNameSemicolon).(*MultiValue) 145 | } else { 146 | if token.value == "location" { 147 | path := walk(TokenNameBrace) 148 | body := walk(TokenNameAny) 149 | p.Value = &ObjectExpression{ 150 | Properties: []*PropertyExpression{ 151 | { 152 | Key: "path", 153 | Value: path, 154 | }, 155 | { 156 | Key: "body", 157 | Value: body, 158 | }, 159 | }, 160 | } 161 | } else { 162 | p.Value = walk(TokenNameAny) 163 | } 164 | } 165 | return p 166 | } 167 | 168 | if token.name == TokenNameBrace && token.value == "{" { 169 | obj := new(ObjectExpression) 170 | current++ 171 | token = tokens[current] 172 | 173 | for token.name != TokenNameBrace || (token.name == TokenNameBrace && token.value != "}") { 174 | obj.Append(walk(TokenNameAny)) 175 | token = tokens[current] 176 | } 177 | current++ 178 | return obj 179 | } 180 | 181 | // not match 182 | current++ 183 | return &PropertyExpression{Key: "", Value: nil} // empty 184 | } 185 | 186 | body := new(ObjectExpression) 187 | for current < len(tokens) { 188 | body.Append(walk(TokenNameAny)) 189 | } 190 | 191 | return body 192 | } 193 | 194 | func ConfigParser(path string) (*ObjectExpression, error) { 195 | path = strings.TrimSpace(path) 196 | color.New(color.FgCyan).Println("> Parsing", path) 197 | if isVisited(path) { 198 | return nil, nil 199 | } 200 | file, err := ioutil.ReadFile(path) 201 | if err != nil { 202 | return nil, err 203 | } 204 | tokens := tokenizer(file) 205 | nginx := parser(tokens) 206 | // 遍历展开include 207 | err = extraNginx(nginx) 208 | if err != nil { 209 | return nil, err 210 | } 211 | return nginx, nil 212 | } 213 | 214 | func extraNginx(nginx *ObjectExpression) error { 215 | includes := nginx.GetMustString("include") 216 | for _, include := range includes { 217 | glob, err := filepath.Glob(include) 218 | if err != nil { 219 | return err 220 | } 221 | for _, s := range glob { 222 | obj, err := ConfigParser(s) 223 | if err != nil { 224 | return err 225 | } 226 | if obj == nil { 227 | continue 228 | } 229 | nginx.Properties = append(nginx.Properties, obj.Properties...) 230 | } 231 | } 232 | 233 | https := nginx.GetMustObject("http") 234 | for _, http := range https { 235 | err := extraNginx(http) 236 | if err != nil { 237 | return err 238 | } 239 | } 240 | 241 | servers := nginx.GetMustObject("server") 242 | for _, server := range servers { 243 | err := extraNginx(server) 244 | if err != nil { 245 | return err 246 | } 247 | } 248 | 249 | return nil 250 | } 251 | -------------------------------------------------------------------------------- /parser/token.go: -------------------------------------------------------------------------------- 1 | package parser 2 | 3 | import "fmt" 4 | 5 | type TokenName int 6 | 7 | const ( 8 | TokenNameString TokenName = iota 9 | TokenNameToken 10 | TokenNameBrace 11 | TokenNameSemicolon 12 | TokenNameComment 13 | TokenNameNewLine 14 | TokenNameAny 15 | ) 16 | 17 | type Token struct { 18 | name TokenName 19 | value string 20 | } 21 | 22 | func (receiver Token) String() string { 23 | name := "" 24 | value := receiver.value 25 | switch receiver.name { 26 | case TokenNameNewLine: 27 | name = "new-line" 28 | value = "\\n" 29 | case TokenNameComment: 30 | name = "comment" 31 | case TokenNameSemicolon: 32 | name = "semicolon" 33 | case TokenNameToken: 34 | name = "token" 35 | case TokenNameBrace: 36 | name = "brace" 37 | case TokenNameString: 38 | name = "string" 39 | } 40 | return fmt.Sprintf("%s: %s", name, value) 41 | } 42 | -------------------------------------------------------------------------------- /parser/visit.go: -------------------------------------------------------------------------------- 1 | package parser 2 | 3 | var visitMap = make(map[string]bool) 4 | 5 | func isVisited(path string) bool { 6 | if has := visitMap[path]; has { 7 | return true 8 | } 9 | visitMap[path] = true 10 | return false 11 | } 12 | --------------------------------------------------------------------------------