v2 / vlib / encoding / txtar / txtar.v
94 lines · 85 sloc · 2.73 KB · 008aaad99981918c51194d7aaaaaccb4c258f244
Raw
1module txtar
2
3// Ported from https://cs.opensource.google/go/x/tools/+/master:txtar/archive.go
4import strings
5
6// Archive is a collection of files
7pub struct Archive {
8pub mut:
9 comment string // the start of the archive; contains potentially multiple lines, before the files
10 files []File // a series of files
11}
12
13// File is a single file in an Archive. Each starting with a `-- FILENAME --` line.
14pub struct File {
15pub mut:
16 path string // 'abc/def.v' from the `-- abc/def.v --` header
17 content string // everything after that, till the next `-- name --` line.
18}
19
20// str returns a string representation of the `a` Archive.
21// It is suitable for storing in a text file.
22// It is also in the same format, that txtar.parse/1 expects.
23pub fn (a &Archive) str() string {
24 mut sb := strings.new_builder(a.comment.len + 200 * a.files.len)
25 sb.write_string(fix_nl(a.comment))
26 for f in a.files {
27 sb.write_string('-- ${f.path} --\n')
28 sb.write_string(fix_nl(f.content))
29 }
30 return sb.str()
31}
32
33// parse parses the serialized form of an Archive.
34// The returned Archive holds slices of data.
35pub fn parse(content string) Archive {
36 mut a := Archive{}
37 comment, mut name, mut data := find_file_marker(content)
38 a.comment = comment
39 for name != '' {
40 mut f := File{name, ''}
41 f.content, name, data = find_file_marker(data)
42 a.files << f
43 }
44 return a
45}
46
47const nlm = '\n-- '
48const mstart = '-- '
49const mend = ' --'
50
51// find_file_marker finds the next file marker in data, extracts the file name,
52// and returns the data before the marker, the file name, and the data after the marker.
53// If there is no next marker, find_file_marker returns fixNL(data), '', ''.
54fn find_file_marker(data string) (string, string, string) {
55 mut i := 0
56 for i < data.len {
57 name, after := is_marker(data[i..])
58 if name != '' {
59 return data[..i], name, after
60 }
61 j := data[i..].index(nlm) or { return fix_nl(data), '', '' }
62 i += j + 1 // positioned at start of new possible marker
63 }
64 return '', '', ''
65}
66
67// is_marker checks whether the data begins with a file marker line.
68// If so, it returns the name from the line, and the data after the line.
69// Otherwise it returns name == "".
70fn is_marker(data string) (string, string) {
71 if !data.starts_with(mstart) {
72 return '', ''
73 }
74 mut ndata := data
75 mut after := ''
76 i := data.index_u8(`\n`)
77 if i >= 0 {
78 ndata, after = data[..i], data[i + 1..]
79 }
80 if !(ndata.ends_with(mend) && ndata.len >= mstart.len + mend.len) {
81 return '', ''
82 }
83 name := ndata[mstart.len..ndata.len - mend.len].trim_space()
84 return name, after
85}
86
87// fix_nl returns the data, if it is empty, or if it ends in \n.
88// Otherwise it returns data + a final \n added.
89fn fix_nl(data string) string {
90 if data.len == 0 || data[data.len - 1] == `\n` {
91 return data
92 }
93 return '${data}\n'
94}
95