Skip to content

Commit ce61511

Browse files
authored
[gen readme] Implement gen readme command (#1728)
## Summary Implements `devbox gen readme [filename]` command. See `devbox.md` for what the output looks like. Highlights: * Adds scripts with local links * Comments in devbox.json scripts are added to readme. (Limited support for these. Comments after script name and before script value are supported) * Adds packages and links to URL (nixhub or github) * Adds comment indicating generated by devbox. This comment can be used to insert readme into an existing readme. ## How was it tested? `devbox gen readme devbox.md`
1 parent af58be9 commit ce61511

File tree

13 files changed

+307
-17
lines changed

13 files changed

+307
-17
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,8 @@ See the [CLI Reference](https://www.jetpack.io/devbox/docs/cli_reference/devbox/
145145

146146
Devbox is an opensource project so contributions are always welcome. Please read [our contributing guide](CONTRIBUTING.md) before submitting pull requests.
147147

148+
[Devbox development readme](devbox.md)
149+
148150
## Related Work
149151

150152
Thanks to [Nix](https://nixos.org/) for providing isolated shells.

devbox.json

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
{
2+
"name": "devbox",
3+
"description": "Instant, easy, and predictable development environments",
24
"packages": {
35
"go": "latest",
46
"runx:golangci/golangci-lint": "latest",
@@ -13,7 +15,9 @@
1315
"test -z $FISH_VERSION && unset CGO_ENABLED GO111MODULE GOARCH GOFLAGS GOMOD GOOS GOROOT GOTOOLCHAIN GOWORK",
1416
],
1517
"scripts": {
16-
"build": "go build -o dist/devbox ./cmd/devbox",
18+
"build":
19+
// Build devbox for the current platform
20+
"go build -o dist/devbox ./cmd/devbox",
1721
"build-darwin-amd64": "GOOS=darwin GOARCH=amd64 go build -o dist/devbox-darwin-amd64 ./cmd/devbox",
1822
"build-darwin-arm64": "GOOS=darwin GOARCH=arm64 go build -o dist/devbox-darwin-arm64 ./cmd/devbox",
1923
"build-linux-amd64": "GOOS=linux GOARCH=amd64 go build -o dist/devbox-linux-amd64 ./cmd/devbox",
@@ -24,7 +28,9 @@
2428
"devbox run build-linux-amd64",
2529
"devbox run build-linux-arm64",
2630
],
27-
"code": "code .",
31+
"code":
32+
// Open VSCode
33+
"code .",
2834
"lint": "golangci-lint run --timeout 5m && scripts/gofumpt.sh",
2935
"fmt": "scripts/gofumpt.sh",
3036
"test": "go test -race -cover ./...",

devbox.md

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
<!-- gen-readme start - generated by https://github.com/jetpack-io/devbox/ -->
2+
# devbox
3+
4+
Instant, easy, and predictable development environments
5+
6+
## Scripts
7+
8+
* [build](#build)
9+
* [build-all](#build-all)
10+
* [build-darwin-amd64](#build-darwin-amd64)
11+
* [build-darwin-arm64](#build-darwin-arm64)
12+
* [build-linux-amd64](#build-linux-amd64)
13+
* [build-linux-arm64](#build-linux-arm64)
14+
* [code](#code)
15+
* [fmt](#fmt)
16+
* [lint](#lint)
17+
* [test](#test)
18+
* [tidy](#tidy)
19+
* [update-examples](#update-examples)
20+
21+
## Init Hook
22+
23+
```sh
24+
test -z $FISH_VERSION && unset CGO_ENABLED GO111MODULE GOARCH GOFLAGS GOMOD GOOS GOROOT GOTOOLCHAIN GOWORK
25+
```
26+
27+
## Packages
28+
29+
* [go@latest](https://www.nixhub.io/packages/go)
30+
* [runx:golangci/golangci-lint@latest](https://www.github.com/golangci/golangci-lint)
31+
* [runx:mvdan/gofumpt@latest](https://www.github.com/mvdan/gofumpt)
32+
* nixpkgs/63143ac2c9186be6d9da6035fa22620018c85932#hello
33+
34+
## Script Details
35+
36+
### build
37+
Build devbox for the current platform
38+
```sh
39+
go build -o dist/devbox ./cmd/devbox
40+
```
41+
42+
### build-all
43+
```sh
44+
devbox run build-darwin-amd64
45+
devbox run build-darwin-arm64
46+
devbox run build-linux-amd64
47+
devbox run build-linux-arm64
48+
```
49+
50+
### build-darwin-amd64
51+
```sh
52+
GOOS=darwin GOARCH=amd64 go build -o dist/devbox-darwin-amd64 ./cmd/devbox
53+
```
54+
55+
### build-darwin-arm64
56+
```sh
57+
GOOS=darwin GOARCH=arm64 go build -o dist/devbox-darwin-arm64 ./cmd/devbox
58+
```
59+
60+
### build-linux-amd64
61+
```sh
62+
GOOS=linux GOARCH=amd64 go build -o dist/devbox-linux-amd64 ./cmd/devbox
63+
```
64+
65+
### build-linux-arm64
66+
```sh
67+
GOOS=linux GOARCH=arm64 go build -o dist/devbox-linux-arm64 ./cmd/devbox
68+
```
69+
70+
### code
71+
Open VSCode
72+
```sh
73+
code .
74+
```
75+
76+
### fmt
77+
```sh
78+
scripts/gofumpt.sh
79+
```
80+
81+
### lint
82+
```sh
83+
golangci-lint run --timeout 5m && scripts/gofumpt.sh
84+
```
85+
86+
### test
87+
```sh
88+
go test -race -cover ./...
89+
```
90+
91+
### tidy
92+
```sh
93+
go mod tidy
94+
```
95+
96+
### update-examples
97+
```sh
98+
devbox run build && go run testscripts/testrunner/updater/main.go
99+
```
100+
101+
102+
103+
<!-- gen-readme end -->

internal/boxcli/generate.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"go.jetpack.io/devbox/internal/cloud"
1111
"go.jetpack.io/devbox/internal/devbox"
1212
"go.jetpack.io/devbox/internal/devbox/devopt"
13+
"go.jetpack.io/devbox/internal/devbox/docgen"
1314
)
1415

1516
type generateCmdFlags struct {
@@ -26,6 +27,7 @@ func generateCmd() *cobra.Command {
2627

2728
command := &cobra.Command{
2829
Use: "generate",
30+
Aliases: []string{"gen"},
2931
Short: "Generate supporting files for your project",
3032
Args: cobra.MaximumNArgs(0),
3133
PersistentPreRunE: ensureNixInstalled,
@@ -34,6 +36,7 @@ func generateCmd() *cobra.Command {
3436
command.AddCommand(dockerfileCmd())
3537
command.AddCommand(debugCmd())
3638
command.AddCommand(direnvCmd())
39+
command.AddCommand(genReadmeCmd())
3740
command.AddCommand(sshConfigCmd())
3841
flags.config.register(command)
3942

@@ -136,6 +139,28 @@ func sshConfigCmd() *cobra.Command {
136139
return command
137140
}
138141

142+
func genReadmeCmd() *cobra.Command {
143+
flags := &generateCmdFlags{}
144+
command := &cobra.Command{
145+
Use: "readme [filename]",
146+
Short: "Generate markdown readme file for this project",
147+
Args: cobra.ExactArgs(1),
148+
RunE: func(cmd *cobra.Command, args []string) error {
149+
box, err := devbox.Open(&devopt.Opts{
150+
Dir: flags.config.path,
151+
Environment: flags.config.environment,
152+
Stderr: cmd.ErrOrStderr(),
153+
})
154+
if err != nil {
155+
return errors.WithStack(err)
156+
}
157+
return docgen.GenerateReadme(box, args[0])
158+
},
159+
}
160+
flags.config.register(command)
161+
return command
162+
}
163+
139164
func runGenerateCmd(cmd *cobra.Command, flags *generateCmdFlags) error {
140165
// Check the directory exists.
141166
box, err := devbox.Open(&devopt.Opts{

internal/devbox/devbox.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,7 @@ func (d *Devbox) ConfigHash() (string, error) {
162162

163163
buf := bytes.Buffer{}
164164
buf.WriteString(h)
165-
for _, pkg := range d.configPackages() {
165+
for _, pkg := range d.ConfigPackages() {
166166
buf.WriteString(pkg.Hash())
167167
}
168168
for _, inc := range d.Includes() {
@@ -1002,15 +1002,15 @@ func (d *Devbox) PackageNames() []string {
10021002
return d.cfg.Packages.VersionedNames()
10031003
}
10041004

1005-
// configPackages returns the packages that are defined in devbox.json
1005+
// ConfigPackages returns the packages that are defined in devbox.json
10061006
// NOTE: the return type is different from devconfig.Packages
1007-
func (d *Devbox) configPackages() []*devpkg.Package {
1007+
func (d *Devbox) ConfigPackages() []*devpkg.Package {
10081008
return devpkg.PackagesFromConfig(d.cfg, d.lockfile)
10091009
}
10101010

10111011
// InstallablePackages returns the packages that are to be installed
10121012
func (d *Devbox) InstallablePackages() []*devpkg.Package {
1013-
return lo.Filter(d.configPackages(), func(pkg *devpkg.Package, _ int) bool {
1013+
return lo.Filter(d.ConfigPackages(), func(pkg *devpkg.Package, _ int) bool {
10141014
return pkg.IsInstallable()
10151015
})
10161016
}
@@ -1033,7 +1033,7 @@ func (d *Devbox) Includes() []plugin.Includable {
10331033
}
10341034

10351035
func (d *Devbox) HasDeprecatedPackages() bool {
1036-
for _, pkg := range d.configPackages() {
1036+
for _, pkg := range d.ConfigPackages() {
10371037
if pkg.IsLegacy() {
10381038
return true
10391039
}
@@ -1046,7 +1046,7 @@ func (d *Devbox) findPackageByName(name string) (*devpkg.Package, error) {
10461046
return nil, errors.New("package name cannot be empty")
10471047
}
10481048
results := map[*devpkg.Package]bool{}
1049-
for _, pkg := range d.configPackages() {
1049+
for _, pkg := range d.ConfigPackages() {
10501050
if pkg.Raw == name || pkg.CanonicalName() == name {
10511051
results[pkg] = true
10521052
}

internal/devbox/docgen/docgen.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package docgen
2+
3+
import (
4+
_ "embed"
5+
"os"
6+
"text/template"
7+
8+
"go.jetpack.io/devbox/internal/devbox"
9+
)
10+
11+
//go:embed readme.tmpl
12+
var readmeTemplate string
13+
14+
func GenerateReadme(devbox *devbox.Devbox, path string) error {
15+
t, err := template.New("readme").Parse(readmeTemplate)
16+
if err != nil {
17+
return err
18+
}
19+
20+
f, err := os.Create(path)
21+
if err != nil {
22+
return err
23+
}
24+
25+
return t.Execute(f, map[string]any{
26+
"Name": devbox.Config().Name,
27+
"Description": devbox.Config().Description,
28+
"Scripts": devbox.Config().Scripts(),
29+
"EnvVars": devbox.Config().Env,
30+
"InitHook": devbox.Config().InitHook(),
31+
"Packages": devbox.ConfigPackages(),
32+
})
33+
}

internal/devbox/docgen/readme.tmpl

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
<!-- gen-readme start - generated by https://github.com/jetpack-io/devbox/ -->
2+
3+
{{- if .Name }}
4+
# {{ .Name }}
5+
{{ end }}
6+
7+
{{- if .Description }}
8+
{{ .Description }}
9+
{{ end }}
10+
11+
{{- if .Scripts }}
12+
## Scripts
13+
{{ range $name, $_ := .Scripts }}
14+
* [{{ $name }}](#{{ $name }})
15+
{{- end }}
16+
{{ end }}
17+
18+
{{- if .Env }}
19+
## Environment
20+
21+
```sh
22+
{{- range $key, $value := .Env }}
23+
{{ $key }}="{{ $value }}"
24+
{{- end }}
25+
```
26+
{{ end }}
27+
28+
{{- if .InitHook }}
29+
## Init Hook
30+
31+
```sh
32+
{{ .InitHook }}
33+
```
34+
{{ end }}
35+
36+
{{- if .Packages }}
37+
## Packages
38+
{{ range .Packages }}
39+
* {{ if .DocsURL }}[{{ .Raw }}]({{ .DocsURL }}){{ else }}{{ .Raw }}{{ end }}
40+
{{- end }}
41+
{{ end }}
42+
43+
{{- if .Scripts }}
44+
## Script Details
45+
{{ range $name, $commands := .Scripts }}
46+
### {{ $name }}
47+
{{- if .Comments }}
48+
{{ .Comments }}
49+
{{- end }}
50+
```sh
51+
{{ $commands }}
52+
```
53+
{{ end }}
54+
{{ end }}
55+
56+
<!-- gen-readme end -->

internal/devbox/packages.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -304,7 +304,7 @@ func (d *Devbox) ensureStateIsUpToDate(ctx context.Context, mode installMode) er
304304
d.lockfile.Tidy()
305305

306306
// Update lockfile with new packages that are not to be installed
307-
for _, pkg := range d.configPackages() {
307+
for _, pkg := range d.ConfigPackages() {
308308
if err := pkg.EnsureUninstallableIsInLockfile(); err != nil {
309309
return err
310310
}

internal/devbox/update.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ func (d *Devbox) inputsToUpdate(
7676
opts devopt.UpdateOpts,
7777
) ([]*devpkg.Package, error) {
7878
if len(opts.Pkgs) == 0 {
79-
return d.configPackages(), nil
79+
return d.ConfigPackages(), nil
8080
}
8181

8282
var pkgsToUpdate []*devpkg.Package

internal/devconfig/ast.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package devconfig
22

33
import (
44
"bytes"
5+
"regexp"
56
"slices"
67

78
"github.com/tailscale/hujson"
@@ -319,3 +320,35 @@ func (c *configAST) appendStringSliceField(name, fieldName string, fieldValues [
319320
}
320321
c.root.Format()
321322
}
323+
324+
func (c *configAST) beforeComment(path ...any) []byte {
325+
elem := c.root
326+
for _, pathItem := range path {
327+
obj := elem.Value.(*hujson.Object)
328+
i, ok := pathItem.(int)
329+
if !ok {
330+
i = c.memberIndex(obj, pathItem.(string))
331+
}
332+
if i == -1 {
333+
return nil
334+
}
335+
elem = obj.Members[i].Value
336+
}
337+
338+
// Match all single are multi line comments.
339+
re := regexp.MustCompile(`(?:\/\/(.*?)\n)|(?s:\/\*(.*?)\*\/)`)
340+
341+
return bytes.TrimSpace(
342+
re.ReplaceAllFunc(elem.BeforeExtra, func(s []byte) []byte {
343+
singleLineRe := regexp.MustCompile(`\/\/(.*?)\n`)
344+
multiLineRe := regexp.MustCompile(`(?s:\/\*(.*?)\*\/)`)
345+
346+
if singleLineRe.Match(s) {
347+
return singleLineRe.ReplaceAll(s, []byte("$1\n"))
348+
} else if multiLineRe.Match(s) {
349+
return multiLineRe.ReplaceAll(s, []byte("$1"))
350+
}
351+
return s
352+
}),
353+
)
354+
}

0 commit comments

Comments
 (0)