Skip to content

Commit 5f04732

Browse files
authored
Add Analyzer framework (#99)
## Summary Adds an `Analyzer` to the `plansdk`. Analyzers can be initialized on a root directory, and provide lots of utility functions to then understand (or analyze) the source code present in that root directory. Analyzer implements several functions that are really handy when implementing all sorts of planners. This PR just introduces the library; future PRs would start using the library in the implementation of planners. ## How was it tested? `go test -v ./...`
1 parent df0fc91 commit 5f04732

File tree

4 files changed

+88
-15
lines changed

4 files changed

+88
-15
lines changed

config.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ type Config struct {
2727
// ReadConfig reads a devbox config file.
2828
func ReadConfig(path string) (*Config, error) {
2929
cfg := &Config{}
30-
err := cuecfg.ReadFile(path, cfg)
30+
err := cuecfg.ParseFile(path, cfg)
3131
if err != nil {
3232
return nil, errors.WithStack(err)
3333
}

cuecfg/cuecfg.go

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -13,39 +13,39 @@ import (
1313

1414
// TODO: add support for .cue
1515

16-
func Marshal(value any, extension string) ([]byte, error) {
17-
err := cuego.Complete(value)
16+
func Marshal(valuePtr any, extension string) ([]byte, error) {
17+
err := cuego.Complete(valuePtr)
1818
if err != nil {
1919
return nil, errors.WithStack(err)
2020
}
2121

2222
switch extension {
2323
case ".json":
24-
return MarshalJSON(value)
24+
return MarshalJSON(valuePtr)
2525
case ".yml", ".yaml":
26-
return MarshalYaml(value)
26+
return MarshalYaml(valuePtr)
2727
case ".toml":
28-
return MarshalToml(value)
28+
return MarshalToml(valuePtr)
2929
}
3030
return nil, errors.Errorf("Unsupported file format '%s' for config file", extension)
3131
}
3232

33-
func Unmarshal(data []byte, extension string, value any) error {
33+
func Unmarshal(data []byte, extension string, valuePtr any) error {
3434
switch extension {
3535
case ".json":
36-
err := UnmarshalJSON(data, value)
36+
err := UnmarshalJSON(data, valuePtr)
3737
if err != nil {
3838
return errors.WithStack(err)
3939
}
4040
return nil
4141
case ".yml", ".yaml":
42-
err := UnmarshalYaml(data, value)
42+
err := UnmarshalYaml(data, valuePtr)
4343
if err != nil {
4444
return errors.WithStack(err)
4545
}
4646
return nil
4747
case ".toml":
48-
err := UnmarshalToml(data, value)
48+
err := UnmarshalToml(data, valuePtr)
4949
if err != nil {
5050
return errors.WithStack(err)
5151
}
@@ -54,28 +54,28 @@ func Unmarshal(data []byte, extension string, value any) error {
5454
return errors.Errorf("Unsupported file format '%s' for config file", extension)
5555
}
5656

57-
func InitFile(path string, value any) (bool, error) {
57+
func InitFile(path string, valuePtr any) (bool, error) {
5858
if _, err := os.Stat(path); err == nil {
5959
// File already exists, don't create a new one.
6060
// TODO: should we read and write again, in case the schema needs updating?
6161
return false, nil
6262
} else if errors.Is(err, os.ErrNotExist) {
6363
// File does not exist, create a new one:
64-
return true, WriteFile(path, value)
64+
return true, WriteFile(path, valuePtr)
6565
} else {
6666
// Error case:
6767
return false, errors.WithStack(err)
6868
}
6969

7070
}
7171

72-
func ReadFile(path string, value any) error {
72+
func ParseFile(path string, valuePtr any) error {
7373
data, err := os.ReadFile(path)
7474
if err != nil {
7575
return errors.WithStack(err)
7676
}
7777

78-
return Unmarshal(data, filepath.Ext(path), value)
78+
return Unmarshal(data, filepath.Ext(path), valuePtr)
7979
}
8080

8181
func WriteFile(path string, value any) error {

planner/languages/javascript/nodejs_planner.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,7 @@ func (p *Planner) startCommand(pkgManager string, project *nodeProject) string {
157157
func (p *Planner) nodeProject(srcDir string) *nodeProject {
158158
packageJSONPath := filepath.Join(srcDir, "package.json")
159159
project := &nodeProject{}
160-
_ = cuecfg.ReadFile(packageJSONPath, project)
160+
_ = cuecfg.ParseFile(packageJSONPath, project)
161161

162162
return project
163163
}

planner/plansdk/analyzer.go

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
// Copyright 2022 Jetpack Technologies Inc and contributors. All rights reserved.
2+
// Use of this source code is governed by the license in the LICENSE file.
3+
4+
package plansdk
5+
6+
import (
7+
"os"
8+
"path/filepath"
9+
10+
"github.com/bmatcuk/doublestar/v4"
11+
"go.jetpack.io/devbox/cuecfg"
12+
)
13+
14+
// Analyzers help understand the source code present in a given directory
15+
// Handy when implementing new Planners that need to analyze files in order
16+
// to determine what to do.
17+
type Analyzer struct {
18+
rootDir string
19+
}
20+
21+
func NewAnalyzer(rootDir string) (*Analyzer, error) {
22+
abs, err := filepath.Abs(rootDir)
23+
if err != nil {
24+
return nil, err
25+
}
26+
27+
return &Analyzer{
28+
rootDir: abs,
29+
}, nil
30+
}
31+
32+
// AbsPath resolves the given path and turns it into an absolute path relative
33+
// to the root directory of the analyzer. If the given path is already absolute
34+
// it leaves it as is.
35+
func (a *Analyzer) absPath(path string) string {
36+
if filepath.IsAbs(path) {
37+
return path
38+
}
39+
40+
return filepath.Join(a.rootDir, path)
41+
}
42+
43+
// GlobFiles returns all the files matching the given glob patterns.
44+
// Patterns can be relative to the analyzer's root directory. Glob patterns
45+
// support "double star" matches.
46+
func (a *Analyzer) GlobFiles(patterns ...string) []string {
47+
results := []string{}
48+
49+
for _, p := range patterns {
50+
pattern := a.absPath(p)
51+
matches, err := doublestar.FilepathGlob(pattern)
52+
if err != nil {
53+
continue
54+
}
55+
results = append(results, matches...)
56+
}
57+
return results
58+
}
59+
60+
func (a *Analyzer) FileExists(relPath string) bool {
61+
_, err := os.Stat(a.absPath(relPath))
62+
return err == nil
63+
}
64+
65+
func (a *Analyzer) HasAnyFile(patterns ...string) bool {
66+
matches := a.GlobFiles(patterns...)
67+
return len(matches) > 0
68+
}
69+
70+
func (a *Analyzer) ParseFile(relPath string, ptr any) error {
71+
abs := a.absPath(relPath)
72+
return cuecfg.ParseFile(abs, ptr)
73+
}

0 commit comments

Comments
 (0)