all repos

clerk @ 1f3a17349e37c8189adb11b8d956acbf3b2080d1

missing tooling for ledger/hledger

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

Oleksandr Smirnov Oleksandr Smirnov
olexsmir@gmail.com
improve loader, 1 day 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
	loader := journal.NewLoader()
23
	if len(paths) == 0 {
24
		src, err := readStdin()
25
		if err != nil {
26
			return err
27
		}
28
		rj := loader.ResolveBytes("stdin", src)
29
		pf := rj.Occurrences[0]
30
		if len(pf.Errors) > 0 || len(pf.FileErrors) > 0 {
31
			for _, e := range pf.Errors {
32
				fmt.Fprintf(os.Stderr, "error: stdin: %s\n", e.Message)
33
			}
34
			for _, fe := range pf.FileErrors {
35
				fmt.Fprintf(os.Stderr, "error: stdin: %s\n", fe.Message)
36
			}
37
			return cli.Exit("", 1)
38
		}
39
		return c.formatFile("stdin", pf, check, diff, list, write)
40
	}
41
42
	files, err := resolvePaths(paths)
43
	if err != nil {
44
		return err
45
	}
46
47
	var hasErrors bool
48
	for _, path := range files {
49
		rj, err := loader.Resolve(path)
50
		if err != nil {
51
			fmt.Fprintf(os.Stderr, "error: %v\n", err)
52
			hasErrors = true
53
			continue
54
		}
55
		pf := rj.Occurrences[0]
56
		if len(pf.Errors) > 0 || len(pf.FileErrors) > 0 {
57
			for _, e := range pf.Errors {
58
				fmt.Fprintf(os.Stderr, "error: %s: %s\n", path, e.Message)
59
			}
60
			for _, fe := range pf.FileErrors {
61
				fmt.Fprintf(os.Stderr, "error: %s: %s\n", path, fe.Message)
62
			}
63
			hasErrors = true
64
			continue
65
		}
66
67
		if err := c.formatFile(path, pf, check, diff, list, write); err != nil {
68
			fmt.Fprintf(os.Stderr, "error: %s: %v\n", path, err)
69
			hasErrors = true
70
		}
71
	}
72
	if hasErrors {
73
		return cli.Exit("", 1)
74
	}
75
	return nil
76
}
77
78
func (c *Cli) formatFile(path string, pf *journal.ParsedFile, check, wantDiff, list, write bool) error {
79
	var buf bytes.Buffer
80
	if err := printer.Fprint(&buf, pf.Ast); err != nil {
81
		return fmt.Errorf("format: %w", err)
82
	}
83
	formatted := buf.Bytes()
84
	changed := !bytes.Equal(pf.Src, formatted)
85
86
	switch {
87
	case check:
88
		if changed {
89
			return fmt.Errorf("not formatted")
90
		}
91
	case list:
92
		if changed {
93
			fmt.Println(path)
94
		}
95
	case wantDiff:
96
		if changed {
97
			diffLines(path, pf.Src, formatted)
98
		}
99
	case write:
100
		if changed {
101
			if err := os.WriteFile(path, formatted, 0o644); err != nil {
102
				return fmt.Errorf("write %s: %w", path, err)
103
			}
104
		}
105
	default:
106
		if _, err := os.Stdout.Write(formatted); err != nil {
107
			return fmt.Errorf("write stdout: %w", err)
108
		}
109
	}
110
	return nil
111
}
112
113
func diffLines(path string, src, formatted []byte) {
114
	fmt.Printf("--- %s\n+++ %s\n", path, path)
115
	srcLines := bytes.Split(src, []byte("\n"))
116
	fmtLines := bytes.Split(formatted, []byte("\n"))
117
	lines := max(len(fmtLines), len(srcLines))
118
	for i := range lines {
119
		var sLine, fLine []byte
120
		if i < len(srcLines) {
121
			sLine = srcLines[i]
122
		}
123
		if i < len(fmtLines) {
124
			fLine = fmtLines[i]
125
		}
126
		if !bytes.Equal(sLine, fLine) {
127
			if len(sLine) > 0 {
128
				fmt.Printf("-%s\n", sLine)
129
			} else {
130
				fmt.Println("-")
131
			}
132
			if len(fLine) > 0 {
133
				fmt.Printf("+%s\n", fLine)
134
			} else {
135
				fmt.Println("+")
136
			}
137
		}
138
	}
139
}