configdb

package module
v0.0.0-...-bf67859 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Mar 11, 2025 License: MIT Imports: 25 Imported by: 0

README

todo:

  • repair old original tests

Configdb GoDoc

package configdb provides convenient access methods to configuration stored as JSON or YAML.

This is production-grade version, care is taken to maintain it backward compatible and reliable.

Originally this was a fork of olebedev/config, which in turn was a fork of moraes/config. Since then it grew and developed quite a lot, got quite a lot of new functionality and power, it also has diverged a lot, and continues to. The same time it is still the development of the same way of doing things as in the prior works.

Can be used not just as a config, but as a general data model storage, with thread-safe high-performance mutability. It has limitations, but if you know what this is about, you'll see them yourself. After all, it's open source, if in doubt - just look at the code.

Synopsis

	c1 := (&configdb.InitContext{}).FromFile(
		"conf-test-files/ddash/20240904-1.yaml",
		"FstTSOp-9WeXmf0h-RFB9rXVPEcnM43_JD1lfBEuga8=",
	).Load().U()

The v2 improvements:

U*() functions are removed. Instead, get-type functions behave this way:

  • If Err() is set, it sets error if any;
  • If Ok() is set, it sets true on success, false on error;
  • If U() is set, it doesn't panic on error, if no Err() or Ok() were set;
  • If neither set, it panics on error (to be intercepted as exception);
  • In either case, the default value is returned;
  • ErrOk() resets error to nil and ok to true, if either is set;
  • ErrPtr and OkPtr are accessible directly as well;

Path now is specified always in P(), and type parsing happens as separate function, and path is specified as a []string. E.g., instead of

    .UDuration("dot.path")

you now have to write:

    .U().P("dot", "path").Duration()

The API model was simplified, calling accessors with empty path is no longer needed, the GetNestedNode() was removed.

Different mapping rules for env variables - now all dashes are removed. For example, if you have a path "a.s-d.f", previously the env variable A_S-D_F would be looked for, now it will be A_SD_F. Obviously, both "sd" and "s-d" would map to the same thing, but it's not a problem if know about it. Update: all punctuations except "_" are removed, see the ExtendByEnvs_WithPrefix(), and also added the more powerful alternative the ExtendByEnvsV2_WithPrefix().

New idiom to load config, with automatic file type or data format detection:

    var err error
    conf := (&configdb.InitContext{}).FromFile("filename.yaml").Err(&err).Load()  // detected by suffix

    err = nil
    conf2 := (&configdb.InitContext{}).FromBytes([]byte(`text here`)).Err(&err).Load() // tries all known formats

Added LoadWithParenting().

Thread-safety

There are three M.O. to use it:

  1. Load once, then use concurrently, but keep it immutable. Most often used case.

  2. Use Set() method, but then it's not thread-safe.

  3. Thread-safe mutability support, using high-performance RCU, and Source object idiom.

Example of initializing the Source object:

        k.Source = configdb.NewSource(func (opts *configdb.NewSource_Options) {
            opts.Node = conf
            opts.Context = ctx
            // + other opts, optionally
        })

Example of using the config from the Source:

        conf := k.Source.Node
        // use conf as usual, but update it once in a while

In variant 2, the Set() method is just a by-pass to the NonThreadSafe_Set().

In 2 and 3, the usage of Set() by user is identical.

Explicit synchronization of a completion of a batch of concurrent Set()

