1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
|
// SPDX-License-Identifier: GPL-3.0-or-later
package plan
import "time"
// jsonNote is carried in every JSON document, warning readers that the
// shape is not yet stable.
const jsonNote = "the shape of this document is unstable before krino 1.0"
// JSON is the --json document. The shape is unstable before 1.0 and says
// so in its own "note" field.
type JSON struct {
Version int `json:"version"`
Note string `json:"note"`
Dirs []JSONDir `json:"dirs"`
}
// JSONDir is one directory's plan.
type JSONDir struct {
Name string `json:"name"`
Root string `json:"root"`
Files []JSONFile `json:"files"`
Warnings []string `json:"warnings,omitempty"`
}
// JSONFile is one file's chain.
type JSONFile struct {
Rel string `json:"rel"`
Size int64 `json:"size"`
ModTime time.Time `json:"mtime"`
Steps []JSONStep `json:"steps"`
Warnings []string `json:"warnings,omitempty"`
}
// JSONStep is one step of a chain. D14: Reason is carried because the text
// plan already shows it and a machine reader should be able to see why a
// file matched too; Conflict (the rule's on-conflict policy) is deliberately
// not: it is a config detail, and its outcome is already visible through
// dst, displaces and skip.
type JSONStep struct {
Action string `json:"action"`
Rule string `json:"rule"`
Src string `json:"src"`
Dst string `json:"dst,omitempty"`
Displaces string `json:"displaces,omitempty"`
Reason string `json:"reason,omitempty"`
Skip string `json:"skip,omitempty"`
}
// actionNames maps a Kind onto its JSON action name. This is its own
// mapping, independent of Kind.String(): the display form renders "DELETE
// permanently", which must never reach a machine reader. These names match
// the log's action names in spec §9, so a later `krino log` and a --json
// plan can be grepped together.
var actionNames = map[Kind]string{
Copy: "copy",
Move: "move",
Rename: "rename",
Trash: "trash",
DeletePermanent: "delete",
}
// NewJSON builds the top-level --json document over dirs. Version and Note
// are set here, in the one place jsonNote's wording already lives: it is
// unexported, so a struct literal built outside this package would
// silently ship an empty "note" and break the document's own contract.
func NewJSON(dirs []JSONDir) JSON {
return JSON{Version: 1, Note: jsonNote, Dirs: dirs}
}
// NewJSONDir converts one directory's chains.
func NewJSONDir(name, root string, chains []Chain, warnings []string) JSONDir {
files := make([]JSONFile, 0, len(chains))
for _, ch := range chains {
steps := make([]JSONStep, 0, len(ch.Steps))
for _, s := range ch.Steps {
steps = append(steps, JSONStep{
Action: actionNames[s.Kind],
Rule: s.Rule,
Src: s.Src,
Dst: s.Dst,
Displaces: s.Displaces,
Reason: s.Reason,
Skip: s.Skip,
})
}
files = append(files, JSONFile{
Rel: ch.File.Rel,
Size: ch.File.Size,
ModTime: ch.File.ModTime,
Steps: steps,
Warnings: ch.Warnings,
})
}
return JSONDir{Name: name, Root: root, Files: files, Warnings: warnings}
}
|