gopkg.in/easygen.v4@v4.1.0/easygen.go (about) 1 //////////////////////////////////////////////////////////////////////////// 2 // Package: easygen 3 // Purpose: Easy to use universal code/text generator 4 // Authors: Tong Sun (c) 2015-2019, All rights reserved 5 //////////////////////////////////////////////////////////////////////////// 6 7 /* 8 9 Package easygen is an easy to use universal code/text generator library. 10 11 It can be used as a text or html generator for arbitrary purposes with arbitrary data and templates. 12 13 It can be used as a code generator, or anything that is structurally repetitive. Some command line parameter handling code generator are provided as examples, including the Go's built-in flag package, and the viper & cobra package. 14 15 Many examples have been provided to showcase its functionality, and different ways to use it. 16 17 */ 18 package easygen 19 20 import ( 21 "encoding/json" 22 "fmt" 23 "io" 24 "io/ioutil" 25 "os" 26 "path/filepath" 27 "regexp" 28 "strings" 29 30 "gopkg.in/yaml.v2" 31 ) 32 33 //////////////////////////////////////////////////////////////////////////// 34 // Constant and data type/structure definitions 35 36 //////////////////////////////////////////////////////////////////////////// 37 // Global variables definitions 38 39 // EgData -- EasyGen driven Data 40 type EgData interface{} 41 42 // Opts holds the actual values from the command line parameters 43 var Opts = Options{ExtYaml: ".yaml", ExtJson: ".json", ExtTmpl: ".tmpl"} 44 45 //////////////////////////////////////////////////////////////////////////// 46 // Function definitions 47 48 // ReadDataFile reads in the driving data from the given file, which can 49 // be optionally without the defined extension 50 func ReadDataFile(fileName string) EgData { 51 if IsExist(fileName + Opts.ExtYaml) { 52 return ReadYamlFile(fileName + Opts.ExtYaml) 53 } else if IsExist(fileName + Opts.ExtJson) { 54 return ReadJsonFile(fileName + Opts.ExtJson) 55 } else if IsExist(fileName) { 56 verbose("Reading exist Data File", 2) 57 fext := filepath.Ext(fileName) 58 fext = fext[1:] // ignore the leading "." 59 if regexp.MustCompile(`(?i)^y`).MatchString(fext) { 60 verbose("Reading YAML file", 2) 61 return ReadYamlFile(fileName) 62 } else if regexp.MustCompile(`(?i)^j`).MatchString(fext) { 63 return ReadJsonFile(fileName) 64 } else { 65 checkError(fmt.Errorf("Unsupported file extension for DataFile '%s'", fileName)) 66 } 67 } 68 checkError(fmt.Errorf("DataFile '%s' cannot be found", fileName)) 69 return nil 70 } 71 72 // ReadYamlFile reads given YAML file as EgData 73 func ReadYamlFile(fileName string) EgData { 74 source, err := ioutil.ReadFile(fileName) 75 checkError(err) 76 77 m := make(map[interface{}]interface{}) 78 79 err = yaml.Unmarshal(source, &m) 80 checkError(err) 81 82 return m 83 } 84 85 // ReadJsonFile reads given JSON file as EgData 86 func ReadJsonFile(fileName string) EgData { 87 source, err := ioutil.ReadFile(fileName) 88 checkError(err) 89 90 m := make(map[string]interface{}) 91 92 err = json.Unmarshal(source, &m) 93 checkError(err) 94 95 return m 96 } 97 98 // IsExist checks if the given file exist 99 func IsExist(fileName string) bool { 100 //fmt.Printf("] Checking %s\n", fileName) 101 _, err := os.Stat(fileName) 102 return err == nil || os.IsExist(err) 103 // CAUTION! os.IsExist(err) != !os.IsNotExist(err) 104 // https://gist.github.com/mastef/05f46d3ab2f5ed6a6787#file-isexist_vs_isnotexist-go-L35-L56 105 } 106 107 // Process will process the standard easygen input: the `fileName` is for both template and data file name, and produce output from the template according to the corresponding driving data. 108 // Process() is using the V3's calling convention and *only* works properly in V4+ in the case that there is only one fileName passed to it. If need to pass more files, use Process2() instead. 109 func Process(t Template, wr io.Writer, fileNames ...string) error { 110 return Process2(t, wr, fileNames[0], fileNames[:1]...) 111 } 112 113 // Process2 will process the case that *both* template and data file names are given, and produce output according to the given template and driving data files, 114 // specified via fileNameTempl and fileNames respectively. 115 // fileNameTempl can be a comma-separated string giving many template files 116 func Process2(t Template, wr io.Writer, fileNameTempl string, fileNames ...string) error { 117 for _, dataFn := range fileNames { 118 for _, templateFn := range strings.Split(fileNameTempl, ",") { 119 err := Process1(t, wr, templateFn, dataFn) 120 checkError(err) 121 } 122 } 123 return nil 124 } 125 126 // Process1 will process a *single* case where both template and data file names are given, and produce output according to the given template and driving data files, 127 // specified via fileNameTempl and fileName respectively. 128 // fileNameTempl is not a comma-separated string, but for a single template file. 129 func Process1(t Template, wr io.Writer, fileNameTempl string, fileName string) error { 130 m := ReadDataFile(fileName) 131 //fmt.Printf("] %+v\n", m) 132 133 // template file 134 fileName = fileNameTempl 135 fileNameT := fileNameTempl 136 if IsExist(fileName + Opts.ExtTmpl) { 137 fileNameT = fileName + Opts.ExtTmpl 138 } else { 139 // guard against that fileNameTempl passed with Opts.ExtYaml extension 140 if fileName[len(fileName)-len(Opts.ExtYaml):] == Opts.ExtYaml { 141 idx := strings.LastIndex(fileName, ".") 142 fileName = fileName[:idx] 143 if IsExist(fileName + Opts.ExtTmpl) { 144 fileNameT = fileName + Opts.ExtTmpl 145 } 146 } else if IsExist(fileName) { 147 // fileNameTempl passed with Opts.ExtTmpl already 148 fileNameT = fileName 149 } 150 } 151 152 return Execute(t, wr, fileNameT, m) 153 } 154 155 // Execute will execute the Template on the given data map `m`. 156 func Execute(t Template, wr io.Writer, fileNameT string, m EgData) error { 157 // 1. Check locally 158 verbose("Checking for template locally: "+fileNameT, 1) 159 if !IsExist(fileNameT) { 160 // 2. Check under /etc/ 161 command := filepath.Base(os.Args[0]) 162 templateFile := fmt.Sprintf("/etc/%s/%s", command, fileNameT) 163 verbose("Checking at "+templateFile, 1) 164 if IsExist(templateFile) { 165 fileNameT = templateFile 166 } else { 167 // 3. Check where executable is 168 ex, e := os.Executable() 169 if e != nil { 170 return e 171 } 172 fileNameT = filepath.Dir(ex) + string(filepath.Separator) + fileNameT 173 verbose("Checking at "+fileNameT, 1) 174 if !IsExist(fileNameT) { 175 checkError(fmt.Errorf("Template file '%s' cannot be found", fileNameT)) 176 } 177 } 178 } 179 180 tn, err := t.ParseFiles(fileNameT) 181 checkError(err) 182 183 return tn.ExecuteTemplate(wr, filepath.Base(fileNameT), m) 184 } 185 186 // Process0 will produce output according to the driving data *without* a template file, using the string from strTempl as the template 187 func Process0(t Template, wr io.Writer, strTempl string, fileNames ...string) error { 188 fileName := fileNames[0] 189 m := ReadDataFile(fileName) 190 191 tmpl, err := t.Parse(strTempl) 192 checkError(err) 193 return tmpl.Execute(wr, m) 194 } 195 196 //////////////////////////////////////////////////////////////////////////// 197 // Support Function definitions 198 199 // Exit if error occurs 200 func checkError(err error) { 201 if err != nil { 202 fmt.Fprintf(os.Stderr, "[%s] Fatal error - %s\n", progname, err) 203 os.Exit(1) 204 } 205 } 206 207 // verbose will print info to stderr according to the verbose level setting 208 func verbose(step string, level int) { 209 if Opts.Debug >= level { 210 print("[", progname, "] ", step, "\n") 211 } 212 }