Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Smart config file creation #141

Merged
merged 11 commits into from
Jan 28, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
### Features

* Adds flag/config entry `report-missing` to report missing docstrings and coverage (#136, #137)
* Command `init` tries to find packages to set up an instantly working project (#141)
* Command `init` adds pre- and post-processing scripts to `modo.yaml` file (#141)
* Command `init` creates a `docs` directory with sub-directories reflecting the recommended structure (#141)

### Documentation

Expand Down
32 changes: 21 additions & 11 deletions assets/config/modo.yaml
Original file line number Diff line number Diff line change
@@ -1,17 +1,22 @@
# Input files as generated by `mojo doc`.
# If a single directory is given, it is processed recursively.
input:
- api.json

{{if .Warning}}# {{.Warning}}
{{end -}}
input:{{if not .InputFiles}} []

{{else}}
{{range .InputFiles}} - {{.}}
{{end}}
{{end -}}
# Output directory.
output: docs/
output: {{if .OutputDir}}{{.OutputDir}}{{else}}docs/{{end}}

# Output directory for doc-tests.
# Remove or set to "" to disable doc-tests.
tests: doctest/
tests: {{if .TestsDir}}{{.TestsDir}}{{else}}doctest/{{end}}

# Output format. One of (plain|hugo|mdbook).
format: plain
format: {{if .RenderFormat}}{{.RenderFormat}}{{else}}plain{{end}}

# Re-structure docs according to package re-exports.
exports: true
Expand All @@ -36,11 +41,12 @@ case-insensitive: false
templates: []

# Bash commands to run before build as well as test.
pre-run:
- |
echo You can use pre- and post-commands...
echo to run 'mojo doc', 'mojo test', etc.
pre-run:{{if not .PreRun}} []

{{else}}
{{range .PreRun}} - {{.}}
{{end}}
{{end -}}
# Bash scripts to run before build.
pre-build: []

Expand All @@ -50,8 +56,12 @@ pre-test: []

# Bash scripts to run after test.
# Also runs after build if 'tests' is given.
post-test: []
post-test:{{if not .PostTest}} []

{{else}}
{{range .PostTest}} - {{.}}
{{end}}
{{end -}}
# Bash scripts to run after build.
post-build: []

Expand Down
243 changes: 233 additions & 10 deletions cmd/init.go
Original file line number Diff line number Diff line change
@@ -1,16 +1,41 @@
package cmd

import (
"bytes"
"fmt"
"io/fs"
"os"
"path"
"text/template"

"github.com/mlange-42/modo/assets"
"github.com/mlange-42/modo/format"
"github.com/spf13/cobra"
)

const srcDir = "src"
const docsDir = "docs"
const docsInDir = "src"
const docsOutDir = "site"
const testsDir = "doctest"

type config struct {
Warning string
InputFiles []string
OutputDir string
TestsDir string
RenderFormat string
PreRun []string
PostTest []string
}

type packageSource struct {
Name string
Path []string
}

func initCommand() (*cobra.Command, error) {
var format string

root := &cobra.Command{
Use: "init",
Short: "Generate a Modo config file in the current directory",
Expand All @@ -21,26 +46,224 @@ Complete documentation at https://mlange-42.github.io/modo/`,
SilenceUsage: true,
RunE: func(cmd *cobra.Command, args []string) error {
file := configFile + ".yaml"
exists, err := fileExists(file)
exists, _, err := fileExists(file)
if err != nil {
return fmt.Errorf("error checking config file %s: %s", file, err.Error())
}
if exists {
return fmt.Errorf("config file %s already exists", file)
}
if format == "" {
format = "plain"
}
return initProject(format)
},
}

root.Flags().StringVarP(&format, "format", "f", "plain", "Output format. One of (plain|mdbook|hugo)")

return root, nil
}

func initProject(f string) error {
_, err := format.GetFormatter(f)
if err != nil {
return err
}

file := configFile + ".yaml"

templ := template.New("all")
templ, err = templ.ParseFS(assets.Config, path.Join("config", file))
if err != nil {
return err
}
sources, warning, err := findSources(f)
if err != nil {
return err
}
inDir, outDir, err := createDocs(f, sources)
if err != nil {
return err
}
preRun, err := createPreRun(f, sources)
if err != nil {
return err
}
config := config{
Warning: warning,
InputFiles: []string{inDir},
OutputDir: outDir,
TestsDir: testsDir,
RenderFormat: f,
PreRun: []string{preRun},
PostTest: []string{createPostTest(sources)},
}

b := bytes.Buffer{}
if err := templ.ExecuteTemplate(&b, "modo.yaml", &config); err != nil {
return err
}
if err := os.WriteFile(file, b.Bytes(), 0644); err != nil {
return err
}

fmt.Println("Modo project initialized.\nSee file 'modo.yaml' for configuration.")
return nil
}

func findSources(f string) ([]packageSource, string, error) {
warning := ""
sources := []packageSource{}
srcExists, srcIsDir, err := fileExists(srcDir)
if err != nil {
return nil, warning, err
}

var allDirs []string
if srcExists && srcIsDir {
allDirs = append(allDirs, srcDir)
} else {
infos, err := os.ReadDir(".")
if err != nil {
return nil, warning, err
}
for _, info := range infos {
if info.IsDir() {
allDirs = append(allDirs, info.Name())
}
}
}

configData, err := fs.ReadFile(assets.Config, path.Join("config", file))
nestedSrc := false

for _, dir := range allDirs {
isPkg, err := isPackage(dir)
if err != nil {
return nil, warning, err
}
if isPkg {
// Package is `<dir>/__init__.mojo`
file := dir
if file == srcDir {
// Package is `src/__init__.mojo`
file, err = GetCwdName()
if err != nil {
return nil, warning, err
}
}
sources = append(sources, packageSource{file, []string{dir}})
continue
}
if dir != srcDir {
isPkg, err := isPackage(path.Join(dir, srcDir))
if err != nil {
return err
return nil, warning, err
}
if err := os.WriteFile(file, configData, 0644); err != nil {
return err
if isPkg {
// Package is `<dir>/src/__init__.mojo`
nestedSrc = true
sources = append(sources, packageSource{dir, []string{dir, srcDir}})
}
continue
}
infos, err := os.ReadDir(dir)
if err != nil {
return nil, warning, err
}
for _, info := range infos {
if info.IsDir() {
isPkg, err := isPackage(path.Join(dir, info.Name()))
if err != nil {
return nil, warning, err
}
if isPkg {
// Package is `src/<dir>/__init__.mojo`
sources = append(sources, packageSource{info.Name(), []string{dir, info.Name()}})
}
}
}
}

fmt.Println("Modo project initialized.")
return nil
},
if nestedSrc && len(sources) > 1 {
warning = "WARNING: with folder structure <pkg>/src/__init__.mojo, only a single package is supported"
fmt.Println(warning)
}

return root, nil
if len(sources) == 0 {
sources = []packageSource{{"mypkg", []string{srcDir, "mypkg"}}}
warning = fmt.Sprintf("WARNING: no package sources found; using %s", path.Join(sources[0].Path...))
fmt.Println(warning)
} else if f == "mdbook" && len(sources) > 1 {
warning = fmt.Sprintf("WARNING: mdbook format can only use a single package but %d were found; using %s", len(sources), path.Join(sources[0].Path...))
sources = sources[:1]
fmt.Println(warning)
}
return sources, warning, nil
}

func createDocs(f string, sources []packageSource) (inDir, outDir string, err error) {
inDir = path.Join(docsDir, docsInDir)
outDir = path.Join(docsDir, docsOutDir)
if f == "hugo" {
outDir = path.Join(outDir, "content")
}
if f == "mdbook" {
inDir = path.Join(docsDir, sources[0].Name+".json")
outDir = path.Join(docsDir)
}

docsExists, _, err := fileExists(docsDir)
if err != nil {
return
}
if docsExists {
fmt.Printf("WARNING: folder %s already exists, skip creating\n", docsDir)
return
}

if f != "mdbook" {
if err = mkDirs(inDir); err != nil {
return
}
if err = os.WriteFile(path.Join(inDir, "_index.md"), []byte("# Landing page\n"), 0644); err != nil {
return
}
}
if err = mkDirs(outDir); err != nil {
return
}
if err = mkDirs(testsDir); err != nil {
return
}
return
}

func createPreRun(f string, sources []packageSource) (string, error) {
s := "|\n echo Running 'mojo test'...\n"

inDir := docsDir
if f != "mdbook" {
inDir = path.Join(docsDir, docsInDir)
}
for _, src := range sources {
s += fmt.Sprintf(" magic run mojo doc -o %s.json %s\n", path.Join(inDir, src.Name), path.Join(src.Path...))
}

s += " echo Done."
return s, nil
}

func createPostTest(sources []packageSource) string {
var src string
if len(sources[0].Path) == 1 {
src = "."
} else {
src = sources[0].Path[0]
}

return fmt.Sprintf(`|
echo Running 'mojo test'...
magic run mojo test -I %s %s
echo Done.`, src, testsDir)
}
Loading
Loading