shithub: treepack

Download patch

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);
+}