• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

mlange-42 / modo / 13180866156

06 Feb 2025 02:21PM CUT coverage: 73.089% (-1.0%) from 74.08%
13180866156

push

github

web-flow
Add tests for CLI commands (#212)

36 of 54 new or added lines in 6 files covered. (66.67%)

2314 of 3166 relevant lines covered (73.09%)

40.88 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

72.97
/internal/cmd/init.go
1
package cmd
2

3
import (
4
        "bytes"
5
        "fmt"
6
        "os"
7
        "path"
8
        "strings"
9
        "text/template"
10

11
        "github.com/mlange-42/modo/assets"
12
        "github.com/mlange-42/modo/internal/document"
13
        "github.com/mlange-42/modo/internal/format"
14
        "github.com/mlange-42/modo/internal/util"
15
        "github.com/spf13/cobra"
16
)
17

18
const srcDir = "src"
19
const docsInDir = "src"
20
const docsOutDir = "site"
21
const testsDir = "test"
22
const gitignoreFile = ".gitignore"
23
const gitUrl = "blob/main"
24

25
type config struct {
26
        Warning      string
27
        InputFiles   []string
28
        Sources      []string
29
        SourceURLs   map[string]string
30
        OutputDir    string
31
        TestsDir     string
32
        RenderFormat string
33
        PreRun       []string
34
        PostTest     []string
35
}
36

37
type initArgs struct {
38
        Format        string
39
        DocsDirectory string
40
        NoFolders     bool
41
}
42

43
func initCommand() (*cobra.Command, error) {
6✔
44
        initArgs := initArgs{}
6✔
45
        var config string
6✔
46

6✔
47
        root := &cobra.Command{
6✔
48
                Use:   "init FORMAT",
6✔
49
                Short: "Set up a Modo project in the current directory",
6✔
50
                Long: `Set up a Modo project in the current directory.
6✔
51

6✔
52
The format argument is required and must be one of (plain|mdbook|hugo).
6✔
53
Complete documentation at https://mlange-42.github.io/modo/`,
6✔
54
                Args:         cobra.ExactArgs(1),
6✔
55
                SilenceUsage: true,
6✔
56
                RunE: func(cmd *cobra.Command, args []string) error {
10✔
57
                        initArgs.Format = args[0]
4✔
58

4✔
59
                        if err := checkConfigFile(config); err != nil {
4✔
60
                                return err
×
61
                        }
×
62
                        exists, _, err := util.FileExists(config)
4✔
63
                        if err != nil {
4✔
64
                                return fmt.Errorf("error checking config file %s: %s", config, err.Error())
×
65
                        }
×
66
                        if exists {
4✔
67
                                return fmt.Errorf("config file %s already exists", config)
×
68
                        }
×
69
                        if initArgs.Format == "" {
4✔
70
                                initArgs.Format = "plain"
×
71
                        }
×
72
                        initArgs.DocsDirectory = strings.ReplaceAll(initArgs.DocsDirectory, "\\", "/")
4✔
73
                        return initProject(config, &initArgs)
4✔
74
                },
75
        }
76
        root.Flags().StringVarP(&config, "config", "c", defaultConfigFile, "Config file in the working directory to use")
6✔
77
        root.Flags().StringVarP(&initArgs.DocsDirectory, "docs", "d", "docs", "Folder for documentation")
6✔
78
        root.Flags().BoolVarP(&initArgs.NoFolders, "no-folders", "F", false, "Don't create any folders")
6✔
79

6✔
80
        root.Flags().SortFlags = false
6✔
81
        root.MarkFlagFilename("config", "yaml")
6✔
82
        root.MarkFlagDirname("docs")
6✔
83

6✔
84
        return root, nil
6✔
85
}
86

87
func initProject(configFile string, initArgs *initArgs) error {
4✔
88
        form, err := format.GetFormatter(initArgs.Format)
4✔
89
        if err != nil {
4✔
90
                return err
×
91
        }
×
92

93
        templ := template.New("all")
4✔
94
        templ, err = templ.ParseFS(assets.Config, "**/*")
4✔
95
        if err != nil {
4✔
96
                return err
×
97
        }
×
98
        sources, warning, err := findSources(initArgs.Format)
4✔
99
        if err != nil {
4✔
100
                return err
×
101
        }
×
102
        gitInfo, err := document.GetGitOrigin(initArgs.DocsDirectory)
4✔
103
        if err != nil {
4✔
104
                return err
×
105
        }
×
106
        inDir, outDir, err := createDocs(initArgs, form, templ, sources)
4✔
107
        if err != nil {
4✔
108
                return err
×
109
        }
×
110
        preRun, err := createPreRun(initArgs.DocsDirectory, sources, form)
4✔
111
        if err != nil {
4✔
112
                return err
×
113
        }
×
114

115
        sourceDirs := make([]string, 0, len(sources))
4✔
116
        sourceURLs := map[string]string{}
4✔
117
        for _, s := range sources {
8✔
118
                sourceDirs = append(sourceDirs, path.Join(s.Path...))
4✔
119
                sourceURLs[s.Name] = path.Join(path.Join(gitInfo.Repo, gitUrl), gitInfo.BasePath, path.Join(s.Path...))
4✔
120
        }
4✔
121

122
        config := config{
4✔
123
                Warning:      warning,
4✔
124
                InputFiles:   []string{inDir},
4✔
125
                Sources:      sourceDirs,
4✔
126
                SourceURLs:   sourceURLs,
4✔
127
                OutputDir:    outDir,
4✔
128
                TestsDir:     path.Join(initArgs.DocsDirectory, testsDir),
4✔
129
                RenderFormat: initArgs.Format,
4✔
130
                PreRun:       []string{preRun},
4✔
131
                PostTest:     []string{createPostTest(initArgs.DocsDirectory, sources)},
4✔
132
        }
4✔
133

4✔
134
        b := bytes.Buffer{}
4✔
135
        if err := templ.ExecuteTemplate(&b, "modo.yaml", &config); err != nil {
4✔
136
                return err
×
137
        }
×
138
        if err := os.WriteFile(configFile, b.Bytes(), 0644); err != nil {
4✔
139
                return err
×
140
        }
×
141

142
        fmt.Println("Modo project initialized.\nSee file 'modo.yaml' for configuration.")
4✔
143
        return nil
4✔
144
}
145

146
func findSources(f string) ([]document.PackageSource, string, error) {
4✔
147
        warning := ""
4✔
148
        sources := []document.PackageSource{}
4✔
149
        srcExists, srcIsDir, err := util.FileExists(srcDir)
4✔
150
        if err != nil {
4✔
151
                return nil, warning, err
×
152
        }
×
153

154
        var allDirs []string
4✔
155
        if srcExists && srcIsDir {
8✔
156
                allDirs = append(allDirs, srcDir)
4✔
157
        }
4✔
158
        infos, err := os.ReadDir(".")
4✔
159
        if err != nil {
4✔
NEW
160
                return nil, warning, err
×
NEW
161
        }
×
162
        for _, info := range infos {
18✔
163
                if info.IsDir() && info.Name() != srcDir {
24✔
164
                        allDirs = append(allDirs, info.Name())
10✔
165
                }
10✔
166
        }
167

168
        nestedSrc := false
4✔
169

4✔
170
        for _, dir := range allDirs {
18✔
171
                isPkg, err := isPackage(dir)
14✔
172
                if err != nil {
14✔
173
                        return nil, warning, err
×
174
                }
×
175
                if isPkg {
16✔
176
                        // Package is `<dir>/__init__.mojo`
2✔
177
                        file := dir
2✔
178
                        if file == srcDir {
3✔
179
                                // Package is `src/__init__.mojo`
1✔
180
                                file, err = util.GetCwdName()
1✔
181
                                if err != nil {
1✔
182
                                        return nil, warning, err
×
183
                                }
×
184
                        }
185
                        sources = append(sources, document.PackageSource{Name: file, Path: []string{dir}})
2✔
186
                        continue
2✔
187
                }
188
                if dir != srcDir {
21✔
189
                        isPkg, err := isPackage(path.Join(dir, srcDir))
9✔
190
                        if err != nil {
9✔
191
                                return nil, warning, err
×
192
                        }
×
193
                        if isPkg {
10✔
194
                                // Package is `<dir>/src/__init__.mojo`
1✔
195
                                nestedSrc = true
1✔
196
                                sources = append(sources, document.PackageSource{Name: dir, Path: []string{dir, srcDir}})
1✔
197
                        }
1✔
198
                        continue
9✔
199
                }
200
                infos, err := os.ReadDir(dir)
3✔
201
                if err != nil {
3✔
202
                        return nil, warning, err
×
203
                }
×
204
                for _, info := range infos {
7✔
205
                        if info.IsDir() {
8✔
206
                                isPkg, err := isPackage(path.Join(dir, info.Name()))
4✔
207
                                if err != nil {
4✔
208
                                        return nil, warning, err
×
209
                                }
×
210
                                if isPkg {
5✔
211
                                        // Package is `src/<dir>/__init__.mojo`
1✔
212
                                        sources = append(sources, document.PackageSource{Name: info.Name(), Path: []string{dir, info.Name()}})
1✔
213
                                }
1✔
214
                        }
215
                }
216
        }
217

218
        if nestedSrc && len(sources) > 1 {
4✔
219
                warning = "WARNING: with folder structure <pkg>/src/__init__.mojo, only a single package is supported"
×
220
                fmt.Println(warning)
×
221
        }
×
222

223
        if len(sources) == 0 {
4✔
224
                sources = []document.PackageSource{{Name: "mypkg", Path: []string{srcDir, "mypkg"}}}
×
225
                warning = fmt.Sprintf("WARNING: no package sources found; using %s", path.Join(sources[0].Path...))
×
226
                fmt.Println(warning)
×
227
        } else if f == "mdbook" && len(sources) > 1 {
4✔
228
                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...))
×
229
                sources = sources[:1]
×
230
                fmt.Println(warning)
×
231
        }
