summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorJo-Philipp Wich <jo@mein.io>2023-10-11 16:50:35 +0200
committerGitHub <noreply@github.com>2023-10-11 16:50:35 +0200
commit2b2a47240c089a2cb10223efd4a1f133638f5dd5 (patch)
tree8aca5fedc9c5584e7d66ce9774a663ea2de073bb
parentf56394f0fc53e5c22cbe52cac0506d9fbef33cb3 (diff)
parent1c1899301a4452582ade76bb5249eaac71cceb22 (diff)
Merge pull request #175 from jow-/docs-improvements
Various documentation improvements
-rw-r--r--docs/ucode.css81
-rw-r--r--docs/ucode.js32
-rw-r--r--lib.c101
-rw-r--r--lib/debug.c14
-rw-r--r--lib/fs.c150
-rw-r--r--lib/uci.c842
6 files changed, 1067 insertions, 153 deletions
diff --git a/docs/ucode.css b/docs/ucode.css
index a40f478..966228a 100644
--- a/docs/ucode.css
+++ b/docs/ucode.css
@@ -6,6 +6,24 @@ h1 {
display: flex;
}
+h3[id].name:not(h2 + h3) {
+ margin-top: 2.5rem;
+ border-top: 1px solid #292929;
+ padding-top: 1.5rem;
+}
+
+h3[id].name:not(h2 + h3) .link-anchor {
+ top: 1.5rem;
+}
+
+.light h3[id].name {
+ border-color: #ccc;
+}
+
+html body {
+ line-height: 1.5;
+}
+
article .description > ul,
article .description > ol {
list-style: initial;
@@ -18,3 +36,66 @@ article .description > ol {
background-position: 1rem center;
padding-left: 3rem;
}
+
+section table td:first-child,
+section .params td:first-child,
+section table thead th:first-child,
+section .params thead th:first-child,
+section .props thead th:first-child {
+ border-top-left-radius: .5rem;
+ border-bottom-left-radius: .5rem;
+}
+
+section table td:last-child,
+section .params td:last-child,
+section table thead th:last-child,
+section .params thead th:last-child,
+section .props thead th:last-child {
+ border-top-right-radius: .5rem;
+ border-bottom-right-radius: .5rem;
+}
+
+section .params td,
+section .params th,
+section .props td,
+section .props th,
+section th,
+section td,
+section .details .details-item-container {
+ padding: .5rem 1rem;
+}
+
+section ul,
+section ol {
+ padding-left: 1.5rem;
+}
+
+section ul li,
+section ol li {
+ padding: .25rem 0;
+}
+
+section p {
+ margin: .75rem 0;
+}
+
+section .details {
+ border-radius: .5rem;
+}
+
+section .pre-div {
+ margin: 1rem 0;
+ border-radius: .5rem;
+}
+
+.param-type {
+ font-family: "code";
+}
+
+.signature-attributes {
+ font-variant: sub;
+}
+
+.method-member-container > strong + dl.param-type > dt {
+ display: none;
+}
diff --git a/docs/ucode.js b/docs/ucode.js
index 6728cdb..2377c7e 100644
--- a/docs/ucode.js
+++ b/docs/ucode.js
@@ -4,4 +4,36 @@ document.addEventListener('DOMContentLoaded', (ev) => {
if (accordionState == null || accordionState == '{}')
document.querySelectorAll('[data-isopen="false"]')
.forEach(item => item.setAttribute('data-isopen', 'true'));
+
+ const moduleName = location.pathname.match(/\/module-(.+)\.html$/)?.[1];
+
+ if (moduleName) {
+ const modulePrefix = `module:${moduleName}.`;
+
+ document.querySelectorAll(`a[href^="module-${CSS.escape(moduleName)}."]`).forEach(a => {
+ if (a.text?.indexOf(modulePrefix) == 0)
+ a.text = a.text.substring(modulePrefix.length);
+ });
+ }
+
+ document.querySelectorAll('.param-type, .type-signature').forEach(span => {
+ let replaced;
+ do {
+ replaced = false;
+ span.innerHTML = span.innerHTML.replace(/\b(Object|Array)\.&lt;((?:(?!&[lg]t;).)+)&gt;/,
+ (m, t, st) => {
+ replaced = true;
+
+ if (t == 'Object')
+ return `Object&lt;${st.replace(/,\s*/, ':&#8239;')}&gt;`;
+ else
+ return `${st}[]`;
+ });
+ } while (replaced);
+ });
+
+ document.querySelectorAll('.type-signature').forEach(span => {
+ span.innerHTML = span.innerHTML.replace(/\(nullable\) (.+)$/,
+ '$1<span class="signature-attributes">nullable</span>');
+ });
});
diff --git a/lib.c b/lib.c
index d23dde2..726a74d 100644
--- a/lib.c
+++ b/lib.c
@@ -365,7 +365,7 @@ uc_print(uc_vm_t *vm, size_t nargs)
*
* @param {Object|Array|string} x - The input object, array, or string.
*
- * @returns {number|null} - The length of the input.
+ * @returns {?number} - The length of the input.
*
* @example
* length("test") // 4
@@ -484,7 +484,7 @@ uc_index(uc_vm_t *vm, size_t nargs, bool right)
* @param {*} needle
* The value to find within the array or string.
*
- * @returns {number|null}
+ * @returns {?number}
*
* @example
* index("Hello hello hello", "ll") // 2
@@ -516,7 +516,7 @@ uc_lindex(uc_vm_t *vm, size_t nargs)
* @param {*} needle
* The value to find within the array or string.
*
- * @returns {number|null}
+ * @returns {?number}
*
* @example
* rindex("Hello hello hello", "ll") // 14
@@ -844,7 +844,7 @@ uc_exit(uc_vm_t *vm, size_t nargs)
* @param {string} [name]
* The name of the environment variable.
*
- * @returns {string|object}
+ * @returns {string|Object<string, string>}
*/
static uc_value_t *
uc_getenv(uc_vm_t *vm, size_t nargs)
@@ -1037,7 +1037,7 @@ uc_int(uc_vm_t *vm, size_t nargs)
* @param {Array} arr
* The array to be joined into a string.
*
- * @returns {string|null}
+ * @returns {?string}
*/
static uc_value_t *
uc_join(uc_vm_t *vm, size_t nargs)
@@ -1073,7 +1073,7 @@ uc_join(uc_vm_t *vm, size_t nargs)
* @param {object} obj
* The object from which to retrieve the key names.
*
- * @returns {Array|null}
+ * @returns {?Array}
*/
static uc_value_t *
uc_keys(uc_vm_t *vm, size_t nargs)
@@ -1104,7 +1104,7 @@ uc_keys(uc_vm_t *vm, size_t nargs)
* @param {string} s
* The input string.
*
- * @returns {string|null}
+ * @returns {?string}
* The lowercase string.
*
* @example
@@ -1211,7 +1211,7 @@ uc_map(uc_vm_t *vm, size_t nargs)
* @param {number} [offset]
* The offset of the character.
*
- * @returns {number|null}
+ * @returns {?number}
*
* @example
* ord("Abc"); // 65
@@ -1265,7 +1265,7 @@ uc_ord(uc_vm_t *vm, size_t nargs)
* @param {*} x
* The value to determine the type of.
*
- * @returns {string|null}
+ * @returns {?string}
*/
static uc_value_t *
uc_type(uc_vm_t *vm, size_t nargs)
@@ -1307,7 +1307,7 @@ uc_type(uc_vm_t *vm, size_t nargs)
* @param {Array|string} arr_or_str
* The input array or string.
*
- * @returns {Array|string|null}
+ * @returns {?(Array|string)}
*
* @example
* reverse([1, 2, 3]); // [ 3, 2, 1 ]
@@ -1938,7 +1938,7 @@ uc_time(uc_vm_t *vm, size_t nargs)
* @param {*} str
* The string to be converted to uppercase.
*
- * @returns {string|null}
+ * @returns {?string}
*
* @example
* uc("hello"); // "HELLO"
@@ -2040,7 +2040,7 @@ uc_uchr(uc_vm_t *vm, size_t nargs)
* @param {*} obj
* The object from which to extract values.
*
- * @returns {Array|null}
+ * @returns {?Array}
*
* @example
* values({ foo: true, bar: false }); // [true, false]
@@ -2774,7 +2774,7 @@ uc_require(uc_vm_t *vm, size_t nargs)
* @param {string} address
* The IP address string to convert.
*
- * @returns {number[]|null}
+ * @returns {?number[]}
*
* @example
* iptoarr("192.168.1.1") // [ 192, 168, 1, 1 ]
@@ -2851,7 +2851,7 @@ check_byte(uc_value_t *v)
* @param {number[]} arr
* The byte array to convert into an IP address string.
*
- * @returns {string|null}
+ * @returns {?string}
*
* @example
* arrtoip([ 192, 168, 1, 1 ]) // "192.168.1.1"
@@ -2928,7 +2928,7 @@ uc_arrtoip(uc_vm_t *vm, size_t nargs)
* @param {RegExp} pattern
* The regular expression pattern.
*
- * @returns {Array|null}
+ * @returns {?Array}
*
* @example
* match("foobarbaz", /b.(.)/) // ["bar", "r"]
@@ -3976,7 +3976,7 @@ uc_trace(uc_vm_t *vm, size_t nargs)
* @param {Object} [proto]
* The optional prototype object.
*
- * @returns {Object|null}
+ * @returns {?Object}
*
* @example
* const arr = [1, 2, 3];
@@ -4222,7 +4222,7 @@ uc_wildcard(uc_vm_t *vm, size_t nargs)
* @param {boolean} [dironly]
* Whether to return only the directory portion of the source file path.
*
- * @returns {string|null}
+ * @returns {?string}
*
* @example
* sourcepath(); // Returns the path of the currently executed file
@@ -4426,7 +4426,7 @@ uc_max(uc_vm_t *vm, size_t nargs)
* @param {string} str
* The base64 encoded string to decode.
*
- * @returns {string|null}
+ * @returns {?string}
*
* @example
* b64dec("VGhpcyBpcyBhIHRlc3Q="); // Returns "This is a test"
@@ -4576,7 +4576,7 @@ static const char Base64[] =
* @param {string} str
* The string to encode.
*
- * @returns {string|null}
+ * @returns {?string}
*
* @example
* b64enc("This is a test"); // Returns "VGhpcyBpcyBhIHRlc3Q="
@@ -4713,7 +4713,7 @@ uc_uniq_ucv_equal(const void *k1, const void *k2)
* @param {Array} array
* The input array.
*
- * @returns {Array|null}
+ * @returns {?Array}
*
* @example
* uniq([1, true, "foo", 2, true, "bar", "foo"]); // Returns [1, true, "foo", 2, "bar"]
@@ -4752,6 +4752,28 @@ uc_uniq(uc_vm_t *vm, size_t nargs)
return uniq;
}
+/**
+ * A time spec is a plain object describing a point in time, it is returned by
+ * the {@link module:core#gmtime|gmtime()} and
+ * {@link module:core#localtime|localtime()} functions and expected as parameter
+ * by the complementary {@link module:core#timegm|timegm()} and
+ * {@link module:core#timelocal|timelocal()} functions.
+ *
+ * When returned by `gmtime()` or `localtime()`, all members of the object will
+ * be initialized, when passed as argument to `timegm()` or `timelocal()`, most
+ * member values are optional.
+ *
+ * @typedef {Object} module:core.TimeSpec
+ * @property {number} sec - Seconds (0..60)
+ * @property {number} min - Minutes (0..59)
+ * @property {number} hour - Hours (0..23)
+ * @property {number} mday - Day of month (1..31)
+ * @property {number} mon - Month (1..12)
+ * @property {number} year - Year (>= 1900)
+ * @property {number} wday - Day of week (1..7, Sunday = 7)
+ * @property {number} yday - Day of year (1-366, Jan 1st = 1)
+ * @property {number} isdst - Daylight saving time in effect (yes = 1)
+ */
static uc_value_t *
uc_gettime_common(uc_vm_t *vm, size_t nargs, bool local)
{
@@ -4782,16 +4804,7 @@ uc_gettime_common(uc_vm_t *vm, size_t nargs, bool local)
* containing broken-down date and time information according to the local
* system timezone.
*
- * The resulting dictionary contains the following fields:
- * - `sec` Seconds (0-60)
- * - `min` Minutes (0-59)
- * - `hour` Hours (0-23)
- * - `mday` Day of month (1-31)
- * - `mon` Month (1-12)
- * - `year` Year (>= 1900)
- * - `wday` Day of the week (1-7, Sunday = 7)
- * - `yday` Day of the year (1-366, Jan 1st = 1)
- * - `isdst` Daylight saving time in effect (yes = 1)
+ * See {@link module:core.TimeSpec|TimeSpec} for a description of the fields.
*
* Note that in contrast to the underlying `localtime(3)` C library function,
* the values for `mon`, `wday`, and `yday` are 1-based, and the `year` is
@@ -4802,7 +4815,7 @@ uc_gettime_common(uc_vm_t *vm, size_t nargs, bool local)
* @param {number} [epoch]
* The epoch timestamp.
*
- * @returns {Object}
+ * @returns {module:core.TimeSpec}
*
* @example
* localtime(1647953502);
@@ -4828,14 +4841,14 @@ uc_localtime(uc_vm_t *vm, size_t nargs)
/**
* Like `localtime()` but interpreting the given epoch value as UTC time.
*
- * See `localtime()` for details on the return value.
+ * See {@link module:core#localtime|localtime()} for details on the return value.
*
* @function module:core#gmtime
*
* @param {number} [epoch]
* The epoch timestamp.
*
- * @returns {Object}
+ * @returns {module:core.TimeSpec}
*
* @example
* gmtime(1647953502);
@@ -4908,9 +4921,9 @@ uc_mktime_common(uc_vm_t *vm, size_t nargs, bool local)
}
/**
- * Performs the inverse operation of `localtime()` by taking a broken-down date
- * and time dictionary and transforming it into an epoch value according to the
- * local system timezone.
+ * Performs the inverse operation of {@link module:core#localtime|localtime()}
+ * by taking a broken-down date and time dictionary and transforming it into an
+ * epoch value according to the local system timezone.
*
* The `wday` and `yday` fields of the given date time specification are
* ignored. Field values outside of their valid range are internally normalized,
@@ -4922,10 +4935,10 @@ uc_mktime_common(uc_vm_t *vm, size_t nargs, bool local)
*
* @function module:core#timelocal
*
- * @param {Object} datetimespec
+ * @param {module:core.TimeSpec} datetimespec
* The broken-down date and time dictionary.
*
- * @returns {number|null}
+ * @returns {?number}
*
* @example
* timelocal({ "sec": 42, "min": 51, "hour": 13, "mday": 22, "mon": 3, "year": 2022, "isdst": 0 });
@@ -4941,14 +4954,14 @@ uc_timelocal(uc_vm_t *vm, size_t nargs)
* Like `timelocal()` but interpreting the given date time specification as UTC
* time.
*
- * See `timelocal()` for details.
+ * See {@link module:core#timelocal|timelocal()} for details.
*
* @function module:core#timegm
*
- * @param {Object} datetimespec
+ * @param {module:core.TimeSpec} datetimespec
* The broken-down date and time dictionary.
*
- * @returns {number|null}
+ * @returns {?number}
*
* @example
* timegm({ "sec": 42, "min": 51, "hour": 13, "mday": 22, "mon": 3, "year": 2022, "isdst": 0 });
@@ -4980,7 +4993,7 @@ uc_timegm(uc_vm_t *vm, size_t nargs)
* @param {boolean} [monotonic]
* Whether to query the monotonic system clock.
*
- * @returns {Array<number>|null}
+ * @returns {?number[]}
*
* @example
* clock(); // [ 1647954926, 798269464 ]
@@ -5078,7 +5091,7 @@ hexval(unsigned char c, bool lo)
* @param {string} [skipchars]
* The characters to skip during decoding.
*
- * @returns {string|null}
+ * @returns {?string}
*
* @example
* hexdec("48656c6c6f20776f726c64210a"); // "Hello world!\n"
@@ -5165,7 +5178,7 @@ uc_hexdec(uc_vm_t *vm, size_t nargs)
* @param {*} [argument]
* The argument for the operation.
*
- * @returns {boolean|number|null}
+ * @returns {?(boolean|number)}
*
* @example
* gc(); // true
diff --git a/lib/debug.c b/lib/debug.c
index 40b044e..0b227d9 100644
--- a/lib/debug.c
+++ b/lib/debug.c
@@ -667,7 +667,7 @@ debug_setup(uc_vm_t *vm)
* @param {string|module:fs.file|module:fs.proc} file
* The file path or open file handle to write report to.
*
- * @return {boolean|null}
+ * @return {?boolean}
*/
static uc_value_t *
uc_memdump(uc_vm_t *vm, size_t nargs)
@@ -818,7 +818,7 @@ uc_traceback(uc_vm_t *vm, size_t nargs)
*
* @function module:debug#sourcepos
*
- * @return {module:debug.SourcePosition|null}
+ * @return {?module:debug.SourcePosition}
*/
/**
@@ -955,7 +955,7 @@ uc_getinfo_upvals(uc_vm_t *vm, uc_closure_t *closure)
* @param {*} value
* The value to query information for.
*
- * @return {module:debug.ValueInformation|null}
+ * @return {?module:debug.ValueInformation}
*/
/**
@@ -1362,7 +1362,7 @@ uc_xlocal(uc_vm_t *vm, uc_value_t *level, uc_value_t *var, uc_value_t **set)
* @param {string|number} variable
* The variable index or variable name to obtain information for.
*
- * @returns {module:debug.LocalInfo|null}
+ * @returns {?module:debug.LocalInfo}
*/
static uc_value_t *
uc_getlocal(uc_vm_t *vm, size_t nargs)
@@ -1406,7 +1406,7 @@ uc_getlocal(uc_vm_t *vm, size_t nargs)
* @param {*} [value=null]
* The value to set the local variable to.
*
- * @returns {module:debug.LocalInfo|null}
+ * @returns {?module:debug.LocalInfo}
*/
static uc_value_t *
uc_setlocal(uc_vm_t *vm, size_t nargs)
@@ -1552,7 +1552,7 @@ uc_xupval(uc_vm_t *vm, uc_value_t *target, uc_value_t *var, uc_value_t **set)
* @param {string|number} variable
* The variable index or variable name to obtain information for.
*
- * @returns {module:debug.UpvalInfo|null}
+ * @returns {?module:debug.UpvalInfo}
*/
static uc_value_t *
uc_getupval(uc_vm_t *vm, size_t nargs)
@@ -1601,7 +1601,7 @@ uc_getupval(uc_vm_t *vm, size_t nargs)
* @param {*} value
* The value to set the variable to.
*
- * @returns {module:debug.UpvalInfo|null}
+ * @returns {?module:debug.UpvalInfo}
*/
static uc_value_t *
uc_setupval(uc_vm_t *vm, size_t nargs)
diff --git a/lib/fs.c b/lib/fs.c
index 0f03ad4..4d9695e 100644
--- a/lib/fs.c
+++ b/lib/fs.c
@@ -79,7 +79,7 @@ static int last_error = 0;
* @function module:fs#error
*
*
- * @returns {string|null}
+ * @returns {?string}
*
* @example
* // Trigger file system error
@@ -258,6 +258,8 @@ uc_fs_fileno_common(uc_vm_t *vm, size_t nargs, const char *type)
* @class module:fs.proc
* @hideconstructor
*
+ * @borrows module:fs#error as module:fs.proc#error
+ *
* @see {@link module:fs#popen|popen()}
*
* @example
@@ -276,26 +278,6 @@ uc_fs_fileno_common(uc_vm_t *vm, size_t nargs, const char *type)
*/
/**
- * Query error information.
- *
- * Returns a string containing a description of the last occurred error or
- * `null` if there is no error information.
- *
- * @function module:fs.proc#error
- *
- * @returns {string|null}
- *
- * @example
- * // Trigger error
- * const fp = popen("command");
- * fp.close();
- * fp.close(); // already closed
- *
- * // Print error (should yield "Bad file descriptor")
- * print(fp.error(), "\n");
- */
-
-/**
* Closes the program handle and awaits program termination.
*
* Upon calling `close()` on the handle, the program's input or output stream
@@ -316,7 +298,7 @@ uc_fs_fileno_common(uc_vm_t *vm, size_t nargs, const char *type)
*
* @function module:fs.proc#close
*
- * @returns {number|null}
+ * @returns {?number}
*/
static uc_value_t *
uc_fs_pclose(uc_vm_t *vm, size_t nargs)
@@ -378,7 +360,7 @@ uc_fs_pclose(uc_vm_t *vm, size_t nargs)
* The length of data to read. Can be a number, the string "line", the string
* "all", or a single character string.
*
- * @returns {string|null}
+ * @returns {?string}
*
* @example
* const fp = popen("command", "r");
@@ -426,7 +408,7 @@ uc_fs_pread(uc_vm_t *vm, size_t nargs)
* @param {*} data
* The data to be written.
*
- * @returns {number|null}
+ * @returns {?number}
*
* @example
* const fp = popen("command", "w");
@@ -448,7 +430,7 @@ uc_fs_pwrite(uc_vm_t *vm, size_t nargs)
*
* @function module:fs.proc#flush
*
- * @returns {boolean|null}
+ * @returns {?boolean}
*
*/
static uc_value_t *
@@ -466,7 +448,7 @@ uc_fs_pflush(uc_vm_t *vm, size_t nargs)
*
* @function module:fs.proc#fileno
*
- * @returns {number|null}
+ * @returns {?number}
*/
static uc_value_t *
uc_fs_pfileno(uc_vm_t *vm, size_t nargs)
@@ -498,7 +480,7 @@ uc_fs_pfileno(uc_vm_t *vm, size_t nargs)
* @param {string} [mode="r"]
* The open mode of the process handle.
*
- * @returns {module:fs.proc|null}
+ * @returns {?module:fs.proc}
*
* @example
* // Open a process
@@ -531,6 +513,8 @@ uc_fs_popen(uc_vm_t *vm, size_t nargs)
* @class module:fs.file
* @hideconstructor
*
+ * @borrows module:fs#error as module:fs.file#error
+ *
* @see {@link module:fs#open|open()}
* @see {@link module:fs#fdopen|fdopen()}
* @see {@link module:fs#mkstemp|mkstemp()}
@@ -556,26 +540,6 @@ uc_fs_popen(uc_vm_t *vm, size_t nargs)
*/
/**
- * Query error information.
- *
- * Returns a string containing a description of the last occurred error or
- * `null` if there is no error information.
- *
- * @function module:fs.file#error
- *
- * @returns {string|null}
- *
- * @example
- * // Trigger error
- * const fp = open("file.txt");
- * fp.close();
- * fp.close(); // already closed
- *
- * // Print error (should yield "Bad file descriptor")
- * print(fp.error(), "\n");
- */
-
-/**
* Closes the file handle.
*
* Upon calling `close()` on the handle, buffered data is flushed and the
@@ -587,7 +551,7 @@ uc_fs_popen(uc_vm_t *vm, size_t nargs)
*
* @function module:fs.file#close
*
- * @returns {boolean|null}
+ * @returns {?boolean}
*/
static uc_value_t *
uc_fs_close(uc_vm_t *vm, size_t nargs)
@@ -639,7 +603,7 @@ uc_fs_close(uc_vm_t *vm, size_t nargs)
* The length of data to read. Can be a number, the string "line", the string
* "all", or a single character string.
*
- * @returns {string|null}
+ * @returns {?string}
*
* @example
* const fp = open("file.txt", "r");
@@ -687,7 +651,7 @@ uc_fs_read(uc_vm_t *vm, size_t nargs)
* @param {*} data
* The data to be written.
*
- * @returns {number|null}
+ * @returns {?number}
*
* @example
* const fp = open("file.txt", "w");
@@ -724,7 +688,7 @@ uc_fs_write(uc_vm_t *vm, size_t nargs)
* | `1` | The given offset is relative to the current read position. |
* | `2` | The given offset is relative to the end of the file. |
*
- * @returns {boolean|null}
+ * @returns {?boolean}
*
* @example
* const fp = open("file.txt", "r");
@@ -785,7 +749,7 @@ uc_fs_seek(uc_vm_t *vm, size_t nargs)
*
* @function module:fs.file#tell
*
- * @returns {number|null}
+ * @returns {?number}
*/
static uc_value_t *
uc_fs_tell(uc_vm_t *vm, size_t nargs)
@@ -818,7 +782,7 @@ uc_fs_tell(uc_vm_t *vm, size_t nargs)
*
* @function module:fs.file#isatty
*
- * @returns {boolean|null}
+ * @returns {?boolean}
*
*/
static uc_value_t *
@@ -847,7 +811,7 @@ uc_fs_isatty(uc_vm_t *vm, size_t nargs)
*
* @function module:fs.file#flush
*
- * @returns {boolean|null}
+ * @returns {?boolean}
*
*/
static uc_value_t *
@@ -865,7 +829,7 @@ uc_fs_flush(uc_vm_t *vm, size_t nargs)
*
* @function module:fs.file#fileno
*
- * @returns {number|null}
+ * @returns {?number}
*/
static uc_value_t *
uc_fs_fileno(uc_vm_t *vm, size_t nargs)
@@ -913,7 +877,7 @@ uc_fs_fileno(uc_vm_t *vm, size_t nargs)
* @param {number} [perm=0o666]
* The file creation permissions (for modes `w…` and `a…`)
*
- * @returns {module:fs.file|null}
+ * @returns {?module:fs.file}
*
* @example
* // Open a file in read-only mode
@@ -1058,6 +1022,8 @@ uc_fs_fdopen(uc_vm_t *vm, size_t nargs)
* @class module:fs.dir
* @hideconstructor
*
+ * @borrows module:fs#error as module:fs.dir#error
+ *
* @see {@link module:fs#opendir|opendir()}
*
* @example
@@ -1075,26 +1041,6 @@ uc_fs_fdopen(uc_vm_t *vm, size_t nargs)
*/
/**
- * Query error information.
- *
- * Returns a string containing a description of the last occurred error or
- * `null` if there is no error information.
- *
- * @function module:fs.dir#error
- *
- * @returns {string|null}
- *
- * @example
- * // Trigger error
- * const fp = opendir("/tmp");
- * fp.close();
- * fp.close(); // already closed
- *
- * // Print error (should yield "Bad file descriptor")
- * print(fp.error(), "\n");
- */
-
-/**
* Read the next entry from the open directory.
*
* Returns a string containing the entry name.
@@ -1105,7 +1051,7 @@ uc_fs_fdopen(uc_vm_t *vm, size_t nargs)
*
* @function module:fs.dir#read
*
- * @returns {string|null}
+ * @returns {?string}
*/
static uc_value_t *
uc_fs_readdir(uc_vm_t *vm, size_t nargs)
@@ -1139,7 +1085,7 @@ uc_fs_readdir(uc_vm_t *vm, size_t nargs)
*
* @function module:fs.dir#tell
*
- * @returns {number|null}
+ * @returns {?number}
*/
static uc_value_t *
uc_fs_telldir(uc_vm_t *vm, size_t nargs)
@@ -1174,7 +1120,7 @@ uc_fs_telldir(uc_vm_t *vm, size_t nargs)
* @param {number} offset
* Position value obtained by `tell()`.
*
- * @returns {boolean|null}
+ * @returns {?boolean}
*
* @example
*
@@ -1218,7 +1164,7 @@ uc_fs_seekdir(uc_vm_t *vm, size_t nargs)
*
* @function module:fs.dir#close
*
- * @returns {boolean|null}
+ * @returns {?boolean}
*/
static uc_value_t *
uc_fs_closedir(uc_vm_t *vm, size_t nargs)
@@ -1247,7 +1193,7 @@ uc_fs_closedir(uc_vm_t *vm, size_t nargs)
* @param {string} path
* The path to the directory.
*
- * @returns {module:fs.dir|null}
+ * @returns {?module:fs.dir}
*
* @example
* // Open a directory
@@ -1282,7 +1228,7 @@ uc_fs_opendir(uc_vm_t *vm, size_t nargs)
* @param {string} path
* The path to the symbolic link.
*
- * @returns {string|null}
+ * @returns {?string}
*
* @example
* // Read the value of a symbolic link
@@ -1456,7 +1402,7 @@ uc_fs_stat_common(uc_vm_t *vm, size_t nargs, bool use_lstat)
* @param {string} path
* The path to the file or directory.
*
- * @returns {module:fs.FileStatResult|null}
+ * @returns {?module:fs.FileStatResult}
*
* @example
* // Get information about a file
@@ -1481,7 +1427,7 @@ uc_fs_stat(uc_vm_t *vm, size_t nargs)
* @param {string} path
* The path to the file or directory.
*
- * @returns {module:fs.FileStatResult|null}
+ * @returns {?module:fs.FileStatResult}
*
* @example
* // Get information about a directory
@@ -1505,7 +1451,7 @@ uc_fs_lstat(uc_vm_t *vm, size_t nargs)
* @param {string} path
* The path to the new directory.
*
- * @returns {boolean|null}
+ * @returns {?boolean}
*
* @example
* // Create a directory
@@ -1539,7 +1485,7 @@ uc_fs_mkdir(uc_vm_t *vm, size_t nargs)
* @param {string} path
* The path to the directory to be removed.
*
- * @returns {boolean|null}
+ * @returns {?boolean}
*
* @example
* // Remove a directory
@@ -1574,7 +1520,7 @@ uc_fs_rmdir(uc_vm_t *vm, size_t nargs)
* @param {string} path
* The path of the symbolic link.
*
- * @returns {boolean|null}
+ * @returns {?boolean}
*
* @example
* // Create a symbolic link
@@ -1608,7 +1554,7 @@ uc_fs_symlink(uc_vm_t *vm, size_t nargs)
* @param {string} path
* The path to the file or symbolic link.
*
- * @returns {boolean|null}
+ * @returns {?boolean}
*
* @example
* // Remove a file
@@ -1637,7 +1583,7 @@ uc_fs_unlink(uc_vm_t *vm, size_t nargs)
*
* @function module:fs#getcwd
*
- * @returns {string|null}
+ * @returns {?string}
*
* @example
* // Get the current working directory
@@ -1692,7 +1638,7 @@ uc_fs_getcwd(uc_vm_t *vm, size_t nargs)
* @param {string} path
* The path to the new working directory.
*
- * @returns {boolean|null}
+ * @returns {?boolean}
*
* @example
* // Change the current working directory
@@ -1728,7 +1674,7 @@ uc_fs_chdir(uc_vm_t *vm, size_t nargs)
* @param {number} mode
* The new mode (permissions).
*
- * @returns {boolean|null}
+ * @returns {?boolean}
*
* @example
* // Change the mode of a file
@@ -1872,7 +1818,7 @@ uc_fs_resolve_group(uc_value_t *v, gid_t *gid)
* The new group's ID. When given as number, it is used as-is, when given as
* string, the group name is resolved to the corresponding gid first.
*
- * @returns {boolean|null}
+ * @returns {?boolean}
*
* @example
* // Change the owner of a file
@@ -1918,7 +1864,7 @@ uc_fs_chown(uc_vm_t *vm, size_t nargs)
* @param {string} newPath
* The new path of the file or directory.
*
- * @returns {boolean|null}
+ * @returns {?boolean}
*
* @example
* // Rename a file
@@ -1980,7 +1926,7 @@ uc_fs_glob(uc_vm_t *vm, size_t nargs)
* @param {string} path
* The path to extract the directory name from.
*
- * @returns {string|null}
+ * @returns {?string}
*
* @example
* // Get the directory name of a path
@@ -2029,7 +1975,7 @@ uc_fs_dirname(uc_vm_t *vm, size_t nargs)
* @param {string} path
* The path to extract the base name from.
*
- * @returns {string|null}
+ * @returns {?string}
*
* @example
* // Get the base name of a path
@@ -2083,7 +2029,7 @@ uc_fs_lsdir_sort_fn(const void *k1, const void *k2)
* @param {string} path
* The path to the directory.
*
- * @returns {string[]|null}
+ * @returns {?string[]}
*
* @example
* // List the content of a directory
@@ -2169,7 +2115,7 @@ uc_fs_lsdir(uc_vm_t *vm, size_t nargs)
* @param {string} [template="/tmp/XXXXXX"]
* The path template to use when forming the temporary file name.
*
- * @returns {module:fs.file|null}
+ * @returns {?module:fs.file}
*
* @example
* // Create a unique temporary file in the current working directory
@@ -2261,7 +2207,7 @@ uc_fs_mkstemp(uc_vm_t *vm, size_t nargs)
* @param {number} [mode="f"]
* Optional access mode.
*
- * @returns {boolean|null}
+ * @returns {?boolean}
*
* @example
* // Check file read and write accessibility
@@ -2329,7 +2275,7 @@ uc_fs_access(uc_vm_t *vm, size_t nargs)
* Number of bytes to limit the result to. When omitted, the entire content is
* returned.
*
- * @returns {string|null}
+ * @returns {?string}
*
* @example
* // Read first 100 bytes of content
@@ -2441,7 +2387,7 @@ uc_fs_readfile(uc_vm_t *vm, size_t nargs)
* Truncates the amount of data to be written to the specified amount of bytes.
* When omitted, the entire content is written.
*
- * @returns {number|null}
+ * @returns {?number}
*
* @example
* // Write string to a file
@@ -2522,7 +2468,7 @@ uc_fs_writefile(uc_vm_t *vm, size_t nargs)
* @param {string} path
* The path to the file or directory.
*
- * @returns {string|null}
+ * @returns {?string}
*
* @example
* // Resolve the absolute path of a file
@@ -2561,7 +2507,7 @@ uc_fs_realpath(uc_vm_t *vm, size_t nargs)
*
* @function module:fs#pipe
*
- * @returns {module:fs.file[]|null}
+ * @returns {?module:fs.file[]}
*
* @example
* // Create a pipe
diff --git a/lib/uci.c b/lib/uci.c
index b7319bf..96cce6a 100644
--- a/lib/uci.c
+++ b/lib/uci.c
@@ -14,6 +14,40 @@
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
+/**
+ * # OpenWrt UCI configuration
+ *
+ * The `uci` module provides access to the native OpenWrt
+ * {@link https://github.com/openwrt/uci libuci} API for reading and
+ * manipulating UCI configuration files.
+ *
+ * Functions can be individually imported and directly accessed using the
+ * {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import#named_import named import}
+ * syntax:
+ *
+ * ```
+ * import { cursor } from 'uci';
+ *
+ * let ctx = cursor();
+ * let hostname = ctx.get_first('system', 'system', 'hostname');
+ * ```
+ *
+ * Alternatively, the module namespace can be imported
+ * using a wildcard import statement:
+ *
+ * ```
+ * import * as uci from 'uci';
+ *
+ * let ctx = uci.cursor();
+ * let hostname = ctx.get_first('system', 'system', 'hostname');
+ * ```
+ *
+ * Additionally, the uci module namespace may also be imported by invoking
+ * the `ucode` interpreter with the `-luci` switch.
+ *
+ * @module uci
+ */
+
#include <string.h>
#include <uci.h>
@@ -30,6 +64,24 @@ enum pkg_cmd {
CMD_REVERT
};
+/**
+ * Query error information.
+ *
+ * Returns a string containing a description of the last occurred error or
+ * `null` if there is no error information.
+ *
+ * @function module:uci#error
+ *
+ * @returns {?string}
+ *
+ * @example
+ * // Trigger error
+ * const ctx = cursor();
+ * ctx.set("not_existing_config", "test", "1");
+ *
+ * // Print error (should yield "Entry not found")
+ * print(ctx.error(), "\n");
+ */
static uc_value_t *
uc_uci_error(uc_vm_t *vm, size_t nargs)
{
@@ -63,6 +115,38 @@ uc_uci_error(uc_vm_t *vm, size_t nargs)
}
+/**
+ * Instantiate uci cursor.
+ *
+ * A uci cursor is a context for interacting with uci configuration files. It's
+ * purpose is to cache and hold changes made to loaded configuration states
+ * until those changes are written out to disk or discared.
+ *
+ * Unsaved and uncommitted changes in a cursor instance are private and not
+ * visible to other cursor instances instantiated by the same program or other
+ * processes on the system.
+ *
+ * Returns the instantiated cursor on success.
+ *
+ * Returns `null` on error, e.g. if an invalid path argument was provided.
+ *
+ * @function module:uci#cursor
+ *
+ * @param {string} [config_dir=/etc/config]
+ * The directory to search for configuration files. It defaults to the well
+ * known uci configuration directory `/etc/config` but may be set to a different
+ * path for special purpose applications.
+ *
+ * @param {string} [delta_dir=/tmp/.uci]
+ * The directory to save delta records in. It defaults to the well known
+ * `/tmp/.uci` path which is used as default by the uci command line tool.
+ *
+ * By changing this path to a different location, it is possible to isolate
+ * uncommitted application changes from the uci cli or other processes on the
+ * system.
+ *
+ * @returns {?module:uci.cursor}
+ */
static uc_value_t *
uc_uci_cursor(uc_vm_t *vm, size_t nargs)
{
@@ -98,6 +182,164 @@ uc_uci_cursor(uc_vm_t *vm, size_t nargs)
}
+/**
+ * Represents a context for interacting with uci configuration files.
+ *
+ * Operations on uci configurations are performed through a uci cursor object
+ * which operates on in-memory representations of loaded configuration files.
+ *
+ * Any changes made to configuration values are local to the cursor object and
+ * held in memory only until they're written out to the filesystem using the
+ * `save()` and `commit()` methods.
+ *
+ * Changes performed in one cursor instance are not reflected in another, unless
+ * the first instance writes those changes to the filesystem and the other
+ * instance explicitly (re)loads the affected configuration files.
+ *
+ * @class module:uci.cursor
+ * @hideconstructor
+ *
+ * @borrows module:uci#error as module.uci.cursor#error
+ *
+ * @see {@link module:uci#cursor|cursor()}
+ *
+ * @example
+ *
+ * const ctx = cursor(…);
+ *
+ * // Enumerate configuration files
+ * ctx.configs();
+ *
+ * // Load configuration files
+ * ctx.load(…);
+ * ctx.unload(…);
+ *
+ * // Query values
+ * ctx.get(…);
+ * ctx.get_all(…);
+ * ctx.get_first(…);
+ * ctx.foreach(…);
+ *
+ * // Modify values
+ * ctx.add(…);
+ * ctx.set(…);
+ * ctx.rename(…);
+ * ctx.reorder(…);
+ * ctx.delete(…);
+ *
+ * // Stage, revert, save changes
+ * ctx.changes(…);
+ * ctx.save(…);
+ * ctx.revert(…);
+ * ctx.commit(…);
+ */
+
+/**
+ * A uci change record is a plain array containing the change operation name as
+ * first element, the affected section ID as second argument and an optional
+ * third and fourth argument whose meanings depend on the operation.
+ *
+ * @typedef {string[]} ChangeRecord
+ * @memberof module:uci.cursor
+ *
+ * @property {string} 0
+ * The operation name - may be one of `add`, `set`, `remove`, `order`,
+ * `list-add`, `list-del` or `rename`.
+ *
+ * @property {string} 1
+ * The section ID targeted by the operation.
+ *
+ * @property {string} 2
+ * The meaning of the third element depends on the operation.
+ * - For `add` it is type of the section that has been added
+ * - For `set` it either is the option name if a fourth element exists, or the
+ * type of a named section which has been added when the change entry only
+ * contains three elements.
+ * - For `remove` it contains the name of the option that has been removed.
+ * - For `order` it specifies the new sort index of the section.
+ * - For `list-add` it contains the name of the list option a new value has been
+ * added to.
+ * - For `list-del` it contains the name of the list option a value has been
+ * removed from.
+ * - For `rename` it contains the name of the option that has been renamed if a
+ * fourth element exists, else it contains the new name a section has been
+ * renamed to if the change entry only contains three elements.
+ *
+ * @property {string} 4
+ * The meaning of the fourth element depends on the operation.
+ * - For `set` it is the value an option has been set to.
+ * - For `list-add` it is the new value that has been added to a list option.
+ * - For `rename` it is the new name of an option that has been renamed.
+ */
+
+/**
+ * A section object represents the options and their corresponding values
+ * enclosed within a configuration section, as well as some additional meta data
+ * such as sort indexes and internal ID.
+ *
+ * Any internal metadata fields are prefixed with a dot which isn't an allowed
+ * character for normal option names.
+ *
+ * @typedef {Object<string, boolean|number|string|string[]>} SectionObject
+ * @memberof module:uci.cursor
+ *
+ * @property {boolean} .anonymous
+ * The `.anonymous` property specifies whether the configuration is
+ * anonymous (`true`) or named (`false`).
+ *
+ * @property {number} .index
+ * The `.index` property specifies the sort order of the section.
+ *
+ * @property {string} .name
+ * The `.name` property holds the name of the section object. It may be either
+ * an anonymous ID in the form `cfgXXXXXX` with `X` being a hexadecimal digit or
+ * a string holding the name of the section.
+ *
+ * @property {string} .type
+ * The `.type` property contains the type of the corresponding uci
+ * section.
+ *
+ * @property {string|string[]} *
+ * A section object may contain an arbitrary number of further properties
+ * representing the uci option enclosed in the section.
+ *
+ * All option property names will be in the form `[A-Za-z0-9_]+` and either
+ * contain a string value or an array of strings, in case the underlying option
+ * is an UCI list.
+ */
+
+/**
+ * The sections callback is invoked for each section found within the given
+ * configuration and receives the section object and its associated name as
+ * arguments.
+ *
+ * @callback module:uci.cursor.SectionCallback
+ *
+ * @param {module:uci.cursor.SectionObject} section
+ * The section object.
+ */
+
+/**
+ * Explicitly reload configuration file.
+ *
+ * Usually, any attempt to query or modify a value within a given configuration
+ * will implicitly load the underlying file into memory. By invoking `load()`
+ * explicitly, a potentially loaded stale configuration is discarded and
+ * reloaded from the file system, ensuring that the latest state is reflected in
+ * the cursor.
+ *
+ * Returns `true` if the configuration was successfully loaded.
+ *
+ * Returns `null` on error, e.g. if the requested configuration does not exist.
+ *
+ * @function module:uci.cursor#load
+ *
+ * @param {string} config
+ * The name of the configuration file to load, e.g. `"system"` to load
+ * `/etc/config/system` into the cursor.
+ *
+ * @returns {?boolean}
+ */
static uc_value_t *
uc_uci_load(uc_vm_t *vm, size_t nargs)
{
@@ -127,6 +369,26 @@ uc_uci_load(uc_vm_t *vm, size_t nargs)
return ucv_boolean_new(true);
}
+/**
+ * Explicitly unload configuration file.
+ *
+ * The `unload()` function forcibly discards a loaded configuration state from
+ * the cursor so that the next attempt to read or modify that configuration
+ * will load it anew from the file system.
+ *
+ * Returns `true` if the configuration was successfully unloaded.
+ *
+ * Returns `false` if the configuration was not loaded to begin with.
+ *
+ * Returns `null` on error, e.g. if the requested configuration does not exist.
+ *
+ * @function module:uci.cursor#unload
+ *
+ * @param {string} config
+ * The name of the configuration file to unload.
+ *
+ * @returns {?boolean}
+ */
static uc_value_t *
uc_uci_unload(uc_vm_t *vm, size_t nargs)
{
@@ -308,18 +570,139 @@ uc_uci_get_any(uc_vm_t *vm, size_t nargs, bool all)
return ucv_string_new(ptr.s->type);
}
+/**
+ * Query a single option value or section type.
+ *
+ * When invoked with three arguments, the function returns the value of the
+ * given option, within the specified section of the given configuration.
+ *
+ * When invoked with just a config and section argument, the function returns
+ * the type of the specified section.
+ *
+ * In either case, the given configuration is implicitly loaded into the cursor
+ * if not already present.
+ *
+ * Returns the configuration value or section type on success.
+ *
+ * Returns `null` on error, e.g. if the requested configuration does not exist
+ * or if an invalid argument was passed.
+ *
+ * @function module:uci.cursor#get
+ *
+ * @param {string} config
+ * The name of the configuration file to query, e.g. `"system"` to query values
+ * in `/etc/config/system`.
+ *
+ * @param {string} section
+ * The name of the section to query within the configuration.
+ *
+ * @param {string} [option]
+ * The name of the option to query within the section. If omitted, the type of
+ * the section is returned instead.
+ *
+ * @returns {?(string|string[])}
+ *
+ * @example
+ * const ctx = cursor(…);
+ *
+ * // Query an option, extended section notation is supported
+ * ctx.get('system', '@system[0]', 'hostname');
+ *
+ * // Query a section type (should yield 'interface')
+ * ctx.get('network', 'lan');
+ */
static uc_value_t *
uc_uci_get(uc_vm_t *vm, size_t nargs)
{
return uc_uci_get_any(vm, nargs, false);
}
+/**
+ * Query a complete section or configuration.
+ *
+ * When invoked with two arguments, the function returns all values of the
+ * specified section within the given configuration as dictionary.
+ *
+ * When invoked with just a config argument, the function returns a nested
+ * dictionary of all sections present within the given configuration.
+ *
+ * In either case, the given configuration is implicitly loaded into the cursor
+ * if not already present.
+ *
+ * Returns the section or configuration dictionary on success.
+ *
+ * Returns `null` on error, e.g. if the requested configuration does not exist
+ * or if an invalid argument was passed.
+ *
+ * @function module:uci.cursor#get_all
+ *
+ * @param {string} config
+ * The name of the configuration file to query, e.g. `"system"` to query values
+ * in `/etc/config/system`.
+ *
+ * @param {string} [section]
+ * The name of the section to query within the configuration. If omitted a
+ * nested dictionary containing all section values is returned.
+ *
+ * @returns {?(Object<string, module:uci.cursor.SectionObject>|module:uci.cursor.SectionObject)}
+ *
+ * @example
+ * const ctx = cursor(…);
+ *
+ * // Query all lan interface details
+ * ctx.get_all('network', 'lan');
+ *
+ * // Dump the entire dhcp configuration
+ * ctx.get_all('dhcp');
+ */
static uc_value_t *
uc_uci_get_all(uc_vm_t *vm, size_t nargs)
{
return uc_uci_get_any(vm, nargs, true);
}
+/**
+ * Query option value or name of first section of given type.
+ *
+ * When invoked with three arguments, the function returns the value of the
+ * given option within the first found section of the specified type in the
+ * given configuration.
+ *
+ * When invoked with just a config and section type argument, the function
+ * returns the name of the first found section of the given type.
+ *
+ * In either case, the given configuration is implicitly loaded into the cursor
+ * if not already present.
+ *
+ * Returns the configuration value or section name on success.
+ *
+ * Returns `null` on error, e.g. if the requested configuration does not exist
+ * or if an invalid argument was passed.
+ *
+ * @function module:uci.cursor#get_first
+ *
+ * @param {string} config
+ * The name of the configuration file to query, e.g. `"system"` to query values
+ * in `/etc/config/system`.
+ *
+ * @param {string} type
+ * The section type to find the first section for within the configuration.
+ *
+ * @param {string} [option]
+ * The name of the option to query within the section. If omitted, the name of
+ * the section is returned instead.
+ *
+ * @returns {?(string|string[])}
+ *
+ * @example
+ * const ctx = cursor(…);
+ *
+ * // Query hostname in first anonymous "system" section of /etc/config/system
+ * ctx.get_first('system', 'system', 'hostname');
+ *
+ * // Figure out name of first network interface section (usually "loopback")
+ * ctx.get_first('network', 'interface');
+ */
static uc_value_t *
uc_uci_get_first(uc_vm_t *vm, size_t nargs)
{
@@ -378,6 +761,49 @@ uc_uci_get_first(uc_vm_t *vm, size_t nargs)
err_return(UCI_ERR_NOTFOUND);
}
+/**
+ * Add anonymous section to given configuration.
+ *
+ * Adds a new anonymous (unnamed) section of the specified type to the given
+ * configuration. In order to add a named section, the three argument form of
+ * `set()` should be used instead.
+ *
+ * In contrast to other query functions, `add()` will not implicitly load the
+ * configuration into the cursor. The configuration either needs to be loaded
+ * explicitly through `load()` beforehand, or implicitly by querying it through
+ * one of the `get()`, `get_all()`, `get_first()` or `foreach()` functions.
+ *
+ * Returns the autogenerated, ephemeral name of the added unnamed section
+ * on success.
+ *
+ * Returns `null` on error, e.g. if the targeted configuration was not loaded or
+ * if an invalid section type value was passed.
+ *
+ * @function module:uci.cursor#add
+ *
+ * @param {string} config
+ * The name of the configuration file to add the section to, e.g. `"system"` to
+ * modify `/etc/config/system`.
+ *
+ * @param {string} type
+ * The type value to use for the added section.
+ *
+ * @returns {?string}
+ *
+ * @example
+ * const ctx = cursor(…);
+ *
+ * // Load firewall configuration
+ * ctx.load('firewall');
+ *
+ * // Add unnamed `config rule` section
+ * const sid = ctx.add('firewall', 'rule');
+ *
+ * // Set values on the newly added section
+ * ctx.set('firewall', sid, 'name', 'A test');
+ * ctx.set('firewall', sid, 'target', 'ACCEPT');
+ * …
+ */
static uc_value_t *
uc_uci_add(uc_vm_t *vm, size_t nargs)
{
@@ -458,6 +884,60 @@ uval_to_uci(uc_vm_t *vm, uc_value_t *val, const char **p, bool *is_list)
}
}
+/**
+ * Set option value or add named section in given configuration.
+ *
+ * When invoked with four arguments, the function sets the value of the given
+ * option within the specified section of the given configuration to the
+ * provided value. A value of `""` (empty string) can be used to delete an
+ * existing option.
+ *
+ * When invoked with three arguments, the function adds a new named section to
+ * the given configuration, using the specified type.
+ *
+ * In either case, the given configuration is implicitly loaded into the cursor
+ * if not already present.
+ *
+ * Returns the `true` if the named section was added or the specified option was
+ * set.
+ *
+ * Returns `null` on error, e.g. if the targeted configuration was not found or
+ * if an invalid value was passed.
+ *
+ * @function module:uci.cursor#set
+ *
+ * @param {string} config
+ * The name of the configuration file to set values in, e.g. `"system"` to
+ * modify `/etc/config/system`.
+ *
+ * @param {string} section
+ * The section name to create or set a value in.
+ *
+ * @param {string} option_or_type
+ * The option name to set within the section or, when the subsequent value
+ * argument is omitted, the type of the section to create within the
+ * configuration.
+ *
+ * @param {(Array<string|boolean|number>|string|boolean|number)} [value]
+ * The option value to set.
+ *
+ * @returns {?boolean}
+ *
+ * @example
+ * const ctx = cursor(…);
+ *
+ * // Add named `config interface guest` section
+ * ctx.set('network', 'guest', 'interface');
+ *
+ * // Set values on the newly added section
+ * ctx.set('network', 'guest', 'proto', 'static');
+ * ctx.set('network', 'guest', 'ipaddr', '10.0.0.1/24');
+ * ctx.set('network', 'guest', 'dns', ['8.8.4.4', '8.8.8.8']);
+ * …
+ *
+ * // Delete 'disabled' option in first wifi-iface section
+ * ctx.set('wireless', '@wifi-iface[0]', 'disabled', '');
+ */
static uc_value_t *
uc_uci_set(uc_vm_t *vm, size_t nargs)
{
@@ -563,6 +1043,50 @@ uc_uci_set(uc_vm_t *vm, size_t nargs)
return ucv_boolean_new(true);
}
+/**
+ * Delete an option or section from given configuration.
+ *
+ * When invoked with three arguments, the function deletes the given option
+ * within the specified section of the given configuration.
+ *
+ * When invoked with two arguments, the function deletes the entire specified
+ * section within the given configuration.
+ *
+ * In either case, the given configuration is implicitly loaded into the cursor
+ * if not already present.
+ *
+ * Returns the `true` if specified option or section has been deleted.
+ *
+ * Returns `null` on error, e.g. if the targeted configuration was not found or
+ * if an invalid value was passed.
+ *
+ * @function module:uci.cursor#delete
+ *
+ * @param {string} config
+ * The name of the configuration file to delete values in, e.g. `"system"` to
+ * modify `/etc/config/system`.
+ *
+ * @param {string} section
+ * The section name to remove the specified option in or, when the subsequent
+ * argument is omitted, the section to remove entirely.
+ *
+ * @param {string} [option]
+ * The option name to remove within the section.
+ *
+ * @returns {?boolean}
+ *
+ * @example
+ * const ctx = cursor(…);
+ *
+ * // Delete 'disabled' option in first wifi-iface section
+ * ctx.delete('wireless', '@wifi-iface[0]', 'disabled');
+ *
+ * // Delete 'wan' interface
+ * ctx.delete('network', 'lan');
+ *
+ * // Delete last firewall rule
+ * ctx.delete('firewall', '@rule[-1]');
+ */
static uc_value_t *
uc_uci_delete(uc_vm_t *vm, size_t nargs)
{
@@ -598,6 +1122,55 @@ uc_uci_delete(uc_vm_t *vm, size_t nargs)
return ucv_boolean_new(true);
}
+/**
+ * Rename an option or section in given configuration.
+ *
+ * When invoked with four arguments, the function renames the given option
+ * within the specified section of the given configuration to the provided
+ * value.
+ *
+ * When invoked with three arguments, the function renames the entire specified
+ * section to the provided value.
+ *
+ * In either case, the given configuration is implicitly loaded into the cursor
+ * if not already present.
+ *
+ * Returns the `true` if specified option or section has been renamed.
+ *
+ * Returns `null` on error, e.g. if the targeted configuration was not found or
+ * if an invalid value was passed.
+ *
+ * @function module:uci.cursor#rename
+ *
+ * @param {string} config
+ * The name of the configuration file to rename values in, e.g. `"system"` to
+ * modify `/etc/config/system`.
+ *
+ * @param {string} section
+ * The section name to rename or to rename an option in.
+ *
+ * @param {string} option_or_name
+ * The option name to rename within the section or, when the subsequent name
+ * argument is omitted, the new name of the renamed section within the
+ * configuration.
+ *
+ * @param {string} [name]
+ * The new name of the option to rename.
+ *
+ * @returns {?boolean}
+ *
+ * @example
+ * const ctx = cursor(…);
+ *
+ * // Assign explicit name to last anonymous firewall rule section
+ * ctx.rename('firewall', '@rule[-1]', 'my_block_rule');
+ *
+ * // Rename 'server' to 'orig_server_list' in ntp section of system config
+ * ctx.rename('system', 'ntp', 'server', 'orig_server_list');
+ *
+ * // Rename 'wan' interface to 'external'
+ * ctx.rename('network', 'wan', 'external');
+ */
static uc_value_t *
uc_uci_rename(uc_vm_t *vm, size_t nargs)
{
@@ -658,6 +1231,74 @@ uc_uci_rename(uc_vm_t *vm, size_t nargs)
return ucv_boolean_new(true);
}
+/**
+ * Reorder sections in given configuration.
+ *
+ * The `reorder()` function moves a single section by repositioning it to the
+ * given index within the configurations section list.
+ *
+ * The given configuration is implicitly loaded into the cursor if not already
+ * present.
+ *
+ * Returns the `true` if specified section has been moved.
+ *
+ * Returns `null` on error, e.g. if the targeted configuration was not found or
+ * if an invalid value was passed.
+ *
+ * @function module:uci.cursor#reorder
+ *
+ * @param {string} config
+ * The name of the configuration file to move the section in, e.g. `"system"` to
+ * modify `/etc/config/system`.
+ *
+ * @param {string} section
+ * The section name to move.
+ *
+ * @param {number} index
+ * The target index to move the section to, starting from `0`.
+ *
+ * @param {string} [name]
+ * The new name of the option to rename.
+ *
+ * @returns {?boolean}
+ *
+ * @example
+ * const ctx = cursor(…);
+ *
+ * // Query whole firewall config and reorder resulting dict by type and name
+ * const type_order = ['defaults', 'zone', 'forwarding', 'redirect', 'rule'];
+ * const values = ctx.get_all('firewall');
+ *
+ * sort(values, (k1, k2, s1, s2) => {
+ * // Get weight from type_order array
+ * let w1 = index(type_order, s1['.type']);
+ * let w2 = index(type_order, s2['.type']);
+ *
+ * // For unknown type orders, use type value itself as weight
+ * if (w1 == -1) w1 = s1['.type'];
+ * if (w2 == -1) w2 = s2['.type'];
+ *
+ * // Get name from name option, fallback to section name
+ * let n1 = s1.name ?? k1;
+ * let n2 = s2.name ?? k2;
+ *
+ * // Order by weight
+ * if (w1 < w2) return -1;
+ * if (w1 > w2) return 1;
+ *
+ * // For same weight order by name
+ * if (n1 < n2) return -1;
+ * if (n1 > n2) return 1;
+ *
+ * return 0;
+ * });
+ *
+ * // Sequentially reorder sorted sections in firewall configuration
+ * let position = 0;
+ *
+ * for (let sid in values)
+ * ctx.reorder('firewall', sid, position++);
+ */
static uc_value_t *
uc_uci_reorder(uc_vm_t *vm, size_t nargs)
{
@@ -766,18 +1407,125 @@ uc_uci_pkg_command(uc_vm_t *vm, size_t nargs, enum pkg_cmd cmd)
return ucv_boolean_new(true);
}
+/**
+ * Save accumulated cursor changes to delta directory.
+ *
+ * The `save()` function writes consolidated changes made to in-memory copies of
+ * loaded configuration files to the uci delta directory which effectively makes
+ * them available to other processes using the same delta directory path as well
+ * as the `uci changes` cli command when using the default delta directory.
+ *
+ * Note that uci deltas are overlayed over the actual configuration file values
+ * so they're reflected by `get()`, `foreach()` etc. even if the underlying
+ * configuration files are not actually changed (yet). The delta records may be
+ * either permanently merged into the configuration by invoking `commit()` or
+ * reverted through `revert()` in order to restore the current state of the
+ * underlying configuration file.
+ *
+ * When the optional "config" parameter is omitted, delta records for all
+ * currently loaded configuration files are written.
+ *
+ * In case that neither sharing changes with other processes nor any revert
+ * functionality is required, changes may be committed directly using `commit()`
+ * instead, bypassing any delta record creation.
+ *
+ * Returns the `true` if operation completed successfully.
+ *
+ * Returns `null` on error, e.g. if the requested configuration was not loaded
+ * or when a file system error occurred.
+ *
+ * @function module:uci.cursor#save
+ *
+ * @param {string} [config]
+ * The name of the configuration file to save delta records for, e.g. `"system"`
+ * to store changes for `/etc/config/system`.
+ *
+ * @returns {?boolean}
+ *
+ * @example
+ * const ctx = cursor(…);
+ *
+ * ctx.set('wireless', '@wifi-iface[0]', 'disabled', '1');
+ * ctx.save('wireless');
+ *
+ * @see {@link module:uci.cursor#commit|commit()}
+ * @see {@link module:uci.cursor#revert|revert()}
+ */
static uc_value_t *
uc_uci_save(uc_vm_t *vm, size_t nargs)
{
return uc_uci_pkg_command(vm, nargs, CMD_SAVE);
}
+/**
+ * Update configuration files with accumulated cursor changes.
+ *
+ * The `commit()` function merges changes made to in-memory copies of loaded
+ * configuration files as well as existing delta records in the cursors
+ * configured delta directory and writes them back into the underlying
+ * configuration files, persistently committing changes to the file system.
+ *
+ * When the optional "config" parameter is omitted, all currently loaded
+ * configuration files with either present delta records or yet unsaved
+ * cursor changes are updated.
+ *
+ * Returns the `true` if operation completed successfully.
+ *
+ * Returns `null` on error, e.g. if the requested configuration was not loaded
+ * or when a file system error occurred.
+ *
+ * @function module:uci.cursor#commit
+ *
+ * @param {string} [config]
+ * The name of the configuration file to commit, e.g. `"system"` to update the
+ * `/etc/config/system` file.
+ *
+ * @returns {?boolean}
+ *
+ * @example
+ * const ctx = cursor(…);
+ *
+ * ctx.set('system', '@system[0]', 'hostname', 'example.org');
+ * ctx.commit('system');
+ */
static uc_value_t *
uc_uci_commit(uc_vm_t *vm, size_t nargs)
{
return uc_uci_pkg_command(vm, nargs, CMD_COMMIT);
}
+/**
+ * Revert accumulated cursor changes and associated delta records.
+ *
+ * The `revert()` function discards any changes made to in-memory copies of
+ * loaded configuration files and discards any related existing delta records in
+ * the cursors configured delta directory.
+ *
+ * When the optional "config" parameter is omitted, all currently loaded
+ * configuration files with either present delta records or yet unsaved
+ * cursor changes are reverted.
+ *
+ * Returns the `true` if operation completed successfully.
+ *
+ * Returns `null` on error, e.g. if the requested configuration was not loaded
+ * or when a file system error occurred.
+ *
+ * @function module:uci.cursor#revert
+ *
+ * @param {string} [config]
+ * The name of the configuration file to revert, e.g. `"system"` to discard any
+ * changes for the `/etc/config/system` file.
+ *
+ * @returns {?boolean}
+ *
+ * @example
+ * const ctx = cursor(…);
+ *
+ * ctx.set('system', '@system[0]', 'hostname', 'example.org');
+ * ctx.revert('system');
+ *
+ * @see {@link module:uci.cursor#save|save()}
+ */
static uc_value_t *
uc_uci_revert(uc_vm_t *vm, size_t nargs)
{
@@ -873,6 +1621,38 @@ changes_to_uval(uc_vm_t *vm, struct uci_context *ctx, const char *package)
return a;
}
+/**
+ * Enumerate pending changes.
+ *
+ * The `changes()` function returns a list of change records for currently
+ * loaded configuration files, originating both from the cursors associated
+ * delta directory and yet unsaved cursor changes.
+ *
+ * When the optional "config" parameter is specified, the requested
+ * configuration is implicitly loaded if it not already loaded into the cursor.
+ *
+ * Returns a dictionary of change record arrays, keyed by configuration name.
+ *
+ * Returns `null` on error, e.g. if the requested configuration could not be
+ * loaded.
+ *
+ * @function module:uci.cursor#changes
+ *
+ * @param {string} [config]
+ * The name of the configuration file to enumerate changes for, e.g. `"system"`
+ * to query pending changes for the `/etc/config/system` file.
+ *
+ * @returns {?Object<string, module:uci.cursor.ChangeRecord[]>}
+ *
+ * @example
+ * const ctx = cursor(…);
+ *
+ * // Enumerate changes for all currently loaded configurations
+ * const deltas = ctx.changes();
+ *
+ * // Explicitly load and enumerate changes for the "system" configuration
+ * const deltas = ctx.changes('system');
+ */
static uc_value_t *
uc_uci_changes(uc_vm_t *vm, size_t nargs)
{
@@ -907,6 +1687,48 @@ uc_uci_changes(uc_vm_t *vm, size_t nargs)
return res;
}
+/**
+ * Iterate configuration sections.
+ *
+ * The `foreach()` function iterates all sections of the given configuration,
+ * optionally filtered by type, and invokes the given callback function for
+ * each encountered section.
+ *
+ * When the optional "type" parameter is specified, the callback is only invoked
+ * for sections of the given type, otherwise it is invoked for all sections.
+ *
+ * The requested configuration is implicitly loaded into the cursor.
+ *
+ * Returns `true` if the callback was executed successfully at least once.
+ *
+ * Returns `false` if the callback was never invoked, e.g. when the
+ * configuration is empty or contains no sections of the given type.
+ *
+ * Returns `null` on error, e.g. when an invalid callback was passed or the
+ * requested configuration not found.
+ *
+ * @function module:uci.cursor#foreach
+ *
+ * @param {string} config
+ * The configuration to iterate sections for, e.g. `"system"` to read the
+ * `/etc/config/system` file.
+ *
+ * @param {?string} type
+ * Invoke the callback only for sections of the specified type.
+ *
+ * @param {module:uci.cursor.SectionCallback} callback
+ * The callback to invoke for each section, will receive a section dictionary
+ * as sole argument.
+ *
+ * @returns {?boolean}
+ *
+ * @example
+ * const ctx = cursor(…);
+ *
+ * // Iterate all network interfaces
+ * ctx.foreach('network', 'interface',
+ * section => print(`Have interface ${section[".name"]}\n`));
+ */
static uc_value_t *
uc_uci_foreach(uc_vm_t *vm, size_t nargs)
{
@@ -967,6 +1789,26 @@ uc_uci_foreach(uc_vm_t *vm, size_t nargs)
return ucv_boolean_new(ret);
}
+/**
+ * Enumerate existing configurations.
+ *
+ * The `configs()` function yields an array of configuration files present in
+ * the cursors associated configuration directory, `/etc/config/` by default.
+ *
+ * Returns an array of configuration names on success.
+ *
+ * Returns `null` on error, e.g. due to filesystem errors.
+ *
+ * @function module:uci.cursor#configs
+ *
+ * @returns {?string[]}
+ *
+ * @example
+ * const ctx = cursor(…);
+ *
+ * // Enumerate all present configuration file names
+ * const configurations = ctx.configs();
+ */
static uc_value_t *
uc_uci_configs(uc_vm_t *vm, size_t nargs)
{