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  }