github.com/filecoin-project/bacalhau@v0.3.23-0.20230228154132-45c989550ace/cmd/bacalhau/flags.go (about)

     1  package bacalhau
     2  
     3  import (
     4  	"fmt"
     5  	"strings"
     6  
     7  	"github.com/filecoin-project/bacalhau/pkg/job"
     8  	"github.com/filecoin-project/bacalhau/pkg/logger"
     9  	"github.com/filecoin-project/bacalhau/pkg/model"
    10  	"github.com/filecoin-project/bacalhau/pkg/storage/url/urldownload"
    11  	"github.com/spf13/pflag"
    12  )
    13  
    14  // A Parser is a function that can convert a string into a native object.
    15  type Parser[T any] func(string) (T, error)
    16  
    17  // A KeyValueParser is like a Parser except that it returns two values
    18  // representing a key and a value.
    19  type KeyValueParser[K comparable, V any] func(string) (K, V, error)
    20  
    21  // A Stringer is a function that can convert a native object into a string.
    22  type Stringer[T any] func(*T) string
    23  
    24  // A KeyValueStringer is like a Stringer except that it converts native objects
    25  // representing a key and a value into a string.
    26  type KeyValueStringer[K comparable, V any] func(*K, *V) string
    27  
    28  // A ValueFlag is a pflag.Value that knows how to take a command line value
    29  // represented as a string and set it as a native object into a struct.
    30  type ValueFlag[T any] struct {
    31  	// A pointer to a variable that will be set by this flag.
    32  	// This will be a pointer to some struct value we want to set.
    33  	value *T
    34  
    35  	// A Parser to turn the command line string into a native value.
    36  	parser Parser[T]
    37  
    38  	// A Stringer to turn the default value for the flag back into a native
    39  	// string, to be printed as help.
    40  	stringer Stringer[T]
    41  
    42  	// How the value should be described in the help string. (e.g. string, int)
    43  	typeStr string
    44  }
    45  
    46  // Set implements pflag.Value
    47  func (s *ValueFlag[T]) Set(input string) error {
    48  	value, err := s.parser(input)
    49  	*s.value = value
    50  	return err
    51  }
    52  
    53  // String implements pflag.Value
    54  func (s *ValueFlag[T]) String() string {
    55  	return s.stringer(s.value)
    56  }
    57  
    58  // Type implements pflag.Value
    59  func (s *ValueFlag[T]) Type() string {
    60  	return s.typeStr
    61  }
    62  
    63  var _ pflag.Value = (*ValueFlag[int])(nil)
    64  
    65  // An ArrayValueFlag is like a ValueFlag except it will add the command line
    66  // value into a slice of values, and hence can be used for flags that are meant
    67  // to appear multiple times.
    68  type ArrayValueFlag[T any] struct {
    69  	// A pointer to a variable that will be set by this flag.
    70  	// This will be a pointer to some struct value we want to set.
    71  	value *[]T
    72  
    73  	// A Parser to turn the command line string into a native value.
    74  	parser Parser[T]
    75  
    76  	// A Stringer to turn the default value for the flag back into a native
    77  	// string, to be printed as help.
    78  	stringer Stringer[T]
    79  
    80  	// How the value should be described in the help string. (e.g. string, int)
    81  	typeStr string
    82  }
    83  
    84  // Set implements pflag.Value
    85  func (s *ArrayValueFlag[T]) Set(input string) error {
    86  	value, err := s.parser(input)
    87  	*s.value = append(*s.value, value)
    88  	return err
    89  }
    90  
    91  // String implements pflag.Value
    92  func (s *ArrayValueFlag[T]) String() string {
    93  	strs := make([]string, 0, len(*s.value))
    94  	for _, spec := range *s.value {
    95  		spec := spec
    96  		strs = append(strs, s.stringer(&spec))
    97  	}
    98  	return strings.Join(strs, ", ")
    99  }
   100  
   101  // Type implements pflag.Value
   102  func (s *ArrayValueFlag[T]) Type() string {
   103  	return s.typeStr
   104  }
   105  
   106  var _ pflag.Value = (*ArrayValueFlag[int])(nil)
   107  
   108  // A MapValueFlag is like a ValueFlag except it will add the command line
   109  // value into a map of values, and hence can be used for flags that are meant
   110  // to appear multiple times and represent a key-value structure.
   111  type MapValueFlag[K comparable, V any] struct {
   112  	// A pointer to a variable that will be set by this flag.
   113  	// This will be a pointer to some struct value we want to set.
   114  	value *map[K]V
   115  
   116  	// A Parser to turn the command line string into a native value.
   117  	parser KeyValueParser[K, V]
   118  
   119  	// A Stringer to turn the default value for the flag back into a native
   120  	// string, to be printed as help.
   121  	stringer KeyValueStringer[K, V]
   122  
   123  	// How the value should be described in the help string. (e.g. string, int)
   124  	typeStr string
   125  }
   126  
   127  // Set implements pflag.Value
   128  func (s *MapValueFlag[K, V]) Set(input string) error {
   129  	key, value, err := s.parser(input)
   130  	(*s.value)[key] = value
   131  	return err
   132  }
   133  
   134  // String implements pflag.Value
   135  func (s *MapValueFlag[K, V]) String() string {
   136  	strs := make([]string, len(*s.value))
   137  	for key, value := range *s.value {
   138  		key, value := key, value
   139  		strs = append(strs, s.stringer(&key, &value))
   140  	}
   141  	return strings.Join(strs, ", ")
   142  }
   143  
   144  // Type implements pflag.Value
   145  func (s *MapValueFlag[K, V]) Type() string {
   146  	return s.typeStr
   147  }
   148  
   149  var _ pflag.Value = (*MapValueFlag[int, int])(nil)
   150  
   151  func separatorParser(sep string) KeyValueParser[string, string] {
   152  	return func(input string) (string, string, error) {
   153  		slices := strings.Split(input, sep)
   154  		if len(slices) != 2 {
   155  			return "", "", fmt.Errorf("%q should contain exactly one %s", input, sep)
   156  		}
   157  		return slices[0], slices[1], nil
   158  	}
   159  }
   160  
   161  func parseIPFSStorageSpec(input string) (model.StorageSpec, error) {
   162  	cid, path, err := separatorParser(":")(input)
   163  	return model.StorageSpec{
   164  		StorageSource: model.StorageSourceIPFS,
   165  		CID:           cid,
   166  		Path:          path,
   167  	}, err
   168  }
   169  
   170  func storageSpecToIPFSMount(input *model.StorageSpec) string {
   171  	return fmt.Sprintf("%s:%s", input.CID, input.Path)
   172  }
   173  
   174  func NewIPFSStorageSpecArrayFlag(value *[]model.StorageSpec) *ArrayValueFlag[model.StorageSpec] {
   175  	return &ArrayValueFlag[model.StorageSpec]{
   176  		value:    value,
   177  		parser:   parseIPFSStorageSpec,
   178  		stringer: storageSpecToIPFSMount,
   179  		typeStr:  "cid:path",
   180  	}
   181  }
   182  
   183  func parseURLStorageSpec(inputURL string) (model.StorageSpec, error) {
   184  	u, err := urldownload.IsURLSupported(inputURL)
   185  	if err != nil {
   186  		return model.StorageSpec{}, err
   187  	}
   188  	return model.StorageSpec{
   189  		StorageSource: model.StorageSourceURLDownload,
   190  		URL:           u.String(),
   191  		Path:          "/inputs",
   192  	}, nil
   193  }
   194  
   195  func NewURLStorageSpecArrayFlag(value *[]model.StorageSpec) *ArrayValueFlag[model.StorageSpec] {
   196  	return &ArrayValueFlag[model.StorageSpec]{
   197  		value:    value,
   198  		parser:   parseURLStorageSpec,
   199  		stringer: func(s *model.StorageSpec) string { return s.URL },
   200  		typeStr:  "url",
   201  	}
   202  }
   203  
   204  func VerifierFlag(value *model.Verifier) *ValueFlag[model.Verifier] {
   205  	return &ValueFlag[model.Verifier]{
   206  		value:    value,
   207  		parser:   model.ParseVerifier,
   208  		stringer: func(v *model.Verifier) string { return v.String() },
   209  		typeStr:  "verifier",
   210  	}
   211  }
   212  
   213  func PublisherFlag(value *model.Publisher) *ValueFlag[model.Publisher] {
   214  	return &ValueFlag[model.Publisher]{
   215  		value:    value,
   216  		parser:   model.ParsePublisher,
   217  		stringer: func(p *model.Publisher) string { return p.String() },
   218  		typeStr:  "publisher",
   219  	}
   220  }
   221  
   222  func NetworkFlag(value *model.Network) *ValueFlag[model.Network] {
   223  	return &ValueFlag[model.Network]{
   224  		value:    value,
   225  		parser:   model.ParseNetwork,
   226  		stringer: func(n *model.Network) string { return n.String() },
   227  		typeStr:  "network-type",
   228  	}
   229  }
   230  
   231  func LoggingFlag(value *logger.LogMode) *ValueFlag[logger.LogMode] {
   232  	return &ValueFlag[logger.LogMode]{
   233  		value:    value,
   234  		parser:   logger.ParseLogMode,
   235  		stringer: func(p *logger.LogMode) string { return string(*p) },
   236  		typeStr:  "logging-mode",
   237  	}
   238  }
   239  
   240  func EnvVarMapFlag(value *map[string]string) *MapValueFlag[string, string] {
   241  	return &MapValueFlag[string, string]{
   242  		value:    value,
   243  		parser:   separatorParser("="),
   244  		stringer: func(k *string, v *string) string { return fmt.Sprintf("%s=%s", *k, *v) },
   245  		typeStr:  "key=value",
   246  	}
   247  }
   248  
   249  func parseTag(s string) (string, error) {
   250  	var err error
   251  	if !job.IsSafeAnnotation(s) {
   252  		err = fmt.Errorf("%q is not a valid tag", s)
   253  	}
   254  	return s, err
   255  }
   256  
   257  func IncludedTagFlag(value *[]model.IncludedTag) *ArrayValueFlag[model.IncludedTag] {
   258  	return &ArrayValueFlag[model.IncludedTag]{
   259  		value: value,
   260  		parser: func(s string) (model.IncludedTag, error) {
   261  			s, err := parseTag(s)
   262  			return model.IncludedTag(s), err
   263  		},
   264  		stringer: func(t *model.IncludedTag) string { return string(*t) },
   265  		typeStr:  "tag",
   266  	}
   267  }
   268  
   269  func ExcludedTagFlag(value *[]model.ExcludedTag) *ArrayValueFlag[model.ExcludedTag] {
   270  	return &ArrayValueFlag[model.ExcludedTag]{
   271  		value: value,
   272  		parser: func(s string) (model.ExcludedTag, error) {
   273  			s, err := parseTag(s)
   274  			return model.ExcludedTag(s), err
   275  		},
   276  		stringer: func(t *model.ExcludedTag) string { return string(*t) },
   277  		typeStr:  "tag",
   278  	}
   279  }