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

mlange-42 / modo / 13606558454

01 Mar 2025 04:12PM CUT coverage: 74.172%. Remained the same
13606558454

push

github

web-flow
Fix CI for repo changes (#225)

2375 of 3202 relevant lines covered (74.17%)

48.18 hits per line

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

67.11
/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
// Version represents the version of Modo.
30
type Version struct {
31
        Major   int
32
        Minor   int
33
        Patch   int
34
        Release bool
35
}
36

37
// NewVersion creates a new Version.
38
func NewVersion(major, minor, patch int, release bool) Version {
3✔
39
        return Version{Major: major, Minor: minor, Patch: patch, Release: release}
3✔
40
}
3✔
41

42
// Version returns the version as a string.
43
func (v *Version) Version() string {
3✔
44
        version := fmt.Sprintf("v%d.%d.%d", v.Major, v.Minor, v.Patch)
3✔
45
        if !v.Release {
5✔
46
                version = fmt.Sprintf("%s-dev", version)
2✔
47
        }
2✔
48
        return version
3✔
49
}
50

51
func runCommand(command string) error {
22✔
52
        commandWithExit := fmt.Sprintf("%s\n%s", setExitOnError, command)
22✔
53
        cmd := exec.Command("bash", "-c", commandWithExit)
22✔
54
        cmd.Stdout = os.Stdout
22✔
55
        cmd.Stderr = os.Stderr
22✔
56
        return cmd.Run()
22✔
57
}
22✔
58

59
func runCommands(commands []string) error {
22✔
60
        for _, command := range commands {
44✔
61
                err := runCommand(command)
22✔
62
                if err != nil {
22✔
63
                        return err
×
64
                }
×
65
        }
66
        return nil
22✔
67
}
68

69
func readDocs(file string) (*document.Docs, error) {
4✔
70
        data, err := read(file)
4✔
71
        if err != nil {
4✔
72
                return nil, err
×
73
        }
×
74

75
        if strings.HasSuffix(file, ".yaml") || strings.HasSuffix(file, ".yml") {
4✔
76
                return document.FromYAML(data)
×
77
        }
×
78

79
        return document.FromJSON(data)
4✔
80
}
81

82
func read(file string) ([]byte, error) {
4✔
83
        if file == "" {
4✔
84
                return io.ReadAll(os.Stdin)
×
85
        }
×
86
        return os.ReadFile(file)
4✔
87
}
88

89
func isPackage(dir string) (isPackage bool, err error) {
27✔
90
        pkgFile := path.Join(dir, initFileText)
27✔
91
        initExists, initIsDir, err := util.FileExists(pkgFile)
27✔
92
        if err != nil {
27✔
93
                return
×
94
        }
×
95
        if initExists && !initIsDir {
31✔
96
                isPackage = true
4✔
97
                return
4✔
98
        }
4✔
99

100
        pkgFile = path.Join(dir, initFileEmoji)
23✔
101
        initExists, initIsDir, err = util.FileExists(pkgFile)
23✔
102
        if err != nil {
23✔
103
                return
×
104
        }
×
105
        if initExists && !initIsDir {
23✔
106
                isPackage = true
×
107
                return
×
108
        }
×
109

110
        return
23✔
111
}
112

113
func mountProject(v *viper.Viper, config string, paths []string) (string, error) {
5✔
114
        cwd, err := os.Getwd()
5✔
115
        if err != nil {
5✔
116
                return "", err
×
117
        }
×
118

119
        withConfig := len(paths) > 0
5✔
120
        p := "."
5✔
121
        if withConfig {
10✔
122
                p = paths[0]
5✔
123
                if err := os.Chdir(p); err != nil {
5✔
124
                        return cwd, err
×
125
                }
×
126
        }
127

128
        exists, isDir, err := util.FileExists(config)
5✔
129
        if err != nil {
5✔
130
                return cwd, err
×
131
        }
×
132
        if !exists || isDir {
5✔
133
                if withConfig {
×
134
                        return cwd, fmt.Errorf("no config file '%s' found in path '%s'", config, p)
×
135
                }
×
136
                return cwd, nil
×
137
        }
138

139
        v.SetConfigName(strings.TrimSuffix(config, path.Ext(config)))
5✔
140
        v.SetConfigType("yaml")
5✔
141
        v.AddConfigPath(".")
5✔
142

5✔
143
        if err := v.ReadInConfig(); err != nil {
5✔
144
                _, notFound := err.(viper.ConfigFileNotFoundError)
×
145
                if !notFound {
×
146
                        return cwd, err
×
147
                }
×
148
                if withConfig {
×
149
                        return cwd, err
×
150
                }
×
151
        }
152
        return cwd, nil
5✔
153
}
154

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

157
func runFilesOrDir(cmd command, args *document.Config, form document.Formatter) error {
4✔
158
        if form != nil {
7✔
159
                if err := form.Accepts(args.InputFiles); err != nil {
3✔
160
                        return err
×
161
                }
×
162
        }
163

164
        if len(args.InputFiles) == 0 || (len(args.InputFiles) == 1 && args.InputFiles[0] == "") {
4✔
165
                if err := cmd("", args, form, "", false, false); err != nil {
×
166
                        return err
×
167
                }
×
168
        }
169

170
        stats := make([]struct {
4✔
171
                file bool
4✔
172
                dir  bool
4✔
173
        }, 0, len(args.InputFiles))
4✔
174

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

189
        for i, file := range args.InputFiles {
8✔
190
                s := stats[i]
4✔
191
                if err := cmd(file, args, form, "", s.file, s.dir); err != nil {
4✔
192
                        return err
×
193
                }
×
194
        }
195
        return nil
4✔
196
}
197

198
func runDir(baseDir string, args *document.Config, form document.Formatter, runFile command) error {
4✔
199
        baseDir = filepath.Clean(baseDir)
4✔
200

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

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

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

235
func checkConfigFile(f string) error {
9✔
236
        if strings.ContainsRune(f, '/') || strings.ContainsRune(f, '\\') {
9✔
237
                return fmt.Errorf("config file must be in Modo's working directory (as set by the PATH argument)")
×
238
        }
×
239
        return nil
9✔
240
}
241

242
func watchAndRun(args *document.Config, command func(*document.Config) error, stop chan struct{}) error {
1✔
243
        args.RemovePostScripts()
1✔
244

1✔
245
        c := make(chan notify.EventInfo, 32)
1✔
246
        collected := make(chan []notify.EventInfo, 1)
1✔
247

1✔
248
        toWatch, err := getWatchPaths(args)
1✔
249
        if err != nil {
1✔
250
                return err
×
251
        }
×
252
        for _, w := range toWatch {
2✔
253
                if err := notify.Watch(w, c, notify.All); err != nil {
1✔
254
                        log.Fatal(err)
×
255
                }
×
256
        }
257
        defer notify.Stop(c)
1✔
258

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

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

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

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