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  }