libk  Check-in [623e0fdd96]

Overview
Comment:continue work on plans for kconf module
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA3-256: 623e0fdd962a5afd17d223db651d8c2e153964f6dd3bedfd4ab182db5ba4aa88
User & Date: lexi on 2019-10-23 10:05:10
Other Links: manifest | tags
Context
2019-10-30
03:34
begin work on kcli module; continue to build out infra for error explanation function check-in: c0e04b9015 user: lexi tags: trunk
2019-10-23
10:05
continue work on plans for kconf module check-in: 623e0fdd96 user: lexi tags: trunk
2019-10-22
22:21
Add errno locations for Android, as an initial step check-in: 357c708c52 user: glow tags: trunk
Changes

Modified mod/kconf/kconf.md from [b260f2842a] to [6e72a9b762].

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



# kconf

while there are a number of existing configuration parser libraries out there, they all have their problems, and all depend on libc. since configuration string parsing is a fairly core functionality that would force many programs to either write their own code or drag libc back into their dependencies, supplying a basic parser with libk makes sense.

## basic principles

kconf is intended to be fast, lightweight, transparent, and low-overhead.

to initialize a kconf structure, we begin by supplying a list of *atoms.* like in Xlib, an atom is a performant way to reference a known string of text from a compiled program. we can do this in two different ways, either by generating the values ourselves at compile time with an enum, or by generating them at runtime using the appropriate kconf interface. note that if you supply your own with an enum, because zero is used for in-band error signalling, the first atom should always be an error token. alternately, it may be explicitly set to a non-zero number and a return value of zero can be checked for implicitly or against a literal 0.

note that, in a recurring libk pattern, if an element count of zero (or `null`) is passed to a function that takes an array of nullables, that function will treat its array argument as a null-terminated array to be counted at runtime.

kconf is initialized by filling out the struct `kconf` with the parameters of operations and then passing it to the appropriate function.



    enum atoms { parse_error, user, pw, email, atomct };
    kconf kc = {
    	.decl = { atomct, {
    		{ user,  {4, "user"    } },
    		{ pw,    {8, "password"} },
    		{ email, {5, "email"   } }

    	}}
    };

	/* with runtime counts:
     * enum atoms { parse_error, user, pw, email };
     * kconf kc = {
     * 	.decl = { null, {
     * 		{ user,  {0, "user"    } },
     * 		{ pw,    {0, "password"} },
     * 		{ email, {0, "email"   } },
	 *		{ null }
     * 	}}
     * }; */

	/* with macros:
     * enum atoms { parse_error, user, password, email };
     * kconf kc = {
     * 	.decl = {Kmpsa(kconf_decl, {
	 * 		Kconf_atom(user),
	 * 		Kconf_atom(password),
	 * 		Kconf_atom(email)
	 * 	})};
	 * }; */


















## types


### struct kconf
	* `union { kconf_decl decl; kconf_gen gen; };`

### struct kconf_atom










 * `sz key`
 * `ksraw string`


### struct kconf_pair


 * `kconf_atom atom`
 * `ksraw* dest`




















## macros

 * `Kconf_atom(atom)` - a convenience macro for definining an pre-defined atom structure (`kconf_atom`), under the assumption that the C enumerator name is the same as the string used to name the field in the configuration file. inserts the text `{atom, {sizeof(#atom), #atom}}`


 * `Kconf_atom_pfx(pfx,atom)` - a convenience macro for definining an pre-defined atom structure (`kconf_atom`), under the assumption that the C enumerator name is the string used to name the field in the configuration file with a prefix attached. inserts the text `{pfx##atom, {sizeof(#atom), #atom}}`





|

|





|

|

>
>
|
|

|
|
<
>





|

|
|
|






|

|
|
|



>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|
>

|
<

|

>
>
>
>
>
>
>
>
>


>

|
>
|
<
<
>
>
>
|
>
>

>
>
>
>
>
>
>
>
>
>
>
>
>
|

<
>
>
|
>
>
>
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
# kconf

while there are a number of existing configuration parser libraries out there, they all have their problems, and all depend on libc. since configuration string parsing is a fairly core functionality that would force many programs to either write their own code or drag libc back into their dependencies, supplying a basic parser with libk makes sense. it may also make sense at some point to add support for a simple binary configuration format, to make it easier to marshal out complex runtime data structures.

# basic principles

kconf is intended to be fast, lightweight, transparent, and low-overhead.

to initialize a kconf structure, we begin by supplying a list of *atoms.* like in Xlib, an atom is a performant way to reference a known string of text from a compiled program. we can do this in two different ways, either by generating the values ourselves at compile time with an enum, or by generating them at runtime using the appropriate kconf interface. note that if you supply your own with an enum, because zero is used for in-band error signalling, the first atom should always be an error token. alternately, it may be explicitly set to a non-zero number and a return value of zero can be checked for implicitly or against a literal 0.

note that, in a recurring libk pattern, if an element count of zero (or `null`) is passed to a function that takes an array of nullables, that function will treat its array argument as a null-terminated array to be counted at runtime. to actually pass an empty array, the pointer to the array itself should be `null`.

kconf has two modes: key-value mode, a very lightweight mode suited for simple applications that only require a few parameters to be specialized, and record mode, which is heavier-weight but allows retrieving and working with complex datastructures.

