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  }