forked from rwxrob/keg
-
Notifications
You must be signed in to change notification settings - Fork 0
/
model.go
202 lines (178 loc) · 5.31 KB
/
model.go
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
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
package keg
import (
"bytes"
"fmt"
"sort"
"strconv"
"strings"
"time"
"github.com/rwxrob/json"
"github.com/rwxrob/term"
)
const IsoDateFmt = `2006-01-02 15:04:05Z`
const IsoDateExpStr = `\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\dZ`
// Local contains a name to full path mapping for kegs stored locally.
type Local struct {
Name string
Path string
}
// DexEntry represents a single line in an index (usually the latest.md
// or nodes.tsv file). All three fields are always required.
type DexEntry struct {
U time.Time // updated
T string // title
N int // node id (also see ID)
}
// MarshalJSON produces JSON text that contains one DexEntry per line
// that has not been HTML escaped (unlike the default) and that uses
// a consistent DateTime format. Note that the (broken) encoding/json
// encoder is not used at all.
func (e *DexEntry) MarshalJSON() ([]byte, error) {
buf := bytes.NewBuffer(make([]byte, 0, 0))
buf.WriteRune('{')
buf.WriteString(`"U":"` + e.U.Format(IsoDateFmt) + `",`)
buf.WriteString(`"N":` + strconv.Itoa(e.N) + `,`)
buf.WriteString(`"T":"` + json.Escape(e.T) + `"`)
buf.WriteRune('}')
return buf.Bytes(), nil
}
func (e DexEntry) TSV() string {
return fmt.Sprintf("%v\t%v\t%v", e.N, e.U.Format(IsoDateFmt), e.T)
}
// ID returns the node identifier as a string instead of an integer.
// Returns an empty string if unable to parse the integer.
func (e DexEntry) ID() string { return strconv.Itoa(e.N) }
// MD returns the entry as a single Markdown list item for inclusion in
// the dex/nodex.md file:
//
// 1. Second last changed in UTC in ISO8601 (RFC3339)
// 2. Current title (always first line of README.md)
// 2. Unique node integer identifier
//
// Note that the second of last change is based on *any* file within the
// node directory changing, not just the README.md or meta files.
func (e DexEntry) MD() string {
return fmt.Sprintf(
"* %v [%v](/%v)",
e.U.Format(IsoDateFmt),
e.T, e.N,
)
}
// String implements fmt.Stringer interface as MD.
func (e DexEntry) String() string { return e.MD() }
// Asinclude returns a KEGML include link list item without the time
// suitable for creating include blocks in node files.
func (e DexEntry) AsInclude() string {
return fmt.Sprintf("* [%v](/%v)", e.T, e.N)
}
// Dex is a collection of DexEntry structs. This allows mapping methods
// for its serialization to different output formats.
type Dex []DexEntry
// MarshalJSON produces JSON text that contains one DexEntry per line
// that has not been HTML escaped (unlike the default).
func (d *Dex) MarshalJSON() ([]byte, error) {
buf := bytes.NewBuffer(make([]byte, 0, 0))
buf.WriteString("[")
for _, entry := range *d {
byt, _ := entry.MarshalJSON()
buf.Write(byt)
buf.WriteString(",\n")
}
byt := buf.Bytes()
byt[len(byt)-2] = ']'
return byt, nil
}
// String fulfills the fmt.Stringer interface as JSON. Any error returns
// a "null" string.
func (e Dex) String() string { return e.TSV() }
// MD renders the entire Dex as a Markdown list suitable for the
// standard dex/latest.md file.
func (e Dex) MD() string {
var str string
for _, entry := range e {
str += entry.MD() + "\n"
}
return str
}
// AsIncludes renders the entire Dex as a KEGML include list (markdown
// bulleted list) and cab be useful from within editing sessions to
// include from the current keg without leaving the terminal editor.
func (e Dex) AsIncludes() string {
var str string
for _, entry := range e {
str += entry.AsInclude() + "\n"
}
return str
}
// TSV renders the entire Dex as a loadable tab-separated values file.
func (e Dex) TSV() string {
var str string
for _, entry := range e {
str += entry.TSV() + "\n"
}
return str
}
// Highest returns the highest integer value identifier.
func (d Dex) Highest() int {
var highest int
for _, e := range d {
if e.N > highest {
highest = e.N
}
}
return highest
}
// Highest returns Highest as string.
func (d Dex) HighestString() string { return strconv.Itoa(d.Highest()) }
// HighestWidth returns width of highest integer identifier.
func (d Dex) HighestWidth() int { return len(d.HighestString()) }
// Pretty returns a string with pretty color string with time stamps
// rendered in more readable way.
func (d Dex) Pretty() string {
var str string
nwidth := d.HighestWidth()
for _, e := range d {
str += fmt.Sprintf(
"%v%v %v%-"+strconv.Itoa(nwidth)+"v %v%v%v\n",
term.Black, e.U.Format(`2006-01-02 15:03Z`),
term.Green, e.N,
term.White, e.T,
term.Reset,
)
}
return str
}
// PrettyLines returns Pretty but each line separate and without line
// return.
func (d Dex) PrettyLines() []string {
lines := make([]string, 0, len(d))
nwidth := d.HighestWidth()
for _, e := range d {
lines = append(lines, fmt.Sprintf(
"%v%v %v%-"+strconv.Itoa(nwidth)+"v %v%v%v",
term.Black, e.U.Format(`2006-01-02 15:03Z`),
term.Green, e.N,
term.White, e.T,
term.Reset,
))
}
return lines
}
// ByID orders the Dex from lowest to highest node ID integer.
func (e Dex) ByID() Dex {
sort.Slice(e, func(i, j int) bool {
return e[i].N < e[j].N
})
return e
}
// WithTitleText filters all nodes with titles that do not contain the text
// substring in the title.
func (e Dex) WithTitleText(keyword string) Dex {
dex := Dex{}
for _, d := range e {
if strings.Index(strings.ToLower(d.T), strings.ToLower(keyword)) >= 0 {
dex = append(dex, d)
}
}
return dex
}