The Set() command is totally async (or how you'd expect to wait for each command completion?) Instead, see next paragraph.

What is you do want to flush you commands immediatelly, and make sure they were indeed executed, so you can safely get an updated Node from Source?

For this, the ChFlushSignal has ChDown, by which you can get notified if flush indeed completed.

Because there can be several concurrent flush customers, we need to make ChFlushSignal buffered, e.g. 10% of ChCmd.

So, if a user wants to make sure its commands took effect, it does this:

	// sync the config
	chDown := make(chan struct{})
	configSource.ChFlushSignal <- &configdb.MsgFlushSignal{ChDown: chDown}
	<-chDown
	
	// here we're certain the commands were flushed

Be careful: if you send explicit flush signal, with ChDown != nil, and never read from it, you'll hang the whole write-back updater goroutine.

Clarification on use of err, ok, and ErrOk()

Repeated use of err, and or misuse of ok, and forgetting to use ErrOk(). No method in this library does explicitly sets ok=true, or err=nil, a user must do this itself. For example:

    // This code is totally wrong

    var err error
    var ok bool  // not set to true

    ... = conf.Ok(&ok).Err(&err).P("a", "s").MapNode()    // expr-1
    if err != nil {}    // correct
    if !ok {}           // wrong, because ok is anyway false

    someFunc(conf)

    ... = conf.Ok(&ok).Err(&err).P("a", "s").MapNode()  // expr-2
    ... = conf.P("a", "s").MapNode()    // expr-3, identical to expr-2, because err and ok are left set in conf
    if err != nil {}    // wrong, will react to the error from expr-1 or expr-4
    if !ok {}           // wrong, because ok is anyway false, also will react to !ok from expr-1 or expr-4

    ... = conf.ErrOk().Ok(&ok).Err(&err).P("a", "s").MapNode()  // expr-5, WRONG

func someFunc(conf *configdb.Node) {
    ... = conf.Duration() // expr-4
}

So how to use it right? Several rules:

  1. Always write ok := true, instead of var ok bool;
  2. Before new expression, if you re-use the err, write err = nil;
  3. Can call .ErrOk(). in the beginning of expression, to reset err=nil and ok=true explicitly;
  4. In an epression where Err() or Ok() are set, the ErrOk() must be after them;
  5. In long expression, only first failing method sets err and ok. This is because the only first one is relevant, all subsequent ones would fail anyway with not useful error values, therefore we are interested in only the first error in an expression.

Here's the same code, re-written correctly:

    var err error
    ok := true

    ... = conf.Ok(&ok).Err(&err).P("a", "s").MapNode()    // expr-1
    if err != nil {}    // correct
    if !ok {}           // correct

    someFunc(conf)

    err = nil
    ok = true
    ... = conf.Ok(&ok).Err(&err).P("a", "s").MapNode()  // expr-2
    if err != nil {}    // correct
    if !ok {}           // correct

    ... = conf.ErrOk().         // using ErrOk() is less LOC
        P("a", "s").MapNode() // expr-3, identical to expr-2, because err and ok are left set in conf
    if err != nil {}    // correct
    if !ok {}           // correct

    ... = conf.Ok(&ok).Err(&err).ErrOk().P("a", "s").MapNode()  // expr-5, correct

func someFunc(conf *configdb.Node) {
    ... = conf.Duration() // expr-4
}

Default value callbacks

An optional callback function can be supplied to each value func, which will be called in case the expression failed, and yield the default value. If this happens, the function is called, and whatever it returns would be the result. The idea is that it's not just a constant default value, but that instead there can be some logic, which makes a dependency on other values (e.g. "default is false if ca-cert is set, true if ca-cert isn't set"), those values being captured in a closure.

Example:

        caUseSystem = php.ErrOk().P("ca-use-system").Bool(func() {
            return !(len(caCert) > 0)   // caCert is captured in a closure
        })

Of course, you can achieve the same effect just setting Ok(), and then checking it in an "if". Or you can check Err(). Yes you can. But this approach is somehow more clean sometimes.

Clarification on how it detects that an expression failed:

  1. If Err() or Ok() were set, we look at them;
  2. Otherwise, we look at the .ExpressionFailure, if it is set:
    • =0 okay, nothing failed
    • =1 failed
    • =2 failed, one callback was already executed, new callbacks will cause panic
  3. The .ExpressionFailure is being reset by ErrOk(), or explicitly;

This means, that if you don't have Err() or Ok() set, please use ErrOk() in the beginning of your expressions.

To protect against missing ErrOk(), the logic guards against repeated calls of default functions in subsequent expressions, without prior ErrOk(), when there's no Err() or Ok() set.

Preserving default values, if the path is missing

Sometimes we have this situation:

    someVariable = "default value"
    someVariable = conf.DotP("path.path").String()

Now we want to keep the someVariable with default value, if the value is missing in the configdb. Of course, we can use if with Ok(), but there's simpler idiom for that:

    someVariable = "default value"
    someVariable = conf.DotP("path.path").String(func() string { return someVariable })

If the value is missing, the same default value will be returned and assigned back to it. This is convenient idiom.

Prototype inheritance

We implement two kinds of prototype inheritance - file-level (parenting) and structural ($isa).

File-level ineritance is called "parenting", because you specify file parents for files.

The structural prototype inheritance, or the $isa inheritance, is similar to the parenting, except the parenting applies to files, and the $isa applies to the structs inside the already loaded data model, after the whole thing is already in memory.

Technically, both the parenting and the $isa are the instances of prototype inheritance, just applied to different things at different stages and different levels.

Parenting

In any configuration file you can specify, at the root level, the field "parent: string", or "parents: [string, ...]", and specify parent config files, paths to them.

How does this work:

1) Load the file;
2) Look for fields "parent" or "parents", make a list of parent files;
3) For each one - recursively repeat this algorithm, from step 1;
4) Apply files on top of each other, in the order they were specified, and the current file apply on top of them all;

