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

mlange-42 / modo / 13179389384

06 Feb 2025 01:02PM CUT coverage: 56.505% (-17.6%) from 74.08%
13179389384

Pull #212

github

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

1772 of 3136 relevant lines covered (56.51%)

21.11 hits per line

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

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

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

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

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

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

×
84
        return root, nil
×
85
}
86

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

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

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

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

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

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

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

154
        var allDirs []string
×
155
        if srcExists && srcIsDir {
×
156
                allDirs = append(allDirs, srcDir)
×
157
        } else {
×
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
×
170

×
171
        for _, dir := range allDirs {
×
172
                isPkg, err := isPackage(dir)
×
173
                if err != nil {
×
174
                        return nil, warning, err
×
175
                }
×
176
                if isPkg {
×
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 {
×
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)
×
202
                if err != nil {
×
203
                        return nil, warning, err
×
204
                }
×
205
                for _, info := range infos {
×
206
                        if info.IsDir() {
×
207
                                isPkg, err := isPackage(path.Join(dir, info.Name()))
×
208
                                if err != nil {
×
209
                                        return nil, warning, err
×
210
                                }
×
211
                                if isPkg {
×
212
                                        // Package is `src/<dir>/__init__.mojo`
×
213
                                        sources = append(sources, document.PackageSource{Name: info.Name(), Path: []string{dir, info.Name()}})
×
214
                                }
×
215
                        }
216
                }
217
        }
218

219
        if nestedSrc && len(sources) > 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 {
×
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 {
×
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
×
234
}
235

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

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

245
        docsExists, _, err := util.FileExists(dir)
×
246
        if err != nil {
×
247
                return
×
248
        }
×
249
        if docsExists {
×
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 {
×
256
                return
×
257
        }
×
258

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

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

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

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

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

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

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