github.com/tooploox/oya@v0.0.21-0.20230524103240-1cda1861aad6/pkg/oyafile/parsing.go (about) 1 package oyafile 2 3 import ( 4 "fmt" 5 "strings" 6 7 "github.com/pkg/errors" 8 log "github.com/sirupsen/logrus" 9 "github.com/tooploox/oya/pkg/raw" 10 "github.com/tooploox/oya/pkg/semver" 11 "github.com/tooploox/oya/pkg/task" 12 "github.com/tooploox/oya/pkg/types" 13 ) 14 15 func Parse(raw *raw.Oyafile) (*Oyafile, error) { 16 of, err := raw.Decode() 17 if err != nil { 18 return nil, err 19 } 20 oyafile, err := New(raw.Path, raw.RootDir) 21 if err != nil { 22 return nil, err 23 } 24 25 for nameI, value := range of { 26 name, ok := nameI.(string) 27 if !ok { 28 return nil, errors.Errorf("Incorrect value name: %v", name) 29 } 30 switch name { 31 case "Import": 32 err := parseImports(value, oyafile) 33 if err != nil { 34 return nil, errors.Wrapf(err, "error parsing key %q", name) 35 } 36 case "Values": 37 err := parseValues(value, oyafile) 38 if err != nil { 39 return nil, errors.Wrapf(err, "error parsing key %q", name) 40 } 41 case "Project": 42 err := parseProject(value, oyafile) 43 if err != nil { 44 return nil, errors.Wrapf(err, "error parsing key %q", name) 45 } 46 case "Ignore": 47 err := parseIgnore(value, oyafile) 48 if err != nil { 49 return nil, errors.Wrapf(err, "error parsing key %q", name) 50 } 51 case "Changeset": 52 err := parseTask(name, value, oyafile) 53 if err != nil { 54 return nil, errors.Wrapf(err, "error parsing key %q", name) 55 } 56 case "Require": 57 if err := ensureProject(raw); err != nil { 58 return nil, errors.Wrapf(err, "unexpected Require directive") 59 } 60 err = parseRequire(name, value, oyafile) 61 if err != nil { 62 return nil, errors.Wrapf(err, "error parsing key %q", name) 63 } 64 case "Replace": 65 if err := ensureProject(raw); err != nil { 66 return nil, errors.Wrapf(err, "unexpected Replace directive") 67 } 68 if err := parseReplace(name, value, oyafile); err != nil { 69 return nil, errors.Wrapf(err, "error parsing key %q", name) 70 } 71 72 default: 73 taskName := task.Name(name) 74 if taskName.IsBuiltIn() { 75 log.Debugf("WARNING: Unrecognized built-in task or directive %q; skipping.", name) 76 continue 77 } 78 79 if err := parseTask(name, value, oyafile); err != nil { 80 return nil, errors.Wrapf(err, "error parsing key %q", name) 81 } 82 } 83 } 84 85 err = oyafile.resolveReplacements() 86 if err != nil { 87 return nil, err 88 } 89 90 err = oyafile.addBuiltIns() 91 if err != nil { 92 return nil, err 93 } 94 return oyafile, nil 95 } 96 97 // resolveReplacements replaces Requires paths based on Requires directives, if any. 98 func (oyafile *Oyafile) resolveReplacements() error { 99 for i, ref := range oyafile.Requires { 100 replPath, ok := oyafile.Replacements[ref.ImportPath] 101 if ok { 102 oyafile.Requires[i].ReplacementPath = replPath 103 } 104 } 105 return nil 106 } 107 108 func parseMeta(metaName, key string) (task.Name, bool) { 109 taskName := strings.TrimSuffix(key, "."+metaName) 110 return task.Name(taskName), taskName != key 111 } 112 113 func parseImports(value interface{}, o *Oyafile) error { 114 if value == nil { 115 return nil 116 } 117 imports, ok := value.(map[interface{}]interface{}) 118 if !ok { 119 return fmt.Errorf("expected map of aliases to paths") 120 } 121 for alias, path := range imports { 122 alias, ok := alias.(string) 123 if !ok { 124 return fmt.Errorf("expected import alias") 125 } 126 path, ok := path.(string) 127 if !ok { 128 return fmt.Errorf("expected import path") 129 } 130 o.Imports[types.Alias(alias)] = types.ImportPath(path) 131 } 132 return nil 133 } 134 135 func parseValues(value interface{}, o *Oyafile) error { 136 if value == nil { 137 return nil 138 } 139 values, ok := value.(map[interface{}]interface{}) 140 if !ok { 141 return fmt.Errorf("expected map of keys to values; got %T", value) 142 } 143 for k, v := range values { 144 key, ok := k.(string) 145 if !ok { 146 return fmt.Errorf("expected map of keys to values") 147 } 148 o.Values[key] = v 149 } 150 return nil 151 } 152 153 func parseProject(value interface{}, o *Oyafile) error { 154 projectName, ok := value.(string) 155 if !ok { 156 return fmt.Errorf("expected project name, actual: %v", value) 157 } 158 o.Project = projectName 159 return nil 160 } 161 162 func parseIgnore(value interface{}, o *Oyafile) error { 163 rulesI, ok := value.([]interface{}) 164 if !ok { 165 return fmt.Errorf("expected an array of ignore rules, actual: %v", value) 166 } 167 rules := make([]string, len(rulesI)) 168 for i, ri := range rulesI { 169 rule, ok := ri.(string) 170 if !ok { 171 return fmt.Errorf("expected an array of ignore rules, actual: %v", ri) 172 } 173 rules[i] = rule 174 } 175 o.Ignore = rules 176 return nil 177 } 178 179 func parseTask(name string, value interface{}, o *Oyafile) error { 180 s, ok := value.(string) 181 if !ok { 182 return fmt.Errorf("expected a script, actual: %v", name) 183 } 184 if taskName, ok := parseMeta("Doc", name); ok { 185 o.Tasks.AddDoc(taskName, s) 186 } else { 187 o.Tasks.AddTask(task.Name(name), task.Script{ 188 Script: s, 189 Shell: o.Shell, 190 Scope: &o.Values, 191 }) 192 } 193 return nil 194 } 195 196 func parseRequire(name string, value interface{}, o *Oyafile) error { 197 if value == nil { 198 return nil 199 } 200 201 defaultErr := fmt.Errorf("expected entries mapping pack import paths to their version, example: \"github.com/tooploox/oya-packReferences/docker: v1.0.0\"") 202 203 requires, ok := value.(map[interface{}]interface{}) 204 if !ok { 205 return defaultErr 206 } 207 208 packReferences := make([]PackReference, 0, len(requires)) 209 for importPathI, versionI := range requires { 210 importPath, ok := importPathI.(string) 211 if !ok { 212 return defaultErr 213 } 214 version, ok := versionI.(string) 215 if !ok { 216 return defaultErr 217 } 218 219 ver, err := semver.Parse(version) 220 if err != nil { 221 return err 222 } 223 packReferences = append(packReferences, 224 PackReference{ 225 ImportPath: types.ImportPath(importPath), 226 Version: ver, 227 }) 228 } 229 230 o.Requires = packReferences 231 return nil 232 } 233 234 func parseReplace(name string, value interface{}, o *Oyafile) error { 235 defaultErr := fmt.Errorf("expected entries mapping pack import paths to paths relative to the project root directory, example: \"github.com/tooploox/oya-pack/docker: /packs/docker\"") 236 237 replacements, ok := value.(map[interface{}]interface{}) 238 if !ok { 239 return defaultErr 240 } 241 242 for importPathI, pathI := range replacements { 243 importPath, ok := importPathI.(string) 244 if !ok { 245 return defaultErr 246 } 247 path, ok := pathI.(string) 248 if !ok { 249 return defaultErr 250 } 251 252 o.Replacements[types.ImportPath(importPath)] = path 253 } 254 255 return nil 256 257 } 258 259 func ensureProject(raw *raw.Oyafile) error { 260 _, hasProject, err := raw.Project() 261 if err != nil { 262 return err 263 } 264 if hasProject { 265 return nil 266 } 267 return errors.Errorf("must be in file with a Project directive") 268 }