In other words: first go deep down the recursion, opening parent files; on returning up, apply each current file on top of the parent one.

"Apply" means that when the fields are in conflict between the file we apply and the file we apply it to, the fields we apply on top get the priority. I.e., the children has priority on parents.

There's an example of this mechanism in common use - the CSS in Internet browsers. They work by the same principle: the styles defined at lower levels in hierarchy take the priority and redefine the styles of higher levels of hierarchy. The difference is that here the hierarchy is specified explicitly, multiple inheritance is possible, and the processing begins from the element (the file) located at the very bottom of the hierarchy.

What it's useful for:

1) It partically substitutes the functionality of "includes" - you can inherit several files, and they'll be "included" in the main one;

2) It can be used to specify default values, and have multitude of specialization configs, which are applied on top of the default one;

This functionality is implemented in the LoadWithParenting().

The resulting file get the field parents-inherited set at root level, which contains an array of paths to all the files inherited. This is useful to see what file took part in it, quickly.

The $isa

It means the "is-a". Implemented by the TheIsa().

Suppose you have this structure:

        objects:
            object-01:
                $isa: objects.object-02
                a: 2
                b: 3
            object-02:
                a: 0
                c: 4

After applying the TheIsa(), we'll get this YAML equivalent:

        objects:
            object-01:
                a: 2
                b: 3
                c: 4
            object-02:
                a: 0
                c: 4

First we get the parent object, and apply the current one on top of that. That is, in case of intersections, the current one takes the priority.

In this case, concerning the object objects.object-01:

  1. we took the objects.object-02, and made a copy of it;
  2. the "a" got a new value;
  3. added the "b";
  4. the "c" was left as it was, inherited from the prototype;

The $isa Advanced

Handling the recursion - we do:

  • recursive traversal of the tree;
  • hash map to protect against loops;
  • deep copy the isa struct, replace the current, remove the isa key from current, then apply current on the top;
  • after this, re-parse the current structure again, to make sure there's no isa inherited left;

Multiple inheritance - you can specify several parent objects, and they all will be applied in order (just like files), and the current one will be on top:

    object-01:
        $isa: [objects.o1, defaults.x1]

Handling recursion - the re-parse happens when applying each consequtive layer.

Using relative paths:

    objects:
        defaults:
            a: 1
            b: 2
        obj-01:
            $isa: ^.defaults
            name: obj-01
        obj-02:
            $isa: ^.defaults
            name: obj-02
        obj-03:
            $isa: ^.defaults
            name: obj-03

Integrity checks

