Skip to content

Commit c35f680

Browse files
committed
Initial implementation
1 parent 7b89199 commit c35f680

File tree

10 files changed

+1549
-0
lines changed

10 files changed

+1549
-0
lines changed

cmd/wapc/main.go

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package main
2+
3+
import (
4+
"github.com/alecthomas/kong"
5+
6+
"github.com/wapc/cli-go/pkg/commands"
7+
)
8+
9+
var cli struct {
10+
// Install installs a module into the module directory.
11+
Install commands.InstallCmd `cmd:"" help:"Install a module."`
12+
// Generate generates code driven by a configuration file.
13+
Generate commands.GenerateCmd `cmd:"" help:"Generate code from a configuration file."`
14+
// New creates a new project from a template.
15+
New commands.NewCmd `cmd:"" help:"Creates a new project from a template."`
16+
}
17+
18+
func main() {
19+
ctx := kong.Parse(&cli)
20+
// Call the Run() method of the selected parsed command.
21+
err := ctx.Run(&commands.Context{})
22+
ctx.FatalIfErrorf(err)
23+
}

go.mod

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
module github.com/wapc/cli-go
2+
3+
go 1.16
4+
5+
require (
6+
github.com/alecthomas/kong v0.2.16
7+
github.com/evanw/esbuild v0.9.6
8+
github.com/google/go-github/v33 v33.0.0
9+
github.com/kr/text v0.2.0 // indirect
10+
github.com/mitchellh/go-homedir v1.1.0
11+
github.com/pkg/errors v0.9.1 // indirect
12+
github.com/stretchr/testify v1.7.0 // indirect
13+
github.com/tcnksm/go-input v0.0.0-20180404061846-548a7d7a8ee8
14+
golang.org/x/crypto v0.0.0-20210317152858-513c2a44f670 // indirect
15+
golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4 // indirect
16+
golang.org/x/term v0.0.0-20210317153231-de623e64d2a6 // indirect
17+
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
18+
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b
19+
rogchap.com/v8go v0.5.1
20+
)

go.sum

+58
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
github.com/alecthomas/kong v0.2.16 h1:F232CiYSn54Tnl1sJGTeHmx4vJDNLVP2b9yCVMOQwHQ=
2+
github.com/alecthomas/kong v0.2.16/go.mod h1:kQOmtJgV+Lb4aj+I2LEn40cbtawdWJ9Y8QLq+lElKxE=
3+
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
4+
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
5+
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
6+
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
7+
github.com/evanw/esbuild v0.9.6 h1:w7NbUL9q5U1H7QGJDZQsiqWqSkMOZk/Wuu2QE3tw5aA=
8+
github.com/evanw/esbuild v0.9.6/go.mod h1:y2AFBAGVelPqPodpdtxWWqe6n2jYf5FrsJbligmRmuw=
9+
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
10+
github.com/google/go-github/v33 v33.0.0 h1:qAf9yP0qc54ufQxzwv+u9H0tiVOnPJxo0lI/JXqw3ZM=
11+
github.com/google/go-github/v33 v33.0.0/go.mod h1:GMdDnVZY/2TsWgp/lkYnpSAh6TrzhANBBwm6k6TTEXg=
12+
github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk=
13+
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
14+
github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
15+
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
16+
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
17+
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
18+
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
19+
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
20+
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
21+
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
22+
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
23+
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
24+
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
25+
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
26+
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
27+
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
28+
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
29+
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
30+
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
31+
github.com/tcnksm/go-input v0.0.0-20180404061846-548a7d7a8ee8 h1:RB0v+/pc8oMzPsN97aZYEwNuJ6ouRJ2uhjxemJ9zvrY=
32+
github.com/tcnksm/go-input v0.0.0-20180404061846-548a7d7a8ee8/go.mod h1:IlWNj9v/13q7xFbaK4mbyzMNwrZLaWSHx/aibKIZuIg=
33+
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
34+
golang.org/x/crypto v0.0.0-20210317152858-513c2a44f670 h1:gzMM0EjIYiRmJI3+jBdFuoynZlpxa2JQZsolKu09BXo=
35+
golang.org/x/crypto v0.0.0-20210317152858-513c2a44f670/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
36+
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
37+
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
38+
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
39+
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
40+
golang.org/x/sys v0.0.0-20200501145240-bc7a7d42d5c3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
41+
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
42+
golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4 h1:EZ2mChiOa8udjfp6rRmswTbtZN/QzUQp4ptM4rnjHvc=
43+
golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
44+
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
45+
golang.org/x/term v0.0.0-20210317153231-de623e64d2a6 h1:EC6+IGYTjPpRfv9a2b/6Puw0W+hLtAhkV1tPsXhutqs=
46+
golang.org/x/term v0.0.0-20210317153231-de623e64d2a6/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
47+
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
48+
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
49+
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
50+
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
51+
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
52+
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
53+
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
54+
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
55+
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
56+
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
57+
rogchap.com/v8go v0.5.1 h1:yx7uQC2ezAHthCHLHl4NuH3eprVLBtNl6Utm/ELjZ+w=
58+
rogchap.com/v8go v0.5.1/go.mod h1:IitZnaOtWSJadY/7qinKHIEHpxsilMWyLQ+Efdo4n4I=

