github.com/sudo-bmitch/version-bump@v0.0.0-20240503123857-70b0e3f646dd/internal/config/config.go (about) 1 // Package config defines the config file and load methods 2 package config 3 4 import ( 5 "errors" 6 "fmt" 7 "io" 8 "os" 9 10 "github.com/sudo-bmitch/version-bump/internal/template" 11 "gopkg.in/yaml.v3" 12 ) 13 14 // File defines a file to process for version bumps 15 type File struct { 16 Name string `yaml:"-" json:"-"` // Name is a filename or glob to match against 17 Scans []string `yaml:"scans" json:"scans"` // Scans are Scan names to apply to the file 18 } 19 20 // Scan defines how to search a file for versions to bump 21 type Scan struct { 22 Name string `yaml:"-" json:"-"` // Name is the name of the scan entry 23 Type string `yaml:"type" json:"type"` // Type is the method for scanning the file 24 Source string `yaml:"source" json:"source"` // Source is the name of the source for updating the version 25 Args map[string]string `yaml:"args" json:"args"` // Args provide additional options used by scanners, sources, and templating 26 } 27 28 // Source defines how to get the latest version 29 type Source struct { 30 Name string `yaml:"-" json:"-"` // Name is the name of the source entry 31 Type string `yaml:"type" json:"type"` // Type is the method used to query the source 32 Key string `yaml:"key" json:"key"` // Key is a unique value to store with the source and version in a lock file 33 Filter SourceFilter `yaml:"filter" json:"filter"` // Filter specifies which items to include from the source 34 Sort SourceSort `yaml:"sort" json:"sort"` // Sort is used to pick from multiple results 35 Template string `yaml:"template" json:"template"` // Template is used to output the version 36 Args map[string]string `yaml:"args" json:"args"` // Args provide additional options used by sources 37 // TODO: delete exec? 38 Exec []string `yaml:"exec" json:"exec"` // Exec defines a command to run for custom sources 39 } 40 41 // SourceFilter defines how items are filtered in from the source. 42 // By default, all items are included. 43 type SourceFilter struct { 44 Expr string `yaml:"expr" json:"expr"` 45 Template string `yaml:"template" json:"template"` 46 } 47 48 // SourceSort defines how multiple results should be filtered and sorted. 49 // By default, sort returns the 0 offset of a descending sort. 50 type SourceSort struct { 51 Method string `yaml:"method" json:"method"` 52 Asc bool `yaml:"asc" json:"asc"` 53 Offset uint `yaml:"offset" json:"offset"` 54 } 55 56 // Script defines an addition command to run 57 type Script struct { 58 Name string `yaml:"-" json:"-"` // Name is the name of the script 59 Step string `yaml:"step" json:"step"` // Step is when to execute the script, pre-check, post-check, pre-update, post-update 60 Exec []string `yaml:"exec" json:"exec"` // Exec defines the command to run for this script 61 } 62 63 // Config contains the configuration options for the project 64 type Config struct { 65 Version int `yaml:"version" json:"version"` 66 Files map[string]*File `yaml:"files" json:"files"` 67 Scans map[string]*Scan `yaml:"scans" json:"scans"` 68 Sources map[string]*Source `yaml:"sources" json:"sources"` 69 Scripts map[string]*Script `yaml:"scripts" json:"scripts"` 70 } 71 72 // SourceTmplData is passed from the scan to the source for performing a version lookup 73 type SourceTmplData struct { 74 Filename string // name of the file being updated 75 ScanArgs map[string]string // args to the scan 76 ScanMatch map[string]string // result of a match 77 SourceArgs map[string]string // args to the source 78 } 79 80 // New creates an empty config 81 func New() *Config { 82 return &Config{ 83 Files: map[string]*File{}, 84 Scans: map[string]*Scan{}, 85 Sources: map[string]*Source{}, 86 Scripts: map[string]*Script{}, 87 } 88 } 89 90 // LoadReader imports a config from a reader 91 func LoadReader(r io.Reader) (*Config, error) { 92 c := New() 93 err := yaml.NewDecoder(r).Decode(c) 94 if err != nil && !errors.Is(err, io.EOF) { 95 return nil, fmt.Errorf("failed to parse config: %w", err) 96 } 97 if c.Version > 1 { 98 return nil, fmt.Errorf("unsupported config version: %d", c.Version) 99 } 100 for k := range c.Files { 101 c.Files[k].Name = k 102 } 103 for k := range c.Scans { 104 c.Scans[k].Name = k 105 } 106 for k := range c.Sources { 107 c.Sources[k].Name = k 108 } 109 for k := range c.Scripts { 110 c.Scripts[k].Name = k 111 } 112 return c, nil 113 } 114 115 // LoadFile imports a config from a filename 116 func LoadFile(filename string) (*Config, error) { 117 file, err := os.Open(filename) 118 if err != nil { 119 return nil, err 120 } 121 defer file.Close() 122 return LoadReader(file) 123 } 124 125 func (s Source) ExpandTemplate(data interface{}) (Source, error) { 126 sExp := Source{ 127 Name: s.Name, 128 Type: s.Type, 129 Filter: s.Filter, 130 Sort: s.Sort, 131 Template: s.Template, 132 Args: map[string]string{}, 133 } 134 var err error 135 sExp.Key, err = template.String(s.Key, data) 136 if err != nil { 137 return sExp, fmt.Errorf("failed to parse template for source \"%s\": \"%s\": %w", s.Name, s.Key, err) 138 } 139 for k := range s.Args { 140 sExp.Args[k], err = template.String(s.Args[k], data) 141 if err != nil { 142 return sExp, fmt.Errorf("failed to parse template for source \"%s\": \"%s\": %w", s.Name, s.Args[k], err) 143 } 144 } 145 sExp.Filter.Expr, err = template.String(s.Filter.Expr, data) 146 if err != nil { 147 return sExp, fmt.Errorf("failed to parse template for source \"%s\": \"%s\": %w", s.Name, s.Filter.Expr, err) 148 } 149 // TODO: support exec field too? 150 return sExp, nil 151 }