1 // Package Tri is a library for defining the configuration parameters for a CLI application, managing the configuration files, application data directory, defining subcommands, parameters, default values and automatically loading configuration structures with the processed configuration.
2 //
3 // The primary construct used in Go to implement generic types is the interface, and to implement any kind of structure that involves lists, the slice-of-interface []interface{} is a structure that can hold zero or more items of any type whatsoever.
4 //
5 // By deriving from this primary primitive using names, the type aliases can become metadata that can be used to specify how its contents are to be interpreted.
6 //
7 // Implementation Notes
8 //
9 // In this implementation you can see I take advantage of the possibility to put any type into the list to place string labels at the heads of the lists as identifiers, which then can form a tagged tree structure that can be addressed by providing a list of the identifier strings at each node.
10 //
11 // Go's strict static typing means that such hierarchies cannot be written without a complete set of types already pre-defined, so in each case the implementation has to be written specifically for the types of data used.
12 //
13 // In spite of it being perhaps less logical, all elements of a Tri are a derivative of a Tri. This way one never has to type assert the container, only the contents, and in most cases, the possible types depend on the type of a branch.
14 //
15 // Declaration Syntax Validation
16 //
17 // Validators for each type in this package automatically trigger the validators for their (valid set of) constituent elements, so only one Valid() invocation is needed, on the Tri type. If Valid functions return an error, it passes back through the cascade to the root where it is printed to tell (the proogrammer) that their declaration was erroneous.
18 //
19 // Without validity checks, in a strict static typing language, dynamic variables can cause errors when assuming the input is correct, and such errors can potentially escape notice for a long time, so by adding these runtime bounds and set membership tests, such errors are caught before the application they configure even starts execution of the main() - the Tri type's Valid() method should be invoked in a init() so it runs after the calls in any var blocks and before the main() tries to parse the CLI flags.
20 //
21 // NOTE: all types use the Tri []interface{} type because, while it is possible to omit fields by using field tags, if the object is another composite object, its type must be specified unless it is an array and the members are implicitly typed. By using interface slice instead, the declaration syntax has minimum redundant type specifications.
22 package tri
23 24 import (
25 )
26 27 // Branch is an interface that all Tri nodes implement to do runtime checks on the contents of a Tri derived type, basically something like Assert for the constraints required of the subtype.
28 //
29 // The validation method is used at runtime and basically is a declaration syntax check, and only the root Valid() function must be called, it does the rest and returns as soon as it finds an error when walking the tree, or proceeds to next step of reading CLI args and compositing defaults, config file, env together filling the configuration structure.
30 //
31 // This validation has no benefit if the Tri declaration is valid, but if it is not checked, the parse/configure process will almost certainly panic when it finds the wrong type of object in the wrong position, so it may seem overly verbose but this ensures that no further checking is required before parsing the several inputs that result in a filled out configuration struct.
32 //
33 // Constraints are not the most visible feature of generic types, but if constraints aren't applied to generic types they can be very hard bugs to spot, and simply do not occur if you first check the parts of the structures are correct.
34 type Branch interface {
35 Validate() error
36 }
37 38 // TODO: write the english version of what structure each of these has
39 40 // Brief is a short description up to 80 characters long containing one string with no control characters, that is intended to describe the item it is embedded in.
41 type Brief Tri
42 43 // Command is the specification for an individual subcommand. Shown below is the full set of allowable items, the metadata items may only appear once, there must be a Brief, the name at the start, and a Handler function.
44 /*
45 {"name",
46 Short{"c"}, // single character shortcut for full length name
47 Brief{"brief"},
48 Usage{"usage"},
49 Help{"help"},
50 Examples{
51 "example 1", "explaining text",
52 ...
53 },
54 Var{...
55 },
56 Trigger{...
57 },
58 func(Tri) int {
59 ...
60 return 0
61 },
62 }
63 */
64 type Command Tri
65 66 // Commands is just an array of Command, providing a symbol-free and human-friendly name for the array of commands in an application declaration.
67 type Commands []Command
68 69 // Default is specifies the default value for a Variable, it must contain only one variable inside its first element.
70 type Default Tri
71 72 // DefaultCommand specifies the Command that should run when no subcommand is specified on the commandline.
73 type DefaultCommand Tri
74 75 // DefaultOn specifies that the trigger it is inside is disabled by its name appearing in the invocation.
76 type DefaultOn Tri
77 78 // Examples is is a list of pairs of strings containing a snippet of an example invocation and a short description of the effect of this example.
79 type Examples Tri
80 81 // Group is a single string tag with the same format as name fields that functions as a tag to gather related items in the help output.
82 type Group Tri
83 84 // Help is a free-form text that is interpreted as markdown syntax and may optionally be formatted using ANSI codes by a preprocessor to represent the structured text that a markdown parser will produce, by default all markdown annotations will be removed.
85 type Help Tri
86 87 // RunAfter is a flag indicating that a Trigger element of a Command should be run during shutdown instead of before startup.
88 type RunAfter Tri
89 90 // Short is a single character symbol that can be used instead of the name at the top of the Tri-derived type in invocation.
91 type Short Tri
92 93 // Slot can contain pointers to one or more items of the same type and is intended to allow the parser to directly populate the value in a possibly external struct.
94 type Slot Tri
95 96 // Terminates is a flag for Trigger types that indicates that the function will terminate execution of the application once it completes its work.
97 type Terminates Tri
98 99 // Tri is the root type where the base of an application parameter definition starts.
100 /*
101 var exampleTri = Tri{
102 "appname", // only letters in Tri tags
103 Brief{"up to 80 char string, no control characters, not nil"},
104 Usage{"up to 80 char string, no control characters, not nil"},
105 Version{0, 1, 1, "alpha"},
106 DefaultCommand{"help"},
107 Var{"datadir",
108 Short{"d"},
109 Brief{"brief"},
110 Usage{"usage"},
111 Help{"help"},
112 Default{"~/.pod"},
113 Slot{""},
114 },
115 Trigger{"trigger",
116 ...
117 },
118 Commands{},
119 }
120 */
121 // Note that this base specification may have variables and triggers associated with it that can be used to set configuration values common to all (or most) of the Commands specified in the declaration.
122 type Tri []interface{}
123 124 // Trigger is for initiating the execution of one-shot functions that often terminate execution, or rewrite, sort, re-index, and this kind of thing.
125 type Trigger Tri
126 127 // Usage is is an example showing the invocation of a Tri CLI flag.
128 type Usage Tri
129 130 // Var is defines a configuration variable and the means to populate this variable in an optionally separate configuration structure.
131 type Var Tri
132 133 // Version is a short specification implementing a semver version string.
134 type Version Tri
135