| 1 | module txtar |
| 2 | |
| 3 | // Ported from https://cs.opensource.google/go/x/tools/+/master:txtar/archive.go |
| 4 | import strings |
| 5 | |
| 6 | // Archive is a collection of files |
| 7 | pub struct Archive { |
| 8 | pub 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. |
| 14 | pub struct File { |
| 15 | pub 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. |
| 23 | pub 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. |
| 35 | pub 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 | |
| 47 | const nlm = '\n-- ' |
| 48 | const mstart = '-- ' |
| 49 | const 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), '', ''. |
| 54 | fn 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 == "". |
| 70 | fn 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. |
| 89 | fn 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 | |