carlos / todomvc-bakeoff Public

Clone
git clone https://amadan.net/carlos/todomvc-bakeoff

Plain git — no account needed to clone.

Download

Download this file

1package bakeoff
2
3import (
4 "bufio"
5 "io/fs"
6 "os"
7 "path/filepath"
8 "regexp"
9 "sort"
10 "strings"
11)
12
13// CodeStats counts non-blank lines by role, plus direct dependencies.
14type CodeStats struct{ Server, Templates, JS, Tests, Deps int }
15
16var (
17 skipDirs = map[string]bool{"dist": true, "vendor": true, ".cache": true, "node_modules": true, ".git": true, "_build": true, "dist-newstyle": true, "target": true, "zig-out": true, ".zig-cache": true, ".build": true}
18 skipExts = map[string]bool{".css": true, ".md": true, ".sum": true, ".lock": true, ".json": true, ".db": true, ".png": true, ".ico": true, ".svg": true}
19 sourceExts = map[string]bool{".go": true, ".rs": true, ".zig": true, ".ml": true, ".mli": true, ".hs": true, ".swift": true, ".mojo": true, ".đŸ”¥": true, ".rb": true, ".nim": true, ".nims": true, ".nimf": true, ".c": true, ".h": true, ".js": true, ".mjs": true, ".html": true, ".tmpl": true, ".erb": true, ".mustache": true, ".eex": true, ".sql": true}
20 templateExt = map[string]bool{".nimf": true, ".html": true, ".tmpl": true, ".erb": true, ".mustache": true, ".eex": true}
21 testName = regexp.MustCompile(`(_test\.|\.test\.|_spec\.|Spec\.hs$|Test\.hs$|Tests\.swift$)`)
22 vanName = regexp.MustCompile(`^van[-.]`)
23)
24
25// Roles a file can play in CountCode and SourceFiles.
26const (
27 roleServer = "server"
28 roleTemplate = "templates"
29 roleJS = "js"
30 roleTest = "tests"
31 roleDeps = "deps"
32)
33
34// walkCode calls f for every counted file under dir with its slash path
35// relative to dir and its role. Build output, vendored code, lockfiles,
36// CSS and docs are skipped.
37func walkCode(dir string, f func(rel, role string) error) error {
38 return filepath.WalkDir(dir, func(p string, d fs.DirEntry, err error) error {
39 if err != nil {
40 return err
41 }
42 rel, _ := filepath.Rel(dir, p)
43 rel = filepath.ToSlash(rel)
44 if d.IsDir() {
45 if p != dir && skipDirs[d.Name()] {
46 return filepath.SkipDir
47 }
48 return nil
49 }
50 if rel == "DEPS" {
51 return f(rel, roleDeps)
52 }
53 ext := strings.ToLower(filepath.Ext(p))
54 if skipExts[ext] || !sourceExts[ext] || vanName.MatchString(d.Name()) || generated(p) {
55 return nil
56 }
57 parts := strings.Split(rel, "/")
58 inDir := func(names ...string) bool {
59 for _, seg := range parts[:len(parts)-1] {
60 for _, n := range names {
61 if seg == n {
62 return true
63 }
64 }
65 }
66 return false
67 }
68 switch {
69 case inDir("test", "tests") || testName.MatchString(d.Name()):
70 return f(rel, roleTest)
71 case ext == ".js" || ext == ".mjs":
72 return f(rel, roleJS)
73 case templateExt[ext] || inDir("templates", "views"):
74 return f(rel, roleTemplate)
75 }
76 return f(rel, roleServer)
77 })
78}
79
80// CountCode counts non-blank lines under dir by role; DEPS counts as Deps.
81func CountCode(dir string) (CodeStats, error) {
82 var c CodeStats
83 err := walkCode(dir, func(rel, role string) error {
84 n, err := lines(filepath.Join(dir, rel))
85 if err != nil {
86 return err
87 }
88 switch role {
89 case roleServer:
90 c.Server += n
91 case roleTemplate:
92 c.Templates += n
93 case roleJS:
94 c.JS += n
95 case roleTest:
96 c.Tests += n
97 case roleDeps:
98 c.Deps = n
99 }
100 return nil
101 })
102 return c, err
103}
104
105// SourceFiles lists the app's own server, template and JS files (no tests),
106// sorted: what a reader of the app has to read.
107func SourceFiles(dir string) ([]string, error) {
108 var out []string
109 err := walkCode(dir, func(rel, role string) error {
110 if role == roleServer || role == roleTemplate || role == roleJS {
111 out = append(out, rel)
112 }
113 return nil
114 })
115 sort.Strings(out)
116 return out, err
117}
118
119func lines(path string) (int, error) {
120 f, err := os.Open(path)
121 if err != nil {
122 return 0, err
123 }
124 defer f.Close()
125 n := 0
126 sc := bufio.NewScanner(f)
127 sc.Buffer(make([]byte, 1<<20), 1<<24)
128 for sc.Scan() {
129 if strings.TrimSpace(sc.Text()) != "" {
130 n++
131 }
132 }
133 return n, sc.Err()
134}
135
136var generatedMark = regexp.MustCompile(`(?i)(\bgenerated\b|do not edit)`)
137
138// generated reports whether a file says, in its first three lines, that a
139// tool wrote it. Such files are copies (embedded assets, say), not code
140// anyone wrote, so they're neither counted nor shown to the scorer.
141func generated(path string) bool {
142 f, err := os.Open(path)
143 if err != nil {
144 return false
145 }
146 defer f.Close()
147 sc := bufio.NewScanner(f)
148 sc.Buffer(make([]byte, 1<<20), 1<<24)
149 for i := 0; i < 3 && sc.Scan(); i++ {
150 if generatedMark.MatchString(sc.Text()) {
151 return true
152 }
153 }
154 return false
155}
156