×
232
        return sources, warning, nil
4✔
233
}
234

235
func createDocs(args *initArgs, form document.Formatter, templ *template.Template, sources []document.PackageSource) (inDir, outDir string, err error) {
4✔
236
        dir := args.DocsDirectory
4✔
237
        inDir, outDir = form.Input(docsInDir, sources), form.Output(docsOutDir)
4✔
238
        inDir, outDir = path.Join(dir, inDir), path.Join(dir, outDir)
4✔
239

4✔
240
        if args.NoFolders {
4✔
241
                return
×
242
        }
×
243

244
        docsExists, _, err := util.FileExists(dir)
4✔
245
        if err != nil {
4✔
246
                return
×
247
        }
×
248
        if docsExists {
4✔
249
                err = fmt.Errorf("documentation folder '%s' already exists.\n"+
×
250
                        "Use flag --docs to use a different folder, --no-folders to skip folders setup", dir)
×
251
                return
×
252
        }
×
253

254
        if err = form.CreateDirs(dir, docsInDir, docsOutDir, sources, templ); err != nil {
4✔
255
                return
×
256
        }
×
257

258
        gitignore := form.GitIgnore(docsInDir, docsOutDir, sources)
4✔
259
        if err = writeGitIgnore(dir, gitignore); err != nil {
4✔
260
                return
×
261
        }
×
262
        return
4✔
263
}
264