It supports integrity checks of source config files:

	var err error
	conf := (&configdb.InitContext{}).
		FromFile("conf-test-files/config.yaml",
			"W7vJif0qw764-gXERIZ2HyQ0Rg0yvX5FnRc1USNAynI=",
			"ldtT3WFuCGSdXyGr5jUvibkNVLJzmlS_ajJzip95jEc=",
			"Kc_snY875G-uzwdVXGRpV-h8o7AodUgF_MfAugsx2PA=",
			"yEsexBCPF8HP86euYrrpDIrK7JHLrrVMhBUJFvYqWsE=",
			"Jo0cHrhVyEwtjIIApxQ0i_fr5UqsOOcE9Y6tWQlKGoM=",
		).
		Err(&err).
		LoadWithParenting()

	fmt.Println("CONFIG HASHES:", conf.InitContext.SourceHashesActual)

This main purpose is to tie test config files to tests, but can also be used for other purposes as well.

DDash language support

  • set
  • string-interpolate
  • append (thread-unsafe (to be improved))

See tests for usage examples.

Concurrent write idiom:

In updater G, single one:

    conf2 := conf.DeepCopy()
    conf2.Set(path, value)
    conf.SwapToNewVersion(conf2)

In the G, who wants to write:

    conf.SendSet(path, value) // works via chan

In any concurrent user G:

  • Always periodically get the conf from centralized place, because the pointer may change.

Documentation

Overview

----------------------------------------------------------------------------- THIS IS UNABRIDGED DOCUMENTATION, PLEASE SEE THE README.md in the project root for more details. -----------------------------------------------------------------------------

package configdb provides convenient access methods to configuration stored as JSON or YAML.

Let's start with a simple YAML file config.yml:

development:
  database:
    host: localhost
  users:
    - name: calvin
      password: yukon
    - name: hobbes
      password: tuna
production:
  database:
    host: 192.168.1.1

We can parse it using ParseYaml(), which will return a *Config instance on success:

c1 := (&config.InitContext{}).FromFile("config.yaml").Load().U()

An equivalent JSON configuration could be built using ParseJson():

c1 := (&config.InitContext{}).FromFile("config.json").Load().U()

From now, we can retrieve configuration values using a path in dotted notation:

// "localhost"
host := c1.DotP("development.database.host").String()

// or...

// "192.168.1.1"
host := c1.DotP("production.database.host").String()

Besides String(), other types can be fetched directly: Bool(), Float64(), Int(), Map() and List(). All these methods will issue an error if the path doesn't exist, or the value doesn't match or can't be converted to the requested type.

A nested configuration can be fetched using DotP(). Here we get a new *Config instance with a subset of the configuration:

c2 := c2.DotP("development")

Then the inner values are fetched relatively to the subset:

// "localhost"
host := c2.DotP("database.host").String()

For lists, the dotted path must use an index to refer to a specific value. To retrieve the information from a user stored in the configuration above:

// map[string]interface{}{ ... }
user1 := c1.DotP("development.users.0").Map()
// map[string]interface{}{ ... }
user2 := c1.DotP("development.users.1").Map()

// or...

// "calvin"
name1 := c1.DotP("development.users.0.name").String()
// "hobbes"
name2 := c1.DotP("development.users.1.name").String()

Index

Constants

View Source
const (
	ExpressionStatus_0_Norm = iota
	ExpressionStatus_1_Failed
	ExpressionStatus_2_DefaultCallbackAlreadyUsedOnce
)

Variables

View Source
var ErrMsg_MultipleCallbackWithoutPriorErrOk = "default value callback used multiple times, without prior ErrOk()"
View Source
var ReEnvs01 = regexp.MustCompile(`\W`)

Functions

func Extend_v2_any

func Extend_v2_any(a1 any, a2 any) any

Recursively extends a1 with a2

func PrintMemUsage

func PrintMemUsage()

PrintMemUsage outputs the current, total and OS memory being used. As well as the number of garage collection cycles completed.

