From 74c9db17348d37fc0a4d05be4cb7e033a55a3e4f Mon Sep 17 00:00:00 2001 From: daved Date: Fri, 31 Mar 2023 19:17:31 -0700 Subject: [PATCH 01/31] Start unification of vars in conds and tmpls --- activestate.yaml | 4 ++ cmd/state/main.go | 9 ++- internal/constraints/constraints.go | 87 +++++++--------------------- pkg/projectfile/vars/vars.go | 88 +++++++++++++++++++++++++++++ 4 files changed, 118 insertions(+), 70 deletions(-) create mode 100644 pkg/projectfile/vars/vars.go diff --git a/activestate.yaml b/activestate.yaml index 2189c1e3d8..447227ce67 100644 --- a/activestate.yaml +++ b/activestate.yaml @@ -46,6 +46,10 @@ constants: if: ne .OS.Name "Windows" value: .sh scripts: + - name: example + language: bash + if: eq .OS.Name "Linux" + value: echo $os.name && echo $project.path() - name: install-deps language: bash if: ne .Shell "cmd" diff --git a/cmd/state/main.go b/cmd/state/main.go index 59e0b11dd6..97a0675dcf 100644 --- a/cmd/state/main.go +++ b/cmd/state/main.go @@ -16,6 +16,7 @@ import ( "github.com/ActiveState/cli/cmd/state/internal/cmdtree" anAsync "github.com/ActiveState/cli/internal/analytics/client/async" + "github.com/ActiveState/cli/internal/captain" "github.com/ActiveState/cli/internal/config" "github.com/ActiveState/cli/internal/constants" "github.com/ActiveState/cli/internal/constraints" @@ -41,6 +42,7 @@ import ( "github.com/ActiveState/cli/pkg/platform/model" "github.com/ActiveState/cli/pkg/project" "github.com/ActiveState/cli/pkg/projectfile" + "github.com/ActiveState/cli/pkg/projectfile/vars" ) func main() { @@ -214,9 +216,12 @@ func run(args []string, isInteractive bool, cfg *config.Instance, out output.Out // Set up conditional, which accesses a lot of primer data sshell := subshell.New(cfg) - conditional := constraints.NewPrimeConditional(auth, pj, sshell.Shell()) + projVars := vars.New(auth, pj, sshell.Shell()) + + conditional := constraints.NewPrimeConditional(projVars) project.RegisterConditional(conditional) - project.RegisterExpander("mixin", project.NewMixin(auth).Expander) + project.RegisterComplexExpander(projVars) + //project.RegisterExpander("mixin", project.NewMixin(auth).Expander) project.RegisterExpander("secrets", project.NewSecretPromptingExpander(secretsapi.Get(), prompter, cfg, auth)) // Run the actual command diff --git a/internal/constraints/constraints.go b/internal/constraints/constraints.go index 0d12f7bcac..84d0b8e7bc 100644 --- a/internal/constraints/constraints.go +++ b/internal/constraints/constraints.go @@ -3,7 +3,6 @@ package constraints import ( "bytes" "fmt" - "path/filepath" "regexp" "sort" "strings" @@ -12,27 +11,11 @@ import ( "github.com/ActiveState/cli/internal/locale" "github.com/ActiveState/cli/internal/logging" "github.com/ActiveState/cli/internal/multilog" - "github.com/ActiveState/cli/internal/rtutils/p" - "github.com/ActiveState/cli/pkg/platform/authentication" "github.com/ActiveState/cli/pkg/projectfile" - "github.com/ActiveState/cli/pkg/sysinfo" + "github.com/ActiveState/cli/pkg/projectfile/vars" "github.com/thoas/go-funk" ) -var cache = make(map[string]interface{}) - -func getCache(key string, getter func() (interface{}, error)) (interface{}, error) { - if v, ok := cache[key]; ok { - return v, nil - } - v, err := getter() - if err != nil { - return nil, err - } - cache[key] = v - return v, err -} - // For testing. var osOverride, osVersionOverride, archOverride, libcOverride, compilerOverride string @@ -41,22 +24,9 @@ type Conditional struct { funcs template.FuncMap } -func NewConditional(a *authentication.Auth) *Conditional { +func NewConditional() *Conditional { c := &Conditional{map[string]interface{}{}, map[string]interface{}{}} - c.RegisterFunc("Mixin", func() map[string]interface{} { - res := map[string]string{ - "Name": "", - "Email": "", - } - if a.Authenticated() { - res["Name"] = a.WhoAmI() - res["Email"] = a.Email() - } - return map[string]interface{}{ - "User": res, - } - }) c.RegisterFunc("Contains", funk.Contains) c.RegisterFunc("HasPrefix", strings.HasPrefix) c.RegisterFunc("HasSuffix", strings.HasSuffix) @@ -72,41 +42,9 @@ func NewConditional(a *authentication.Auth) *Conditional { return c } -type projectable interface { - Owner() string - Name() string - NamespaceString() string - CommitID() string - BranchName() string - Path() string - URL() string -} - -func NewPrimeConditional(auth *authentication.Auth, pj projectable, subshellName string) *Conditional { - var ( - pjOwner string - pjName string - pjNamespace string - pjURL string - pjCommit string - pjBranch string - pjPath string - ) - if !p.IsNil(pj) { - pjOwner = pj.Owner() - pjName = pj.Name() - pjNamespace = pj.NamespaceString() - pjURL = pj.URL() - pjCommit = pj.CommitID() - pjBranch = pj.BranchName() - pjPath = pj.Path() - if pjPath != "" { - pjPath = filepath.Dir(pjPath) - } - } - - c := NewConditional(auth) - c.RegisterParam("Project", map[string]string{ +func NewPrimeConditional(vs *vars.Vars) *Conditional { + c := NewConditional() + /*c.RegisterParam("Project", map[string]string{ // map[string]interface{} should also work here "Owner": pjOwner, "Name": pjName, "Namespace": pjNamespace, @@ -127,8 +65,21 @@ func NewPrimeConditional(auth *authentication.Auth, pj projectable, subshellName "Version": osVersion, "Architecture": sysinfo.Architecture().String(), }) - c.RegisterParam("Shell", subshellName) + c.RegisterParam("Shell", subshellName)*/ + /*c.RegisterFunc("Mixin", func() map[string]interface{} { // looks like lazy loading + res := map[string]string{ + "Name": "", + "Email": "", + } + if a.Authenticated() { + res["Name"] = a.WhoAmI() + res["Email"] = a.Email() + } + return map[string]interface{}{ + "User": res, + } + })*/ return c } diff --git a/pkg/projectfile/vars/vars.go b/pkg/projectfile/vars/vars.go new file mode 100644 index 0000000000..de899829f0 --- /dev/null +++ b/pkg/projectfile/vars/vars.go @@ -0,0 +1,88 @@ +package vars + +import ( + "path/filepath" + + "github.com/ActiveState/cli/internal/multilog" + "github.com/ActiveState/cli/internal/rtutils/p" + "github.com/ActiveState/cli/pkg/platform/authentication" + "github.com/ActiveState/cli/pkg/sysinfo" +) + +type projectable interface { + Owner() string + Name() string + NamespaceString() string + CommitID() string + BranchName() string + Path() string + URL() string +} + +type Project struct { + Namespace string `vars:"NamespacePrefix"` + Name string + Owner string + URL string `vars:"Url"` + Commit string + Branch string + Path string +} + +type OS struct { + Name string + Version *sysinfo.OSVersionInfo + Architecture string +} + +type User struct { + Name string + Email string +} + +type Mixin struct { + User *User +} + +type Vars struct { + Project *Project + OS *OS + Shell string + Mixin *Mixin +} + +func New(auth *authentication.Auth, pj projectable, subshellName string) *Vars { + var ( + proj = &Project{} + ) + if !p.IsNil(pj) { + proj.Namespace = pj.NamespaceString() + proj.Owner = pj.Owner() + proj.Name = pj.Name() + proj.URL = pj.URL() + proj.Commit = pj.CommitID() + proj.Branch = pj.BranchName() + proj.Path = pj.Path() + if proj.Path != "" { + proj.Path = filepath.Dir(proj.Path) + } + } + + osVersion, err := sysinfo.OSVersion() + if err != nil { + multilog.Error("Could not detect OSVersion: %v", err) + } + + os := &OS{ + Name: sysinfo.OS().String(), + Version: osVersion, + Architecture: sysinfo.Architecture().String(), + } + + return &Vars{ + Project: proj, + OS: os, + Shell: subshellName, + Mixin: mixin, + } +} From 5aa013b5a58ee2089e7cde5d8459cd124f76cce5 Mon Sep 17 00:00:00 2001 From: daved Date: Tue, 11 Apr 2023 18:26:58 -0700 Subject: [PATCH 02/31] Fix constraints conditional from value construction --- activestate.yaml | 2 +- cmd/state/main.go | 5 +- internal/constraints/constraints.go | 60 +++++++++------------ pkg/projectfile/vars/vars.go | 83 ++++++++++++++++++----------- 4 files changed, 81 insertions(+), 69 deletions(-) diff --git a/activestate.yaml b/activestate.yaml index 447227ce67..a3aa56eb53 100644 --- a/activestate.yaml +++ b/activestate.yaml @@ -49,7 +49,7 @@ scripts: - name: example language: bash if: eq .OS.Name "Linux" - value: echo $os.name && echo $project.path() + value: echo .Project.Name && echo .OS.Name && echo $os.name && echo $project.path() && echo .Mixin.User.Name - name: install-deps language: bash if: ne .Shell "cmd" diff --git a/cmd/state/main.go b/cmd/state/main.go index 97a0675dcf..b85fe22777 100644 --- a/cmd/state/main.go +++ b/cmd/state/main.go @@ -216,12 +216,11 @@ func run(args []string, isInteractive bool, cfg *config.Instance, out output.Out // Set up conditional, which accesses a lot of primer data sshell := subshell.New(cfg) - projVars := vars.New(auth, pj, sshell.Shell()) + projVars := vars.New(auth, vars.NewProject(pj), sshell.Shell()) conditional := constraints.NewPrimeConditional(projVars) project.RegisterConditional(conditional) - project.RegisterComplexExpander(projVars) - //project.RegisterExpander("mixin", project.NewMixin(auth).Expander) + //project.RegisterComplexExpander(projVars) project.RegisterExpander("secrets", project.NewSecretPromptingExpander(secretsapi.Get(), prompter, cfg, auth)) // Run the actual command diff --git a/internal/constraints/constraints.go b/internal/constraints/constraints.go index 84d0b8e7bc..985b0d3695 100644 --- a/internal/constraints/constraints.go +++ b/internal/constraints/constraints.go @@ -3,6 +3,7 @@ package constraints import ( "bytes" "fmt" + "reflect" "regexp" "sort" "strings" @@ -12,7 +13,6 @@ import ( "github.com/ActiveState/cli/internal/logging" "github.com/ActiveState/cli/internal/multilog" "github.com/ActiveState/cli/pkg/projectfile" - "github.com/ActiveState/cli/pkg/projectfile/vars" "github.com/thoas/go-funk" ) @@ -42,44 +42,36 @@ func NewConditional() *Conditional { return c } -func NewPrimeConditional(vs *vars.Vars) *Conditional { +const ( + varTag = "vars" +) + +func NewPrimeConditional(vs interface{}) *Conditional { c := NewConditional() - /*c.RegisterParam("Project", map[string]string{ // map[string]interface{} should also work here - "Owner": pjOwner, - "Name": pjName, - "Namespace": pjNamespace, - "Url": pjURL, - "Commit": pjCommit, - "Branch": pjBranch, - "Path": pjPath, - - // Legacy - "NamespacePrefix": pjNamespace, - }) - osVersion, err := sysinfo.OSVersion() - if err != nil { - multilog.Error("Could not detect OSVersion: %v", err) + + v := reflect.ValueOf(vs) + if v.Kind() == reflect.Ptr { + v = v.Elem() } - c.RegisterParam("OS", map[string]interface{}{ - "Name": sysinfo.OS().String(), - "Version": osVersion, - "Architecture": sysinfo.Architecture().String(), - }) - c.RegisterParam("Shell", subshellName)*/ + to := v.Type() + fields := reflect.VisibleFields(to) - /*c.RegisterFunc("Mixin", func() map[string]interface{} { // looks like lazy loading - res := map[string]string{ - "Name": "", - "Email": "", + for _, f := range fields { + sv := v.FieldByIndex(f.Index) + if sv.Kind() == reflect.Ptr { + sv = sv.Elem() } - if a.Authenticated() { - res["Name"] = a.WhoAmI() - res["Email"] = a.Email() - } - return map[string]interface{}{ - "User": res, + sto := sv.Type() + + switch sto.Kind() { + case reflect.Func: + c.RegisterFunc(f.Name, sv.Interface()) + + default: + c.RegisterParam(f.Name, sv.Interface()) } - })*/ + } + return c } diff --git a/pkg/projectfile/vars/vars.go b/pkg/projectfile/vars/vars.go index de899829f0..1b4f693b73 100644 --- a/pkg/projectfile/vars/vars.go +++ b/pkg/projectfile/vars/vars.go @@ -9,7 +9,7 @@ import ( "github.com/ActiveState/cli/pkg/sysinfo" ) -type projectable interface { +type projectDataProvider interface { Owner() string Name() string NamespaceString() string @@ -19,14 +19,39 @@ type projectable interface { URL() string } -type Project struct { - Namespace string `vars:"NamespacePrefix"` +type ProjectData struct { + Namespace string Name string Owner string - URL string `vars:"Url"` + Url string Commit string Branch string Path string + + // legacy fields + NamespacePrefix string +} + +func NewProject(pj projectDataProvider) *ProjectData { + var ( + project = &ProjectData{} + ) + if !p.IsNil(pj) { + project.Namespace = pj.NamespaceString() + project.Name = pj.Name() + project.Owner = pj.Owner() + project.Url = pj.URL() + project.Commit = pj.CommitID() + project.Branch = pj.BranchName() + project.Path = pj.Path() + if project.Path != "" { + project.Path = filepath.Dir(project.Path) + } + + project.NamespacePrefix = pj.NamespaceString() + } + + return project } type OS struct { @@ -35,53 +60,49 @@ type OS struct { Architecture string } +func NewOS(osVersion *sysinfo.OSVersionInfo) *OS { + return &OS{ + Name: sysinfo.OS().String(), + Version: osVersion, + Architecture: sysinfo.Architecture().String(), + } +} + type User struct { Name string Email string } type Mixin struct { - User *User + User *User + Example string } type Vars struct { - Project *Project + Project *ProjectData OS *OS Shell string - Mixin *Mixin + Mixin func() *Mixin } -func New(auth *authentication.Auth, pj projectable, subshellName string) *Vars { - var ( - proj = &Project{} - ) - if !p.IsNil(pj) { - proj.Namespace = pj.NamespaceString() - proj.Owner = pj.Owner() - proj.Name = pj.Name() - proj.URL = pj.URL() - proj.Commit = pj.CommitID() - proj.Branch = pj.BranchName() - proj.Path = pj.Path() - if proj.Path != "" { - proj.Path = filepath.Dir(proj.Path) - } - } - +func New(auth *authentication.Auth, project *ProjectData, subshellName string) *Vars { osVersion, err := sysinfo.OSVersion() if err != nil { multilog.Error("Could not detect OSVersion: %v", err) } - os := &OS{ - Name: sysinfo.OS().String(), - Version: osVersion, - Architecture: sysinfo.Architecture().String(), + mixin := func() *Mixin { + return &Mixin{ + User: &User{ + Name: "NAME", + Email: "EMAIL", + }, + Example: "EXAMPLE", + } } - return &Vars{ - Project: proj, - OS: os, + Project: project, + OS: NewOS(osVersion), Shell: subshellName, Mixin: mixin, } From 647cb7fc86e0defd931caf21091ac96e3b9487b9 Mon Sep 17 00:00:00 2001 From: daved Date: Tue, 11 Apr 2023 18:28:26 -0700 Subject: [PATCH 03/31] Remove unused var tag constant from constraints pkg --- internal/constraints/constraints.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/internal/constraints/constraints.go b/internal/constraints/constraints.go index 985b0d3695..28c94de3a4 100644 --- a/internal/constraints/constraints.go +++ b/internal/constraints/constraints.go @@ -42,10 +42,6 @@ func NewConditional() *Conditional { return c } -const ( - varTag = "vars" -) - func NewPrimeConditional(vs interface{}) *Conditional { c := NewConditional() From 23bea1b349468609492066a5ce75715be45477d4 Mon Sep 17 00:00:00 2001 From: daved Date: Thu, 13 Apr 2023 16:45:41 -0700 Subject: [PATCH 04/31] Start use of reflect in project expander --- cmd/state/main.go | 2 +- internal/constraints/constraints.go | 4 +- pkg/project/expander.go | 94 ++++++----------------------- pkg/project/registry.go | 69 ++++++++++++++++++++- 4 files changed, 89 insertions(+), 80 deletions(-) diff --git a/cmd/state/main.go b/cmd/state/main.go index b85fe22777..1df6df668b 100644 --- a/cmd/state/main.go +++ b/cmd/state/main.go @@ -220,7 +220,7 @@ func run(args []string, isInteractive bool, cfg *config.Instance, out output.Out conditional := constraints.NewPrimeConditional(projVars) project.RegisterConditional(conditional) - //project.RegisterComplexExpander(projVars) + project.RegisterStruct(projVars) project.RegisterExpander("secrets", project.NewSecretPromptingExpander(secretsapi.Get(), prompter, cfg, auth)) // Run the actual command diff --git a/internal/constraints/constraints.go b/internal/constraints/constraints.go index 28c94de3a4..822a2f4d8c 100644 --- a/internal/constraints/constraints.go +++ b/internal/constraints/constraints.go @@ -42,10 +42,10 @@ func NewConditional() *Conditional { return c } -func NewPrimeConditional(vs interface{}) *Conditional { +func NewPrimeConditional(val interface{}) *Conditional { c := NewConditional() - v := reflect.ValueOf(vs) + v := reflect.ValueOf(val) if v.Kind() == reflect.Ptr { v = v.Elem() } diff --git a/pkg/project/expander.go b/pkg/project/expander.go index 76be15ca13..202439c165 100644 --- a/pkg/project/expander.go +++ b/pkg/project/expander.go @@ -1,23 +1,19 @@ package project import ( - "path/filepath" "regexp" "runtime" "strings" + "github.com/ActiveState/cli/internal/constants" + "github.com/ActiveState/cli/internal/constraints" "github.com/ActiveState/cli/internal/errs" "github.com/ActiveState/cli/internal/language" "github.com/ActiveState/cli/internal/locale" "github.com/ActiveState/cli/internal/osutils" + "github.com/ActiveState/cli/internal/rxutils" "github.com/ActiveState/cli/internal/scriptfile" - "github.com/ActiveState/cli/pkg/platform/authentication" "github.com/ActiveState/cli/pkg/projectfile" - - "github.com/ActiveState/cli/internal/rxutils" - - "github.com/ActiveState/cli/internal/constants" - "github.com/ActiveState/cli/internal/constraints" ) type Expansion struct { @@ -47,7 +43,7 @@ func (ctx *Expansion) ApplyWithMaxDepth(s string, depth int) (string, error) { variable = groups[0] if len(groups) == 2 { - category = "toplevel" + category = TopLevelExpanderName name = groups[1] } if len(groups) > 2 { @@ -181,38 +177,6 @@ func expandPath(name string, script *Script) (string, error) { return sf.Filename(), nil } -// userExpander -func userExpander(auth *authentication.Auth, element string) string { - if element == "name" { - return auth.WhoAmI() - } - if element == "email" { - return auth.Email() - } - if element == "jwt" { - return auth.BearerToken() - } - return "" -} - -// Mixin provides expansions that are not sourced from a project file -type Mixin struct { - auth *authentication.Auth -} - -// NewMixin creates a Mixin object providing extra expansions -func NewMixin(auth *authentication.Auth) *Mixin { - return &Mixin{auth} -} - -// Expander expands mixin variables -func (m *Mixin) Expander(_ string, name string, meta string, _ bool, _ *Expansion) (string, error) { - if name == "user" { - return userExpander(m.auth, meta), nil - } - return "", nil -} - // ConstantExpander expands constants defined in the project-file. func ConstantExpander(_ string, name string, meta string, isFunction bool, ctx *Expansion) (string, error) { projectFile := ctx.Project.Source() @@ -228,41 +192,6 @@ func ConstantExpander(_ string, name string, meta string, isFunction bool, ctx * return "", nil } -// ProjectExpander expands constants defined in the project-file. -func ProjectExpander(_ string, name string, _ string, isFunction bool, ctx *Expansion) (string, error) { - if !isFunction { - return "", nil - } - - project := ctx.Project - switch name { - case "url": - return project.URL(), nil - case "commit": - return project.CommitID(), nil - case "branch": - return project.BranchName(), nil - case "owner": - return project.Namespace().Owner, nil - case "name": - return project.Namespace().Project, nil - case "namespace": - return project.Namespace().String(), nil - case "path": - path := project.Source().Path() - if path == "" { - return path, nil - } - dir := filepath.Dir(path) - if ctx.BashifyPaths { - return osutils.BashifyPath(dir) - } - return dir, nil - } - - return "", nil -} - func TopLevelExpander(variable string, name string, _ string, _ bool, ctx *Expansion) (string, error) { projectFile := ctx.Project.Source() switch name { @@ -270,6 +199,21 @@ func TopLevelExpander(variable string, name string, _ string, _ bool, ctx *Expan return projectFile.Project, nil case "lock": return projectFile.Lock, nil + default: + if v, ok := topLevelLookup[name]; ok { + return v, nil + } } return variable, nil } + +func MakeExpanderFuncFromMap(m map[string]map[string]string) ExpanderFunc { + return func(v, name, meta string, isFunc bool, ctx *Expansion) (string, error) { + if sub, ok := m[name]; ok { + if v, ok := sub[meta]; ok { + return v, nil + } + } + return "", nil + } +} diff --git a/pkg/project/registry.go b/pkg/project/registry.go index 30f78a7d66..c5621cea68 100644 --- a/pkg/project/registry.go +++ b/pkg/project/registry.go @@ -1,6 +1,8 @@ package project import ( + "fmt" + "reflect" "strings" "github.com/ActiveState/cli/internal/errs" @@ -12,20 +14,53 @@ var expanderRegistry = map[string]ExpanderFunc{} var ( ErrExpandBadName = errs.New("Bad expander name") ErrExpandNoFunc = errs.New("Expander has no handler") + topLevelLookup = make(map[string]string) ) -const TopLevelExpanderName = "toplevel" +const ( + TopLevelExpanderName = "toplevel" +) func init() { expanderRegistry = map[string]ExpanderFunc{ "events": EventExpander, "scripts": ScriptExpander, "constants": ConstantExpander, - "project": ProjectExpander, TopLevelExpanderName: TopLevelExpander, } } +func RegisterStruct(val interface{}) error { + v := reflect.ValueOf(val) + if v.Kind() == reflect.Ptr { + v = v.Elem() + } + to := v.Type() + fields := reflect.VisibleFields(to) // we know this is a struct + + for _, f := range fields { // Vars.(OS).Version.Name + sv := v.FieldByIndex(f.Index) + if sv.Kind() == reflect.Ptr { + sv = sv.Elem() + } + sto := sv.Type() + + switch sto.Kind() { + case reflect.Struct: + m := makeStringMapMap(sto, sv) + RegisterExpander(strings.ToLower(f.Name), MakeExpanderFuncFromMap(m)) + + case reflect.Func: + //c.RegisterFunc(f.Name, sv.Interface()) + + default: + topLevelLookup[strings.ToLower(f.Name)] = fmt.Sprintf("%v", sv.Interface()) + } + } + + return nil +} + // RegisterExpander registers an Expander Func for some given handler value. The handler value // must not effectively be a blank string and the Func must be defined. It is definitely possible // to replace an existing handler using this function. @@ -53,3 +88,33 @@ func IsRegistered(handle string) bool { _, ok := expanderRegistry[handle] return ok } + +func makeStringMapMap(structure reflect.Type, value reflect.Value) map[string]map[string]string { + m := make(map[string]map[string]string) + fields := reflect.VisibleFields(structure) + + for _, f := range fields { // Vars.OS.(Version).Name + subValue := value.FieldByIndex(f.Index) + if subValue.Kind() == reflect.Ptr { + subValue = subValue.Elem() + } + subType := subValue.Type() + + switch subType.Kind() { + case reflect.Struct: + innerMap := make(map[string]string) + subFields := reflect.VisibleFields(subType) + + for _, sf := range subFields { // Vars.OS.Version.(Name) + subSubValue := subValue.FieldByIndex(sf.Index) + innerMap[strings.ToLower(sf.Name)] = fmt.Sprintf("%v", subSubValue.Interface()) + } + m[strings.ToLower(f.Name)] = innerMap + + default: + m[strings.ToLower(f.Name)] = map[string]string{"": fmt.Sprintf("%v", value.Interface())} + } + } + + return m +} From 25ad33d609519ddaa93ba411be3e060c6da3d52b Mon Sep 17 00:00:00 2001 From: daved Date: Thu, 13 Apr 2023 18:18:11 -0700 Subject: [PATCH 05/31] Handle func fields in project expander --- pkg/project/expander.go | 72 ++++++++++++++++++++++++++++++++++++ pkg/project/registry.go | 56 ++++++++++++---------------- pkg/projectfile/vars/vars.go | 47 +++++++++++++++-------- pkg/sysinfo/sysinfo.go | 2 +- 4 files changed, 128 insertions(+), 49 deletions(-) diff --git a/pkg/project/expander.go b/pkg/project/expander.go index 202439c165..8cd7c8e3d7 100644 --- a/pkg/project/expander.go +++ b/pkg/project/expander.go @@ -1,6 +1,8 @@ package project import ( + "fmt" + "reflect" "regexp" "runtime" "strings" @@ -207,6 +209,52 @@ func TopLevelExpander(variable string, name string, _ string, _ bool, ctx *Expan return variable, nil } +func makeStringMap(structure reflect.Type, val reflect.Value) map[string]string { + m := make(map[string]string) + fields := reflect.VisibleFields(structure) + + for _, f := range fields { + if !f.IsExported() { + continue + } + + subValue := val.FieldByIndex(f.Index) + m[strings.ToLower(f.Name)] = fmt.Sprintf("%v", subValue.Interface()) + } + + return m +} + +func makeStringMapMap(structure reflect.Type, value reflect.Value) map[string]map[string]string { + m := make(map[string]map[string]string) + fields := reflect.VisibleFields(structure) + + for _, f := range fields { + if !f.IsExported() { + continue + } + + subValue := value.FieldByIndex(f.Index) + if subValue.Kind() == reflect.Ptr { + subValue = subValue.Elem() + } + subType := subValue.Type() + + switch subType.Kind() { + case reflect.Struct: + // Vars.OS.Version.(Name) + m[strings.ToLower(f.Name)] = makeStringMap(subType, subValue) + + default: + m[strings.ToLower(f.Name)] = map[string]string{ + "": fmt.Sprintf("%v", value.Interface()), + } + } + } + + return m +} + func MakeExpanderFuncFromMap(m map[string]map[string]string) ExpanderFunc { return func(v, name, meta string, isFunc bool, ctx *Expansion) (string, error) { if sub, ok := m[name]; ok { @@ -217,3 +265,27 @@ func MakeExpanderFuncFromMap(m map[string]map[string]string) ExpanderFunc { return "", nil } } + +func MakeExpanderFuncFromFunc(fn reflect.Type, val reflect.Value) ExpanderFunc { + return func(v, name, meta string, isFunc bool, ctx *Expansion) (string, error) { + vals := val.Call(nil) + if len(vals) > 1 { + return "", vals[1].Interface().(error) + } + val := vals[0] + if val.Kind() == reflect.Ptr { + val = val.Elem() + } + + switch val.Kind() { + case reflect.Struct: + // Vars.OS.(Version).Name + m := makeStringMapMap(val.Type(), val) + expandFromMap := MakeExpanderFuncFromMap(m) + return expandFromMap(v, name, meta, isFunc, ctx) + + default: + return fmt.Sprintf("%v", val.Interface()), nil + } + } +} diff --git a/pkg/project/registry.go b/pkg/project/registry.go index c5621cea68..ab3a075ec8 100644 --- a/pkg/project/registry.go +++ b/pkg/project/registry.go @@ -38,7 +38,12 @@ func RegisterStruct(val interface{}) error { to := v.Type() fields := reflect.VisibleFields(to) // we know this is a struct - for _, f := range fields { // Vars.(OS).Version.Name + // Vars.(OS).Version.Name + for _, f := range fields { + if !f.IsExported() { + continue + } + sv := v.FieldByIndex(f.Index) if sv.Kind() == reflect.Ptr { sv = sv.Elem() @@ -47,11 +52,26 @@ func RegisterStruct(val interface{}) error { switch sto.Kind() { case reflect.Struct: + // Vars.OS.(Version).Name m := makeStringMapMap(sto, sv) - RegisterExpander(strings.ToLower(f.Name), MakeExpanderFuncFromMap(m)) + name := strings.ToLower(f.Name) + err := RegisterExpander(name, MakeExpanderFuncFromMap(m)) + if err != nil { + return errs.Wrap( + err, "project_expand_register_expander_map", + "Cannot register expander (map)", + ) + } case reflect.Func: - //c.RegisterFunc(f.Name, sv.Interface()) + name := strings.ToLower(f.Name) + err := RegisterExpander(name, MakeExpanderFuncFromFunc(sto, sv)) + if err != nil { + return errs.Wrap( + err, "project_expand_register_expander_func", + "Cannot register expander (func)", + ) + } default: topLevelLookup[strings.ToLower(f.Name)] = fmt.Sprintf("%v", sv.Interface()) @@ -88,33 +108,3 @@ func IsRegistered(handle string) bool { _, ok := expanderRegistry[handle] return ok } - -func makeStringMapMap(structure reflect.Type, value reflect.Value) map[string]map[string]string { - m := make(map[string]map[string]string) - fields := reflect.VisibleFields(structure) - - for _, f := range fields { // Vars.OS.(Version).Name - subValue := value.FieldByIndex(f.Index) - if subValue.Kind() == reflect.Ptr { - subValue = subValue.Elem() - } - subType := subValue.Type() - - switch subType.Kind() { - case reflect.Struct: - innerMap := make(map[string]string) - subFields := reflect.VisibleFields(subType) - - for _, sf := range subFields { // Vars.OS.Version.(Name) - subSubValue := subValue.FieldByIndex(sf.Index) - innerMap[strings.ToLower(sf.Name)] = fmt.Sprintf("%v", subSubValue.Interface()) - } - m[strings.ToLower(f.Name)] = innerMap - - default: - m[strings.ToLower(f.Name)] = map[string]string{"": fmt.Sprintf("%v", value.Interface())} - } - } - - return m -} diff --git a/pkg/projectfile/vars/vars.go b/pkg/projectfile/vars/vars.go index 1b4f693b73..0938b62b92 100644 --- a/pkg/projectfile/vars/vars.go +++ b/pkg/projectfile/vars/vars.go @@ -54,16 +54,30 @@ func NewProject(pj projectDataProvider) *ProjectData { return project } +type OSVersion struct { + Name string + Version string + Major int + Minor int + Micro int +} + type OS struct { Name string - Version *sysinfo.OSVersionInfo + Version OSVersion Architecture string } func NewOS(osVersion *sysinfo.OSVersionInfo) *OS { return &OS{ - Name: sysinfo.OS().String(), - Version: osVersion, + Name: sysinfo.OS().String(), + Version: OSVersion{ + Name: osVersion.Name, + Version: osVersion.Version, + Major: osVersion.Major, + Minor: osVersion.Minor, + Micro: osVersion.Micro, + }, Architecture: sysinfo.Architecture().String(), } } @@ -71,11 +85,23 @@ func NewOS(osVersion *sysinfo.OSVersionInfo) *OS { type User struct { Name string Email string + JWT string } type Mixin struct { - User *User - Example string + auth *authentication.Auth + User *User +} + +func NewMixin(auth *authentication.Auth) *Mixin { + return &Mixin{ + auth: auth, + User: &User{ + Name: auth.WhoAmI(), + Email: auth.Email(), + JWT: auth.BearerToken(), + }, + } } type Vars struct { @@ -91,19 +117,10 @@ func New(auth *authentication.Auth, project *ProjectData, subshellName string) * multilog.Error("Could not detect OSVersion: %v", err) } - mixin := func() *Mixin { - return &Mixin{ - User: &User{ - Name: "NAME", - Email: "EMAIL", - }, - Example: "EXAMPLE", - } - } return &Vars{ Project: project, OS: NewOS(osVersion), Shell: subshellName, - Mixin: mixin, + Mixin: func() *Mixin { return NewMixin(auth) }, } } diff --git a/pkg/sysinfo/sysinfo.go b/pkg/sysinfo/sysinfo.go index e407e58819..b822e53f02 100644 --- a/pkg/sysinfo/sysinfo.go +++ b/pkg/sysinfo/sysinfo.go @@ -211,4 +211,4 @@ func parseVersionInfo(v string) (*VersionInfo, error) { Minor: minor, Micro: micro, }, nil -} \ No newline at end of file +} From f377c1eef8528ad345be75f62103140077fcd7f8 Mon Sep 17 00:00:00 2001 From: daved Date: Thu, 13 Apr 2023 18:22:54 -0700 Subject: [PATCH 06/31] Fix err handling in project registry funcs --- cmd/state/main.go | 2 +- pkg/project/registry.go | 9 +++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/cmd/state/main.go b/cmd/state/main.go index 1df6df668b..a5ab41dad5 100644 --- a/cmd/state/main.go +++ b/cmd/state/main.go @@ -220,7 +220,7 @@ func run(args []string, isInteractive bool, cfg *config.Instance, out output.Out conditional := constraints.NewPrimeConditional(projVars) project.RegisterConditional(conditional) - project.RegisterStruct(projVars) + _ = project.RegisterStruct(projVars) project.RegisterExpander("secrets", project.NewSecretPromptingExpander(secretsapi.Get(), prompter, cfg, auth)) // Run the actual command diff --git a/pkg/project/registry.go b/pkg/project/registry.go index ab3a075ec8..f2af7fe714 100644 --- a/pkg/project/registry.go +++ b/pkg/project/registry.go @@ -6,6 +6,7 @@ import ( "strings" "github.com/ActiveState/cli/internal/errs" + "github.com/ActiveState/cli/internal/locale" ) // expanderRegistry maps category names to their Expander Func implementations. @@ -57,7 +58,7 @@ func RegisterStruct(val interface{}) error { name := strings.ToLower(f.Name) err := RegisterExpander(name, MakeExpanderFuncFromMap(m)) if err != nil { - return errs.Wrap( + return locale.WrapError( err, "project_expand_register_expander_map", "Cannot register expander (map)", ) @@ -67,7 +68,7 @@ func RegisterStruct(val interface{}) error { name := strings.ToLower(f.Name) err := RegisterExpander(name, MakeExpanderFuncFromFunc(sto, sv)) if err != nil { - return errs.Wrap( + return locale.WrapError( err, "project_expand_register_expander_func", "Cannot register expander (func)", ) @@ -87,9 +88,9 @@ func RegisterStruct(val interface{}) error { func RegisterExpander(handle string, expanderFn ExpanderFunc) error { cleanHandle := strings.TrimSpace(handle) if cleanHandle == "" { - return errs.Wrap(ErrExpandBadName, "secrets_expander_err_empty_name") + return locale.WrapError(ErrExpandBadName, "secrets_expander_err_empty_name") } else if expanderFn == nil { - return errs.Wrap(ErrExpandNoFunc, "secrets_expander_err_undefined") + return locale.WrapError(ErrExpandNoFunc, "secrets_expander_err_undefined") } expanderRegistry[cleanHandle] = expanderFn return nil From 9fce069ac69e07c79538d4d830dd4a91cf3cd564 Mon Sep 17 00:00:00 2001 From: daved Date: Thu, 13 Apr 2023 18:54:20 -0700 Subject: [PATCH 07/31] Fix state main imports --- cmd/state/main.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/cmd/state/main.go b/cmd/state/main.go index a5ab41dad5..8b37ef03c4 100644 --- a/cmd/state/main.go +++ b/cmd/state/main.go @@ -11,10 +11,8 @@ import ( "strings" "time" - "github.com/ActiveState/cli/cmd/state/internal/cmdtree/intercepts/messenger" - "github.com/ActiveState/cli/internal/captain" - "github.com/ActiveState/cli/cmd/state/internal/cmdtree" + "github.com/ActiveState/cli/cmd/state/internal/cmdtree/intercepts/messenger" anAsync "github.com/ActiveState/cli/internal/analytics/client/async" "github.com/ActiveState/cli/internal/captain" "github.com/ActiveState/cli/internal/config" From 5678f64f293b7301ed4f4e6d90bb91e66727020c Mon Sep 17 00:00:00 2001 From: daved Date: Thu, 13 Apr 2023 20:52:57 -0700 Subject: [PATCH 08/31] Handle asy var func notation --- activestate.yaml | 4 --- pkg/project/expander.go | 58 ++++++++++++++++++++++++++---------- pkg/project/registry.go | 4 +-- pkg/projectfile/vars/vars.go | 14 ++++----- 4 files changed, 52 insertions(+), 28 deletions(-) diff --git a/activestate.yaml b/activestate.yaml index a3aa56eb53..2189c1e3d8 100644 --- a/activestate.yaml +++ b/activestate.yaml @@ -46,10 +46,6 @@ constants: if: ne .OS.Name "Windows" value: .sh scripts: - - name: example - language: bash - if: eq .OS.Name "Linux" - value: echo .Project.Name && echo .OS.Name && echo $os.name && echo $project.path() && echo .Mixin.User.Name - name: install-deps language: bash if: ne .Shell "cmd" diff --git a/pkg/project/expander.go b/pkg/project/expander.go index 8cd7c8e3d7..365f5c0117 100644 --- a/pkg/project/expander.go +++ b/pkg/project/expander.go @@ -18,6 +18,11 @@ import ( "github.com/ActiveState/cli/pkg/projectfile" ) +const ( + expandStructTag = "expand" + expandTagOptIsFunc = "isFunc" +) + type Expansion struct { Project *Project Script *Script @@ -209,9 +214,28 @@ func TopLevelExpander(variable string, name string, _ string, _ bool, ctx *Expan return variable, nil } -func makeStringMap(structure reflect.Type, val reflect.Value) map[string]string { - m := make(map[string]string) - fields := reflect.VisibleFields(structure) +type entry struct { + isFunc bool + value string +} + +func newEntry(tag string, val reflect.Value) entry { + var isFunc bool + + tParts := strings.Split(tag, ",") + if len(tParts) > 1 && strings.Contains(tParts[1], expandTagOptIsFunc) { + isFunc = true + } + + return entry{ + isFunc: isFunc, + value: fmt.Sprintf("%v", val.Interface()), + } +} + +func makeEntryMap(val reflect.Value) map[string]entry { + m := make(map[string]entry) + fields := reflect.VisibleFields(val.Type()) for _, f := range fields { if !f.IsExported() { @@ -219,15 +243,15 @@ func makeStringMap(structure reflect.Type, val reflect.Value) map[string]string } subValue := val.FieldByIndex(f.Index) - m[strings.ToLower(f.Name)] = fmt.Sprintf("%v", subValue.Interface()) + m[strings.ToLower(f.Name)] = newEntry(f.Tag.Get(expandStructTag), subValue) } return m } -func makeStringMapMap(structure reflect.Type, value reflect.Value) map[string]map[string]string { - m := make(map[string]map[string]string) - fields := reflect.VisibleFields(structure) +func makeEntryMapMap(value reflect.Value) map[string]map[string]entry { + m := make(map[string]map[string]entry) + fields := reflect.VisibleFields(value.Type()) for _, f := range fields { if !f.IsExported() { @@ -243,11 +267,11 @@ func makeStringMapMap(structure reflect.Type, value reflect.Value) map[string]ma switch subType.Kind() { case reflect.Struct: // Vars.OS.Version.(Name) - m[strings.ToLower(f.Name)] = makeStringMap(subType, subValue) + m[strings.ToLower(f.Name)] = makeEntryMap(subValue) default: - m[strings.ToLower(f.Name)] = map[string]string{ - "": fmt.Sprintf("%v", value.Interface()), + m[strings.ToLower(f.Name)] = map[string]entry{ + "": newEntry(f.Tag.Get(expandStructTag), subValue), } } } @@ -255,18 +279,22 @@ func makeStringMapMap(structure reflect.Type, value reflect.Value) map[string]ma return m } -func MakeExpanderFuncFromMap(m map[string]map[string]string) ExpanderFunc { +func MakeExpanderFuncFromMap(m map[string]map[string]entry) ExpanderFunc { return func(v, name, meta string, isFunc bool, ctx *Expansion) (string, error) { + if isFunc && meta == "()" { + meta = "" + } + if sub, ok := m[name]; ok { - if v, ok := sub[meta]; ok { - return v, nil + if e, ok := sub[meta]; ok && isFunc == e.isFunc { + return e.value, nil } } return "", nil } } -func MakeExpanderFuncFromFunc(fn reflect.Type, val reflect.Value) ExpanderFunc { +func MakeExpanderFuncFromFunc(val reflect.Value) ExpanderFunc { return func(v, name, meta string, isFunc bool, ctx *Expansion) (string, error) { vals := val.Call(nil) if len(vals) > 1 { @@ -280,7 +308,7 @@ func MakeExpanderFuncFromFunc(fn reflect.Type, val reflect.Value) ExpanderFunc { switch val.Kind() { case reflect.Struct: // Vars.OS.(Version).Name - m := makeStringMapMap(val.Type(), val) + m := makeEntryMapMap(val) expandFromMap := MakeExpanderFuncFromMap(m) return expandFromMap(v, name, meta, isFunc, ctx) diff --git a/pkg/project/registry.go b/pkg/project/registry.go index f2af7fe714..e0edde9251 100644 --- a/pkg/project/registry.go +++ b/pkg/project/registry.go @@ -54,7 +54,7 @@ func RegisterStruct(val interface{}) error { switch sto.Kind() { case reflect.Struct: // Vars.OS.(Version).Name - m := makeStringMapMap(sto, sv) + m := makeEntryMapMap(sv) name := strings.ToLower(f.Name) err := RegisterExpander(name, MakeExpanderFuncFromMap(m)) if err != nil { @@ -66,7 +66,7 @@ func RegisterStruct(val interface{}) error { case reflect.Func: name := strings.ToLower(f.Name) - err := RegisterExpander(name, MakeExpanderFuncFromFunc(sto, sv)) + err := RegisterExpander(name, MakeExpanderFuncFromFunc(sv)) if err != nil { return locale.WrapError( err, "project_expand_register_expander_func", diff --git a/pkg/projectfile/vars/vars.go b/pkg/projectfile/vars/vars.go index 0938b62b92..bb50ec6f86 100644 --- a/pkg/projectfile/vars/vars.go +++ b/pkg/projectfile/vars/vars.go @@ -20,13 +20,13 @@ type projectDataProvider interface { } type ProjectData struct { - Namespace string - Name string - Owner string - Url string - Commit string - Branch string - Path string + Namespace string `expand:",isFunc"` + Name string `expand:",isFunc"` + Owner string `expand:",isFunc"` + Url string `expand:",isFunc"` + Commit string `expand:",isFunc"` + Branch string `expand:",isFunc"` + Path string `expand:",isFunc"` // legacy fields NamespacePrefix string From 9b2ac3bdd1516a0a32a044747c3c5e627f25852f Mon Sep 17 00:00:00 2001 From: daved Date: Thu, 13 Apr 2023 22:07:14 -0700 Subject: [PATCH 09/31] Fix project pkg expander tests --- pkg/project/expander_test.go | 36 +++++++++++++++++++++++------------- pkg/project/project_test.go | 6 +++++- pkg/projectfile/vars/vars.go | 12 ++++++------ 3 files changed, 34 insertions(+), 20 deletions(-) diff --git a/pkg/project/expander_test.go b/pkg/project/expander_test.go index 4e98d70cbf..e4330fbed3 100644 --- a/pkg/project/expander_test.go +++ b/pkg/project/expander_test.go @@ -17,9 +17,10 @@ import ( "github.com/ActiveState/cli/pkg/project" "github.com/ActiveState/cli/pkg/projectfile" + "github.com/ActiveState/cli/pkg/projectfile/vars" ) -func loadProject(t *testing.T) *project.Project { +func loadProjectAndRegisterVals(t *testing.T) *project.Project { projectfile.Reset() pjFile := &projectfile.Project{} @@ -60,13 +61,22 @@ scripts: pjFile.Persist() - return project.Get() + pj := project.Get() + + projVars := vars.New(nil, vars.NewProject(pj), "noshell") + _ = project.RegisterStruct(projVars) + + return pj } func TestExpandProject(t *testing.T) { - prj := loadProject(t) + prj := loadProjectAndRegisterVals(t) prj.Source().SetPath(fmt.Sprintf("spoofed path%sactivestate.yaml", string(os.PathSeparator))) + // needed after project update + projVars := vars.New(nil, vars.NewProject(prj), "noshell") + _ = project.RegisterStruct(projVars) + expanded, err := project.ExpandFromProject("$project.url()", prj) require.NoError(t, err) assert.Equal(t, prj.URL(), expanded) @@ -104,7 +114,7 @@ func TestExpandProject(t *testing.T) { } func TestExpandTopLevel(t *testing.T) { - prj := loadProject(t) + prj := loadProjectAndRegisterVals(t) expanded, err := project.ExpandFromProject("$project", prj) assert.NoError(t, err, "Ran without failure") @@ -121,7 +131,7 @@ func TestExpandTopLevel(t *testing.T) { } func TestExpandProjectScript(t *testing.T) { - prj := loadProject(t) + prj := loadProjectAndRegisterVals(t) expanded, err := project.ExpandFromProject("$ $scripts.test", prj) assert.NoError(t, err, "Ran without failure") @@ -129,7 +139,7 @@ func TestExpandProjectScript(t *testing.T) { } func TestExpandProjectConstant(t *testing.T) { - prj := loadProject(t) + prj := loadProjectAndRegisterVals(t) expanded, err := project.ExpandFromProject("$ $constants.constant", prj) assert.NoError(t, err, "Ran without failure") @@ -141,7 +151,7 @@ func TestExpandProjectConstant(t *testing.T) { } func TestExpandProjectSecret(t *testing.T) { - pj := loadProject(t) + pj := loadProjectAndRegisterVals(t) project.RegisterExpander("secrets", func(_ string, category string, meta string, isFunction bool, ctx *project.Expansion) (string, error) { if category == project.ProjectCategory { @@ -160,7 +170,7 @@ func TestExpandProjectSecret(t *testing.T) { } func TestExpandProjectAlternateSyntax(t *testing.T) { - prj := loadProject(t) + prj := loadProjectAndRegisterVals(t) expanded, err := project.ExpandFromProject("${project.name()}", prj) assert.NoError(t, err, "Ran without failure") @@ -168,7 +178,7 @@ func TestExpandProjectAlternateSyntax(t *testing.T) { } func TestExpandProjectUnknownCategory(t *testing.T) { - prj := loadProject(t) + prj := loadProjectAndRegisterVals(t) expanded, err := project.ExpandFromProject("$unknown.unknown", prj) assert.NoError(t, err, "Ran without failure") @@ -176,7 +186,7 @@ func TestExpandProjectUnknownCategory(t *testing.T) { } func TestExpandProjectInfiniteRecursion(t *testing.T) { - prj := loadProject(t) + prj := loadProjectAndRegisterVals(t) _, err := project.ExpandFromProject("$scripts.recursive", prj) require.Error(t, err, "Ran with failure") @@ -204,7 +214,7 @@ scripts: } func TestExpandScriptPath(t *testing.T) { - prj := loadProject(t) + prj := loadProjectAndRegisterVals(t) expanded, err := project.ExpandFromProject("$scripts.scriptPath", prj) assert.NoError(t, err, "Ran without failure") @@ -217,7 +227,7 @@ func TestExpandScriptPath(t *testing.T) { } func TestExpandScriptPathRecursive(t *testing.T) { - prj := loadProject(t) + prj := loadProjectAndRegisterVals(t) expanded, err := project.ExpandFromProject("$scripts.scriptRecursive", prj) assert.NoError(t, err, "Ran without failure") @@ -228,7 +238,7 @@ func TestExpandScriptPathRecursive(t *testing.T) { } func TestExpandBashScriptPath(t *testing.T) { - prj := loadProject(t) + prj := loadProjectAndRegisterVals(t) script := prj.ScriptByName("bashScriptPath") require.NotNil(t, script, "bashScriptPath script does not exist") value, err := script.Value() diff --git a/pkg/project/project_test.go b/pkg/project/project_test.go index bfd4ff5bda..fc563b3d2b 100644 --- a/pkg/project/project_test.go +++ b/pkg/project/project_test.go @@ -15,6 +15,7 @@ import ( "github.com/ActiveState/cli/pkg/platform/authentication" "github.com/ActiveState/cli/pkg/project" "github.com/ActiveState/cli/pkg/projectfile" + "github.com/ActiveState/cli/pkg/projectfile/vars" "github.com/stretchr/testify/suite" ) @@ -45,7 +46,10 @@ func (suite *ProjectTestSuite) BeforeTest(suiteName, testName string) { cfg, err := config.New() suite.Require().NoError(err) - project.RegisterConditional(constraints.NewPrimeConditional(nil, suite.project, subshell.New(cfg).Shell())) + + projVars := vars.New(nil, vars.NewProject(suite.project), subshell.New(cfg).Shell()) + project.RegisterConditional(constraints.NewPrimeConditional(projVars)) + _ = project.RegisterStruct(projVars) } func (suite *ProjectTestSuite) TestGet() { diff --git a/pkg/projectfile/vars/vars.go b/pkg/projectfile/vars/vars.go index bb50ec6f86..bea283c663 100644 --- a/pkg/projectfile/vars/vars.go +++ b/pkg/projectfile/vars/vars.go @@ -19,22 +19,22 @@ type projectDataProvider interface { URL() string } -type ProjectData struct { +type Project struct { Namespace string `expand:",isFunc"` Name string `expand:",isFunc"` Owner string `expand:",isFunc"` Url string `expand:",isFunc"` Commit string `expand:",isFunc"` Branch string `expand:",isFunc"` - Path string `expand:",isFunc"` + Path string `expand:",isFunc;isPath"` // legacy fields NamespacePrefix string } -func NewProject(pj projectDataProvider) *ProjectData { +func NewProject(pj projectDataProvider) *Project { var ( - project = &ProjectData{} + project = &Project{} ) if !p.IsNil(pj) { project.Namespace = pj.NamespaceString() @@ -105,13 +105,13 @@ func NewMixin(auth *authentication.Auth) *Mixin { } type Vars struct { - Project *ProjectData + Project *Project OS *OS Shell string Mixin func() *Mixin } -func New(auth *authentication.Auth, project *ProjectData, subshellName string) *Vars { +func New(auth *authentication.Auth, project *Project, subshellName string) *Vars { osVersion, err := sysinfo.OSVersion() if err != nil { multilog.Error("Could not detect OSVersion: %v", err) From 35fbde4ee1ed37a111af05459d34b68ba8401237 Mon Sep 17 00:00:00 2001 From: daved Date: Thu, 13 Apr 2023 22:15:08 -0700 Subject: [PATCH 10/31] Ensure paths are bashified in expansions --- pkg/project/expander.go | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/pkg/project/expander.go b/pkg/project/expander.go index 365f5c0117..f36a7d82a9 100644 --- a/pkg/project/expander.go +++ b/pkg/project/expander.go @@ -21,6 +21,7 @@ import ( const ( expandStructTag = "expand" expandTagOptIsFunc = "isFunc" + expandTagOptIsPath = "isPath" ) type Expansion struct { @@ -216,19 +217,26 @@ func TopLevelExpander(variable string, name string, _ string, _ bool, ctx *Expan type entry struct { isFunc bool + isPath bool value string } func newEntry(tag string, val reflect.Value) entry { - var isFunc bool + var isFunc, isPath bool tParts := strings.Split(tag, ",") - if len(tParts) > 1 && strings.Contains(tParts[1], expandTagOptIsFunc) { - isFunc = true + if len(tParts) > 1 { + if strings.Contains(tParts[1], expandTagOptIsFunc) { + isFunc = true + } + if strings.Contains(tParts[1], expandTagOptIsPath) { + isPath = true + } } return entry{ isFunc: isFunc, + isPath: isPath, value: fmt.Sprintf("%v", val.Interface()), } } @@ -287,9 +295,15 @@ func MakeExpanderFuncFromMap(m map[string]map[string]entry) ExpanderFunc { if sub, ok := m[name]; ok { if e, ok := sub[meta]; ok && isFunc == e.isFunc { - return e.value, nil + value := e.value + if ctx.BashifyPaths && e.isPath { + return osutils.BashifyPath(value) + } + + return value, nil } } + return "", nil } } From d105f381853df53f547e3f1b852612275ee58a4f Mon Sep 17 00:00:00 2001 From: daved Date: Fri, 14 Apr 2023 09:02:13 -0700 Subject: [PATCH 11/31] Fix expander unit test and usage in parallelize --- pkg/project/expander_test.go | 3 ++- scripts/ci/parallelize/parallelize.go | 13 +++++++------ 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/pkg/project/expander_test.go b/pkg/project/expander_test.go index e4330fbed3..42ff3ee228 100644 --- a/pkg/project/expander_test.go +++ b/pkg/project/expander_test.go @@ -12,6 +12,7 @@ import ( "github.com/stretchr/testify/assert" "gopkg.in/yaml.v2" + "github.com/ActiveState/cli/internal/errs" "github.com/ActiveState/cli/internal/fileutils" "github.com/ActiveState/cli/internal/language" @@ -108,7 +109,7 @@ func TestExpandProject(t *testing.T) { if runtime.GOOS == "windows" { prj.Source().SetPath(fmt.Sprintf(`c:\another\spoofed path\activestate.yaml`)) expanded, err = project.ExpandFromProjectBashifyPaths("$project.path()", prj) - require.NoError(t, err) + require.NoError(t, err, errs.JoinMessage(err)) assert.Equal(t, `/c/another/spoofed\ path`, expanded) } } diff --git a/scripts/ci/parallelize/parallelize.go b/scripts/ci/parallelize/parallelize.go index 24360ace9a..c7b6a2e26a 100644 --- a/scripts/ci/parallelize/parallelize.go +++ b/scripts/ci/parallelize/parallelize.go @@ -17,6 +17,7 @@ import ( "github.com/ActiveState/cli/internal/installation/storage" "github.com/ActiveState/cli/internal/osutils" "github.com/ActiveState/cli/pkg/project" + "github.com/ActiveState/cli/pkg/projectfile/vars" "github.com/gammazero/workerpool" ) @@ -35,7 +36,7 @@ func main() { } func run() error { - if len(os.Args) <= 1{ + if len(os.Args) <= 1 { return errs.New("Must provide single argument with JSON blob, or [job ] to check the results of a job.") } @@ -104,7 +105,8 @@ func runJob(job Job) { return } - cond := constraints.NewPrimeConditional(nil, pj, "") + projVars := vars.New(nil, vars.NewProject(pj), "noshell") + cond := constraints.NewPrimeConditional(projVars) run, err := cond.Eval(job.If) if err != nil { failure("Could not evaluate conditonal: %s, error: %s\n", job.If, errs.JoinMessage(err)) @@ -120,8 +122,7 @@ func runJob(job Job) { return } - - code, _, err := exeutils.Execute(job.Args[0] + osutils.ExeExt, job.Args[1:], func(cmd *exec.Cmd) error { + code, _, err := exeutils.Execute(job.Args[0]+osutils.ExeExt, job.Args[1:], func(cmd *exec.Cmd) error { cmd.Stdout = outfile cmd.Stderr = outfile cmd.Env = append(job.Env, os.Environ()...) @@ -136,14 +137,14 @@ func runJob(job Job) { func readJob(id string) error { jobfile := filepath.Join(jobDir(), fmt.Sprintf("%s.out", id)) - if ! fileutils.FileExists(jobfile) { + if !fileutils.FileExists(jobfile) { return errs.New("Job does not exist: %s", jobfile) } contents := strings.Split(string(fileutils.ReadFileUnsafe(jobfile)), "\n") code, err := strconv.Atoi(contents[len(contents)-1]) if err != nil { - return errs.Wrap(err,"Expected last line to be the exit code, instead found: %s", contents[len(contents)-1]) + return errs.Wrap(err, "Expected last line to be the exit code, instead found: %s", contents[len(contents)-1]) } fmt.Println(strings.Join(contents[0:(len(contents)-2)], "\n")) From b31ecb260624521a32e36f587b6aa1431b627b4c Mon Sep 17 00:00:00 2001 From: daved Date: Fri, 14 Apr 2023 16:29:22 -0700 Subject: [PATCH 12/31] Rename isFunc project expander tag opt to asFunc --- pkg/project/expander.go | 14 +++++++------- pkg/projectfile/vars/vars.go | 14 +++++++------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/pkg/project/expander.go b/pkg/project/expander.go index f36a7d82a9..61e265f50f 100644 --- a/pkg/project/expander.go +++ b/pkg/project/expander.go @@ -20,7 +20,7 @@ import ( const ( expandStructTag = "expand" - expandTagOptIsFunc = "isFunc" + expandTagOptAsFunc = "asFunc" expandTagOptIsPath = "isPath" ) @@ -216,18 +216,18 @@ func TopLevelExpander(variable string, name string, _ string, _ bool, ctx *Expan } type entry struct { - isFunc bool + asFunc bool isPath bool value string } func newEntry(tag string, val reflect.Value) entry { - var isFunc, isPath bool + var asFunc, isPath bool tParts := strings.Split(tag, ",") if len(tParts) > 1 { - if strings.Contains(tParts[1], expandTagOptIsFunc) { - isFunc = true + if strings.Contains(tParts[1], expandTagOptAsFunc) { + asFunc = true } if strings.Contains(tParts[1], expandTagOptIsPath) { isPath = true @@ -235,7 +235,7 @@ func newEntry(tag string, val reflect.Value) entry { } return entry{ - isFunc: isFunc, + asFunc: asFunc, isPath: isPath, value: fmt.Sprintf("%v", val.Interface()), } @@ -294,7 +294,7 @@ func MakeExpanderFuncFromMap(m map[string]map[string]entry) ExpanderFunc { } if sub, ok := m[name]; ok { - if e, ok := sub[meta]; ok && isFunc == e.isFunc { + if e, ok := sub[meta]; ok && isFunc == e.asFunc { value := e.value if ctx.BashifyPaths && e.isPath { return osutils.BashifyPath(value) diff --git a/pkg/projectfile/vars/vars.go b/pkg/projectfile/vars/vars.go index bea283c663..d5b08618d0 100644 --- a/pkg/projectfile/vars/vars.go +++ b/pkg/projectfile/vars/vars.go @@ -20,13 +20,13 @@ type projectDataProvider interface { } type Project struct { - Namespace string `expand:",isFunc"` - Name string `expand:",isFunc"` - Owner string `expand:",isFunc"` - Url string `expand:",isFunc"` - Commit string `expand:",isFunc"` - Branch string `expand:",isFunc"` - Path string `expand:",isFunc;isPath"` + Namespace string `expand:",asFunc"` + Name string `expand:",asFunc"` + Owner string `expand:",asFunc"` + Url string `expand:",asFunc"` + Commit string `expand:",asFunc"` + Branch string `expand:",asFunc"` + Path string `expand:",asFunc;isPath"` // legacy fields NamespacePrefix string From 54c28767741bed6f6c798467439eaef75228046f Mon Sep 17 00:00:00 2001 From: daved Date: Fri, 14 Apr 2023 16:31:06 -0700 Subject: [PATCH 13/31] Fix project expander unit test for windows --- pkg/project/expander_test.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pkg/project/expander_test.go b/pkg/project/expander_test.go index 42ff3ee228..74d004ec1e 100644 --- a/pkg/project/expander_test.go +++ b/pkg/project/expander_test.go @@ -108,6 +108,11 @@ func TestExpandProject(t *testing.T) { if runtime.GOOS == "windows" { prj.Source().SetPath(fmt.Sprintf(`c:\another\spoofed path\activestate.yaml`)) + + // needed after project update + projVars := vars.New(nil, vars.NewProject(prj), "noshell") + _ = project.RegisterStruct(projVars) + expanded, err = project.ExpandFromProjectBashifyPaths("$project.path()", prj) require.NoError(t, err, errs.JoinMessage(err)) assert.Equal(t, `/c/another/spoofed\ path`, expanded) From 45056f92d9424d7abc5a4d1629325dc19a28cbad Mon Sep 17 00:00:00 2001 From: daved Date: Mon, 17 Apr 2023 14:40:59 -0700 Subject: [PATCH 14/31] Add project update callback to keep expander projectvars updated --- cmd/state/main.go | 15 ++++++++---- internal/primer/primer.go | 29 +++++++--------------- internal/runners/show/show.go | 45 +++++++++------------------------- pkg/project/expander_test.go | 42 +++++++++++++++---------------- pkg/project/project.go | 4 +++ pkg/project/project_test.go | 11 ++++++--- pkg/projectfile/projectfile.go | 31 +++++++++++++++++++---- 7 files changed, 88 insertions(+), 89 deletions(-) diff --git a/cmd/state/main.go b/cmd/state/main.go index 8b37ef03c4..435fa362e2 100644 --- a/cmd/state/main.go +++ b/cmd/state/main.go @@ -214,15 +214,20 @@ func run(args []string, isInteractive bool, cfg *config.Instance, out output.Out // Set up conditional, which accesses a lot of primer data sshell := subshell.New(cfg) - projVars := vars.New(auth, vars.NewProject(pj), sshell.Shell()) + registerProjectVars := func() { + projVars := vars.New(auth, vars.NewProject(pj), sshell.Shell()) + conditional := constraints.NewPrimeConditional(projVars) + project.RegisterConditional(conditional) + _ = project.RegisterStruct(projVars) + } + + pj.SetUpdateCallback(registerProjectVars) + registerProjectVars() - conditional := constraints.NewPrimeConditional(projVars) - project.RegisterConditional(conditional) - _ = project.RegisterStruct(projVars) project.RegisterExpander("secrets", project.NewSecretPromptingExpander(secretsapi.Get(), prompter, cfg, auth)) // Run the actual command - cmds := cmdtree.New(primer.New(pj, out, auth, prompter, sshell, conditional, cfg, ipcClient, svcmodel, an), args...) + cmds := cmdtree.New(primer.New(pj, out, auth, prompter, sshell, cfg, ipcClient, svcmodel, an), args...) childCmd, err := cmds.Command().Find(args[1:]) if err != nil { diff --git a/internal/primer/primer.go b/internal/primer/primer.go index f3cd8d8d8f..063185e604 100644 --- a/internal/primer/primer.go +++ b/internal/primer/primer.go @@ -3,7 +3,6 @@ package primer import ( "github.com/ActiveState/cli/internal/analytics" "github.com/ActiveState/cli/internal/config" - "github.com/ActiveState/cli/internal/constraints" "github.com/ActiveState/cli/internal/output" "github.com/ActiveState/cli/internal/prompt" "github.com/ActiveState/cli/internal/subshell" @@ -21,7 +20,6 @@ type Values struct { auth *authentication.Auth prompt prompt.Prompter subshell subshell.SubShell - conditional *constraints.Conditional config *config.Instance ipComm svcctl.IPCommunicator svcModel *model.SvcModel @@ -30,19 +28,18 @@ type Values struct { func New( project *project.Project, output output.Outputer, auth *authentication.Auth, prompt prompt.Prompter, - subshell subshell.SubShell, conditional *constraints.Conditional, config *config.Instance, + subshell subshell.SubShell, config *config.Instance, ipComm svcctl.IPCommunicator, svcModel *model.SvcModel, an analytics.Dispatcher) *Values { v := &Values{ - output: output, - auth: auth, - prompt: prompt, - subshell: subshell, - conditional: conditional, - config: config, - ipComm: ipComm, - svcModel: svcModel, - analytics: an, + output: output, + auth: auth, + prompt: prompt, + subshell: subshell, + config: config, + ipComm: ipComm, + svcModel: svcModel, + analytics: an, } if project != nil { v.project = project @@ -91,10 +88,6 @@ type Subsheller interface { Subshell() subshell.SubShell } -type Conditioner interface { - Conditional() *constraints.Conditional -} - func (v *Values) Project() *project.Project { return v.project } @@ -127,10 +120,6 @@ func (v *Values) SvcModel() *model.SvcModel { return v.svcModel } -func (v *Values) Conditional() *constraints.Conditional { - return v.conditional -} - func (v *Values) Config() *config.Instance { return v.config } diff --git a/internal/runners/show/show.go b/internal/runners/show/show.go index 387a2445c5..9af9769965 100644 --- a/internal/runners/show/show.go +++ b/internal/runners/show/show.go @@ -7,7 +7,6 @@ import ( "github.com/go-openapi/strfmt" - "github.com/ActiveState/cli/internal/constraints" "github.com/ActiveState/cli/internal/errs" "github.com/ActiveState/cli/internal/fileutils" "github.com/ActiveState/cli/internal/locale" @@ -22,7 +21,6 @@ import ( "github.com/ActiveState/cli/pkg/platform/runtime/setup" "github.com/ActiveState/cli/pkg/platform/runtime/target" "github.com/ActiveState/cli/pkg/project" - "github.com/ActiveState/cli/pkg/projectfile" ) // Params describes the data required for the show run func. @@ -32,10 +30,9 @@ type Params struct { // Show manages the show run execution context. type Show struct { - project *project.Project - out output.Outputer - conditional *constraints.Conditional - auth *authentication.Auth + project *project.Project + out output.Outputer + auth *authentication.Auth } type auther interface { @@ -45,7 +42,6 @@ type auther interface { type primeable interface { primer.Projecter primer.Outputer - primer.Conditioner primer.Auther } @@ -128,7 +124,6 @@ func New(prime primeable) *Show { return &Show{ prime.Project(), prime.Output(), - prime.Conditional(), prime.Auth(), } } @@ -182,12 +177,12 @@ func (s *Show) Run(params Params) error { projectURL = s.project.URL() branchName = s.project.BranchName() - events, err = eventsData(s.project.Source(), s.conditional) + events, err = eventsData(s.project) if err != nil { return locale.WrapError(err, "err_show_events", "Could not parse events") } - scripts, err = scriptsData(s.project.Source(), s.conditional) + scripts, err = scriptsData(s.project) if err != nil { return locale.WrapError(err, "err_show_scripts", "Could not parse scripts") } @@ -278,41 +273,23 @@ type languageRow struct { Version string `json:"version" locale:"state_show_language_version,Version"` } -func eventsData(project *projectfile.Project, conditional *constraints.Conditional) ([]string, error) { - if len(project.Events) == 0 { - return nil, nil - } - - constrained, err := constraints.FilterUnconstrained(conditional, project.Events.AsConstrainedEntities()) - if err != nil { - return nil, locale.WrapError(err, "err_event_condition", "Event has invalid conditional") - } - - es := projectfile.MakeEventsFromConstrainedEntities(constrained) +func eventsData(pj *project.Project) ([]string, error) { + es := pj.Events() var data []string for _, event := range es { - data = append(data, event.Name) + data = append(data, event.Name()) } return data, nil } -func scriptsData(project *projectfile.Project, conditional *constraints.Conditional) (map[string]string, error) { - if len(project.Scripts) == 0 { - return nil, nil - } - - constrained, err := constraints.FilterUnconstrained(conditional, project.Scripts.AsConstrainedEntities()) - if err != nil { - return nil, locale.WrapError(err, "err_script_condition", "Script has invalid conditional") - } - - scripts := projectfile.MakeScriptsFromConstrainedEntities(constrained) +func scriptsData(pj *project.Project) (map[string]string, error) { + scripts := pj.Scripts() data := make(map[string]string) for _, script := range scripts { - data[script.Name] = script.Description + data[script.Name()] = script.Description() } return data, nil diff --git a/pkg/project/expander_test.go b/pkg/project/expander_test.go index 74d004ec1e..bacd0d1366 100644 --- a/pkg/project/expander_test.go +++ b/pkg/project/expander_test.go @@ -21,7 +21,7 @@ import ( "github.com/ActiveState/cli/pkg/projectfile/vars" ) -func loadProjectAndRegisterVals(t *testing.T) *project.Project { +func loadProject(t *testing.T) *project.Project { projectfile.Reset() pjFile := &projectfile.Project{} @@ -64,19 +64,21 @@ scripts: pj := project.Get() - projVars := vars.New(nil, vars.NewProject(pj), "noshell") - _ = project.RegisterStruct(projVars) + registerProjectVars := func() { + projVars := vars.New(nil, vars.NewProject(pj), "noshell") + _ = project.RegisterStruct(projVars) + } + + pj.SetUpdateCallback(registerProjectVars) + registerProjectVars() return pj } func TestExpandProject(t *testing.T) { - prj := loadProjectAndRegisterVals(t) - prj.Source().SetPath(fmt.Sprintf("spoofed path%sactivestate.yaml", string(os.PathSeparator))) + prj := loadProject(t) - // needed after project update - projVars := vars.New(nil, vars.NewProject(prj), "noshell") - _ = project.RegisterStruct(projVars) + prj.Source().SetPath(fmt.Sprintf("spoofed path%sactivestate.yaml", string(os.PathSeparator))) expanded, err := project.ExpandFromProject("$project.url()", prj) require.NoError(t, err) @@ -109,10 +111,6 @@ func TestExpandProject(t *testing.T) { if runtime.GOOS == "windows" { prj.Source().SetPath(fmt.Sprintf(`c:\another\spoofed path\activestate.yaml`)) - // needed after project update - projVars := vars.New(nil, vars.NewProject(prj), "noshell") - _ = project.RegisterStruct(projVars) - expanded, err = project.ExpandFromProjectBashifyPaths("$project.path()", prj) require.NoError(t, err, errs.JoinMessage(err)) assert.Equal(t, `/c/another/spoofed\ path`, expanded) @@ -120,7 +118,7 @@ func TestExpandProject(t *testing.T) { } func TestExpandTopLevel(t *testing.T) { - prj := loadProjectAndRegisterVals(t) + prj := loadProject(t) expanded, err := project.ExpandFromProject("$project", prj) assert.NoError(t, err, "Ran without failure") @@ -137,7 +135,7 @@ func TestExpandTopLevel(t *testing.T) { } func TestExpandProjectScript(t *testing.T) { - prj := loadProjectAndRegisterVals(t) + prj := loadProject(t) expanded, err := project.ExpandFromProject("$ $scripts.test", prj) assert.NoError(t, err, "Ran without failure") @@ -145,7 +143,7 @@ func TestExpandProjectScript(t *testing.T) { } func TestExpandProjectConstant(t *testing.T) { - prj := loadProjectAndRegisterVals(t) + prj := loadProject(t) expanded, err := project.ExpandFromProject("$ $constants.constant", prj) assert.NoError(t, err, "Ran without failure") @@ -157,7 +155,7 @@ func TestExpandProjectConstant(t *testing.T) { } func TestExpandProjectSecret(t *testing.T) { - pj := loadProjectAndRegisterVals(t) + pj := loadProject(t) project.RegisterExpander("secrets", func(_ string, category string, meta string, isFunction bool, ctx *project.Expansion) (string, error) { if category == project.ProjectCategory { @@ -176,7 +174,7 @@ func TestExpandProjectSecret(t *testing.T) { } func TestExpandProjectAlternateSyntax(t *testing.T) { - prj := loadProjectAndRegisterVals(t) + prj := loadProject(t) expanded, err := project.ExpandFromProject("${project.name()}", prj) assert.NoError(t, err, "Ran without failure") @@ -184,7 +182,7 @@ func TestExpandProjectAlternateSyntax(t *testing.T) { } func TestExpandProjectUnknownCategory(t *testing.T) { - prj := loadProjectAndRegisterVals(t) + prj := loadProject(t) expanded, err := project.ExpandFromProject("$unknown.unknown", prj) assert.NoError(t, err, "Ran without failure") @@ -192,7 +190,7 @@ func TestExpandProjectUnknownCategory(t *testing.T) { } func TestExpandProjectInfiniteRecursion(t *testing.T) { - prj := loadProjectAndRegisterVals(t) + prj := loadProject(t) _, err := project.ExpandFromProject("$scripts.recursive", prj) require.Error(t, err, "Ran with failure") @@ -220,7 +218,7 @@ scripts: } func TestExpandScriptPath(t *testing.T) { - prj := loadProjectAndRegisterVals(t) + prj := loadProject(t) expanded, err := project.ExpandFromProject("$scripts.scriptPath", prj) assert.NoError(t, err, "Ran without failure") @@ -233,7 +231,7 @@ func TestExpandScriptPath(t *testing.T) { } func TestExpandScriptPathRecursive(t *testing.T) { - prj := loadProjectAndRegisterVals(t) + prj := loadProject(t) expanded, err := project.ExpandFromProject("$scripts.scriptRecursive", prj) assert.NoError(t, err, "Ran without failure") @@ -244,7 +242,7 @@ func TestExpandScriptPathRecursive(t *testing.T) { } func TestExpandBashScriptPath(t *testing.T) { - prj := loadProjectAndRegisterVals(t) + prj := loadProject(t) script := prj.ScriptByName("bashScriptPath") require.NotNil(t, script, "bashScriptPath script does not exist") value, err := script.Value() diff --git a/pkg/project/project.go b/pkg/project/project.go index 4eb646478e..a7b0ec1aa7 100644 --- a/pkg/project/project.go +++ b/pkg/project/project.go @@ -58,6 +58,10 @@ func (p *Project) SetCommit(commitID string) error { return p.Source().SetCommit(commitID, p.IsHeadless()) } +func (p *Project) SetUpdateCallback(fn func()) { + p.projectfile.SetUpdateCallback(fn) +} + // Constants returns a reference to projectfile.Constants func (p *Project) Constants() []*Constant { constrained, err := constraints.FilterUnconstrained(pConditional, p.projectfile.Constants.AsConstrainedEntities()) diff --git a/pkg/project/project_test.go b/pkg/project/project_test.go index fc563b3d2b..ee9c57a6fe 100644 --- a/pkg/project/project_test.go +++ b/pkg/project/project_test.go @@ -47,9 +47,14 @@ func (suite *ProjectTestSuite) BeforeTest(suiteName, testName string) { cfg, err := config.New() suite.Require().NoError(err) - projVars := vars.New(nil, vars.NewProject(suite.project), subshell.New(cfg).Shell()) - project.RegisterConditional(constraints.NewPrimeConditional(projVars)) - _ = project.RegisterStruct(projVars) + registerProjectVars := func() { + projVars := vars.New(nil, vars.NewProject(suite.project), subshell.New(cfg).Shell()) + project.RegisterConditional(constraints.NewPrimeConditional(projVars)) + _ = project.RegisterStruct(projVars) + } + + suite.project.SetUpdateCallback(registerProjectVars) + registerProjectVars() } func (suite *ProjectTestSuite) TestGet() { diff --git a/pkg/projectfile/projectfile.go b/pkg/projectfile/projectfile.go index 977835feb9..a39f2affb0 100644 --- a/pkg/projectfile/projectfile.go +++ b/pkg/projectfile/projectfile.go @@ -104,6 +104,8 @@ type Project struct { parsedURL projectURL // parsed url data parsedBranch string parsedVersion string + + updateCallback func() } // Build covers the build map, which can go under languages or packages @@ -558,11 +560,6 @@ func (p *Project) Path() string { return p.path } -// SetPath sets the path of the project file and should generally only be used by tests -func (p *Project) SetPath(path string) { - p.path = path -} - // VersionBranch returns the branch as it was interpreted from the lock func (p *Project) VersionBranch() string { return p.parsedBranch @@ -687,8 +684,21 @@ func (p *Project) save(cfg ConfigGetter, path string) error { return nil } +func (p *Project) runUpdateCallback() { + if p.updateCallback == nil { + return + } + p.updateCallback() +} + +func (p *Project) SetUpdateCallback(fn func()) { + p.updateCallback = fn +} + // SetNamespace updates the namespace in the project file func (p *Project) SetNamespace(owner, project string) error { + defer p.runUpdateCallback() + pf := NewProjectField() if err := pf.LoadProject(p.Project); err != nil { return errs.Wrap(err, "Could not load activestate.yaml") @@ -710,6 +720,8 @@ func (p *Project) SetNamespace(owner, project string) error { // in-place so that line order is preserved. // If headless is true, the project is defined by a commit-id only func (p *Project) SetCommit(commitID string, headless bool) error { + defer p.runUpdateCallback() + pf := NewProjectField() if err := pf.LoadProject(p.Project); err != nil { return errs.Wrap(err, "Could not load activestate.yaml") @@ -727,6 +739,8 @@ func (p *Project) SetCommit(commitID string, headless bool) error { // SetBranch sets the branch within the current project file. This is done // in-place so that line order is preserved. func (p *Project) SetBranch(branch string) error { + defer p.runUpdateCallback() + pf := NewProjectField() if err := pf.LoadProject(p.Project); err != nil { @@ -746,6 +760,13 @@ func (p *Project) SetBranch(branch string) error { return nil } +// SetPath sets the path of the project file and should generally only be used by tests +func (p *Project) SetPath(path string) { + defer p.runUpdateCallback() + + p.path = path +} + // GetProjectFilePath returns the path to the project activestate.yaml func GetProjectFilePath() (string, error) { defer profile.Measure("GetProjectFilePath", time.Now()) From 7f9addc486180bfae533f885f96793b393899e20 Mon Sep 17 00:00:00 2001 From: daved Date: Mon, 17 Apr 2023 15:41:38 -0700 Subject: [PATCH 15/31] Fix call to primer construction in svc --- cmd/state-svc/main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/state-svc/main.go b/cmd/state-svc/main.go index 7189d0a00e..07d384291a 100644 --- a/cmd/state-svc/main.go +++ b/cmd/state-svc/main.go @@ -111,7 +111,7 @@ func run(cfg *config.Instance) error { return runStart(out, "svc-start:mouse") } - p := primer.New(nil, out, nil, nil, nil, nil, cfg, nil, nil, an) + p := primer.New(nil, out, nil, nil, nil, cfg, nil, nil, an) cmd := captain.NewCommand( path.Base(os.Args[0]), "", "", p, nil, nil, From d51d1e2be2ae163ccefd6554d09711cc141b715d Mon Sep 17 00:00:00 2001 From: daved Date: Mon, 17 Apr 2023 15:51:26 -0700 Subject: [PATCH 16/31] Fix call to primer construction in installer --- cmd/state-installer/cmd.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/state-installer/cmd.go b/cmd/state-installer/cmd.go index 58d3b7556e..7ad8b0ae1e 100644 --- a/cmd/state-installer/cmd.go +++ b/cmd/state-installer/cmd.go @@ -133,7 +133,7 @@ func main() { "state-installer", "", "Installs or updates the State Tool", - primer.New(nil, out, nil, nil, nil, nil, cfg, nil, nil, an), + primer.New(nil, out, nil, nil, nil, cfg, nil, nil, an), []*captain.Flag{ // The naming of these flags is slightly inconsistent due to backwards compatibility requirements { Name: "command", From b6f7f6c0cd7ac751370d127965f9a95873f2e745 Mon Sep 17 00:00:00 2001 From: daved Date: Mon, 17 Apr 2023 15:52:58 -0700 Subject: [PATCH 17/31] Fix call to primer construction in remote installer --- cmd/state-remote-installer/main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/state-remote-installer/main.go b/cmd/state-remote-installer/main.go index 75e031c01d..ade84c6282 100644 --- a/cmd/state-remote-installer/main.go +++ b/cmd/state-remote-installer/main.go @@ -104,7 +104,7 @@ func main() { "state-installer", "", "Installs or updates the State Tool", - primer.New(nil, out, nil, nil, nil, nil, cfg, nil, nil, an), + primer.New(nil, out, nil, nil, nil, cfg, nil, nil, an), []*captain.Flag{ // The naming of these flags is slightly inconsistent due to backwards compatibility requirements { Name: "channel", From 8afd893c1dc5fb0e6a3b5559ca1d6d11575c3a36 Mon Sep 17 00:00:00 2001 From: daved Date: Mon, 17 Apr 2023 20:03:02 -0700 Subject: [PATCH 18/31] Guard projectfile call in project setupdatecallback --- pkg/project/project.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pkg/project/project.go b/pkg/project/project.go index a7b0ec1aa7..ffd8c988dd 100644 --- a/pkg/project/project.go +++ b/pkg/project/project.go @@ -59,6 +59,9 @@ func (p *Project) SetCommit(commitID string) error { } func (p *Project) SetUpdateCallback(fn func()) { + if p.projectfile == nil { + return + } p.projectfile.SetUpdateCallback(fn) } From 2918fd8800e9c0b3fb153dcf55f4e8338c073994 Mon Sep 17 00:00:00 2001 From: daved Date: Mon, 17 Apr 2023 22:23:03 -0700 Subject: [PATCH 19/31] Move project/vars setup to own pkg --- cmd/state/main.go | 44 ++++++------------------------- pkg/project/expander_test.go | 13 +++------ pkg/project/project_test.go | 16 +++-------- pkg/projget/projget.go | 51 ++++++++++++++++++++++++++++++++++++ 4 files changed, 66 insertions(+), 58 deletions(-) create mode 100644 pkg/projget/projget.go diff --git a/cmd/state/main.go b/cmd/state/main.go index 435fa362e2..584a7c6abc 100644 --- a/cmd/state/main.go +++ b/cmd/state/main.go @@ -17,7 +17,6 @@ import ( "github.com/ActiveState/cli/internal/captain" "github.com/ActiveState/cli/internal/config" "github.com/ActiveState/cli/internal/constants" - "github.com/ActiveState/cli/internal/constraints" "github.com/ActiveState/cli/internal/errs" "github.com/ActiveState/cli/internal/events" "github.com/ActiveState/cli/internal/installation" @@ -39,8 +38,7 @@ import ( "github.com/ActiveState/cli/pkg/platform/authentication" "github.com/ActiveState/cli/pkg/platform/model" "github.com/ActiveState/cli/pkg/project" - "github.com/ActiveState/cli/pkg/projectfile" - "github.com/ActiveState/cli/pkg/projectfile/vars" + "github.com/ActiveState/cli/pkg/projget" ) func main() { @@ -169,24 +167,14 @@ func run(args []string, isInteractive bool, cfg *config.Instance, out output.Out return logData }) - // Retrieve project file - pjPath, err := projectfile.GetProjectFilePath() - if err != nil && errs.Matches(err, &projectfile.ErrorNoProjectFromEnv{}) { - // Fail if we are meant to inherit the projectfile from the environment, but the file doesn't exist - return err - } + auth := authentication.New(cfg) + defer events.Close("auth", auth.Close) - // Set up project (if we have a valid path) - var pj *project.Project - if pjPath != "" { - pjf, err := projectfile.FromPath(pjPath) - if err != nil { - return err - } - pj, err = project.New(pjf, out) - if err != nil { - return err - } + sshell := subshell.New(cfg) + + pj, err := projget.NewProject(out, auth, sshell.Shell()) + if err != nil { + return err } pjNamespace := "" @@ -194,9 +182,6 @@ func run(args []string, isInteractive bool, cfg *config.Instance, out output.Out pjNamespace = pj.Namespace().String() } - auth := authentication.New(cfg) - defer events.Close("auth", auth.Close) - if err := auth.Sync(); err != nil { logging.Warning("Could not sync authenticated state: %s", err.Error()) } @@ -211,19 +196,6 @@ func run(args []string, isInteractive bool, cfg *config.Instance, out output.Out // Set up prompter prompter := prompt.New(isInteractive, an) - // Set up conditional, which accesses a lot of primer data - sshell := subshell.New(cfg) - - registerProjectVars := func() { - projVars := vars.New(auth, vars.NewProject(pj), sshell.Shell()) - conditional := constraints.NewPrimeConditional(projVars) - project.RegisterConditional(conditional) - _ = project.RegisterStruct(projVars) - } - - pj.SetUpdateCallback(registerProjectVars) - registerProjectVars() - project.RegisterExpander("secrets", project.NewSecretPromptingExpander(secretsapi.Get(), prompter, cfg, auth)) // Run the actual command diff --git a/pkg/project/expander_test.go b/pkg/project/expander_test.go index bacd0d1366..44d484b110 100644 --- a/pkg/project/expander_test.go +++ b/pkg/project/expander_test.go @@ -18,7 +18,7 @@ import ( "github.com/ActiveState/cli/pkg/project" "github.com/ActiveState/cli/pkg/projectfile" - "github.com/ActiveState/cli/pkg/projectfile/vars" + "github.com/ActiveState/cli/pkg/projget" ) func loadProject(t *testing.T) *project.Project { @@ -62,15 +62,8 @@ scripts: pjFile.Persist() - pj := project.Get() - - registerProjectVars := func() { - projVars := vars.New(nil, vars.NewProject(pj), "noshell") - _ = project.RegisterStruct(projVars) - } - - pj.SetUpdateCallback(registerProjectVars) - registerProjectVars() + pj, err := projget.NewProjectForTest() + require.NoError(t, err) return pj } diff --git a/pkg/project/project_test.go b/pkg/project/project_test.go index ee9c57a6fe..85f7d3ffb8 100644 --- a/pkg/project/project_test.go +++ b/pkg/project/project_test.go @@ -7,15 +7,15 @@ import ( "testing" "github.com/ActiveState/cli/internal/config" - "github.com/ActiveState/cli/internal/constraints" "github.com/ActiveState/cli/internal/environment" "github.com/ActiveState/cli/internal/errs" "github.com/ActiveState/cli/internal/language" + "github.com/ActiveState/cli/internal/output" "github.com/ActiveState/cli/internal/subshell" "github.com/ActiveState/cli/pkg/platform/authentication" "github.com/ActiveState/cli/pkg/project" "github.com/ActiveState/cli/pkg/projectfile" - "github.com/ActiveState/cli/pkg/projectfile/vars" + "github.com/ActiveState/cli/pkg/projget" "github.com/stretchr/testify/suite" ) @@ -41,20 +41,12 @@ func (suite *ProjectTestSuite) BeforeTest(suiteName, testName string) { projectFile.Persist() suite.projectFile = projectFile suite.Require().Nil(err, "Should retrieve projectfile without issue.") - suite.project, err = project.GetSafe() - suite.Require().Nil(err, "Should retrieve project without issue.") cfg, err := config.New() suite.Require().NoError(err) - registerProjectVars := func() { - projVars := vars.New(nil, vars.NewProject(suite.project), subshell.New(cfg).Shell()) - project.RegisterConditional(constraints.NewPrimeConditional(projVars)) - _ = project.RegisterStruct(projVars) - } - - suite.project.SetUpdateCallback(registerProjectVars) - registerProjectVars() + suite.project, err = projget.NewProject(output.Get(), nil, subshell.New(cfg).Shell()) + suite.Require().Nil(err, "Should retrieve project without issue.") } func (suite *ProjectTestSuite) TestGet() { diff --git a/pkg/projget/projget.go b/pkg/projget/projget.go new file mode 100644 index 0000000000..33751c593c --- /dev/null +++ b/pkg/projget/projget.go @@ -0,0 +1,51 @@ +package projget + +import ( + "github.com/ActiveState/cli/internal/constraints" + "github.com/ActiveState/cli/internal/errs" + "github.com/ActiveState/cli/internal/output" + "github.com/ActiveState/cli/pkg/platform/authentication" + "github.com/ActiveState/cli/pkg/project" + "github.com/ActiveState/cli/pkg/projectfile" + "github.com/ActiveState/cli/pkg/projectfile/vars" +) + +func NewProject(out output.Outputer, auth *authentication.Auth, shell string) (*project.Project, error) { + // Retrieve project file + pjPath, err := projectfile.GetProjectFilePath() + if err != nil && errs.Matches(err, &projectfile.ErrorNoProjectFromEnv{}) { + // Fail if we are meant to inherit the projectfile from the environment, but the file doesn't exist + return nil, err + } + + // Set up project (if we have a valid path) + var pj *project.Project + if pjPath != "" { + pjf, err := projectfile.FromPath(pjPath) + if err != nil { + return nil, err + } + pj, err = project.New(pjf, out) + if err != nil { + return nil, err + } + } + + if pj != nil { + registerProjectVars := func() { + projVars := vars.New(auth, vars.NewProject(pj), shell) + conditional := constraints.NewPrimeConditional(projVars) + project.RegisterConditional(conditional) + _ = project.RegisterStruct(projVars) + } + + pj.SetUpdateCallback(registerProjectVars) + registerProjectVars() + } + + return pj, nil +} + +func NewProjectForTest() (*project.Project, error) { + return NewProject(output.Get(), nil, "noshell") +} From 0dcce0690a2422e1b0209c9ee6cbb6d38df4d02a Mon Sep 17 00:00:00 2001 From: daved Date: Tue, 18 Apr 2023 00:25:13 -0700 Subject: [PATCH 20/31] Fix project pkg expander tests --- pkg/project/expander_test.go | 2 +- pkg/projget/projget.go | 43 +++++++++++++++++++++--------------- 2 files changed, 26 insertions(+), 19 deletions(-) diff --git a/pkg/project/expander_test.go b/pkg/project/expander_test.go index 44d484b110..03f7ce2027 100644 --- a/pkg/project/expander_test.go +++ b/pkg/project/expander_test.go @@ -62,7 +62,7 @@ scripts: pjFile.Persist() - pj, err := projget.NewProjectForTest() + pj, err := projget.NewProjectForTest(pjFile) require.NoError(t, err) return pj diff --git a/pkg/projget/projget.go b/pkg/projget/projget.go index 33751c593c..abb016048a 100644 --- a/pkg/projget/projget.go +++ b/pkg/projget/projget.go @@ -11,6 +11,8 @@ import ( ) func NewProject(out output.Outputer, auth *authentication.Auth, shell string) (*project.Project, error) { + var pjf *projectfile.Project + // Retrieve project file pjPath, err := projectfile.GetProjectFilePath() if err != nil && errs.Matches(err, &projectfile.ErrorNoProjectFromEnv{}) { @@ -18,34 +20,39 @@ func NewProject(out output.Outputer, auth *authentication.Auth, shell string) (* return nil, err } - // Set up project (if we have a valid path) - var pj *project.Project if pjPath != "" { - pjf, err := projectfile.FromPath(pjPath) - if err != nil { - return nil, err - } - pj, err = project.New(pjf, out) + pjf, err = projectfile.FromPath(pjPath) if err != nil { return nil, err } } - if pj != nil { - registerProjectVars := func() { - projVars := vars.New(auth, vars.NewProject(pj), shell) - conditional := constraints.NewPrimeConditional(projVars) - project.RegisterConditional(conditional) - _ = project.RegisterStruct(projVars) - } + return newProject(out, auth, shell, pjf) +} - pj.SetUpdateCallback(registerProjectVars) - registerProjectVars() +func newProject(out output.Outputer, auth *authentication.Auth, shell string, pjf *projectfile.Project) (*project.Project, error) { + if pjf == nil { + return nil, errs.New("No projectfile provided") } + pj, err := project.New(pjf, out) + if err != nil { + return nil, err + } + + registerProjectVars := func() { + projVars := vars.New(auth, vars.NewProject(pj), shell) + conditional := constraints.NewPrimeConditional(projVars) + project.RegisterConditional(conditional) + _ = project.RegisterStruct(projVars) + } + + pj.SetUpdateCallback(registerProjectVars) + registerProjectVars() + return pj, nil } -func NewProjectForTest() (*project.Project, error) { - return NewProject(output.Get(), nil, "noshell") +func NewProjectForTest(pjf *projectfile.Project) (*project.Project, error) { + return newProject(output.Get(), nil, "noshell", pjf) } From 25a74f9d1df8d2f5817dc699e591c744a38a7ffb Mon Sep 17 00:00:00 2001 From: daved Date: Tue, 18 Apr 2023 00:46:50 -0700 Subject: [PATCH 21/31] Return nil project when no projectfile provided --- pkg/projget/projget.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/projget/projget.go b/pkg/projget/projget.go index abb016048a..eb56bc574e 100644 --- a/pkg/projget/projget.go +++ b/pkg/projget/projget.go @@ -32,7 +32,7 @@ func NewProject(out output.Outputer, auth *authentication.Auth, shell string) (* func newProject(out output.Outputer, auth *authentication.Auth, shell string, pjf *projectfile.Project) (*project.Project, error) { if pjf == nil { - return nil, errs.New("No projectfile provided") + return nil, nil } pj, err := project.New(pjf, out) From 18438c35d594d535e9da94369cfd2ad6a061d75b Mon Sep 17 00:00:00 2001 From: daved Date: Tue, 18 Apr 2023 08:36:53 -0700 Subject: [PATCH 22/31] Add vars pkg comment --- pkg/projectfile/vars/vars.go | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/pkg/projectfile/vars/vars.go b/pkg/projectfile/vars/vars.go index d5b08618d0..70f1f2f4d8 100644 --- a/pkg/projectfile/vars/vars.go +++ b/pkg/projectfile/vars/vars.go @@ -1,3 +1,19 @@ +// Package vars provides a single type expressing the data accessible by the +// activestate.yaml for conditionals and variable expansions. +// +// The structure should not grow beyond a depth of 3. That is, .OS.Version.Major +// is fine, but .OS.Version.Major.Something is not. External (leaf) nodes must +// be able to resolve to a string using `fmt.Sprintf("%v")`. Keep in mind that +// the Vars type itself is depth 0, so it does not count for depth, and should +// not be represented in the activestate.yaml content. +// +// Nodes may be a function, but the return value must also resolve to a string +// using `fmt.Sprintf("%v")`. A second return value of `error` is allowed. For +// variable expansion, a non-function node may be tagged as a function (asFunc) +// so that it must be called using parenthesis (`$project.name()`). +// +// Path nodes should be tagged (isPath) so that bashification of the path is +// applied when necessary. package vars import ( From 3f2bb37b3b65858237914a61e46d7db1123609aa Mon Sep 17 00:00:00 2001 From: daved Date: Tue, 18 Apr 2023 19:28:12 -0700 Subject: [PATCH 23/31] Clarify var naming in and add comments to vars work --- internal/constraints/constraints.go | 27 +++++++++++-------- pkg/project/expander.go | 41 ++++++++++++++++++----------- pkg/project/registry.go | 31 +++++++++++++--------- 3 files changed, 60 insertions(+), 39 deletions(-) diff --git a/internal/constraints/constraints.go b/internal/constraints/constraints.go index 822a2f4d8c..9d75871f9f 100644 --- a/internal/constraints/constraints.go +++ b/internal/constraints/constraints.go @@ -42,29 +42,34 @@ func NewConditional() *Conditional { return c } -func NewPrimeConditional(val interface{}) *Conditional { +func NewPrimeConditional(structure interface{}) *Conditional { c := NewConditional() - v := reflect.ValueOf(val) + v := reflect.ValueOf(structure) + // deref if needed if v.Kind() == reflect.Ptr { v = v.Elem() } - to := v.Type() - fields := reflect.VisibleFields(to) + fields := reflect.VisibleFields(v.Type()) + + // Work at depth 1: Vars.[OS].Version.Name for _, f := range fields { - sv := v.FieldByIndex(f.Index) - if sv.Kind() == reflect.Ptr { - sv = sv.Elem() + d1Val := v.FieldByIndex(f.Index) + if d1Val.Kind() == reflect.Ptr { + d1Val = d1Val.Elem() } - sto := sv.Type() - switch sto.Kind() { + // Only nodes at depth 1 need to be registered since the genertic type + // handling within the templating package will do the rest. If function + // registration is needed at greater depths, this will need to be + // reworked (and may not be possible without expansive refactoring). + switch d1Val.Type().Kind() { case reflect.Func: - c.RegisterFunc(f.Name, sv.Interface()) + c.RegisterFunc(f.Name, d1Val.Interface()) default: - c.RegisterParam(f.Name, sv.Interface()) + c.RegisterParam(f.Name, d1Val.Interface()) } } diff --git a/pkg/project/expander.go b/pkg/project/expander.go index 61e265f50f..b453fb70c3 100644 --- a/pkg/project/expander.go +++ b/pkg/project/expander.go @@ -215,6 +215,7 @@ func TopLevelExpander(variable string, name string, _ string, _ bool, ctx *Expan return variable, nil } +// entry manages a simple value held by a field as well as the field's metadata. type entry struct { asFunc bool isPath bool @@ -245,6 +246,7 @@ func makeEntryMap(val reflect.Value) map[string]entry { m := make(map[string]entry) fields := reflect.VisibleFields(val.Type()) + // Work at depth 3: Vars.Struct.Struct.[Simple] for _, f := range fields { if !f.IsExported() { continue @@ -257,29 +259,31 @@ func makeEntryMap(val reflect.Value) map[string]entry { return m } -func makeEntryMapMap(value reflect.Value) map[string]map[string]entry { +func makeEntryMapMap(structure reflect.Value) map[string]map[string]entry { m := make(map[string]map[string]entry) - fields := reflect.VisibleFields(value.Type()) + fields := reflect.VisibleFields(structure.Type()) + // Work at depth 2: Vars.Struct.[Struct].Simple for _, f := range fields { if !f.IsExported() { continue } - subValue := value.FieldByIndex(f.Index) - if subValue.Kind() == reflect.Ptr { - subValue = subValue.Elem() + d2Val := structure.FieldByIndex(f.Index) + if d2Val.Kind() == reflect.Ptr { + d2Val = d2Val.Elem() } - subType := subValue.Type() - switch subType.Kind() { + switch d2Val.Type().Kind() { + // Convert type (to map) to express advanced control like tag handling. case reflect.Struct: - // Vars.OS.Version.(Name) - m[strings.ToLower(f.Name)] = makeEntryMap(subValue) + m[strings.ToLower(f.Name)] = makeEntryMap(d2Val) + // Format simple value. This is a leaf: Vars.Struct.[Simple] + // Conform to map-map, store at zero-valued key of inner map. default: m[strings.ToLower(f.Name)] = map[string]entry{ - "": newEntry(f.Tag.Get(expandStructTag), subValue), + "": newEntry(f.Tag.Get(expandStructTag), d2Val), } } } @@ -287,7 +291,7 @@ func makeEntryMapMap(value reflect.Value) map[string]map[string]entry { return m } -func MakeExpanderFuncFromMap(m map[string]map[string]entry) ExpanderFunc { +func makeExpanderFuncFromMap(m map[string]map[string]entry) ExpanderFunc { return func(v, name, meta string, isFunc bool, ctx *Expansion) (string, error) { if isFunc && meta == "()" { meta = "" @@ -308,24 +312,31 @@ func MakeExpanderFuncFromMap(m map[string]map[string]entry) ExpanderFunc { } } -func MakeExpanderFuncFromFunc(val reflect.Value) ExpanderFunc { +func makeExpanderFuncFromFunc(val reflect.Value) ExpanderFunc { return func(v, name, meta string, isFunc bool, ctx *Expansion) (string, error) { + // Call function; It should not require any arguments. + // Work at depth 1: Vars.[FuncReturnsSomething]... vals := val.Call(nil) if len(vals) > 1 { - return "", vals[1].Interface().(error) + if !vals[1].IsNil() { + return "", vals[1].Interface().(error) + } } + val := vals[0] + // deref if needed if val.Kind() == reflect.Ptr { val = val.Elem() } switch val.Kind() { + // Convert type (to map-map) to express advanced control like tag handling. case reflect.Struct: - // Vars.OS.(Version).Name m := makeEntryMapMap(val) - expandFromMap := MakeExpanderFuncFromMap(m) + expandFromMap := makeExpanderFuncFromMap(m) return expandFromMap(v, name, meta, isFunc, ctx) + // Format simple value. This is a leaf: Vars.[FuncReturnsSimple] default: return fmt.Sprintf("%v", val.Interface()), nil } diff --git a/pkg/project/registry.go b/pkg/project/registry.go index e0edde9251..86afefbb04 100644 --- a/pkg/project/registry.go +++ b/pkg/project/registry.go @@ -33,30 +33,33 @@ func init() { func RegisterStruct(val interface{}) error { v := reflect.ValueOf(val) + // deref if needed if v.Kind() == reflect.Ptr { v = v.Elem() } - to := v.Type() - fields := reflect.VisibleFields(to) // we know this is a struct - // Vars.(OS).Version.Name + fields := reflect.VisibleFields(v.Type()) + + // Work at depth 1: Vars.[Struct].Struct.Simple for _, f := range fields { if !f.IsExported() { continue } - sv := v.FieldByIndex(f.Index) - if sv.Kind() == reflect.Ptr { - sv = sv.Elem() + d1Val := v.FieldByIndex(f.Index) + if d1Val.Kind() == reflect.Ptr { + d1Val = d1Val.Elem() } - sto := sv.Type() - switch sto.Kind() { + // If function registration is needed at greater depths, this + // will need to be reworked (and may not be possible without + // expansive refactoring). + switch d1Val.Type().Kind() { + // Convert type (to map-map) to express advanced control like tag handling. case reflect.Struct: - // Vars.OS.(Version).Name - m := makeEntryMapMap(sv) + m := makeEntryMapMap(d1Val) name := strings.ToLower(f.Name) - err := RegisterExpander(name, MakeExpanderFuncFromMap(m)) + err := RegisterExpander(name, makeExpanderFuncFromMap(m)) if err != nil { return locale.WrapError( err, "project_expand_register_expander_map", @@ -64,9 +67,10 @@ func RegisterStruct(val interface{}) error { ) } + // Expand from function. case reflect.Func: name := strings.ToLower(f.Name) - err := RegisterExpander(name, MakeExpanderFuncFromFunc(sv)) + err := RegisterExpander(name, makeExpanderFuncFromFunc(d1Val)) if err != nil { return locale.WrapError( err, "project_expand_register_expander_func", @@ -74,8 +78,9 @@ func RegisterStruct(val interface{}) error { ) } + // Format simple value. This is a leaf: Vars.[Simple] default: - topLevelLookup[strings.ToLower(f.Name)] = fmt.Sprintf("%v", sv.Interface()) + topLevelLookup[strings.ToLower(f.Name)] = fmt.Sprintf("%v", d1Val.Interface()) } } From 685a93e7d5ee5fe65fc8de695ab245884972bd60 Mon Sep 17 00:00:00 2001 From: daved Date: Wed, 19 Apr 2023 16:56:04 -0700 Subject: [PATCH 24/31] Improve more var names and comments for projectfile vars handling --- internal/constraints/constraints.go | 4 ++-- pkg/project/expander.go | 24 ++++++++++++------------ pkg/projectfile/vars/vars.go | 13 +++++++------ 3 files changed, 21 insertions(+), 20 deletions(-) diff --git a/internal/constraints/constraints.go b/internal/constraints/constraints.go index 9d75871f9f..47d379c01e 100644 --- a/internal/constraints/constraints.go +++ b/internal/constraints/constraints.go @@ -53,14 +53,14 @@ func NewPrimeConditional(structure interface{}) *Conditional { fields := reflect.VisibleFields(v.Type()) - // Work at depth 1: Vars.[OS].Version.Name + // Work at depth 1: Vars.[Struct].Struct.Simple for _, f := range fields { d1Val := v.FieldByIndex(f.Index) if d1Val.Kind() == reflect.Ptr { d1Val = d1Val.Elem() } - // Only nodes at depth 1 need to be registered since the genertic type + // Only nodes at depth 1 need to be registered since the generic type // handling within the templating package will do the rest. If function // registration is needed at greater depths, this will need to be // reworked (and may not be possible without expansive refactoring). diff --git a/pkg/project/expander.go b/pkg/project/expander.go index b453fb70c3..67b33a5a4d 100644 --- a/pkg/project/expander.go +++ b/pkg/project/expander.go @@ -242,9 +242,9 @@ func newEntry(tag string, val reflect.Value) entry { } } -func makeEntryMap(val reflect.Value) map[string]entry { +func makeEntryMap(structure reflect.Value) map[string]entry { m := make(map[string]entry) - fields := reflect.VisibleFields(val.Type()) + fields := reflect.VisibleFields(structure.Type()) // Work at depth 3: Vars.Struct.Struct.[Simple] for _, f := range fields { @@ -252,8 +252,8 @@ func makeEntryMap(val reflect.Value) map[string]entry { continue } - subValue := val.FieldByIndex(f.Index) - m[strings.ToLower(f.Name)] = newEntry(f.Tag.Get(expandStructTag), subValue) + d3Val := structure.FieldByIndex(f.Index) + m[strings.ToLower(f.Name)] = newEntry(f.Tag.Get(expandStructTag), d3Val) } return m @@ -312,33 +312,33 @@ func makeExpanderFuncFromMap(m map[string]map[string]entry) ExpanderFunc { } } -func makeExpanderFuncFromFunc(val reflect.Value) ExpanderFunc { +func makeExpanderFuncFromFunc(fn reflect.Value) ExpanderFunc { return func(v, name, meta string, isFunc bool, ctx *Expansion) (string, error) { // Call function; It should not require any arguments. // Work at depth 1: Vars.[FuncReturnsSomething]... - vals := val.Call(nil) + vals := fn.Call(nil) if len(vals) > 1 { if !vals[1].IsNil() { return "", vals[1].Interface().(error) } } - val := vals[0] + d1Val := vals[0] // deref if needed - if val.Kind() == reflect.Ptr { - val = val.Elem() + if d1Val.Kind() == reflect.Ptr { + d1Val = d1Val.Elem() } - switch val.Kind() { + switch d1Val.Kind() { // Convert type (to map-map) to express advanced control like tag handling. case reflect.Struct: - m := makeEntryMapMap(val) + m := makeEntryMapMap(d1Val) expandFromMap := makeExpanderFuncFromMap(m) return expandFromMap(v, name, meta, isFunc, ctx) // Format simple value. This is a leaf: Vars.[FuncReturnsSimple] default: - return fmt.Sprintf("%v", val.Interface()), nil + return fmt.Sprintf("%v", d1Val.Interface()), nil } } } diff --git a/pkg/projectfile/vars/vars.go b/pkg/projectfile/vars/vars.go index 70f1f2f4d8..cb81333c22 100644 --- a/pkg/projectfile/vars/vars.go +++ b/pkg/projectfile/vars/vars.go @@ -4,13 +4,14 @@ // The structure should not grow beyond a depth of 3. That is, .OS.Version.Major // is fine, but .OS.Version.Major.Something is not. External (leaf) nodes must // be able to resolve to a string using `fmt.Sprintf("%v")`. Keep in mind that -// the Vars type itself is depth 0, so it does not count for depth, and should -// not be represented in the activestate.yaml content. +// the Vars type itself is depth 0, so it does not count for depth, and is +// represented in the activestate.yaml as either the first `.` or the `$`. // -// Nodes may be a function, but the return value must also resolve to a string -// using `fmt.Sprintf("%v")`. A second return value of `error` is allowed. For -// variable expansion, a non-function node may be tagged as a function (asFunc) -// so that it must be called using parenthesis (`$project.name()`). +// Nodes at depth 1 may be a function, but the return value must also resolve +// to a string using `fmt.Sprintf("%v")`. A second return value of `error` is +// allowed. For variable expansion, a non-function node may be tagged as a +// function (asFunc) so that it must be called using parenthesis +// (`$project.name()`). // // Path nodes should be tagged (isPath) so that bashification of the path is // applied when necessary. From 09269dd2625a1bfaff59d529073734dd648cdc21 Mon Sep 17 00:00:00 2001 From: daved Date: Sun, 4 Jun 2023 20:28:51 -0700 Subject: [PATCH 25/31] Trial direct registration of top level expanders --- pkg/project/registry.go | 52 +++++++++++++++++++++++++++++++++++++++-- pkg/projget/projget.go | 4 +++- 2 files changed, 53 insertions(+), 3 deletions(-) diff --git a/pkg/project/registry.go b/pkg/project/registry.go index 86afefbb04..c4402d9bc2 100644 --- a/pkg/project/registry.go +++ b/pkg/project/registry.go @@ -31,7 +31,55 @@ func init() { } } -func RegisterStruct(val interface{}) error { +func RegisterTopLevelStruct(name string, val interface{}) error { + v := reflect.ValueOf(val) + if v.Kind() == reflect.Ptr { + v = v.Elem() + } + + m := makeEntryMapMap(v) + name = strings.ToLower(name) + err := RegisterExpander(name, makeExpanderFuncFromMap(m)) + if err != nil { + return locale.WrapError( + err, "project_expand_register_expander_map", + "Cannot register expander (map)", + ) + } + + return nil +} + +func RegisterTopLevelFunc(name string, val interface{}) error { + v := reflect.ValueOf(val) + if v.Kind() == reflect.Ptr { + v = v.Elem() + } + + name = strings.ToLower(name) + err := RegisterExpander(name, makeExpanderFuncFromFunc(v)) + if err != nil { + return locale.WrapError( + err, "project_expand_register_expander_func", + "Cannot register expander (func)", + ) + } + + return nil +} + +func RegisterTopLevelStringer(name string, val interface{}) error { + v := reflect.ValueOf(val) + if v.Kind() == reflect.Ptr { + v = v.Elem() + } + + topLevelLookup[strings.ToLower(name)] = fmt.Sprintf("%v", v.Interface()) + + return nil +} + +/*func RegisterStruct(val interface{}) error { v := reflect.ValueOf(val) // deref if needed if v.Kind() == reflect.Ptr { @@ -85,7 +133,7 @@ func RegisterStruct(val interface{}) error { } return nil -} +}*/ // RegisterExpander registers an Expander Func for some given handler value. The handler value // must not effectively be a blank string and the Func must be defined. It is definitely possible diff --git a/pkg/projget/projget.go b/pkg/projget/projget.go index eb56bc574e..94ff9441f0 100644 --- a/pkg/projget/projget.go +++ b/pkg/projget/projget.go @@ -44,7 +44,9 @@ func newProject(out output.Outputer, auth *authentication.Auth, shell string, pj projVars := vars.New(auth, vars.NewProject(pj), shell) conditional := constraints.NewPrimeConditional(projVars) project.RegisterConditional(conditional) - _ = project.RegisterStruct(projVars) + _ = project.RegisterTopLevelStruct("project", projVars.Project) + _ = project.RegisterTopLevelFunc("mixin", projVars.Mixin) + _ = project.RegisterTopLevelStringer("shell", projVars.Shell) } pj.SetUpdateCallback(registerProjectVars) From 0c8f91ceb1bcd0fd8cff4c4896feb7814ceb5d5e Mon Sep 17 00:00:00 2001 From: daved Date: Mon, 5 Jun 2023 19:37:14 -0700 Subject: [PATCH 26/31] Setup lazy expanderfunc to avoid projectfile set callback --- pkg/project/expander.go | 16 ++++++++++ pkg/project/project.go | 7 ----- pkg/project/registry.go | 56 +++------------------------------- pkg/projectfile/projectfile.go | 21 ------------- pkg/projectfile/vars/vars.go | 50 ++++++++++++------------------ pkg/projget/projget.go | 15 +++------ 6 files changed, 43 insertions(+), 122 deletions(-) diff --git a/pkg/project/expander.go b/pkg/project/expander.go index 67b33a5a4d..7fa8937b48 100644 --- a/pkg/project/expander.go +++ b/pkg/project/expander.go @@ -291,6 +291,22 @@ func makeEntryMapMap(structure reflect.Value) map[string]map[string]entry { return m } +func makeLazyExpanderFuncFromPtrToStruct(val reflect.Value) ExpanderFunc { + return func(v, name, meta string, isFunc bool, ctx *Expansion) (string, error) { + iface := val.Interface() + if u, ok := iface.(interface{ Update(*Project) }); ok { + u.Update(ctx.Project) + } + + if val.Kind() == reflect.Ptr { + val = val.Elem() + } + fn := makeExpanderFuncFromMap(makeEntryMapMap(val)) + + return fn(v, name, meta, isFunc, ctx) + } +} + func makeExpanderFuncFromMap(m map[string]map[string]entry) ExpanderFunc { return func(v, name, meta string, isFunc bool, ctx *Expansion) (string, error) { if isFunc && meta == "()" { diff --git a/pkg/project/project.go b/pkg/project/project.go index ffd8c988dd..4eb646478e 100644 --- a/pkg/project/project.go +++ b/pkg/project/project.go @@ -58,13 +58,6 @@ func (p *Project) SetCommit(commitID string) error { return p.Source().SetCommit(commitID, p.IsHeadless()) } -func (p *Project) SetUpdateCallback(fn func()) { - if p.projectfile == nil { - return - } - p.projectfile.SetUpdateCallback(fn) -} - // Constants returns a reference to projectfile.Constants func (p *Project) Constants() []*Constant { constrained, err := constraints.FilterUnconstrained(pConditional, p.projectfile.Constants.AsConstrainedEntities()) diff --git a/pkg/project/registry.go b/pkg/project/registry.go index c4402d9bc2..b4e6caba3a 100644 --- a/pkg/project/registry.go +++ b/pkg/project/registry.go @@ -31,55 +31,7 @@ func init() { } } -func RegisterTopLevelStruct(name string, val interface{}) error { - v := reflect.ValueOf(val) - if v.Kind() == reflect.Ptr { - v = v.Elem() - } - - m := makeEntryMapMap(v) - name = strings.ToLower(name) - err := RegisterExpander(name, makeExpanderFuncFromMap(m)) - if err != nil { - return locale.WrapError( - err, "project_expand_register_expander_map", - "Cannot register expander (map)", - ) - } - - return nil -} - -func RegisterTopLevelFunc(name string, val interface{}) error { - v := reflect.ValueOf(val) - if v.Kind() == reflect.Ptr { - v = v.Elem() - } - - name = strings.ToLower(name) - err := RegisterExpander(name, makeExpanderFuncFromFunc(v)) - if err != nil { - return locale.WrapError( - err, "project_expand_register_expander_func", - "Cannot register expander (func)", - ) - } - - return nil -} - -func RegisterTopLevelStringer(name string, val interface{}) error { - v := reflect.ValueOf(val) - if v.Kind() == reflect.Ptr { - v = v.Elem() - } - - topLevelLookup[strings.ToLower(name)] = fmt.Sprintf("%v", v.Interface()) - - return nil -} - -/*func RegisterStruct(val interface{}) error { +func RegisterStruct(val interface{}) error { v := reflect.ValueOf(val) // deref if needed if v.Kind() == reflect.Ptr { @@ -95,6 +47,7 @@ func RegisterTopLevelStringer(name string, val interface{}) error { } d1Val := v.FieldByIndex(f.Index) + d1ValOrig := d1Val if d1Val.Kind() == reflect.Ptr { d1Val = d1Val.Elem() } @@ -105,9 +58,8 @@ func RegisterTopLevelStringer(name string, val interface{}) error { switch d1Val.Type().Kind() { // Convert type (to map-map) to express advanced control like tag handling. case reflect.Struct: - m := makeEntryMapMap(d1Val) name := strings.ToLower(f.Name) - err := RegisterExpander(name, makeExpanderFuncFromMap(m)) + err := RegisterExpander(name, makeLazyExpanderFuncFromPtrToStruct(d1ValOrig)) if err != nil { return locale.WrapError( err, "project_expand_register_expander_map", @@ -133,7 +85,7 @@ func RegisterTopLevelStringer(name string, val interface{}) error { } return nil -}*/ +} // RegisterExpander registers an Expander Func for some given handler value. The handler value // must not effectively be a blank string and the Func must be defined. It is definitely possible diff --git a/pkg/projectfile/projectfile.go b/pkg/projectfile/projectfile.go index a39f2affb0..9f56ca6e45 100644 --- a/pkg/projectfile/projectfile.go +++ b/pkg/projectfile/projectfile.go @@ -104,8 +104,6 @@ type Project struct { parsedURL projectURL // parsed url data parsedBranch string parsedVersion string - - updateCallback func() } // Build covers the build map, which can go under languages or packages @@ -684,21 +682,8 @@ func (p *Project) save(cfg ConfigGetter, path string) error { return nil } -func (p *Project) runUpdateCallback() { - if p.updateCallback == nil { - return - } - p.updateCallback() -} - -func (p *Project) SetUpdateCallback(fn func()) { - p.updateCallback = fn -} - // SetNamespace updates the namespace in the project file func (p *Project) SetNamespace(owner, project string) error { - defer p.runUpdateCallback() - pf := NewProjectField() if err := pf.LoadProject(p.Project); err != nil { return errs.Wrap(err, "Could not load activestate.yaml") @@ -720,8 +705,6 @@ func (p *Project) SetNamespace(owner, project string) error { // in-place so that line order is preserved. // If headless is true, the project is defined by a commit-id only func (p *Project) SetCommit(commitID string, headless bool) error { - defer p.runUpdateCallback() - pf := NewProjectField() if err := pf.LoadProject(p.Project); err != nil { return errs.Wrap(err, "Could not load activestate.yaml") @@ -739,8 +722,6 @@ func (p *Project) SetCommit(commitID string, headless bool) error { // SetBranch sets the branch within the current project file. This is done // in-place so that line order is preserved. func (p *Project) SetBranch(branch string) error { - defer p.runUpdateCallback() - pf := NewProjectField() if err := pf.LoadProject(p.Project); err != nil { @@ -762,8 +743,6 @@ func (p *Project) SetBranch(branch string) error { // SetPath sets the path of the project file and should generally only be used by tests func (p *Project) SetPath(path string) { - defer p.runUpdateCallback() - p.path = path } diff --git a/pkg/projectfile/vars/vars.go b/pkg/projectfile/vars/vars.go index cb81333c22..7d7c86aa91 100644 --- a/pkg/projectfile/vars/vars.go +++ b/pkg/projectfile/vars/vars.go @@ -21,21 +21,11 @@ import ( "path/filepath" "github.com/ActiveState/cli/internal/multilog" - "github.com/ActiveState/cli/internal/rtutils/p" "github.com/ActiveState/cli/pkg/platform/authentication" + "github.com/ActiveState/cli/pkg/project" "github.com/ActiveState/cli/pkg/sysinfo" ) -type projectDataProvider interface { - Owner() string - Name() string - NamespaceString() string - CommitID() string - BranchName() string - Path() string - URL() string -} - type Project struct { Namespace string `expand:",asFunc"` Name string `expand:",asFunc"` @@ -49,26 +39,24 @@ type Project struct { NamespacePrefix string } -func NewProject(pj projectDataProvider) *Project { - var ( - project = &Project{} - ) - if !p.IsNil(pj) { - project.Namespace = pj.NamespaceString() - project.Name = pj.Name() - project.Owner = pj.Owner() - project.Url = pj.URL() - project.Commit = pj.CommitID() - project.Branch = pj.BranchName() - project.Path = pj.Path() - if project.Path != "" { - project.Path = filepath.Dir(project.Path) - } +func NewProject(pj *project.Project) *Project { + p := &Project{} + p.Update(pj) + return p +} - project.NamespacePrefix = pj.NamespaceString() +func (p *Project) Update(pj *project.Project) { + p.Namespace = pj.NamespaceString() + p.Name = pj.Name() + p.Owner = pj.Owner() + p.Url = pj.URL() + p.Commit = pj.CommitID() + p.Branch = pj.BranchName() + p.Path = pj.Path() + if p.Path != "" { + p.Path = filepath.Dir(p.Path) } - - return project + p.NamespacePrefix = pj.NamespaceString() } type OSVersion struct { @@ -128,14 +116,14 @@ type Vars struct { Mixin func() *Mixin } -func New(auth *authentication.Auth, project *Project, subshellName string) *Vars { +func New(auth *authentication.Auth, pj *project.Project, subshellName string) *Vars { osVersion, err := sysinfo.OSVersion() if err != nil { multilog.Error("Could not detect OSVersion: %v", err) } return &Vars{ - Project: project, + Project: NewProject(pj), OS: NewOS(osVersion), Shell: subshellName, Mixin: func() *Mixin { return NewMixin(auth) }, diff --git a/pkg/projget/projget.go b/pkg/projget/projget.go index 94ff9441f0..022a7a8a7a 100644 --- a/pkg/projget/projget.go +++ b/pkg/projget/projget.go @@ -40,17 +40,10 @@ func newProject(out output.Outputer, auth *authentication.Auth, shell string, pj return nil, err } - registerProjectVars := func() { - projVars := vars.New(auth, vars.NewProject(pj), shell) - conditional := constraints.NewPrimeConditional(projVars) - project.RegisterConditional(conditional) - _ = project.RegisterTopLevelStruct("project", projVars.Project) - _ = project.RegisterTopLevelFunc("mixin", projVars.Mixin) - _ = project.RegisterTopLevelStringer("shell", projVars.Shell) - } - - pj.SetUpdateCallback(registerProjectVars) - registerProjectVars() + projVars := vars.New(auth, pj, shell) + conditional := constraints.NewPrimeConditional(projVars) + project.RegisterConditional(conditional) + _ = project.RegisterStruct(projVars) return pj, nil } From ece577f3d8b2d2533ee801d43754b987ee500014 Mon Sep 17 00:00:00 2001 From: daved Date: Tue, 6 Jun 2023 16:48:07 -0700 Subject: [PATCH 27/31] Move vars into project pkg, drop projget --- cmd/state/main.go | 3 +- pkg/project/expander_test.go | 3 +- pkg/project/project.go | 42 ++++++++++++++++++ pkg/project/project_test.go | 3 +- pkg/{projectfile/vars => project}/vars.go | 36 +++++++-------- pkg/projget/projget.go | 53 ----------------------- scripts/ci/parallelize/parallelize.go | 2 +- 7 files changed, 64 insertions(+), 78 deletions(-) rename pkg/{projectfile/vars => project}/vars.go (87%) delete mode 100644 pkg/projget/projget.go diff --git a/cmd/state/main.go b/cmd/state/main.go index 584a7c6abc..a0245fc66f 100644 --- a/cmd/state/main.go +++ b/cmd/state/main.go @@ -38,7 +38,6 @@ import ( "github.com/ActiveState/cli/pkg/platform/authentication" "github.com/ActiveState/cli/pkg/platform/model" "github.com/ActiveState/cli/pkg/project" - "github.com/ActiveState/cli/pkg/projget" ) func main() { @@ -172,7 +171,7 @@ func run(args []string, isInteractive bool, cfg *config.Instance, out output.Out sshell := subshell.New(cfg) - pj, err := projget.NewProject(out, auth, sshell.Shell()) + pj, err := project.NewWithVars(out, auth, sshell.Shell()) if err != nil { return err } diff --git a/pkg/project/expander_test.go b/pkg/project/expander_test.go index 03f7ce2027..05d4d1d880 100644 --- a/pkg/project/expander_test.go +++ b/pkg/project/expander_test.go @@ -18,7 +18,6 @@ import ( "github.com/ActiveState/cli/pkg/project" "github.com/ActiveState/cli/pkg/projectfile" - "github.com/ActiveState/cli/pkg/projget" ) func loadProject(t *testing.T) *project.Project { @@ -62,7 +61,7 @@ scripts: pjFile.Persist() - pj, err := projget.NewProjectForTest(pjFile) + pj, err := project.NewWithVarsForTest(pjFile) require.NoError(t, err) return pj diff --git a/pkg/project/project.go b/pkg/project/project.go index 4eb646478e..24616121a1 100644 --- a/pkg/project/project.go +++ b/pkg/project/project.go @@ -274,6 +274,48 @@ func NewLegacy(p *projectfile.Project) (*Project, error) { return New(p, output.Get()) } +func NewWithVars(out output.Outputer, auth *authentication.Auth, shell string) (*Project, error) { + var pjf *projectfile.Project + + // Retrieve project file + pjPath, err := projectfile.GetProjectFilePath() + if err != nil && errs.Matches(err, &projectfile.ErrorNoProjectFromEnv{}) { + // Fail if we are meant to inherit the projectfile from the environment, but the file doesn't exist + return nil, err + } + + if pjPath != "" { + pjf, err = projectfile.FromPath(pjPath) + if err != nil { + return nil, err + } + } + + return newWithVars(out, auth, shell, pjf) +} + +func newWithVars(out output.Outputer, auth *authentication.Auth, shell string, pjf *projectfile.Project) (*Project, error) { + if pjf == nil { + return nil, nil + } + + pj, err := New(pjf, out) + if err != nil { + return nil, err + } + + projVars := NewVars(auth, pj, shell) + conditional := constraints.NewPrimeConditional(projVars) + RegisterConditional(conditional) + _ = RegisterStruct(projVars) + + return pj, nil +} + +func NewWithVarsForTest(pjf *projectfile.Project) (*Project, error) { + return newWithVars(output.Get(), nil, "not set", pjf) +} + // Parse will parse the given projectfile and instantiate a Project struct with it func Parse(fpath string) (*Project, error) { pjfile, err := projectfile.Parse(fpath) diff --git a/pkg/project/project_test.go b/pkg/project/project_test.go index 85f7d3ffb8..403c3bf518 100644 --- a/pkg/project/project_test.go +++ b/pkg/project/project_test.go @@ -15,7 +15,6 @@ import ( "github.com/ActiveState/cli/pkg/platform/authentication" "github.com/ActiveState/cli/pkg/project" "github.com/ActiveState/cli/pkg/projectfile" - "github.com/ActiveState/cli/pkg/projget" "github.com/stretchr/testify/suite" ) @@ -45,7 +44,7 @@ func (suite *ProjectTestSuite) BeforeTest(suiteName, testName string) { cfg, err := config.New() suite.Require().NoError(err) - suite.project, err = projget.NewProject(output.Get(), nil, subshell.New(cfg).Shell()) + suite.project, err = project.NewWithVars(output.Get(), nil, subshell.New(cfg).Shell()) suite.Require().Nil(err, "Should retrieve project without issue.") } diff --git a/pkg/projectfile/vars/vars.go b/pkg/project/vars.go similarity index 87% rename from pkg/projectfile/vars/vars.go rename to pkg/project/vars.go index 7d7c86aa91..e31048e85f 100644 --- a/pkg/projectfile/vars/vars.go +++ b/pkg/project/vars.go @@ -1,4 +1,14 @@ -// Package vars provides a single type expressing the data accessible by the +package project + +import ( + "path/filepath" + + "github.com/ActiveState/cli/internal/multilog" + "github.com/ActiveState/cli/pkg/platform/authentication" + "github.com/ActiveState/cli/pkg/sysinfo" +) + +// vars provides a single type expressing the data accessible by the // activestate.yaml for conditionals and variable expansions. // // The structure should not grow beyond a depth of 3. That is, .OS.Version.Major @@ -15,18 +25,8 @@ // // Path nodes should be tagged (isPath) so that bashification of the path is // applied when necessary. -package vars - -import ( - "path/filepath" - - "github.com/ActiveState/cli/internal/multilog" - "github.com/ActiveState/cli/pkg/platform/authentication" - "github.com/ActiveState/cli/pkg/project" - "github.com/ActiveState/cli/pkg/sysinfo" -) -type Project struct { +type Projvars struct { Namespace string `expand:",asFunc"` Name string `expand:",asFunc"` Owner string `expand:",asFunc"` @@ -39,13 +39,13 @@ type Project struct { NamespacePrefix string } -func NewProject(pj *project.Project) *Project { - p := &Project{} +func NewProjvars(pj *Project) *Projvars { + p := &Projvars{} p.Update(pj) return p } -func (p *Project) Update(pj *project.Project) { +func (p *Projvars) Update(pj *Project) { p.Namespace = pj.NamespaceString() p.Name = pj.Name() p.Owner = pj.Owner() @@ -110,20 +110,20 @@ func NewMixin(auth *authentication.Auth) *Mixin { } type Vars struct { - Project *Project + Project *Projvars OS *OS Shell string Mixin func() *Mixin } -func New(auth *authentication.Auth, pj *project.Project, subshellName string) *Vars { +func NewVars(auth *authentication.Auth, pj *Project, subshellName string) *Vars { osVersion, err := sysinfo.OSVersion() if err != nil { multilog.Error("Could not detect OSVersion: %v", err) } return &Vars{ - Project: NewProject(pj), + Project: NewProjvars(pj), OS: NewOS(osVersion), Shell: subshellName, Mixin: func() *Mixin { return NewMixin(auth) }, diff --git a/pkg/projget/projget.go b/pkg/projget/projget.go deleted file mode 100644 index 022a7a8a7a..0000000000 --- a/pkg/projget/projget.go +++ /dev/null @@ -1,53 +0,0 @@ -package projget - -import ( - "github.com/ActiveState/cli/internal/constraints" - "github.com/ActiveState/cli/internal/errs" - "github.com/ActiveState/cli/internal/output" - "github.com/ActiveState/cli/pkg/platform/authentication" - "github.com/ActiveState/cli/pkg/project" - "github.com/ActiveState/cli/pkg/projectfile" - "github.com/ActiveState/cli/pkg/projectfile/vars" -) - -func NewProject(out output.Outputer, auth *authentication.Auth, shell string) (*project.Project, error) { - var pjf *projectfile.Project - - // Retrieve project file - pjPath, err := projectfile.GetProjectFilePath() - if err != nil && errs.Matches(err, &projectfile.ErrorNoProjectFromEnv{}) { - // Fail if we are meant to inherit the projectfile from the environment, but the file doesn't exist - return nil, err - } - - if pjPath != "" { - pjf, err = projectfile.FromPath(pjPath) - if err != nil { - return nil, err - } - } - - return newProject(out, auth, shell, pjf) -} - -func newProject(out output.Outputer, auth *authentication.Auth, shell string, pjf *projectfile.Project) (*project.Project, error) { - if pjf == nil { - return nil, nil - } - - pj, err := project.New(pjf, out) - if err != nil { - return nil, err - } - - projVars := vars.New(auth, pj, shell) - conditional := constraints.NewPrimeConditional(projVars) - project.RegisterConditional(conditional) - _ = project.RegisterStruct(projVars) - - return pj, nil -} - -func NewProjectForTest(pjf *projectfile.Project) (*project.Project, error) { - return newProject(output.Get(), nil, "noshell", pjf) -} diff --git a/scripts/ci/parallelize/parallelize.go b/scripts/ci/parallelize/parallelize.go index c7b6a2e26a..2f475f9891 100644 --- a/scripts/ci/parallelize/parallelize.go +++ b/scripts/ci/parallelize/parallelize.go @@ -105,7 +105,7 @@ func runJob(job Job) { return } - projVars := vars.New(nil, vars.NewProject(pj), "noshell") + projVars := vars.NewVars(nil, vars.NewProject(pj), "noshell") cond := constraints.NewPrimeConditional(projVars) run, err := cond.Eval(job.If) if err != nil { From 1b955de7da8482e7109d7610864096147c88fe1e Mon Sep 17 00:00:00 2001 From: daved Date: Tue, 6 Jun 2023 17:27:41 -0700 Subject: [PATCH 28/31] Move projectfile setpath back to old location --- pkg/projectfile/projectfile.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/pkg/projectfile/projectfile.go b/pkg/projectfile/projectfile.go index 9f56ca6e45..977835feb9 100644 --- a/pkg/projectfile/projectfile.go +++ b/pkg/projectfile/projectfile.go @@ -558,6 +558,11 @@ func (p *Project) Path() string { return p.path } +// SetPath sets the path of the project file and should generally only be used by tests +func (p *Project) SetPath(path string) { + p.path = path +} + // VersionBranch returns the branch as it was interpreted from the lock func (p *Project) VersionBranch() string { return p.parsedBranch @@ -741,11 +746,6 @@ func (p *Project) SetBranch(branch string) error { return nil } -// SetPath sets the path of the project file and should generally only be used by tests -func (p *Project) SetPath(path string) { - p.path = path -} - // GetProjectFilePath returns the path to the project activestate.yaml func GetProjectFilePath() (string, error) { defer profile.Measure("GetProjectFilePath", time.Now()) From 78093135863c8d8f978f6a0c12a92fa101f49ea6 Mon Sep 17 00:00:00 2001 From: daved Date: Tue, 6 Jun 2023 17:32:01 -0700 Subject: [PATCH 29/31] Fix project vars usage in parallelize script --- scripts/ci/parallelize/parallelize.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/scripts/ci/parallelize/parallelize.go b/scripts/ci/parallelize/parallelize.go index 2f475f9891..cf21bf17b4 100644 --- a/scripts/ci/parallelize/parallelize.go +++ b/scripts/ci/parallelize/parallelize.go @@ -17,7 +17,6 @@ import ( "github.com/ActiveState/cli/internal/installation/storage" "github.com/ActiveState/cli/internal/osutils" "github.com/ActiveState/cli/pkg/project" - "github.com/ActiveState/cli/pkg/projectfile/vars" "github.com/gammazero/workerpool" ) @@ -105,7 +104,7 @@ func runJob(job Job) { return } - projVars := vars.NewVars(nil, vars.NewProject(pj), "noshell") + projVars := project.NewVars(nil, pj, "noshell") cond := constraints.NewPrimeConditional(projVars) run, err := cond.Eval(job.If) if err != nil { From cfd2ae6b0ca690d790c7f3c163c64d4a6ceab2eb Mon Sep 17 00:00:00 2001 From: daved Date: Fri, 30 Jun 2023 11:53:44 -0700 Subject: [PATCH 30/31] Ensure struct expanderfunc context is not mutated --- pkg/project/expander.go | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/pkg/project/expander.go b/pkg/project/expander.go index 7fa8937b48..ec9712e9ee 100644 --- a/pkg/project/expander.go +++ b/pkg/project/expander.go @@ -292,16 +292,18 @@ func makeEntryMapMap(structure reflect.Value) map[string]map[string]entry { } func makeLazyExpanderFuncFromPtrToStruct(val reflect.Value) ExpanderFunc { + // This function's args maintain scope across multiple calls; Do not overwrite the args. return func(v, name, meta string, isFunc bool, ctx *Expansion) (string, error) { iface := val.Interface() if u, ok := iface.(interface{ Update(*Project) }); ok { u.Update(ctx.Project) } - if val.Kind() == reflect.Ptr { - val = val.Elem() + valDeref := val + if valDeref.Kind() == reflect.Ptr { + valDeref = valDeref.Elem() } - fn := makeExpanderFuncFromMap(makeEntryMapMap(val)) + fn := makeExpanderFuncFromMap(makeEntryMapMap(valDeref)) return fn(v, name, meta, isFunc, ctx) } From a8891b57f3e283f65adcec135847b2d273884242 Mon Sep 17 00:00:00 2001 From: daved Date: Fri, 30 Jun 2023 14:42:26 -0700 Subject: [PATCH 31/31] Return unhandled error in project --- pkg/project/project.go | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/pkg/project/project.go b/pkg/project/project.go index 24616121a1..bc5ed2d4df 100644 --- a/pkg/project/project.go +++ b/pkg/project/project.go @@ -28,8 +28,10 @@ import ( // Build covers the build structure type Build map[string]string -var pConditional *constraints.Conditional -var normalizeRx *regexp.Regexp +var ( + pConditional *constraints.Conditional + normalizeRx *regexp.Regexp +) func init() { var err error @@ -307,7 +309,9 @@ func newWithVars(out output.Outputer, auth *authentication.Auth, shell string, p projVars := NewVars(auth, pj, shell) conditional := constraints.NewPrimeConditional(projVars) RegisterConditional(conditional) - _ = RegisterStruct(projVars) + if err := RegisterStruct(projVars); err != nil { + return nil, err + } return pj, nil }