all repos

clerk @ 5491069

missing tooling for ledger/hledger

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

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