all repos

clerk @ 17c786e0f3f26481a635695160d516a7039a3bef

missing tooling for ledger/hledger

clerk/internal/cli/cmd_format.go (view raw)

Oleksandr Smirnov Oleksandr Smirnov
olexsmir@gmail.com
feat: config (#6), 1 month ago
1
package cli
2
3
import (
4
	"bytes"
5
	"context"
6
	"fmt"
7
	"os"
8
9
	"github.com/urfave/cli/v3"
10
11
	"olexsmir.xyz/clerk/journal"
12
	"olexsmir.xyz/clerk/journal/printer"
13
)
14
15
func (c *Cli) formatAction(ctx context.Context, cmd *cli.Command) error {
16
	check := cmd.Bool("check")
17
	diff := cmd.Bool("diff")
18
	list := cmd.Bool("list")
19
	write := cmd.Bool("write")
20
	paths := cmd.StringArgs("journals")
21
22
	sets, warns, err := loadConfig(cmd)
23
	if err != nil {
24
		return err
25
	}
26
	for i := range warns {
27
		fmt.Fprintf(os.Stderr, "warning: %s\n", warns[i])
28
	}
29
30
	loader := journal.NewLoader()
31
	if len(paths) == 0 {
32
		src, rerr := readStdin()
33
		if rerr != nil {
34
			return rerr
35
		}
36
		rj := loader.ResolveBytes("stdin", src)
37
		pf := rj.Occurrences[0]
38
		if len(pf.Errors) > 0 || len(pf.FileErrors) > 0 {
39
			for _, e := range pf.Errors {
40
				fmt.Fprintf(os.Stderr, "error: stdin: %s\n", e.Message)
41
			}
42
			for _, fe := range pf.FileErrors {
43
				fmt.Fprintf(os.Stderr, "error: stdin: %s\n", fe.Message)
44
			}
45
			return cli.Exit("", 1)
46
		}
47
		return c.formatFile("stdin", pf, sets.Format, check, diff, list, write)
48
	}
49
50
	files, err := resolvePaths(paths)
51
	if err != nil {
52
		return err
53
	}
54
55
	var hasErrors bool
56
	for _, path := range files {
57
		rj, err := loader.Resolve(path)
58
		if err != nil {
59
			fmt.Fprintf(os.Stderr, "error: %v\n", err)
60
			hasErrors = true
61
			continue
62
		}
63
		pf := rj.Occurrences[0]
64
		if len(pf.Errors) > 0 || len(pf.FileErrors) > 0 {
65
			for _, e := range pf.Errors {
66
				fmt.Fprintf(os.Stderr, "error: %s: %s\n", path, e.Message)
67
			}
68
			for _, fe := range pf.FileErrors {
69
				fmt.Fprintf(os.Stderr, "error: %s: %s\n", path, fe.Message)
70
			}
71
			hasErrors = true
72
			continue
73
		}
74
75
		if err := c.formatFile(path, pf, sets.Format, check, diff, list, write); err != nil {
76
			fmt.Fprintf(os.Stderr, "error: %s: %v\n", path, err)
77
			hasErrors = true
78
		}
79
	}
80
	if hasErrors {
81
		return cli.Exit("", 1)
82
	}
83
	return nil
84
}
85
86
func (c *Cli) formatFile(path string, pf *journal.ParsedFile, cfg printer.Config, check, wantDiff, list, write bool) error {
87
	var buf bytes.Buffer
88
	if err := cfg.Fprint(&buf, pf.Ast); err != nil {
89
		return fmt.Errorf("format: %w", err)
90
	}
91
	formatted := buf.Bytes()
92
	changed := !bytes.Equal(pf.Src, formatted)
93
94
	switch {
95
	case check:
96
		if changed {
97
			return fmt.Errorf("not formatted")
98
		}
99
	case list:
100
		if changed {
101
			fmt.Println(path)
102
		}
103
	case wantDiff:
104
		if changed {
105
			diffLines(path, pf.Src, formatted)
106
		}
107
	case write:
108
		if changed {
109
			if err := os.WriteFile(path, formatted, 0o644); err != nil {
110
				return fmt.Errorf("write %s: %w", path, err)
111
			}
112
		}
113
	default:
114
		if _, err := os.Stdout.Write(formatted); err != nil {
115
			return fmt.Errorf("write stdout: %w", err)
116
		}
117
	}
118
	return nil
119
}
120
121
func diffLines(path string, src, formatted []byte) {
122
	fmt.Printf("--- %s\n+++ %s\n", path, path)
123
	srcLines := bytes.Split(src, []byte("\n"))
124
	fmtLines := bytes.Split(formatted, []byte("\n"))
125
	lines := max(len(fmtLines), len(srcLines))
126
	for i := range lines {
127
		var sLine, fLine []byte
128
		if i < len(srcLines) {
129
			sLine = srcLines[i]
130
		}
131
		if i < len(fmtLines) {
132
			fLine = fmtLines[i]
133
		}
134
		if !bytes.Equal(sLine, fLine) {
135
			if len(sLine) > 0 {
136
				fmt.Printf("-%s\n", sLine)
137
			} else {
138
				fmt.Println("-")
139
			}
140
			if len(fLine) > 0 {
141
				fmt.Printf("+%s\n", fLine)
142
			} else {
143
				fmt.Println("+")
144
			}
145
		}
146
	}
147
}