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

mlange-42 / modo / 13180131511

06 Feb 2025 01:42PM CUT coverage: 71.952% (-2.1%) from 74.08%
13180131511

Pull #212

github

web-flow
Merge 3f3f63e16 into 1566231bf
Pull Request #212: Add tests for CLI commands

30 of 46 new or added lines in 5 files covered. (65.22%)

2278 of 3166 relevant lines covered (71.95%)

40.24 hits per line

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

62.61
/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) {
3✔
44
        initArgs := initArgs{}
3✔
45
        var config string
3✔
46

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

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

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

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

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

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

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

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

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

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

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

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

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

169
        nestedSrc := false
1✔
170

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

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

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

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

1✔
241
        if args.NoFolders {
1✔
242
                return
×
243
        }
×
244

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

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

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

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

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

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

283
        s += "    echo Done."
1✔
284
        return s, nil
1✔
285
}
286

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

296
        return fmt.Sprintf(`|
1✔
297
    echo Running 'mojo test'...
1✔
298
    magic run mojo test -I %s %s
1✔
299
    echo Done.`, src, testOurDir)
1✔
300
}
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