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 }