in key-value mode, kconf is initialized by filling out the appropriate `kconf` struct with the parameters required and then passing it to the appropriate function.

    enum atoms { parse_error, user, pw, email, atoms_count };
    kconf_kv kc = {
    	.decl = { atomct, {
    		{ user,     {4, "user"    }, kconf_string },
    		{ password, {8, "password"}, kconf_string },

    		{ age,      {5, "age"     }, kconf_u8 }
    	}}
    };

	/* with runtime counts:
     * enum atoms { parse_error, user, pw, email };
     * kconf_kv kc = {
     * 	.decl = { null, {
     * 		{ user,     {0, "user"    }, kconf_string },
     * 		{ password, {0, "password"}, kconf_string },
     * 		{ age,      {0, "age"     }, kconf_u8 },
	 *		{ null }
     * 	}}
     * }; */

	/* with macros:
     * enum atoms { parse_error, user, password, email };
     * kconf_kv kc = {
     * 	.decl = {Kmpsa(kconf_decl, {
	 * 		Kconf_atom(user, string),
	 * 		Kconf_atom(password, string),
	 * 		Kconf_atom(age, u8)
	 * 	})};
	 * }; */

when invoked, `kconf_kv_read` populates an array with values read from the config file, and returns a structure containing a status report. kconf can be invoked on a kfile handle (with `kconf_kv_parse_file()`) or a string in memory (with `kconf_kv_parse_string()`), but in the normal course of usage, you'll simply use the `kconf_kv_read()` function, which automatically loads a configuration file from the appropriate location for the system (such as `$XDG_CONFIG_HOME/app` on linux or `~/Library/app preferences` on Mac OS X, where 'app' is the name the binary was invoked with. conveniently, this enables the user to have different 'profiles' simply by creating symbolic links to the binary).

    kconf_val conf[atoms_count];
    kconf_result kr = kconf_read(kc, conf);
    struct { ksraw username, pw; u8 age; } user;
    user.username = conf[user].string;
    user.pw = conf[password].string;
    user.age = conf[age].u8;

kconf record mode works somewhat differently. rather than access the values directly, you'll call the `kconf_rec_read()` function (or `kconf_rec_parse_file()` / `kconf_rec_parse_string()`) to retrieve a struct of type `kconf_digest,` which can then be queried with the `kconf_query()` function. kconf_query takes two arguments, a `struct kconf_digest*` and a `kconf_path.`

    kconf_digest db = kconf_rec_read();
    ksraw user_six_name = kconf_query(&db, (kconf_path){"user", 6, "name", 0} ).val.string;
    /* or with macros:
     * ksraw user_six_name = kconf_query(&db, Kconf_path("user", 6, "name")).val.string;
     */

# types
 * `typedef kconf_path kconf_path_elt[]` - a `kconf_path` is used to unambiguously identify a single field in a `kconf_digest`. it consists of a zero-terminated array of names and machine word-size integers. for instance, the path `(kconf_path){"user", 6, "name", 0}` can be used to identify the name of the sixth member of the "user" array. note that lists are **1-indexed**, to prevent collision with the list terminator.

## struct kconf_kv


## struct kconf_rec

## struct kconf_digest

## enum kconf_type
  * `string`- a string, represented as a ksraw
  * `bool` - a boolean value, either true or false
  * `file` - a file, represented by a filename in system-local format. the file will be opened and a reference to it will be returned in a `kfile` object.
  * additionally, kconf_type will have a member for every kind of machine integer the architecture supports. these members will be named according to libk integer types, so if the value a signed 16-bit integer, you would name its type as `s16`.
 
## struct kconf_atom
 * `sz key`
 * `ksraw string`
 * `kconf_type type`

## struct kconf_result
  * `enum kconf_cond cond`
  


## struct kconf_answer
 * `enum kconf_cond cond`
 * `union kconf_val val`
  
## union kconf_val
  * kconf_val is a union containing fields that match the enumerations in `kconf_type` - for instance, `ksraw string,` `bool bool,` `kfile file,` or `s16`.

## union kconf_path\_elt
 * `const char* name`
 * `sz index`

# functions
 * `struct kconf_result kconf_kv_read()`
 * `struct kconf_result kconf_kv_parse_file()`
 * `struct kconf_result kconf_kv_parse_string()`
 * `struct kconf_digest kconf_rec_read()`
 * `struct kconf_digest kconf_rec_parse_file()`
 * `struct kconf_digest kconf_rec_parse_string()`
 * `struct kconf_answer kconf_query()`

# macros


 * `Kconf_atom(id, atom, type)` - a convenience macro for definining an atom structure (`kconf_atom`). inserts the text `{id, {sizeof(#atom), #atom}, kconf_##type}`
 * `Kconf_atomi(atom, type)`- equivalent to `Kconf_atom(atom,atom,type)`
 * `Kconf_atomp(atom,type)` - a convenience macro for definining an pre-defined atom structure (`kconf_atom`), under the assumption that the C enumerator name is the string used to name the field in the configuration file with a prefix attached. define the prefix with `#define KVconf_prefix` before use. inserts the text `{KVconf_prefix##atom, {sizeof(#atom), #atom}, kconf_##type}`
 * `Kconf_path(path...)` - provides a slightly nicer path syntax than a zero-terminated compound literal (but still requires compound literal support). inserts the text `((kconf_path) {path, 0})`

# file format