pkg/commands/generate.go

+222
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,222 @@
1+
package commands
2+
3+
import (
4+
_ "embed"
5+
"errors"
6+
"fmt"
7+
"os"
8+
"os/exec"
9+
"path/filepath"
10+
"strings"
11+
"sync"
12+
13+
"github.com/evanw/esbuild/pkg/api"
14+
"gopkg.in/yaml.v3"
15+
16+
"github.com/wapc/cli-go/pkg/js"
17+
)
18+
19+
type Context struct{}
20+
21+
type GenerateCmd struct {
22+
Config string `arg:"" help:"The code generation configuration file" type:"existingfile"`
23+
24+
prettier *js.JS
25+
once sync.Once
26+
}
27+
28+
type Config struct {
29+
Schema string `json:"schema" yaml:"schema"`
30+
Generates map[string]Target `json:"generates" yaml:"generates"`
31+
}
32+
33+
type Target struct {
34+
Module string `json:"module" yaml:"module"`
35+
VisitorClass string `json:"visitorClass" yaml:"visitorClass"`
36+
IfNotExists bool `json:"ifNotExists,omitempty" yaml:"ifNotExists,omitempty"`
37+
Config map[string]interface{} `json:"config,omitempty" yaml:"config,omitempty"`
38+
}
39+
40+
const generateTemplate = `import { parse } from "@wapc/widl";
41+
import { Context, Writer } from "@wapc/widl/ast";
42+
import { {{visitorClass}} } from "{{module}}";
43+
44+
export function generate(widl, config) {
45+
const doc = parse(widl);
46+
const context = new Context(config);
47+
48+
const writer = new Writer();
49+
const visitor = new {{visitorClass}}(writer);
50+
doc.accept(context, visitor);
51+
let source = writer.string();
52+
console.log(source);
53+
54+
return source;
55+
}
56+
57+
js_exports["generate"] = generate;`
58+
59+
func (c *GenerateCmd) Run(ctx *Context) error {
60+
defer func() {
61+
if c.prettier != nil {
62+
c.prettier.Dispose()
63+
}
64+
}()
65+
66+
configBytes, err := os.ReadFile(c.Config)
67+
if err != nil {
68+
return err
69+
}
70+
71+
configs := strings.Split(string(configBytes), "---")
72+
for _, config := range configs {
73+
if err := c.generate(config); err != nil {
74+
return err
75+
}
76+
}
77+
78+
return nil
79+
}
80+
81+
func (c *GenerateCmd) generate(configYAML string) error {
82+
var config Config
83+
if err := yaml.Unmarshal([]byte(configYAML), &config); err != nil {
84+
return err
85+
}
86+
87+
schemaBytes, err := os.ReadFile(config.Schema)
88+
if err != nil {
89+
return err
90+
}
91+
schema := string(schemaBytes)
92+
93+
homeDir, err := getHomeDirectory()
94+
if err != nil {
95+
return err
96+
}
97+
srcDir := filepath.Join(homeDir, "src")
98+
99+
for filename, target := range config.Generates {
100+
if target.IfNotExists {
101+
_, err := os.Stat(filename)
102+
if err != nil && !os.IsNotExist(err) {
103+
return err
104+
}
105+
if err == nil {
106+
fmt.Printf("Skipping %s...\n", filename)
107+
continue
108+
}
109+
}
110+
fmt.Printf("Generating %s...\n", filename)
111+
generateTS := generateTemplate
112+
generateTS = strings.Replace(generateTS, "{{module}}", target.Module, 1)
113+
generateTS = strings.Replace(generateTS, "{{visitorClass}}", target.VisitorClass, -1)
114+
115+
result := api.Build(api.BuildOptions{
116+
Stdin: &api.StdinOptions{
117+
Contents: generateTS,
118+
Sourcefile: "generate.ts",
119+
ResolveDir: srcDir,
120+
},
121+
Bundle: true,
122+
NodePaths: []string{srcDir},
123+
LogLevel: api.LogLevelInfo,
124+
})
125+
if len(result.Errors) > 0 {
126+
return fmt.Errorf("esbuild returned errors: %v", result.Errors)
127+
}
128+
if len(result.OutputFiles) != 1 {
129+
return errors.New("esbuild did not produce exactly 1 output file")
130+
}
131+
132+
bundle := string(result.OutputFiles[0].Contents)
133+
134+
j, err := js.Compile(bundle)
135+
if err != nil {
136+
return err
137+
}
138+
defer j.Dispose()
139+
140+
if target.Config == nil {
141+
target.Config = map[string]interface{}{}
142+
}
143+
res, err := j.Invoke("generate", schema, target.Config)
144+
if err != nil {
145+
return err
146+
}
147+
148+
source := res.(string)
149+
ext := filepath.Ext(filename)
150+
switch ext {
151+
case ".ts":
152+
source, err = c.formatTypeScript(source)
153+
if err != nil {
154+
return err
155+
}
156+
}
157+
158+
dir := filepath.Dir(filename)
159+
if dir != "" {
160+
if err = os.MkdirAll(dir, 0777); err != nil {
161+
return err
162+
}
163+
}
164+
if err = os.WriteFile(filename, []byte(source), 0666); err != nil {
165+
return err
166+
}
167+
}
168+
169+
// Some CLI-based formatters actually check for types referenced in other files
170+
// so we must call these after all the files are generated.
171+
for filename := range config.Generates {
172+
ext := filepath.Ext(filename)
173+
switch ext {
174+
case ".rs":
175+
fmt.Printf("Formatting %s...\n", filename)
176+
if err = formatRust(filename); err != nil {
177+
return err
178+
}
179+
case ".go":
180+
fmt.Printf("Formatting %s...\n", filename)
181+
if err = formatGolang(filename); err != nil {
182+
return err
183+
}
184+
}
185+
}
186+
187+
return nil
188+
}
189+
190+
//go:embed prettier.js
191+
var prettierSource string
192+
193+
func (c *GenerateCmd) formatTypeScript(source string) (string, error) {
194+
var err error
195+
c.once.Do(func() {
196+
c.prettier, err = js.Compile(prettierSource)
197+
})
198+
if err != nil {
199+
return "", err
200+
}
201+
202+
res, err := c.prettier.Invoke("formatTypeScript", source)
203+
if err != nil {
204+
return "", err
205+
}
206+
207+
return res.(string), nil
208+
}
209+
210+
func formatRust(filename string) error {
211+
cmd := exec.Command("rustfmt", filename)
212+
cmd.Stdout = os.Stdout
213+
cmd.Stderr = os.Stderr
214+
return cmd.Run()
215+
}
216+
217+
func formatGolang(filename string) error {
218+
cmd := exec.Command("gofmt", "-w", filename)
219+
cmd.Stdout = os.Stdout
220+
cmd.Stderr = os.Stderr
221+
return cmd.Run()
222+
}