func RenderJson

func RenderJson(c interface{}) (string, error)

RenderJson renders a JSON configuration.

func RenderYaml

func RenderYaml(c interface{}) (string, error)

RenderYaml renders a YAML configuration.

func SplitPathToParts

func SplitPathToParts(key string) []string

func TranslateEnvs_KeySuffix

func TranslateEnvs_KeySuffix(s string) string

Does this replacements:

p__ -> "." (point)
d__ -> "-" (dash)
u__ -> "__" (underscore)
D__ -> "$" (dollar)
A__ -> "@" (at)

For example, the line "Somethingp__superd__duperp__D__isa" will become "Something.super-duper.$isa". Because the key is located at the end of words, this makes minimal possible impact on readability.

Types

type Command

type Command int
const (
	Command_Set Command = iota
)

type CurrPath

type CurrPath []string

func (*CurrPath) Pop

func (c *CurrPath) Pop() string

func (*CurrPath) Push

func (c *CurrPath) Push(p string)

func (*CurrPath) String

func (c *CurrPath) String() string

type ExpressionFailure

type ExpressionFailure int

type ISerkDataAPI

type ISerkDataAPI interface {
	Test()
}

this is just a temporary example plug

type InitContext

type InitContext struct {
	CurrentFileName      string
	SourceHashesRequired []string
	SourceHashesActual   []string
	Data                 []byte
	Logger               *zerolog.Logger
	ErrPtr               *error
	OkPtr                *bool
}

func (*InitContext) Err

func (ic *InitContext) Err(err *error) *InitContext

func (*InitContext) FromBytes

func (ic *InitContext) FromBytes(data []byte, hashes ...string) *InitContext

func (*InitContext) FromFile

func (ic *InitContext) FromFile(fileName string, hashes ...string) *InitContext

func (*InitContext) Load

func (ic *InitContext) Load() *Node

func (*InitContext) LoadWithParenting

func (ic *InitContext) LoadWithParenting() (result *Node)

func (*InitContext) Ok

func (ic *InitContext) Ok(ok *bool) *InitContext

func (*InitContext) WithLogger

func (ic *InitContext) WithLogger(logger *zerolog.Logger) *InitContext

type Location

type Location []string

func (*Location) Pop

func (loc *Location) Pop()

func (*Location) Push

func (loc *Location) Push(s string)

type MsgCmd

type MsgCmd struct {
	Command  Command
	FullPath []string
	V        any
	Err      error
}

type MsgFlushSignal

type MsgFlushSignal struct {
	ChDown chan struct{}
}

type NewSource_Options

type NewSource_Options struct {
	Config            *Node
	Context           context.Context
	CommandBufferSize int
	UpdatePeriod      time.Duration
}

type Node

type Node struct {
	Sub              any
	OkPtr            *bool
	ErrPtr           *error
	ExpressionStatus ExpressionFailure
	RTag             string

	Source      *Source      `json:"-"`
	InitContext *InitContext `json:"-"`
	// contains filtered or unexported fields
}

Node represents a configuration with convenient access methods.

func (*Node) Args

func (c *Node) Args(args ...string) *Node

Args command line arguments, based on existing config keys.

func (*Node) At

func (c *Node) At(pathParts ...string) *Node

can be used to explicitly augment the current path

func (*Node) Bool

func (c *Node) Bool(defaultValueFunc ...func() bool) bool

func (*Node) BytesFromBase64

func (c *Node) BytesFromBase64(defaultValueFunc ...func() []byte) []byte

func (*Node) ChildCopy

func (c *Node) ChildCopy() (c2 *Node)

Makes a copy of Config struct. The actual data is linked by reference.

func (*Node) DDash

func (c *Node) DDash()

func (*Node) DotP

func (c *Node) DotP(path string) *Node

func (*Node) Duration

func (c *Node) Duration(defaultValueFunc ...func() time.Duration) time.Duration

func (*Node) Err

func (c *Node) Err(err *error) (c2 *Node)