265
func writeGitIgnore(dir string, gitignore []string) error {
4✔
266
        s := strings.Join(gitignore, "\n") + "\n"
4✔
267
        return os.WriteFile(path.Join(dir, gitignoreFile), []byte(s), 0644)
4✔
268
}
4✔
269

270
func createPreRun(docsDir string, sources []document.PackageSource, form document.Formatter) (string, error) {
4✔
271
        s := "|\n    echo Running 'mojo doc'...\n"
4✔
272

4✔
273
        inDir := path.Join(docsDir, form.Input(docsInDir, sources))
4✔
274
        for _, src := range sources {
8✔
275
                outFile := inDir
4✔
276
                if !strings.HasSuffix(outFile, ".json") {
8✔
277
                        outFile = path.Join(inDir, src.Name+".json")
4✔
278
                }
4✔
279
                s += fmt.Sprintf("    magic run mojo doc -o %s %s\n", outFile, path.Join(src.Path...))
4✔
280
        }
281

282
        s += "    echo Done."
4✔
283
        return s, nil
4✔
284
}
285

286
func createPostTest(docsDir string, sources []document.PackageSource) string {
4✔
287
        testOurDir := path.Join(docsDir, testsDir)
4✔
288
        var src string
4✔
289
        if len(sources[0].Path) == 1 {
6✔
290
                src = "."
2✔
291
        } else {
4✔
292
                src = sources[0].Path[0]
2✔
293
        }
2✔
294

295
        return fmt.Sprintf(`|
4✔
296
    echo Running 'mojo test'...
4✔
297
    magic run mojo test -I %s %s
4✔
298
    echo Done.`, src, testOurDir)
4✔
299
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc