1
0

options.go 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403
  1. package options
  2. import (
  3. "encoding/json"
  4. "fmt"
  5. "iter"
  6. "log/slog"
  7. "maps"
  8. "slices"
  9. "strconv"
  10. "strings"
  11. "time"
  12. )
  13. // Options is an interface for storing and retrieving dynamic option values.
  14. //
  15. // Copies of Options are shallow, meaning the underlying map is shared.
  16. type Options struct {
  17. m map[string]any
  18. main *Options // Pointer to the main Options if this is a child
  19. child *Options // Pointer to the child Options
  20. }
  21. // New creates a new Options map
  22. func New() *Options {
  23. return &Options{
  24. m: make(map[string]any),
  25. }
  26. }
  27. // Main returns the main Options if this is a child Options.
  28. // If this is the main Options, it returns itself.
  29. func (o *Options) Main() *Options {
  30. if o.main == nil {
  31. return o
  32. }
  33. return o.main
  34. }
  35. // Child returns the child Options if any.
  36. func (o *Options) Child() *Options {
  37. return o.child
  38. }
  39. // Descendants returns an iterator over the child Options if any.
  40. func (o *Options) Descendants() iter.Seq[*Options] {
  41. return func(yield func(*Options) bool) {
  42. for c := o.child; c != nil; c = c.child {
  43. if !yield(c) {
  44. return
  45. }
  46. }
  47. }
  48. }
  49. // HasChild checks if the Options has a child Options.
  50. func (o *Options) HasChild() bool {
  51. return o.child != nil
  52. }
  53. // AddChild creates a new child Options that inherits from the current Options.
  54. // If the current Options already has a child, it returns the existing child.
  55. func (o *Options) AddChild() *Options {
  56. if o.child != nil {
  57. return o.child
  58. }
  59. child := New()
  60. child.main = o.Main()
  61. o.child = child
  62. return child
  63. }
  64. // Depth returns the depth of the Options in the hierarchy.
  65. // The main Options has a depth of 0, its child has a depth of 1, and so on.
  66. func (o *Options) Depth() int {
  67. depth := 0
  68. for p := o.main; p != nil && p != o; p = p.child {
  69. depth++
  70. }
  71. return depth
  72. }
  73. // Get retrieves a value of the specified type from the options.
  74. // If the key does not exist, it returns the provided default value.
  75. // If the value exists but is of a different type, it panics.
  76. func Get[T any](o *Options, key string, def T) T {
  77. v, ok := o.m[key]
  78. if !ok {
  79. return def
  80. }
  81. if vt, ok := v.(T); ok {
  82. return vt
  83. }
  84. panic(newTypeMismatchError(key, v, def))
  85. }
  86. // AppendToSlice appends a value to a slice option.
  87. // If the option does not exist, it creates a new slice with the value.
  88. func AppendToSlice[T any](o *Options, key string, value ...T) {
  89. if v, ok := o.m[key]; ok {
  90. vt := v.([]T)
  91. o.m[key] = append(vt, value...)
  92. return
  93. }
  94. o.m[key] = append([]T(nil), value...)
  95. }
  96. // SliceContains checks if a slice option contains a specific value.
  97. // If the option does not exist, it returns false.
  98. // If the value exists but is of a different type, it panics.
  99. func SliceContains[T comparable](o *Options, key string, value T) bool {
  100. arr := Get(o, key, []T(nil))
  101. return slices.Contains(arr, value)
  102. }
  103. // Set sets a value for a specific option key.
  104. func (o *Options) Set(key string, value any) {
  105. o.m[key] = value
  106. }
  107. // Propagate propagates a value under the given key to the child Options if any.
  108. func (o *Options) Propagate(key string) {
  109. if o.child == nil {
  110. return
  111. }
  112. if v, ok := o.m[key]; ok {
  113. for c := range o.Descendants() {
  114. c.m[key] = v
  115. }
  116. }
  117. }
  118. // Delete removes an option by its key.
  119. func (o *Options) Delete(key string) {
  120. delete(o.m, key)
  121. }
  122. // DeleteByPrefix removes all options that start with the given prefix.
  123. func (o *Options) DeleteByPrefix(prefix string) {
  124. for k := range o.m {
  125. if strings.HasPrefix(k, prefix) {
  126. delete(o.m, k)
  127. }
  128. }
  129. }
  130. // DeleteFromChildren removes an option by its key from the child Options if any.
  131. func (o *Options) DeleteFromChildren(key string) {
  132. if o.child == nil {
  133. return
  134. }
  135. for c := range o.Descendants() {
  136. delete(c.m, key)
  137. }
  138. }
  139. // CopyValue copies a value from one option key to another.
  140. func (o *Options) CopyValue(fromKey, toKey string) {
  141. if v, ok := o.m[fromKey]; ok {
  142. o.m[toKey] = v
  143. }
  144. }
  145. // Has checks if an option key exists.
  146. func (o *Options) Has(key string) bool {
  147. _, ok := o.m[key]
  148. return ok
  149. }
  150. // GetInt retrieves an int value from the options.
  151. // If the key does not exist, GetInt returns the provided default value.
  152. // If the key exists but the value is of a different integer type,
  153. // GetInt converts it to int.
  154. // If the key exists but the value is not an integer type, GetInt panics.
  155. func (o *Options) GetInt(key string, def int) int {
  156. v, ok := o.m[key]
  157. if !ok {
  158. return def
  159. }
  160. switch t := v.(type) {
  161. case int:
  162. return t
  163. case int8:
  164. return int(t)
  165. case int16:
  166. return int(t)
  167. case int32:
  168. return int(t)
  169. case int64:
  170. return int(t)
  171. case uint:
  172. return int(t)
  173. case uint8:
  174. return int(t)
  175. case uint16:
  176. return int(t)
  177. case uint32:
  178. return int(t)
  179. case uint64:
  180. return int(t)
  181. default:
  182. panic(newTypeMismatchError(key, v, def))
  183. }
  184. }
  185. // GetFloat retrieves a float64 value from the options.
  186. // If the key does not exist, GetFloat returns the provided default value.
  187. // If the key value exists but the value is of a different float or integer type,
  188. // GetFloat converts it to float64.
  189. // If the key exists but the value is not a float or integer type, GetFloat panics.
  190. func (o *Options) GetFloat(key string, def float64) float64 {
  191. v, ok := o.m[key]
  192. if !ok {
  193. return def
  194. }
  195. switch t := v.(type) {
  196. case int:
  197. return float64(t)
  198. case int8:
  199. return float64(t)
  200. case int16:
  201. return float64(t)
  202. case int32:
  203. return float64(t)
  204. case int64:
  205. return float64(t)
  206. case uint:
  207. return float64(t)
  208. case uint8:
  209. return float64(t)
  210. case uint16:
  211. return float64(t)
  212. case uint32:
  213. return float64(t)
  214. case uint64:
  215. return float64(t)
  216. case float32:
  217. return float64(t)
  218. case float64:
  219. return t
  220. default:
  221. panic(newTypeMismatchError(key, v, def))
  222. }
  223. }
  224. // GetString retrieves a string value.
  225. // If the key doesn't exist, it returns the provided default value.
  226. // If the value exists but is of a different type, it panics.
  227. func (o *Options) GetString(key string, def string) string {
  228. return Get(o, key, def)
  229. }
  230. // GetBool retrieves a bool value.
  231. // If the key doesn't exist, it returns the provided default value.
  232. // If the value exists but is of a different type, it panics.
  233. func (o *Options) GetBool(key string, def bool) bool {
  234. return Get(o, key, def)
  235. }
  236. // GetTime retrieves a [time.Time] value.
  237. // If the key doesn't exist, it returns the zero time.
  238. // If the value exists but is of a different type, it panics.
  239. func (o *Options) GetTime(key string) time.Time {
  240. return Get(o, key, time.Time{})
  241. }
  242. // Map returns a copy of the Options as a map[string]any
  243. // If the Options has a child, it combines the main and child maps,
  244. // prepending each key with the options depth
  245. // (e.g., "0.key" for main options, "1.key" for child options, "2.key" for grandchild options, etc.)
  246. func (o *Options) Map() map[string]any {
  247. if o.child == nil {
  248. return maps.Clone(o.m)
  249. }
  250. totalEntries := len(o.m)
  251. for c := range o.Descendants() {
  252. totalEntries += len(c.m)
  253. }
  254. result := make(map[string]any, totalEntries)
  255. for k, v := range o.m {
  256. result["0."+k] = v
  257. }
  258. depth := 1
  259. for c := range o.Descendants() {
  260. for k, v := range c.m {
  261. result[strconv.Itoa(depth)+"."+k] = v
  262. }
  263. depth++
  264. }
  265. return result
  266. }
  267. // NestedMap returns Options as a nested map[string]any.
  268. // Each key is split by dots (.) and the resulting keys are used to create a nested structure.
  269. // If the Options has a child, it puts the main and child maps under "0", "1", "2", etc. keys
  270. // representing the options depth
  271. // (e.g., "0" for main options, "1" for child options, "2" for grandchild options, etc.)
  272. func (o *Options) NestedMap() map[string]any {
  273. if o.child == nil {
  274. return o.nestedMap()
  275. }
  276. totalMaps := 1
  277. for child := o.child; child != nil; child = child.child {
  278. totalMaps++
  279. }
  280. result := make(map[string]any, totalMaps)
  281. result["0"] = o.nestedMap()
  282. depth := 1
  283. for c := range o.Descendants() {
  284. result[strconv.Itoa(depth)] = c.nestedMap()
  285. depth++
  286. }
  287. return result
  288. }
  289. func (o *Options) nestedMap() map[string]any {
  290. nm := make(map[string]any)
  291. for k, v := range o.m {
  292. nestedMapSet(nm, k, v)
  293. }
  294. return nm
  295. }
  296. // String returns Options as a string representation of the map.
  297. func (o *Options) String() string {
  298. return fmt.Sprintf("%v", o.Map())
  299. }
  300. // MarshalJSON returns Options as a JSON byte slice.
  301. func (o *Options) MarshalJSON() ([]byte, error) {
  302. return json.Marshal(o.NestedMap())
  303. }
  304. // LogValue returns Options as [slog.Value]
  305. func (o *Options) LogValue() slog.Value {
  306. return toSlogValue(o.NestedMap())
  307. }
  308. // nestedMapSet sets a value in a nested map[string]any structure.
  309. // If the key has more than one element, it creates nested maps as needed.
  310. func nestedMapSet(m map[string]any, key string, value any) {
  311. key, rest, isGroup := strings.Cut(key, ".")
  312. if !isGroup {
  313. m[key] = value
  314. return
  315. }
  316. mm, ok := m[key].(map[string]any)
  317. if !ok {
  318. mm = make(map[string]any)
  319. }
  320. nestedMapSet(mm, rest, value)
  321. m[key] = mm
  322. }
  323. func toSlogValue(v any) slog.Value {
  324. m, ok := v.(map[string]any)
  325. if !ok {
  326. return slog.AnyValue(v)
  327. }
  328. attrs := make([]slog.Attr, 0, len(m))
  329. for k, v := range m {
  330. attrs = append(attrs, slog.Attr{Key: k, Value: toSlogValue(v)})
  331. }
  332. // Sort attributes by key to have a consistent order
  333. slices.SortFunc(attrs, func(a, b slog.Attr) int {
  334. return strings.Compare(a.Key, b.Key)
  335. })
  336. return slog.GroupValue(attrs...)
  337. }