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

mlange-42 / modo / 13183004398

06 Feb 2025 04:09PM CUT coverage: 74.117%. Remained the same
13183004398

push

github

web-flow
Add Modo and Hugo to the docs site footer (#214)

2351 of 3172 relevant lines covered (74.12%)

48.86 hits per line

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

67.26
/internal/cmd/util.go
1
package cmd
2

3
import (
4
        "fmt"
5
        "io"
6
        "log"
7
        "os"
8
        "os/exec"
9
        "path"
10
        "path/filepath"
11
        "strings"
12
        "time"
13

14
        "github.com/mlange-42/modo/internal/document"
15
        "github.com/mlange-42/modo/internal/util"
16
        "github.com/rjeczalik/notify"
17
        "github.com/spf13/pflag"
18
        "github.com/spf13/viper"
19
)
20

21
const defaultConfigFile = "modo.yaml"
22
const setExitOnError = "set -e"
23

24
const initFileText = "__init__.mojo"
25
const initFileEmoji = "__init__.🔥"
26

27
var watchExtensions = []string{".md", ".mojo", ".🔥"}
28

29
type Version struct {
30
        Major int
31
        Minor int
32
        Patch int
33
        Dev   bool
34
}
35

36
func (v *Version) Version() string {
1✔
37
        version := fmt.Sprintf("%d.%d.%d", v.Major, v.Minor, v.Patch)
1✔
38
        if v.Dev {
2✔
39
                version = fmt.Sprintf("%s-dev", version)
1✔
40
        }
1✔
41
        return version
1✔
42
}
43

44
func runCommand(command string) error {
22✔
45
        commandWithExit := fmt.Sprintf("%s\n%s", setExitOnError, command)
22✔
46
        cmd := exec.Command("bash", "-c", commandWithExit)
22✔
47
        cmd.Stdout = os.Stdout
22✔
48
        cmd.Stderr = os.Stderr
22✔
49
        return cmd.Run()
22✔
50
}
22✔
51

52
func runCommands(commands []string) error {
22✔
53
        for _, command := range commands {
44✔
54
                err := runCommand(command)
22✔
55
                if err != nil {
22✔
56
                        return err
×
57
                }
×
58
        }
59
        return nil
22✔
60
}
61

62
func readDocs(file string) (*document.Docs, error) {
4✔
63
        data, err := read(file)
4✔
64
        if err != nil {
4✔
65
                return nil, err
×
66
        }
×
67

68
        if strings.HasSuffix(file, ".yaml") || strings.HasSuffix(file, ".yml") {
4✔
69
                return document.FromYaml(data)
×
70
        }
×
71

72
        return document.FromJson(data)
4✔
73
}
74

75
func read(file string) ([]byte, error) {
4✔
76
        if file == "" {
4✔
77
                return io.ReadAll(os.Stdin)
×
78
        } else {
4✔
79
                return os.ReadFile(file)
4✔
80
        }
4✔
81
}
82

83
func isPackage(dir string) (isPackage bool, err error) {
27✔
84
        pkgFile := path.Join(dir, initFileText)
27✔
85
        initExists, initIsDir, err := util.FileExists(pkgFile)
27✔
86
        if err != nil {
27✔
87
                return
×
88
        }
×
89
        if initExists && !initIsDir {
31✔
90
                isPackage = true
4✔
91
                return
4✔
92
        }
4✔
93

94
        pkgFile = path.Join(dir, initFileEmoji)
23✔
95
        initExists, initIsDir, err = util.FileExists(pkgFile)
23✔
96
        if err != nil {
23✔
97
                return
×
98
        }
×
99
        if initExists && !initIsDir {
23✔
100
                isPackage = true
×
101
                return
×
102
        }
×
103

104
        return
23✔
105
}
106

107
func mountProject(v *viper.Viper, config string, paths []string) (string, error) {
5✔
108
        cwd, err := os.Getwd()
5✔
109
        if err != nil {
5✔
110
                return "", err
×
111
        }
×
112

113
        withConfig := len(paths) > 0
5✔
114
        p := "."
5✔
115
        if withConfig {
10✔
116
                p = paths[0]
5✔
117
                if err := os.Chdir(p); err != nil {
5✔
118
                        return cwd, err
×
119
                }
×
120
        }
121

122
        exists, isDir, err := util.FileExists(config)
5✔
123
        if err != nil {
5✔
124
                return cwd, err
×
125
        }
×
126
        if !exists || isDir {
5✔
127
                if withConfig {
×
128
                        return cwd, fmt.Errorf("no config file '%s' found in path '%s'", config, p)
×
129
                }
×
130
                return cwd, nil
×
131
        }
132

133
        v.SetConfigName(strings.TrimSuffix(config, path.Ext(config)))
5✔
134
        v.SetConfigType("yaml")
5✔
135
        v.AddConfigPath(".")
5✔
136

5✔
137
        if err := v.ReadInConfig(); err != nil {
5✔
138
                _, notFound := err.(viper.ConfigFileNotFoundError)
×
139
                if !notFound {
×
140
                        return cwd, err
×
141
                }
×
142
                if withConfig {
×
143
                        return cwd, err
×
144
                }
×
145
        }
146
        return cwd, nil
5✔
147
}
148

149
type command = func(file string, args *document.Config, form document.Formatter, subdir string, isFile, isDir bool) error
150

151
func runFilesOrDir(cmd command, args *document.Config, form document.Formatter) error {
4✔
152
        if form != nil {
7✔
153
                if err := form.Accepts(args.InputFiles); err != nil {
3✔
154
                        return err
×
155
                }
×
156
        }
157

158
        if len(args.InputFiles) == 0 || (len(args.InputFiles) == 1 && args.InputFiles[0] == "") {
4✔
159
                if err := cmd("", args, form, "", false, false); err != nil {
×
160
                        return err
×
161
                }
×
162
        }
163

164
        stats := make([]struct {
4✔
165
                file bool
4✔
166
                dir  bool
4✔
167
        }, 0, len(args.InputFiles))
4✔
168

4✔
169
        for _, file := range args.InputFiles {
8✔
170
                if s, err := os.Stat(file); err == nil {
8✔
171
                        if s.IsDir() && len(args.InputFiles) > 1 {
4✔
172
                                return fmt.Errorf("only a single directory at a time can be processed")
×
173
                        }
×
174
                        stats = append(stats, struct {
4✔
175
                                file bool
4✔
176
                                dir  bool
4✔
177
                        }{!s.IsDir(), s.IsDir()})
4✔
178
                } else {
×
179
                        return err
×
180
                }
×
181
        }
182

183
        for i, file := range args.InputFiles {
8✔
184
                s := stats[i]
4✔
185
                if err := cmd(file, args, form, "", s.file, s.dir); err != nil {
4✔
186
                        return err
×
187
                }
×
188
        }
189
        return nil
4✔
190
}
191

192
func runDir(baseDir string, args *document.Config, form document.Formatter, runFile command) error {
4✔
193
        baseDir = filepath.Clean(baseDir)
4✔
194

4✔
195
        err := filepath.WalkDir(baseDir,
4✔
196
                func(p string, info os.DirEntry, err error) error {
20✔
197
                        if err != nil {
16✔
198
                                return err
×
199
                        }
×
200
                        if info.IsDir() {
20✔
201
                                return nil
4✔
202
                        }
4✔
203
                        if !strings.HasSuffix(strings.ToLower(p), ".json") {
20✔
204
                                return nil
8✔
205
                        }
8✔
206
                        cleanDir, _ := filepath.Split(path.Clean(p))
4✔
207
                        relDir := filepath.Clean(strings.TrimPrefix(cleanDir, baseDir))
4✔
208
                        return runFile(p, args, form, relDir, true, false)
4✔
209
                })
210
        return err
4✔
211
}
212

213
func commandError(commandType string, err error) error {
×
214
        return fmt.Errorf("in script %s: %s\nTo skip pre- and post-processing scripts, use flag '--bare'", commandType, err)
×
215
}
×
216

217
// bindFlags binds flags to Viper, filtering out the `--watch` and `--config` flag.
218
func bindFlags(v *viper.Viper, flags *pflag.FlagSet) error {
8✔
219
        newFlags := pflag.NewFlagSet("root", pflag.ExitOnError)
8✔
220
        flags.VisitAll(func(f *pflag.Flag) {
105✔
221
                if f.Name == "watch" || f.Name == "config" {
113✔
222
                        return
16✔
223
                }
16✔
224
                newFlags.AddFlag(f)
81✔
225
        })
226
        return v.BindPFlags(newFlags)
8✔
227
}
228

229
func checkConfigFile(f string) error {
9✔
230
        if strings.ContainsRune(f, '/') || strings.ContainsRune(f, '\\') {
9✔
231
                return fmt.Errorf("config file must be in Modo's working directory (as set by the PATH argument)")
×
232
        }
×
233
        return nil
9✔
234
}
235

236
func watchAndRun(args *document.Config, command func(*document.Config) error, stop chan struct{}) error {
1✔
237
        args.RemovePostScripts()
1✔
238

1✔
239
        c := make(chan notify.EventInfo, 32)
1✔
240
        collected := make(chan []notify.EventInfo, 1)
1✔
241

1✔
242
        toWatch, err := getWatchPaths(args)
1✔
243
        if err != nil {
1✔
244
                return err
×
245
        }
×
246
        for _, w := range toWatch {
2✔
247
                if err := notify.Watch(w, c, notify.All); err != nil {
1✔
248
                        log.Fatal(err)
×
249
                }
×
250
        }
251
        defer notify.Stop(c)
1✔
252

1✔
253
        fmt.Printf("Watching for changes: %s\nExit with Ctrl + C\n", strings.Join(toWatch, ", "))
1✔
254
        ticker := time.NewTicker(1 * time.Second)
1✔
255
        defer ticker.Stop()
1✔
256

1✔
257
        go func() {
2✔
258
                var events []notify.EventInfo
1✔
259
                for {
6✔
260
                        select {
5✔
261
                        case evt := <-c:
×
262
                                events = append(events, evt)
×
263
                        case <-ticker.C:
4✔
264
                                if len(events) > 0 {
4✔
265
                                        collected <- events
×
266
                                        events = nil
×
267
                                } else {
4✔
268
                                        collected <- nil
4✔
269
                                }
4✔
270
                        }
271
                }
272
        }()
273

274
        for {
6✔
275
                select {
5✔
276
                case events := <-collected:
4✔
277
                        if events == nil {
8✔
278
                                continue
4✔
279
                        }
280
                        trigger := false
×
281
                        for _, e := range events {
×
282
                                for _, ext := range watchExtensions {
×
283
                                        if strings.HasSuffix(e.Path(), ext) {
×
284
                                                trigger = true
×
285
                                                break
×
286
                                        }
287
                                }
288
                        }
289
                        if trigger {
×
290
                                if err := command(args); err != nil {
×
291
                                        return err
×
292
                                }
×
293
                                fmt.Printf("Watching for changes: %s\n", strings.Join(toWatch, ", "))
×
294
                        }
295
                case <-stop:
1✔
296
                        return nil
1✔
297
                }
298
        }
299
}
300

301
func getWatchPaths(args *document.Config) ([]string, error) {
2✔
302
        toWatch := append([]string{}, args.Sources...)
2✔
303
        toWatch = append(toWatch, args.InputFiles...)
2✔
304
        for i, w := range toWatch {
5✔
305
                p := w
3✔
306
                exists, isDir, err := util.FileExists(p)
3✔
307
                if err != nil {
3✔
308
                        return nil, err
×
309
                }
×
310
                if !exists {
3✔
311
                        return nil, fmt.Errorf("file or directory '%s' to watch does not exist", p)
×
312
                }
×
313
                if isDir {
6✔
314
                        p = path.Join(w, "...")
3✔
315
                }
3✔
316
                toWatch[i] = p
3✔
317
        }
318
        return toWatch, nil
2✔
319
}
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