@@ -42,13 +42,14 @@ func RegisterFormat(format Format) {
4242func Identify (ctx context.Context , filename string , stream io.Reader ) (Format , io.Reader , error ) {
4343 var compression Compression
4444 var archival Archival
45+ var extraction Extraction
4546
4647 rewindableStream , err := newRewindReader (stream )
4748 if err != nil {
4849 return nil , nil , err
4950 }
5051
51- // try compression format first, since that's the outer "layer"
52+ // try compression format first, since that's the outer "layer" if combined
5253 for name , format := range formats {
5354 cf , isCompression := format .(Compression )
5455 if ! isCompression {
@@ -68,10 +69,11 @@ func Identify(ctx context.Context, filename string, stream io.Reader) (Format, i
6869 }
6970 }
7071
71- // try archive format next
72+ // try archival and extraction format next
7273 for name , format := range formats {
73- af , isArchive := format .(Archival )
74- if ! isArchive {
74+ ar , isArchive := format .(Archival )
75+ ex , isExtract := format .(Extraction )
76+ if ! isArchive && ! isExtract {
7577 continue
7678 }
7779
@@ -81,20 +83,23 @@ func Identify(ctx context.Context, filename string, stream io.Reader) (Format, i
8183 }
8284
8385 if matchResult .Matched () {
84- archival = af
86+ archival = ar
87+ extraction = ex
8588 break
8689 }
8790 }
8891
89- // the stream should be rewound by identifyOne
92+ // the stream should be rewound by identifyOne; then return the most specific type of match
9093 bufferedStream := rewindableStream .reader ()
9194 switch {
92- case compression != nil && archival == nil :
95+ case compression != nil && archival == nil && extraction == nil :
9396 return compression , bufferedStream , nil
94- case compression == nil && archival != nil :
97+ case compression == nil && archival != nil && extraction == nil :
9598 return archival , bufferedStream , nil
96- case compression != nil && archival != nil :
97- return CompressedArchive {compression , archival }, bufferedStream , nil
99+ case compression == nil && archival == nil && extraction != nil :
100+ return extraction , bufferedStream , nil
101+ case archival != nil || extraction != nil :
102+ return Archive {compression , archival , extraction }, bufferedStream , nil
98103 default :
99104 return nil , bufferedStream , NoMatch
100105 }
@@ -161,44 +166,44 @@ func readAtMost(stream io.Reader, n int) ([]byte, error) {
161166 return nil , err
162167}
163168
164- // CompressedArchive combines a compression format on top of an archive
165- // format (e.g. "tar.gz") and provides both functionalities in a single
166- // type. It ensures that archive functions are wrapped by compressors and
169+ // Archive represents an archive which may be compressed at the outer layer.
170+ // It combines a compression format on top of an archive/extraction
171+ // format (e.g. ".tar.gz") and provides both functionalities in a single
172+ // type. It ensures that archival functions are wrapped by compressors and
167173// decompressors. However, compressed archives have some limitations; for
168174// example, files cannot be inserted/appended because of complexities with
169175// modifying existing compression state (perhaps this could be overcome,
170176// but I'm not about to try it).
171177//
172- // As this type is intended to compose compression and archive formats,
173- // both must be specified in order for this value to be valid, or its
174- // methods will return errors .
175- type CompressedArchive struct {
178+ // The embedded Archival and Extraction values are used for writing and
179+ // reading, respectively. Compression is optional and is only needed if the
180+ // format is compressed externally (for example, tar archives) .
181+ type Archive struct {
176182 Compression
177183 Archival
184+ Extraction
178185}
179186
180- // Name returns a concatenation of the archive format name
181- // and the compression format name.
182- func (caf CompressedArchive ) Extension () string {
183- if caf .Compression == nil && caf .Archival == nil {
184- panic ("missing both compression and archive formats" )
185- }
187+ // Name returns a concatenation of the archive and compression format extensions.
188+ func (ar Archive ) Extension () string {
186189 var name string
187- if caf .Archival != nil {
188- name += caf .Archival .Extension ()
190+ if ar .Archival != nil {
191+ name += ar .Archival .Extension ()
192+ } else if ar .Extraction != nil {
193+ name += ar .Extraction .Extension ()
189194 }
190- if caf .Compression != nil {
191- name += caf .Compression .Extension ()
195+ if ar .Compression != nil {
196+ name += ar .Compression .Extension ()
192197 }
193198 return name
194199}
195200
196- // Match matches if the input matches both the compression and archive format.
197- func (caf CompressedArchive ) Match (ctx context.Context , filename string , stream io.Reader ) (MatchResult , error ) {
201+ // Match matches if the input matches both the compression and archival/extraction format.
202+ func (ar Archive ) Match (ctx context.Context , filename string , stream io.Reader ) (MatchResult , error ) {
198203 var conglomerate MatchResult
199204
200- if caf .Compression != nil {
201- matchResult , err := caf .Compression .Match (ctx , filename , stream )
205+ if ar .Compression != nil {
206+ matchResult , err := ar .Compression .Match (ctx , filename , stream )
202207 if err != nil {
203208 return MatchResult {}, err
204209 }
@@ -208,7 +213,7 @@ func (caf CompressedArchive) Match(ctx context.Context, filename string, stream
208213
209214 // wrap the reader with the decompressor so we can
210215 // attempt to match the archive by reading the stream
211- rc , err := caf .Compression .OpenReader (stream )
216+ rc , err := ar .Compression .OpenReader (stream )
212217 if err != nil {
213218 return matchResult , err
214219 }
@@ -218,8 +223,8 @@ func (caf CompressedArchive) Match(ctx context.Context, filename string, stream
218223 conglomerate = matchResult
219224 }
220225
221- if caf .Archival != nil {
222- matchResult , err := caf .Archival .Match (ctx , filename , stream )
226+ if ar .Archival != nil {
227+ matchResult , err := ar .Archival .Match (ctx , filename , stream )
223228 if err != nil {
224229 return MatchResult {}, err
225230 }
@@ -234,26 +239,32 @@ func (caf CompressedArchive) Match(ctx context.Context, filename string, stream
234239}
235240
236241// Archive adds files to the output archive while compressing the result.
237- func (caf CompressedArchive ) Archive (ctx context.Context , output io.Writer , files []FileInfo ) error {
238- if caf .Compression != nil {
239- wc , err := caf .Compression .OpenWriter (output )
242+ func (ar Archive ) Archive (ctx context.Context , output io.Writer , files []FileInfo ) error {
243+ if ar .Archival == nil {
244+ return fmt .Errorf ("no archival format" )
245+ }
246+ if ar .Compression != nil {
247+ wc , err := ar .Compression .OpenWriter (output )
240248 if err != nil {
241249 return err
242250 }
243251 defer wc .Close ()
244252 output = wc
245253 }
246- return caf .Archival .Archive (ctx , output , files )
254+ return ar .Archival .Archive (ctx , output , files )
247255}
248256
249257// ArchiveAsync adds files to the output archive while compressing the result asynchronously.
250- func (caf CompressedArchive ) ArchiveAsync (ctx context.Context , output io.Writer , jobs <- chan ArchiveAsyncJob ) error {
251- do , ok := caf .Archival .(ArchiverAsync )
258+ func (ar Archive ) ArchiveAsync (ctx context.Context , output io.Writer , jobs <- chan ArchiveAsyncJob ) error {
259+ if ar .Archival == nil {
260+ return fmt .Errorf ("no archival format" )
261+ }
262+ do , ok := ar .Archival .(ArchiverAsync )
252263 if ! ok {
253- return fmt .Errorf ("%s archive does not support async writing" , caf . Extension () )
264+ return fmt .Errorf ("%T archive does not support async writing" , ar . Archival )
254265 }
255- if caf .Compression != nil {
256- wc , err := caf .Compression .OpenWriter (output )
266+ if ar .Compression != nil {
267+ wc , err := ar .Compression .OpenWriter (output )
257268 if err != nil {
258269 return err
259270 }
@@ -264,16 +275,19 @@ func (caf CompressedArchive) ArchiveAsync(ctx context.Context, output io.Writer,
264275}
265276
266277// Extract reads files out of an archive while decompressing the results.
267- func (caf CompressedArchive ) Extract (ctx context.Context , sourceArchive io.Reader , pathsInArchive []string , handleFile FileHandler ) error {
268- if caf .Compression != nil {
269- rc , err := caf .Compression .OpenReader (sourceArchive )
278+ func (ar Archive ) Extract (ctx context.Context , sourceArchive io.Reader , handleFile FileHandler ) error {
279+ if ar .Extraction == nil {
280+ return fmt .Errorf ("no extraction format" )
281+ }
282+ if ar .Compression != nil {
283+ rc , err := ar .Compression .OpenReader (sourceArchive )
270284 if err != nil {
271285 return err
272286 }
273287 defer rc .Close ()
274288 sourceArchive = rc
275289 }
276- return caf . Archival .Extract (ctx , sourceArchive , pathsInArchive , handleFile )
290+ return ar . Extraction .Extract (ctx , sourceArchive , handleFile )
277291}
278292
279293// MatchResult returns true if the format was matched either
@@ -408,8 +422,8 @@ var formats = make(map[string]Format)
408422
409423// Interface guards
410424var (
411- _ Format = (* CompressedArchive )(nil )
412- _ Archiver = (* CompressedArchive )(nil )
413- _ ArchiverAsync = (* CompressedArchive )(nil )
414- _ Extractor = (* CompressedArchive )(nil )
425+ _ Format = (* Archive )(nil )
426+ _ Archiver = (* Archive )(nil )
427+ _ ArchiverAsync = (* Archive )(nil )
428+ _ Extractor = (* Archive )(nil )
415429)
0 commit comments