go.mway.dev/x@v0.0.0-20240520034138-950aede9a3fb/archive/extract/options.go (about)

     1  // Copyright (c) 2024 Matt Way
     2  //
     3  // Permission is hereby granted, free of charge, to any person obtaining a copy
     4  // of this software and associated documentation files (the "Software"), to
     5  // deal in the Software without restriction, including without limitation the
     6  // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
     7  // sell copies of the Software, and to permit persons to whom the Software is
     8  // furnished to do so, subject to the following conditions:
     9  //
    10  // The above copyright notice and this permission notice shall be included in
    11  // all copies or substantial portions of the Software.
    12  //
    13  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    14  // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    15  // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    16  // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    17  // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
    18  // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
    19  // IN THE THE SOFTWARE.
    20  
    21  package extract
    22  
    23  import (
    24  	"context"
    25  	"io"
    26  	"maps"
    27  	"slices"
    28  )
    29  
    30  var (
    31  	_ Option = Callback(nil)
    32  	_ Option = Options{}
    33  )
    34  
    35  // A Callback is a function called by [Extract] once an archive has been
    36  // extracted. When called, the current working directory is the extract
    37  // destination, and is also passed as the path parameter.
    38  type Callback func(ctx context.Context, path string) error
    39  
    40  func (c Callback) apply(dst *Options) {
    41  	dst.Callback = c
    42  }
    43  
    44  // Options configure the behavior of [Extract].
    45  type Options struct {
    46  	Callback     Callback
    47  	Output       io.Writer
    48  	StripPrefix  string
    49  	IncludePaths map[string]string
    50  	ExcludePaths []string
    51  	Delete       bool
    52  }
    53  
    54  // With returns a new [Options] with opts merged on top of o.
    55  func (o Options) With(opts ...Option) Options {
    56  	for _, opt := range opts {
    57  		opt.apply(&o)
    58  	}
    59  	return o
    60  }
    61  
    62  func (o Options) apply(dst *Options) {
    63  	if o.Callback != nil {
    64  		dst.Callback = o.Callback
    65  	}
    66  
    67  	if o.Output != nil {
    68  		dst.Output = o.Output
    69  	}
    70  
    71  	if len(o.StripPrefix) > 0 {
    72  		dst.StripPrefix = o.StripPrefix
    73  	}
    74  
    75  	if len(o.IncludePaths) > 0 {
    76  		dst.IncludePaths = maps.Clone(o.IncludePaths)
    77  	}
    78  
    79  	if len(o.ExcludePaths) > 0 {
    80  		dst.ExcludePaths = slices.Clone(o.ExcludePaths)
    81  	}
    82  
    83  	if o.Delete {
    84  		dst.Delete = true
    85  	}
    86  }
    87  
    88  // An Option configures the behavior of [Extract].
    89  type Option interface {
    90  	apply(*Options)
    91  }
    92  
    93  // Output returns a new [Option] that configures [Extract] to write any output
    94  // to the given writer.
    95  func Output(output io.Writer) Option {
    96  	return optionFunc(func(dst *Options) {
    97  		dst.Output = output
    98  	})
    99  }
   100  
   101  // StripPrefix returns a new [Option] that configures [Extract] to root all
   102  // extraction at the given prefix. Note that paths that are not descendants of
   103  // the given prefix will not be extracted.
   104  func StripPrefix(prefix string) Option {
   105  	return optionFunc(func(dst *Options) {
   106  		dst.StripPrefix = prefix
   107  	})
   108  }
   109  
   110  // IncludePaths returns a new [Option] that configures [Extract] to only
   111  // consider the given paths for extraction. The given map's keys should be
   112  // relative to either the archive root or the stripped prefix, and may be
   113  // globs; the map's values are optional and specify non-default destinations
   114  // for any path(s) matched by the corresponding key.
   115  func IncludePaths(paths map[string]string) Option {
   116  	return optionFunc(func(dst *Options) {
   117  		dst.IncludePaths = maps.Clone(paths)
   118  	})
   119  }
   120  
   121  // ExcludePaths returns a new [Option] that configures [Extract] to exclude any
   122  // matching paths from extraction. The given paths should be relative to either
   123  // the archive root or the stripped prefix, and may be globs.
   124  func ExcludePaths(paths []string) Option {
   125  	return optionFunc(func(dst *Options) {
   126  		dst.ExcludePaths = slices.Clone(paths)
   127  	})
   128  }
   129  
   130  // Delete returns a new [Option] that configures [Extract] to delete any
   131  // archive directories from the destination before extracting them.
   132  func Delete(del bool) Option {
   133  	return optionFunc(func(dst *Options) {
   134  		dst.Delete = del
   135  	})
   136  }
   137  
   138  type optionFunc func(*Options)
   139  
   140  func (f optionFunc) apply(dst *Options) {
   141  	f(dst)
   142  }