github.com/rclone/rclone@v1.66.1-0.20240517100346-7b89735ae726/fs/bits.go (about)

     1  package fs
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"strings"
     7  )
     8  
     9  // Bits is an option which can be any combination of the Choices.
    10  //
    11  // Suggested implementation is something like this:
    12  //
    13  //	type bits = Bits[bitsChoices]
    14  //
    15  //	const (
    16  //		bitA bits = 1 << iota
    17  //		bitB
    18  //		bitC
    19  //	)
    20  //
    21  //	type bitsChoices struct{}
    22  //
    23  //	func (bitsChoices) Choices() []BitsChoicesInfo {
    24  //		return []BitsChoicesInfo{
    25  //			{Bit: uint64(0), Name: "OFF"}, // Optional Off value - "" if not defined
    26  //			{Bit: uint64(bitA), Name: "A"},
    27  //			{Bit: uint64(bitB), Name: "B"},
    28  //			{Bit: uint64(bitC), Name: "C"},
    29  //		}
    30  //	}
    31  type Bits[C BitsChoices] uint64
    32  
    33  // BitsChoicesInfo should be returned from the Choices method
    34  type BitsChoicesInfo struct {
    35  	Bit  uint64
    36  	Name string
    37  }
    38  
    39  // BitsChoices returns the valid choices for this type.
    40  //
    41  // It must work on the zero value.
    42  //
    43  // Note that when using this in an Option the ExampleBitsChoices will be
    44  // filled in automatically.
    45  type BitsChoices interface {
    46  	// Choices returns the valid choices for each bit of this type
    47  	Choices() []BitsChoicesInfo
    48  }
    49  
    50  // String turns a Bits into a string
    51  func (b Bits[C]) String() string {
    52  	var out []string
    53  	choices := b.Choices()
    54  	// Return an off value if set
    55  	if b == 0 {
    56  		for _, info := range choices {
    57  			if info.Bit == 0 {
    58  				return info.Name
    59  			}
    60  		}
    61  	}
    62  	for _, info := range choices {
    63  		if info.Bit == 0 {
    64  			continue
    65  		}
    66  		if b&Bits[C](info.Bit) != 0 {
    67  			out = append(out, info.Name)
    68  			b &^= Bits[C](info.Bit)
    69  		}
    70  	}
    71  	if b != 0 {
    72  		out = append(out, fmt.Sprintf("Unknown-0x%X", int(b)))
    73  	}
    74  	return strings.Join(out, ",")
    75  }
    76  
    77  // Help returns a comma separated list of all possible bits.
    78  func (b Bits[C]) Help() string {
    79  	var out []string
    80  	for _, info := range b.Choices() {
    81  		out = append(out, info.Name)
    82  	}
    83  	return strings.Join(out, ", ")
    84  }
    85  
    86  // Choices returns the possible values of the Bits.
    87  func (b Bits[C]) Choices() []BitsChoicesInfo {
    88  	var c C
    89  	return c.Choices()
    90  }
    91  
    92  // Set a Bits as a comma separated list of flags
    93  func (b *Bits[C]) Set(s string) error {
    94  	var flags Bits[C]
    95  	parts := strings.Split(s, ",")
    96  	choices := b.Choices()
    97  	for _, part := range parts {
    98  		found := false
    99  		part = strings.TrimSpace(part)
   100  		if part == "" {
   101  			continue
   102  		}
   103  		for _, info := range choices {
   104  			if strings.EqualFold(info.Name, part) {
   105  				found = true
   106  				flags |= Bits[C](info.Bit)
   107  			}
   108  		}
   109  		if !found {
   110  			return fmt.Errorf("invalid choice %q from: %s", part, b.Help())
   111  		}
   112  	}
   113  	*b = flags
   114  	return nil
   115  }
   116  
   117  // IsSet returns true all the bits in mask are set in b.
   118  func (b Bits[C]) IsSet(mask Bits[C]) bool {
   119  	return (b & mask) == mask
   120  }
   121  
   122  // Type of the value.
   123  //
   124  // If C has a Type() string method then it will be used instead.
   125  func (b Bits[C]) Type() string {
   126  	var c C
   127  	if do, ok := any(c).(typer); ok {
   128  		return do.Type()
   129  	}
   130  	return "Bits"
   131  }
   132  
   133  // Scan implements the fmt.Scanner interface
   134  func (b *Bits[C]) Scan(s fmt.ScanState, ch rune) error {
   135  	token, err := s.Token(true, nil)
   136  	if err != nil {
   137  		return err
   138  	}
   139  	return b.Set(string(token))
   140  }
   141  
   142  // UnmarshalJSON makes sure the value can be parsed as a string or integer in JSON
   143  func (b *Bits[C]) UnmarshalJSON(in []byte) error {
   144  	return UnmarshalJSONFlag(in, b, func(i int64) error {
   145  		*b = (Bits[C])(i)
   146  		return nil
   147  	})
   148  }
   149  
   150  // MarshalJSON encodes it as string
   151  func (b *Bits[C]) MarshalJSON() ([]byte, error) {
   152  	return json.Marshal(b.String())
   153  }