Attaches an err error variable to the expression, by reference. Make sure to reset it to nil when reusing between expressions.

func (*Node) ErrOk

func (c *Node) ErrOk() *Node

Resets any errors, accumulated in previous expressions on this Config object. Sets Ok=true, Err=nil, ExpressionStatus=0_Norm, if any

func (*Node) ExtendBy

func (c *Node) ExtendBy(c2 *Node) *Node

ExtendBy() extends current config with another config: i.e. all values from another config are added to the current config, and overwritten with new values if already present. It implements limited prototype-based inheritance. It does not add elements, if not present in the prototype. Note that if you extend with different structure type you will get an error. See: `.Set()` method for details.

func (*Node) ExtendByEnvsV2_WithPrefix

func (c *Node) ExtendByEnvsV2_WithPrefix(prefix string, transformerFuncs ...func(s string) string)

Unlike the ExtendByEnvs_WithPrefix(), this function allows to create new nodes in the config, based on the envs. It scans all envs matching the specified prefix, then strips the prefix, then what is left is used as a valid dot-path as is. For example, if the prefix was PRFX, and you specify an env var "PRFX_asd-qwe.zxc.123", then this variable will set, and create if necessary, the node at path "asd-qwe.zxc.123". If such names are supported in your OS is up to you, but see the https://stackoverflow.com/questions/2821043/allowed-characters-in-linux-environment-variable-names. Update: As it turns out, the OS may support it, but the bash didn't, and so you can use it. To solve this issue, I am adding a transformation callback, which can additionally transform the path string, so you can avoid using non-alphanumerics in env names. See also the TranslateEnvs_KeySuffix().

func (*Node) ExtendByEnvs_WithPrefix

func (c *Node) ExtendByEnvs_WithPrefix(prefix string) *Node

Fetch data from system env using prefix, based on existing config keys. The algorithm: for all possible paths in config do { join by "_"; make uppercase; remove all punctuation except "_"; add prefix; lookup if there is an env with that name; if it is - get its value and set to the config at the path}. VERY IMPORTANT USAGE NOTE: this can override what is already present in the config, but it cannot create new things, which were not in the configdb.

In case of OS environment all existing at the moment of parsing keys will be scanned in OS environment, but in uppercase and the separator will be `_` instead of a `.`. If EnvPrefix() is used the given prefix will be used to lookup the environment variable, e.g PREFIX_FOO_BAR will set foo.bar. In case of flags separator will be `-`. In case of command line arguments possible to use regular dot notation syntax for all keys. For see existing keys we can run application with `-h`.

func (*Node) ExtendBy_v2

func (c *Node) ExtendBy_v2(c2 *Node) *Node

ExtendBy() extends current config with another config: i.e. all values from another config are added to the current config, and overwritten with new values if already present. It implements prototype-based inheritance.

func (*Node) Flag

func (c *Node) Flag() *Node

Parse command line arguments, based on existing config keys.

func (*Node) Float64

func (c *Node) Float64(defaultValueFunc ...func() float64) float64

func (*Node) GetCurrentLocationPlusPath

func (c *Node) GetCurrentLocationPlusPath(pathParts ...string) (path []string)

func (*Node) Int

func (c *Node) Int(defaultValueFunc ...func() int) int

func (*Node) List

func (c *Node) List(defaultValueFunc ...func() []any) []any

func (*Node) ListDuration

func (c *Node) ListDuration(defaultValueFunc ...func() []time.Duration) []time.Duration

func (*Node) ListFloat64

func (c *Node) ListFloat64(defaultValueFunc ...func() []float64) []float64

func (*Node) ListInt

func (c *Node) ListInt(defaultValueFunc ...func() []int) []int

func (*Node) ListNode

func (c *Node) ListNode() []*Node

func (*Node) ListString

func (c *Node) ListString(defaultValueFunc ...func() []string) []string

func (*Node) ListTime

func (c *Node) ListTime(defaultValueFunc ...func() []time.Time) []time.Time

