ref: a89274a190cae04738d909c09ab678c0f550ae0f
author: kvik <kvik@a-b.xyz>
date: Tue May 26 16:29:12 EDT 2020
BEGIN
--- /dev/null
+++ b/@build
@@ -1,0 +1,3 @@
+#!/bin/sh -e
+./cc treeload.c
+./ld -lucl treeload.o -o treeload
--- /dev/null
+++ b/@test
@@ -1,0 +1,3 @@
+#!/bin/sh
+
+./treeload outdir test/array.ucl
--- /dev/null
+++ b/README.md
@@ -1,0 +1,54 @@
+# treepack — file-tree marshaling
+
+## Programs
+
+* treeload directory file
+ * Loads a `file` into a tree rooted at `directory` according
+ to rules described later.
+* treedump directory file
+ * Dumps a tree rooted at `directory` into a `file` in one of the
+ supported formats.
+
+Currently `libucl` is used to read and write `file`s so that `UCL` or
+`JSON` files can be loaded with `treeload`; and `JSON` (pretty,
+compact), `YAML` (compact), and msgpack, can be dumped with `treedump`.
+
+## Tree-making rules
+
+* Tree leaves, that is non-object, non-array object members or array
+ elements become regular files with their key as the file name and
+ value as file content.
+ Key of array elements is their implicit zero-based index.
+* Objects and arrays become directories named after their key. They
+ contain files and directories corresponding to child leaves or
+ other objects and arrays.
+
+Let's consider a simple example with a structure in `UCL` format:
+
+ people = [
+ {name = "Alice", genius = yes, items = [phaser]}
+ {name = "Bob", genius = no, items = [cap, stick]},
+ ]
+
+The resulting file tree produced by `treeload` is as follows:
+
+ people/
+ 0/
+ name -> Joe
+ genius -> no
+ items/
+ 0 -> cap
+ 1 -> stick
+ 1/
+ name -> Alice
+ genius -> yes
+ items/
+ 0 -> phaser
+
+## TODO
+
+* Write `treedump`.
+* Write actual tree-making code instead of emitting a shell script.
+* Rewrite as a Lua host (script?) driven by Lua tables.
+ * This way any structured format can be generically plugged in by
+ writing a binding and using it to fill a Lua table.
--- /dev/null
+++ b/cc
@@ -1,0 +1,2 @@
+#!/bin/sh -x
+exec clang `cat conf-cc` -c "$@"
--- /dev/null
+++ b/conf-cc
@@ -1,0 +1,1 @@
+-D_DEFAULT_SOURCE -O2 -Wall -Werror -pipe -I/usr/local/include
--- /dev/null
+++ b/conf-ld
@@ -1,0 +1,1 @@
+-L/usr/local/lib
--- /dev/null
+++ b/ld
@@ -1,0 +1,2 @@
+#!/bin/sh -x
+exec clang `cat conf-ld` "$@"
--- /dev/null
+++ b/treeload.c
@@ -1,0 +1,125 @@
+#include <assert.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <limits.h>
+
+#include <ucl.h>
+
+#define USAGE "usage: treeload directory file\n"
+
+void
+errexit(int code, char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ vfprintf(stderr, fmt, ap);
+ va_end(ap);
+ exit(code);
+}
+
+typedef struct Walkstate Walkstate;
+struct Walkstate {
+ const ucl_object_t *obj;
+ int index;
+ const char *key;
+ ucl_type_t type;
+ char *path;
+} walkstatezero = {
+ .obj = NULL,
+ .index = 0,
+ .key = NULL,
+ .type = UCL_NULL,
+ .path = ""
+};
+
+void
+mkdir(char *path)
+{
+ printf("mkdir -p %s\n", path);
+}
+
+void
+mkfile(char *path, const char *data, size_t dlen)
+{
+ (void)dlen;
+ printf("echo '%s' > %s\n", data, path);
+}
+
+void
+leaf(const ucl_object_t *obj, Walkstate *parent)
+{
+ char *p;
+
+ if(parent->type == UCL_ARRAY)
+ asprintf(&p, "%s/%d", parent->path, parent->index);
+ else
+ asprintf(&p, "%s/%s", parent->path, ucl_object_key(obj));
+
+ mkfile(p, ucl_object_tostring_forced(obj), -1);
+ free(p);
+}
+
+void
+inner(const ucl_object_t *obj, Walkstate *parent)
+{
+ Walkstate this = walkstatezero;
+ const ucl_object_t *cur = NULL;
+ ucl_object_iter_t it = NULL;
+
+ this.key = ucl_object_key(obj);
+ this.type = ucl_object_type(obj);
+ if(this.key != NULL)
+ asprintf(&this.path, "%s/%s", parent->path, this.key);
+ else
+ asprintf(&this.path, "%s", parent->path);
+ if(parent->type == UCL_ARRAY){
+ free(this.path);
+ asprintf(&this.path, "%s/%d", parent->path, parent->index);
+ }
+ mkdir(this.path);
+
+ it = ucl_object_iterate_new(obj);
+ while((cur = ucl_object_iterate_safe(it, true)) != NULL){
+ switch(ucl_object_type(cur)){
+ case UCL_OBJECT:
+ case UCL_ARRAY:
+ inner(cur, &this); break;
+ default:
+ leaf(cur, &this); break;
+ }
+ this.index += 1;
+ }
+ ucl_object_iterate_free(it);
+ free(this.path);
+}
+
+int
+main(int argc, char *argv[])
+{
+ char *dn = NULL, *fn = NULL;
+ struct ucl_parser *parser = NULL;
+ ucl_object_t *obj = NULL;
+
+ if(argc != 3) errexit(111, USAGE);
+ dn = argv[1];
+ fn = argv[2];
+
+ if((parser = ucl_parser_new(0)) == NULL)
+ errexit(100, "can't allocate a parser\n");
+ if(ucl_parser_add_file(parser, fn) == false)
+ errexit(100, "%s\n", ucl_parser_get_error(parser));
+ if((obj = ucl_parser_get_object(parser)) == NULL)
+ errexit(100, "%s\n", ucl_parser_get_error(parser));
+
+ {
+ Walkstate root = walkstatezero;
+
+ root.path = dn;
+ mkdir(root.path);
+ inner(obj, &root);
+ }
+ exit(0);
+}