File: flag.go

package info (click to toggle)
golang-github-thediveo-enumflag 2.1.0-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 212 kB
  • sloc: makefile: 6
file content (172 lines) | stat: -rw-r--r-- 7,164 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
// Copyright 2020, 2022 Harald Albrecht.
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may not
// use this file except in compliance with the License. You may obtain a copy
// of the License at
//
//    http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.

package enumflag

import (
	"fmt"

	"github.com/spf13/cobra"
)

// Flag represents a CLI (enumeration) flag which can take on only a single
// enumeration value out of a fixed set of enumeration values. Applications
// using the enumflag package might want to “derive” their enumeration flags
// from Flag for documentation purposes; for instance:
//
//	type MyFoo enumflag.Flag
//
// However, applications don't need to base their own enum types on Flag. The
// only requirement for user-defined enumeration flags is that they must be
// (“somewhat”) compatible with the Flag type, or more precise: user-defined
// enumerations must satisfy the predeclared type identifier [comparable].
//
// [comparable]: https://go.dev/blog/comparable
type Flag uint

// EnumCaseSensitivity specifies whether the textual representations of enum
// values are considered to be case sensitive, or not.
type EnumCaseSensitivity bool

// Controls whether the textual representations for enum values are case
// sensitive, or not.
const (
	EnumCaseInsensitive EnumCaseSensitivity = false
	EnumCaseSensitive   EnumCaseSensitivity = true
)

// EnumFlagValue wraps a user-defined enum type value satisfying comparable or
// []comparable. It implements the [github.com/spf13/pflag.Value] interface, so
// the user-defined enum type value can directly be used with the fine pflag
// drop-in package for Golang CLI flags.
type EnumFlagValue[E comparable] struct {
	value    enumValue[E]  // enum value of a user-defined enum scalar or slice type.
	enumtype string        // user-friendly name of the user-defined enum type.
	names    enumMapper[E] // enum value names.
}

// enumValue supports getting, setting, and stringifying an scalar or slice enum
// enumValue.
//
// Do I smell preemptive interfacing here...? Now watch the magic of “cleanest
// code”: by just moving the interface type from the source file with the struct
// types to the source file with the consumer we achieve immediate Go
// perfectness! Strike!
type enumValue[E comparable] interface {
	Get() any
	Set(val string, names enumMapper[E]) error
	String(names enumMapper[E]) string
	NewCompletor(enums EnumIdentifiers[E], help Help[E]) Completor
}

// New wraps a given enum variable (satisfying the predeclared type identifier
// comparable) so that it can be used as a flag Value with
// [github.com/spf13/pflag.Var] and [github.com/spf13/pflag.VarP]. In case no
// default enum value should be set and therefore no default shown in
// [spf13/cobra], use [NewWithoutDefault] instead.
//
// [spf13/cobra]: https://github.com/spf13/cobra
func New[E comparable](flag *E, typename string, mapping EnumIdentifiers[E], sensitivity EnumCaseSensitivity) *EnumFlagValue[E] {
	return new("New", flag, typename, mapping, sensitivity, false)
}

// NewWithoutDefault wraps a given enum variable (satisfying the predeclared
// type identifier comparable) so that it can be used as a flag Value with
// [github.com/spf13/pflag.Var] and [github.com/spf13/pflag.VarP]. Please note
// that the zero enum value must not be mapped and thus not be assigned to any
// enum value textual representation.
//
// [spf13/cobra] won't show any default value in its help for CLI enum flags
// created with NewWithoutDefault.
//
// [spf13/cobra]: https://github.com/spf13/cobra
func NewWithoutDefault[E comparable](flag *E, typename string, mapping EnumIdentifiers[E], sensitivity EnumCaseSensitivity) *EnumFlagValue[E] {
	return new("NewWithoutDefault", flag, typename, mapping, sensitivity, true)
}

// new returns a new enum variable to be used with pflag.Var and pflag.VarP.
func new[E comparable](ctor string, flag *E, typename string, mapping EnumIdentifiers[E], sensitivity EnumCaseSensitivity, nodefault bool) *EnumFlagValue[E] {
	if flag == nil {
		panic(fmt.Sprintf("%s requires flag to be a non-nil pointer to an enum value satisfying comparable", ctor))
	}
	if mapping == nil {
		panic(fmt.Sprintf("%s requires mapping not to be nil", ctor))
	}
	return &EnumFlagValue[E]{
		value:    &enumScalar[E]{v: flag, nodefault: nodefault},
		enumtype: typename,
		names:    newEnumMapper(mapping, sensitivity),
	}
}

// NewSlice wraps a given enum slice variable (satisfying [comparable])
// so that it can be used as a flag Value with [github.com/spf13/pflag.Var] and
// [github.com/spf13/pflag.VarP].
func NewSlice[E comparable](flag *[]E, typename string, mapping EnumIdentifiers[E], sensitivity EnumCaseSensitivity) *EnumFlagValue[E] {
	if flag == nil {
		panic("NewSlice requires flag to be a non-nil pointer to an enum value slice satisfying []any")
	}
	if mapping == nil {
		panic("NewSlice requires mapping not to be nil")
	}
	return &EnumFlagValue[E]{
		value:    &enumSlice[E]{v: flag},
		enumtype: typename,
		names:    newEnumMapper(mapping, sensitivity),
	}
}

// Set sets the enum flag to the specified enum value. If the specified value
// isn't a valid enum value, then the enum flag won't be set and an error is
// returned instead.
func (e *EnumFlagValue[E]) Set(val string) error {
	return e.value.Set(val, e.names)
}

// String returns the textual representation of an enumeration (flag) value. In
// case multiple textual representations (~identifiers) exist for the same
// enumeration value, then only the first textual representation is returned,
// which is considered to be the canonical one.
func (e *EnumFlagValue[E]) String() string { return e.value.String(e.names) }

// Type returns the name of the flag value type. The type name is used in error
// messages.
func (e *EnumFlagValue[E]) Type() string { return e.enumtype }

// Get returns the current enum value for convenience. Please note that the enum
// value is either scalar or slice, depending on how the enum flag was created.
func (e *EnumFlagValue[E]) Get() any { return e.value.Get() }

// RegisterCompletion registers completions for the specified (flag) name, with
// optional help texts.
func (e *EnumFlagValue[E]) RegisterCompletion(cmd *cobra.Command, name string, help Help[E]) error {
	return cmd.RegisterFlagCompletionFunc(
		name, e.value.NewCompletor(e.names.Mapping(), help))
}

// GetValue returns the (scalar) enum value of type E, otherwise it returns the
// zero value for type E.
func (e *EnumFlagValue[E]) GetValue() (v E) {
	ev := e.Get() // returns E, not *E
	v, _ = ev.(E)
	return
}

// GetSliceValue returns the slice enum value of type []E, otherwise it returns
// the zero value for type []E.
func (e *EnumFlagValue[E]) GetSliceValue() (v []E) {
	ev := e.Get() // returns []E, not *[]E
	v, _ = ev.([]E)
	return
}