github.com/haraldrudell/parl@v0.4.176/yamlo/apply-yaml.go (about)

     1  /*
     2  © 2022–present Harald Rudell <harald.rudell@gmail.com> (https://haraldrudell.github.io/haraldrudell/)
     3  ISC License
     4  */
     5  
     6  // Package yamlo allows the yamler package to unmarshal unexported types and finds yaml configuration files.
     7  package yamlo
     8  
     9  import (
    10  	"strings"
    11  
    12  	"github.com/haraldrudell/parl"
    13  	"github.com/haraldrudell/parl/perrors"
    14  	"github.com/haraldrudell/parl/pflags"
    15  )
    16  
    17  const (
    18  	// key name from options or a default ‘options’
    19  	defaultTopKey = "options"
    20  )
    21  
    22  // ApplyYaml updates effective options with values read from a yaml file
    23  //   - program is app name “date” used to construct yaml file name
    24  //   - yamlFile is a specified file, or for empty string, a scan for files:
    25  //   - — filename: [program]-[hostname].yaml [program].yaml
    26  //   - — Directories: ~/apps .. /etc
    27  //   - — if a specified file is missing, that is error
    28  //   - — if no default file exists, or file was empty,
    29  //     no yaml options are loaded
    30  //   - yamlDictionaryKey is the key read form the top-level dictionary
    31  //     in yaml, empty string is default “options:”
    32  //   - genericYaml is a wrapper of unknown types
    33  //   - optionData is the list of options, containing pointers to effective
    34  //     option values
    35  //   - The top entry in the yaml file must be a dictionary
    36  //   - The value for yamlDictionaryKey must be a dictionary
    37  //   - the remainder of the yamlDictionaryKey is read when it matches
    38  //     the YamData struct
    39  //   - -verbose=yamlo.ApplyYaml “github.com/haraldrudell/parl/yamlo.ApplyYaml”
    40  func ApplyYaml(
    41  	program, yamlFile, yamlDictionaryKey string, doYaml bool,
    42  	genericYaml GenericYaml,
    43  	optionData []pflags.OptionData,
    44  ) (err error) {
    45  	if genericYaml == nil {
    46  		panic(perrors.NewPF("genericYaml cannot be nil"))
    47  	} else if !doYaml {
    48  		return
    49  	}
    50  
    51  	// read text from the yaml file
    52  	var yamlText []byte
    53  	parl.Debug("Arguments: yamlFile: %q yamlKey: %q", yamlFile, yamlDictionaryKey)
    54  	if yamlDictionaryKey == "" {
    55  		yamlDictionaryKey = defaultTopKey
    56  	}
    57  	// the filename actually read
    58  	var filename string
    59  	if filename, yamlText, err = FindFile(yamlFile, program); err != nil {
    60  		return // yaml read failure return
    61  	} else if filename == "" || len(yamlText) == 0 {
    62  		parl.Debug("no yaml file")
    63  		return // no default file existed, or file was empty: noop
    64  	}
    65  	parl.Debug("filename: %q top-level key: %q bytes: %q", filename, yamlDictionaryKey, string(yamlText))
    66  
    67  	// unmarshal yaml into genericYaml value pointer
    68  	//	- ie. main.y effective yaml values
    69  	var hasData bool
    70  	if hasData, err = genericYaml.Unmarshal(yamlText, yamlDictionaryKey); perrors.IsPF(&err, "filename: %q: %w", filename, err) {
    71  		return // error during unmarshaling return
    72  	} else if !hasData {
    73  		parl.Debug("has no data")
    74  		return // yaml contained no data, eg. no “options:” key
    75  	}
    76  
    77  	// get a map of the field references that is appearing in yaml
    78  	var yamlVisistedReferences map[any]string
    79  	if yamlVisistedReferences, err = genericYaml.VisitedReferencesMap(yamlText, yamlDictionaryKey); err != nil {
    80  		return // unmarshal failure return
    81  	}
    82  	yamlText = nil
    83  	if parl.IsThisDebug() {
    84  		parl.Log("yamlVisitedKeys: %v\n", yamlVisistedReferences)
    85  		var oMap = make(map[string]any)
    86  		for _, o := range optionData {
    87  			var yamlFieldReference = o.Y
    88  			if yamlFieldReference == nil {
    89  				continue
    90  			}
    91  			oMap[o.Name] = o.Y
    92  		}
    93  		parl.Log("option-references: %v\n", oMap)
    94  		parl.Log("yaml-y: %s", genericYaml.YDump())
    95  		var effectiveValueMap, _ = pflags.OptionValues(optionData)
    96  		parl.Log("option-values: %v\n", effectiveValueMap)
    97  	}
    98  
    99  	// get map of visited options
   100  	var visitedOptions = pflags.NewVisitedOptions().Map()
   101  	if parl.IsThisDebug() {
   102  		var opts []string
   103  		for k := range visitedOptions {
   104  			opts = append(opts, k)
   105  		}
   106  		parl.Log("visitedOptions: %s", strings.Join(opts, "\x20"))
   107  	}
   108  
   109  	// iterate over options and apply yaml changes
   110  	//	- ignore if no yaml key
   111  	//	- ignore if yamlVisitedKeys exists and do not have the option
   112  	for _, optionData := range optionData {
   113  		if visitedOptions[optionData.Name] || // was specified on command line, overrides yaml
   114  			optionData.Y == nil || // does not have yaml value
   115  			yamlVisistedReferences[optionData.Y] == "" { // was not visted by yaml
   116  			continue
   117  		}
   118  		if err = optionData.ApplyYaml(); err != nil {
   119  			return
   120  		}
   121  	}
   122  
   123  	if parl.IsThisDebug() {
   124  		var effectiveValueMap, _ = pflags.OptionValues(optionData)
   125  		parl.Log("resulting option-values: %v\n", effectiveValueMap)
   126  	}
   127  
   128  	return
   129  }