github.com/theliebeskind/genfig@v0.1.5-alpha/generator/generator.go (about) 1 package generator 2 3 import ( 4 "errors" 5 "fmt" 6 "io/ioutil" 7 "os" 8 "path/filepath" 9 "regexp" 10 "strings" 11 12 "github.com/theliebeskind/genfig/writers" 13 14 "github.com/theliebeskind/genfig/models" 15 16 "github.com/theliebeskind/genfig/parsers" 17 "github.com/theliebeskind/genfig/util" 18 ) 19 20 const ( 21 defaultEnvName = "default" 22 defaultSchemaFilename = "schema.go" 23 defaultEnvsFilename = "envs.go" 24 defaultInitFilename = "init.go" 25 defaulGenfigFilename = "genfig.go" 26 defaultConfigFilePrefix = "env_" 27 defaultPackage = "config" 28 defaultCmd = "genfig" 29 ) 30 31 var ( 32 ymlStrategy = parsers.YamlStrategy{} 33 tomlStrategy = parsers.TomlStrategy{} 34 dotenvStrategy = parsers.DotenvStrategy{} 35 ) 36 37 var ( 38 allowedExtensions = []string{"\\.yml", "\\.yaml", "\\.json", "\\.toml"} 39 allowedPrefixes = []string{"\\.env"} 40 parsersMap = map[string]parsers.ParsingStrategy{ 41 "yml": &ymlStrategy, 42 "json": &ymlStrategy, 43 "toml": &tomlStrategy, 44 "dotenv": &dotenvStrategy, 45 } 46 envReStr = `((?:` + strings.Join(allowedPrefixes, "|") + `)\.([\w\.]+))|(([\w\.]+)(` + strings.Join(allowedExtensions, "|") + `))` 47 envRe = regexp.MustCompile(envReStr) 48 ) 49 50 // Generate generates the go config files 51 func Generate(files []string, params models.Params) ([]string, error) { 52 var err error 53 if len(files) == 0 { 54 return nil, errors.New("No files to generate from") 55 } 56 57 if !filepath.IsAbs(params.Dir) { 58 params.Dir, _ = filepath.Abs(params.Dir) 59 } 60 61 envs := map[string]string{} 62 envMap := make(map[string]map[string]interface{}) 63 fileMap := make(map[string]string) 64 65 for _, f := range files { 66 if _, err := os.Stat(f); err != nil { 67 return nil, err 68 } 69 70 env, typ := parseFilename(filepath.Base(f)) 71 if env == "" { 72 continue 73 } 74 if _, exists := parsersMap[typ]; !exists { 75 continue 76 } 77 if _, exists := envMap[env]; exists { 78 return nil, fmt.Errorf("Environment '%s' does already exist", env) 79 } 80 var err error 81 envMap[env], err = parseFile(f, parsersMap[typ]) 82 if err != nil { 83 return nil, err 84 } 85 fileMap[env] = f 86 } 87 88 if len(envMap) == 0 { 89 return nil, errors.New("No suitable config files found") 90 } 91 92 if params.DefaultEnv == "" { 93 params.DefaultEnv = defaultEnvName 94 } 95 96 var defaultEnv map[string]interface{} 97 var hasDefault bool 98 if defaultEnv, hasDefault = envMap[params.DefaultEnv]; !hasDefault { 99 return nil, errors.New("Missing default config") 100 } 101 102 if err := os.MkdirAll(params.Dir, 0777); params.Dir != "" && err != nil { 103 return nil, err 104 } 105 106 gofiles := []string{} 107 108 // write schemafile 109 var schema models.SchemaMap 110 schemaFileName := filepath.Join(params.Dir, defaultSchemaFilename) 111 source := fmt.Sprintf("%s (schema built from '%s')", defaultCmd, filepath.Base(fileMap[params.DefaultEnv])) 112 if err := func() (err error) { 113 var f *os.File 114 defer func() { 115 if f != nil { 116 _ = f.Close() 117 } 118 }() 119 if f, err = os.Create(schemaFileName); err != nil { 120 return err 121 } else if err = writers.WriteHeader(f, defaultPackage, source); err != nil { 122 return err 123 } else if schema, err = writers.WriteAndReturnSchema(f, defaultEnv); err != nil { 124 return err 125 } 126 return 127 }(); err != nil { 128 return nil, err 129 } 130 gofiles = append(gofiles, schemaFileName) 131 132 // write config files 133 for env, data := range envMap { 134 out := defaultConfigFilePrefix 135 if env == "test" { 136 out += "test_.go" 137 } else { 138 out += env + ".go" 139 } 140 path := filepath.Join(params.Dir, out) 141 source := fmt.Sprintf("%s (config built by merging '%s' and '%s')", defaultCmd, filepath.Base(fileMap[params.DefaultEnv]), filepath.Base(fileMap[env])) 142 name := strings.ReplaceAll(strings.Title(strings.ReplaceAll(env, "_", ".")), ".", "") 143 envs[env] = name 144 145 // Check of schema of this config does conform the the global schema 146 // If is has additional fields or fields with different schema themselves, 147 // it fails 148 var configSchema models.SchemaMap 149 if configSchema, err = writers.WriteAndReturnSchema(util.NoopWriter{}, data); err != nil { 150 return nil, err 151 } 152 for k, s := range configSchema { 153 if s.IsStruct { 154 continue 155 } 156 if _, exists := schema[k]; !exists || s.Content != schema[k].Content { 157 return nil, fmt.Errorf("%s has at leas one non-conformant field: '%s': %s != '%s'", fileMap[env], k, s.Content, schema[k].Content) 158 } 159 } 160 161 if err := func() (err error) { 162 var f *os.File 163 defer func() { 164 if f != nil { 165 _ = f.Close() 166 } 167 }() 168 if f, err = os.Create(path); err != nil { 169 return err 170 } else if err = writers.WriteHeader(f, defaultPackage, source); err != nil { 171 return err 172 } else if err = writers.WriteConfig(f, schema, data, defaultEnv, name); err != nil { 173 return err 174 } 175 return 176 }(); err != nil { 177 return nil, err 178 } 179 180 gofiles = append(gofiles, path) 181 } 182 183 // write env file 184 envsFileName := filepath.Join(params.Dir, defaultEnvsFilename) 185 if err := func() (err error) { 186 var f *os.File 187 defer func() { 188 if f != nil { 189 _ = f.Close() 190 } 191 }() 192 if f, err = os.Create(envsFileName); err != nil { 193 return err 194 } else if err = writers.WriteHeader(f, defaultPackage, defaultCmd); err != nil { 195 return err 196 } else if err = writers.WriteEnvs(f, envs); err != nil { 197 return err 198 } 199 return 200 }(); err != nil { 201 return nil, err 202 } 203 gofiles = append(gofiles, envsFileName) 204 205 pluginCalls := map[string]string{} 206 // write plugins files 207 var pfiles []string 208 if pfiles, err = writers.WritePlugins(schema, params.Dir, defaultPackage, defaultCmd, pluginCalls); err != nil { 209 return nil, err 210 } 211 gofiles = append(gofiles, pfiles...) 212 213 // write init file 214 initFileName := filepath.Join(params.Dir, defaultInitFilename) 215 if err := func() (err error) { 216 var f *os.File 217 defer func() { 218 if f != nil { 219 _ = f.Close() 220 } 221 }() 222 if f, err = os.Create(initFileName); err != nil { 223 return err 224 } else if err = writers.WriteHeader(f, defaultPackage, defaultCmd); err != nil { 225 return err 226 } else if err = writers.WriteInit(f, pluginCalls); err != nil { 227 return err 228 } 229 return 230 }(); err != nil { 231 return nil, err 232 } 233 gofiles = append(gofiles, schemaFileName) 234 235 return gofiles, nil 236 } 237 238 func parseFile(f string, s parsers.ParsingStrategy) (map[string]interface{}, error) { 239 data, err := ioutil.ReadFile(f) 240 if err != nil { 241 return nil, err 242 } 243 return s.Parse(data) 244 } 245 246 func parseFilename(f string) (string, string) { 247 typ := filepath.Ext(f) 248 if len(typ) == 0 { 249 return "", "" 250 } 251 typ = typ[1:] 252 if typ == "yaml" { 253 typ = "yml" 254 } else if strings.HasPrefix(f, ".env") { 255 typ = "dotenv" 256 } 257 258 match := envRe.FindAllStringSubmatch(f, 1) 259 if len(match) == 0 { 260 return "", typ 261 } 262 if match[0][2] != "" { 263 return match[0][2], typ 264 } 265 if match[0][4] != "" { 266 return match[0][4], typ 267 } 268 return "", typ 269 }