pkg/commands/home.go

+69
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
package commands
2+
3+
import (
4+
"fmt"
5+
"os"
6+
"path/filepath"
7+
8+
"github.com/mitchellh/go-homedir"
9+
)
10+
11+
var baseDependencies = map[string][]string{
12+
"@wapc/widl": {
13+
filepath.Join("src", "@wapc", "widl"),
14+
},
15+
"@wapc/widl-codegen": {
16+
filepath.Join("src", "@wapc", "widl-codegen"),
17+
filepath.Join("templates", "@wapc", "widl-codegen"),
18+
},
19+
}
20+
21+
func getHomeDirectory() (string, error) {
22+
home, err := homedir.Dir()
23+
if err != nil {
24+
return "", err
25+
}
26+
home, err = homedir.Expand(home)
27+
if err != nil {
28+
return "", err
29+
}
30+
31+
wapcHome := filepath.Join(home, ".wapc")
32+
srcDir := filepath.Join(wapcHome, "src")
33+
templatesDir := filepath.Join(wapcHome, "templates")
34+
35+
if _, err := os.Stat(srcDir); os.IsNotExist(err) {
36+
if err = os.MkdirAll(srcDir, 0700); err != nil {
37+
return "", err
38+
}
39+
}
40+
41+
if _, err := os.Stat(templatesDir); os.IsNotExist(err) {
42+
if err = os.MkdirAll(templatesDir, 0700); err != nil {
43+
return "", err
44+
}
45+
}
46+
47+
missing := make(map[string]struct{}, len(baseDependencies))
48+
for dependency, checks := range baseDependencies {
49+
for _, check := range checks {
50+
if _, err := os.Stat(filepath.Join(wapcHome, check)); os.IsNotExist(err) {
51+
missing[dependency] = struct{}{}
52+
}
53+
}
54+
}
55+
56+
if len(missing) > 0 {
57+
fmt.Println("Installing base dependencies...")
58+
for dependency := range missing {
59+
cmd := InstallCmd{
60+
Location: dependency,
61+
}
62+
if err = cmd.doRun(&Context{}, wapcHome); err != nil {
63+
return "", err
64+
}
65+
}
66+
}
67+
68+
return wapcHome, nil
69+
}

0 commit comments

Comments
 (0)