func (*Node) Map

func (c *Node) Map(defaultValueFunc ...func() map[string]any) map[string]any

func (*Node) MapBool

func (c *Node) MapBool(defaultValueFunc ...func() map[string]bool) map[string]bool

func (*Node) MapDuration

func (c *Node) MapDuration(defaultValueFunc ...func() map[string]time.Duration) map[string]time.Duration

func (*Node) MapFloat64

func (c *Node) MapFloat64(defaultValueFunc ...func() map[string]float64) map[string]float64

func (*Node) MapInt

func (c *Node) MapInt(defaultValueFunc ...func() map[string]int) map[string]int

func (*Node) MapNode

func (c *Node) MapNode() map[string]*Node

func (*Node) MapString

func (c *Node) MapString(defaultValueFunc ...func() map[string]string) map[string]string

func (*Node) MapTime

func (c *Node) MapTime(defaultValueFunc ...func() map[string]time.Time) map[string]time.Time

func (*Node) Ok

func (c *Node) Ok(okRef *bool) (c2 *Node)

Attaches a ok bool variable to the expression, by reference, with it's current value. Failures in subsequent operations may set it to false only, so make sure its current state is true.

func (*Node) P

func (c *Node) P(pathParts ...string) *Node

Traverses the struct down the path. P() does not create a path, if it didn't exist. So, if used before Set(), it will take you as far as there is something, not farther.

func (*Node) PP

func (c *Node) PP(path string) *Node

func (*Node) PrintJson

func (c *Node) PrintJson(tag string)

func (*Node) RaisableWithTag

func (c *Node) RaisableWithTag(tag string) (c2 *Node)

Useful if you want to work with exceptions

func (*Node) Set

func (c *Node) Set(pathParts []string, v interface{})

Sets a nested config according to a path, relative from current location. Without a Source object set, acts as a by-pass to the NonThreadSafe_Set(). If you don't want to specify path, and just want to use it from current location, then invoke with nil path, c.Set(nil, value) It is totally asynchronous, and it's effect is somewhat delayed. Use explicit flush signal if you want to synchronize explicitly. P() does not create a path, if it didn't exist. So, if used before Set(), it will take you as far as there is something, not farther.

func (*Node) Set_ThreadUnsafe

func (c *Node) Set_ThreadUnsafe(pathParts []string, v interface{})

Sets a nested config according to a path, relative from current location. If you don't want to specify path, and just want to use it from current location, then invoke with nil path.

func (*Node) SlashP

func (c *Node) SlashP(path string) *Node

func (*Node) String

func (c *Node) String(defaultValueFunc ...func() string) string

func (*Node) TheIsa

func (c *Node) TheIsa()

func (*Node) Time

func (c *Node) Time(defaultValueFunc ...func() time.Time) time.Time

Parses almost any date-time format

func (*Node) U

func (c *Node) U() (c2 *Node)

Sets dontPanicFlag=true, so that failing operations won't panic, if there's no Err or Ok set.

func (*Node) UnU

func (c *Node) UnU() (c2 *Node)

Reverse of U()

type SerkAdapterAsync

type SerkAdapterAsync struct {
	CS *Source
}

thread-safe

func (*SerkAdapterAsync) Test

func (a *SerkAdapterAsync) Test()

type SerkAdapterSync

type SerkAdapterSync struct {
	C *Node
}

thread-unsafe

func (*SerkAdapterSync) Test

func (a *SerkAdapterSync) Test()

type Source

type Source struct {
	RootNode      *Node
	ChCmd         chan *MsgCmd
	ChFlushSignal chan *MsgFlushSignal
	Opts          *NewSource_Options
}

func NewSource

func NewSource(f ...func(opts *NewSource_Options)) (s *Source)

type TreeTraversalContext

type TreeTraversalContext struct {
	// contains filtered or unexported fields
}

Directories

Path Synopsis
deepcopy makes deep copies of things.
deepcopy makes deep copies of things.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL