pkg.re/essentialkaos/ek.10@v12.41.0+incompatible/knf/knf_parser.go (about) 1 package knf 2 3 // ////////////////////////////////////////////////////////////////////////////////// // 4 // // 5 // Copyright (c) 2022 ESSENTIAL KAOS // 6 // Apache License, Version 2.0 <https://www.apache.org/licenses/LICENSE-2.0> // 7 // // 8 // ////////////////////////////////////////////////////////////////////////////////// // 9 10 import ( 11 "bufio" 12 "fmt" 13 "io" 14 "os" 15 "path" 16 "regexp" 17 "strings" 18 "sync" 19 ) 20 21 // ////////////////////////////////////////////////////////////////////////////////// // 22 23 const ( 24 _COMMENT_SYMBOL = "#" 25 _SECTION_START_SYMBOL = "[" 26 _SECTION_END_SYMBOL = "[" 27 _PROP_DELIMITER = ":" 28 _MACRO_START_SYMBOL = "{" 29 _MACRO_END_SYMBOL = "}" 30 _MACRO_DELIMITER = ":" 31 ) 32 33 // ////////////////////////////////////////////////////////////////////////////////// // 34 35 // macroRE is a regexp for extracting macroses 36 var macroRE = regexp.MustCompile(`\{([\w\-]+):([\w\-]+)\}`) 37 38 // ////////////////////////////////////////////////////////////////////////////////// // 39 40 // readKNFFile reads KNF file 41 func readKNFFile(file string) (*Config, error) { 42 fd, err := os.OpenFile(path.Clean(file), os.O_RDONLY, 0) 43 44 if err != nil { 45 return nil, err 46 } 47 48 defer fd.Close() 49 50 config, err := readKNFData(fd) 51 52 if err != nil { 53 return nil, err 54 } 55 56 config.file = file 57 58 return config, nil 59 } 60 61 // readKNFData reads data from given reader 62 func readKNFData(r io.Reader) (*Config, error) { 63 reader := bufio.NewReader(r) 64 scanner := bufio.NewScanner(reader) 65 66 config := &Config{ 67 data: make(map[string]string), 68 mx: &sync.RWMutex{}, 69 } 70 71 var isDataRead bool 72 var section string 73 var lineNum int 74 75 for scanner.Scan() { 76 line := strings.Trim(scanner.Text(), " \t") 77 lineNum++ 78 79 if line == "" || strings.HasPrefix(line, _COMMENT_SYMBOL) { 80 continue 81 } 82 83 isDataRead = true 84 85 if strings.HasPrefix(line, _SECTION_START_SYMBOL) && 86 strings.HasPrefix(line, _SECTION_END_SYMBOL) { 87 section = strings.Trim(line, "[]") 88 config.data[strings.ToLower(section)] = "!" 89 config.sections = append(config.sections, section) 90 continue 91 } 92 93 if section == "" { 94 return nil, fmt.Errorf("Error at line %d: Data defined before section", lineNum) 95 } 96 97 propName, propValue, err := parseKNFProperty(line, config) 98 99 if err != nil { 100 return nil, fmt.Errorf("Error at line %d: %w", lineNum, err) 101 } 102 103 fullPropName := genPropName(section, propName) 104 105 if config.HasProp(fullPropName) { 106 return nil, fmt.Errorf("Error at line %d: Property \"%s\" defined more than once", lineNum, propName) 107 } 108 109 config.props = append(config.props, fullPropName) 110 config.data[strings.ToLower(fullPropName)] = propValue 111 } 112 113 if !isDataRead { 114 return nil, fmt.Errorf("Configuration file doesn't contain any valid data") 115 } 116 117 return config, scanner.Err() 118 } 119 120 // parseKNFProperty parses line with property name and value 121 func parseKNFProperty(line string, config *Config) (string, string, error) { 122 di := strings.Index(line, _PROP_DELIMITER) 123 124 if di == -1 { 125 return "", "", fmt.Errorf("Property must have \":\" as a delimiter") 126 } 127 128 name, value := line[:di], line[di+1:] 129 130 name = strings.Trim(name, " \t") 131 value = strings.Trim(value, " \t") 132 133 if !strings.Contains(value, _MACRO_START_SYMBOL) && 134 !strings.Contains(value, _MACRO_END_SYMBOL) { 135 return name, value, nil 136 } 137 138 var err error 139 140 value, err = evalMacroses(value, config) 141 142 if err != nil { 143 return "", "", err 144 } 145 146 return name, value, nil 147 } 148 149 // evalMacroses evaluates all macroses in given string 150 func evalMacroses(value string, config *Config) (string, error) { 151 macroses := macroRE.FindAllStringSubmatch(value, -1) 152 153 for _, macros := range macroses { 154 full, section, prop := macros[0], macros[1], macros[2] 155 156 if !config.HasProp(genPropName(section, prop)) { 157 return "", fmt.Errorf("Unknown property %s", full) 158 } 159 160 propVal := config.GetS(genPropName(section, prop)) 161 value = strings.ReplaceAll(value, full, propVal) 162 } 163 164 return value, nil 165 } 166 167 // genPropName generates "full property name" which contains section and 168 // property name 169 func genPropName(section, prop string) string { 170 return section + _PROP_DELIMITER + prop 171 }