-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy patharchive.go
142 lines (116 loc) · 2.7 KB
/
archive.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
package assets
import (
"archive/tar"
"archive/zip"
"bytes"
"compress/gzip"
"errors"
"io"
"io/ioutil"
"regexp"
)
// ArchiveFormat enumerates archive formats.
type ArchiveFormat int
const (
// Zip is the zip file format.
Zip = iota
// TarGz is the tar.gz file format.
TarGz
)
var (
// ErrArchiveUnknown is returned when an invalid archive format is specified
ErrArchiveUnknown = errors.New("unknown archive format")
)
// PathMapper specifies a function that is executed on all files in the archive.
// The mapper receives the full path to each file in the archive and returns
// the path to use in the asset file system. If "" is returned, the file is
// dropped.
type PathMapper func(string) string
// ReMap returns a PathMapper that compares file paths to the input pattern
// and maps matches to the replacement string (see Regexp.ReplaceAllString).
func ReMap(pattern string, replacement string) PathMapper {
re := regexp.MustCompile(pattern)
return func(filePath string) string {
if !re.MatchString(filePath) {
return ""
}
return re.ReplaceAllString(filePath, replacement)
}
}
// Archive describes an archive format for the asset source.
type Archive struct {
Format ArchiveFormat
PathMapper PathMapper
}
func processArchive(arch *Archive, data []byte) ([]*file, error) {
switch arch.Format {
case Zip:
return processZip(arch, data)
case TarGz:
return processTarGz(arch, data)
default:
return nil, ErrArchiveUnknown
}
}
func processZip(arch *Archive, data []byte) ([]*file, error) {
r, err := zip.NewReader(bytes.NewReader(data), int64(len(data)))
if err != nil {
return nil, err
}
files := []*file{}
for _, fh := range r.File {
if fh.FileInfo().IsDir() {
continue
}
fp := mapPath(arch.PathMapper, fh.Name)
if fp == "" {
continue
}
fr, err := fh.Open()
if err != nil {
return nil, err
}
fdata, err := ioutil.ReadAll(fr)
if err != nil {
return nil, err
}
files = append(files, &file{fp, fdata, fh.ModTime()})
}
return files, nil
}
func processTarGz(arch *Archive, data []byte) ([]*file, error) {
zr, err := gzip.NewReader(bytes.NewReader(data))
if err != nil {
return nil, err
}
r := tar.NewReader(zr)
files := []*file{}
for {
h, err := r.Next()
if err == io.EOF {
break
}
if err != nil {
return nil, err
}
if h.Typeflag != tar.TypeReg && h.Typeflag != tar.TypeRegA {
continue
}
fp := mapPath(arch.PathMapper, h.Name)
if fp == "" {
continue
}
fdata, err := ioutil.ReadAll(r)
if err != nil {
return nil, err
}
files = append(files, &file{fp, fdata, h.ModTime})
}
return files, nil
}
func mapPath(mapper PathMapper, path string) string {
if mapper == nil {
return path
}
return mapper(path)
}