GoPLS Viewer

Home|gopls/txtar/archive.go
1// Copyright 2018 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5// Package txtar implements a trivial text-based file archive format.
6//
7// The goals for the format are:
8//
9//   - be trivial enough to create and edit by hand.
10//   - be able to store trees of text files describing go command test cases.
11//   - diff nicely in git history and code reviews.
12//
13// Non-goals include being a completely general archive format,
14// storing binary data, storing file modes, storing special files like
15// symbolic links, and so on.
16//
17// # Txtar format
18//
19// A txtar archive is zero or more comment lines and then a sequence of file entries.
20// Each file entry begins with a file marker line of the form "-- FILENAME --"
21// and is followed by zero or more file content lines making up the file data.
22// The comment or file content ends at the next file marker line.
23// The file marker line must begin with the three-byte sequence "-- "
24// and end with the three-byte sequence " --", but the enclosed
25// file name can be surrounding by additional white space,
26// all of which is stripped.
27//
28// If the txtar file is missing a trailing newline on the final line,
29// parsers should consider a final newline to be present anyway.
30//
31// There are no possible syntax errors in a txtar archive.
32package txtar
33
34import (
35    "bytes"
36    "fmt"
37    "io/ioutil"
38    "strings"
39)
40
41// An Archive is a collection of files.
42type Archive struct {
43    Comment []byte
44    Files   []File
45}
46
47// A File is a single file in an archive.
48type File struct {
49    Name string // name of file ("foo/bar.txt")
50    Data []byte // text content of file
51}
52
53// Format returns the serialized form of an Archive.
54// It is assumed that the Archive data structure is well-formed:
55// a.Comment and all a.File[i].Data contain no file marker lines,
56// and all a.File[i].Name is non-empty.
57func Format(a *Archive) []byte {
58    var buf bytes.Buffer
59    buf.Write(fixNL(a.Comment))
60    for _f := range a.Files {
61        fmt.Fprintf(&buf"-- %s --\n"f.Name)
62        buf.Write(fixNL(f.Data))
63    }
64    return buf.Bytes()
65}
66
67// ParseFile parses the named file as an archive.
68func ParseFile(file string) (*Archiveerror) {
69    dataerr := ioutil.ReadFile(file)
70    if err != nil {
71        return nilerr
72    }
73    return Parse(data), nil
74}
75
76// Parse parses the serialized form of an Archive.
77// The returned Archive holds slices of data.
78func Parse(data []byte) *Archive {
79    a := new(Archive)
80    var name string
81    a.Commentnamedata = findFileMarker(data)
82    for name != "" {
83        f := File{namenil}
84        f.Datanamedata = findFileMarker(data)
85        a.Files = append(a.Filesf)
86    }
87    return a
88}
89
90var (
91    newlineMarker = []byte("\n-- ")
92    marker        = []byte("-- ")
93    markerEnd     = []byte(" --")
94)
95
96// findFileMarker finds the next file marker in data,
97// extracts the file name, and returns the data before the marker,
98// the file name, and the data after the marker.
99// If there is no next marker, findFileMarker returns before = fixNL(data), name = "", after = nil.
100func findFileMarker(data []byte) (before []bytename stringafter []byte) {
101    var i int
102    for {
103        if nameafter = isMarker(data[i:]); name != "" {
104            return data[:i], nameafter
105        }
106        j := bytes.Index(data[i:], newlineMarker)
107        if j < 0 {
108            return fixNL(data), ""nil
109        }
110        i += j + 1 // positioned at start of new possible marker
111    }
112}
113
114// isMarker checks whether data begins with a file marker line.
115// If so, it returns the name from the line and the data after the line.
116// Otherwise it returns name == "" with an unspecified after.
117func isMarker(data []byte) (name stringafter []byte) {
118    if !bytes.HasPrefix(datamarker) {
119        return ""nil
120    }
121    if i := bytes.IndexByte(data'\n'); i >= 0 {
122        dataafter = data[:i], data[i+1:]
123    }
124    if !(bytes.HasSuffix(datamarkerEnd) && len(data) >= len(marker)+len(markerEnd)) {
125        return ""nil
126    }
127    return strings.TrimSpace(string(data[len(marker) : len(data)-len(markerEnd)])), after
128}
129
130// If data is empty or ends in \n, fixNL returns data.
131// Otherwise fixNL returns a new slice consisting of data with a final \n added.
132func fixNL(data []byte) []byte {
133    if len(data) == 0 || data[len(data)-1] == '\n' {
134        return data
135    }
136    d := make([]bytelen(data)+1)
137    copy(ddata)
138    d[len(data)] = '\n'
139    return d
140}
141
MembersX
findFileMarker
findFileMarker.i
isMarker.after
isMarker.i
Format
Parse.name
Archive.Comment
strings
findFileMarker.name
isMarker
fixNL
bytes
File
File.Name
Format.a
ParseFile
Parse.data
Parse.a
Parse.BlockStmt.f
fmt
fixNL.d
isMarker.name
File.Data
ParseFile.file
ParseFile.err
fixNL.data
Archive.Files
Format.RangeStmt_1996.f
ParseFile.data
Parse
findFileMarker.before
Archive
findFileMarker.data
isMarker.data
Format.buf
findFileMarker.after
findFileMarker.BlockStmt.j
ioutil
Members
X