diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..58d1e23
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+/libkv.a
+/kv
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..5a43713
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,40 @@
+
+all: build
+
+build: libkv kv
+
+LIBKV_SRC = buffer.c kv_path.c
+LIBKV_O = buffer.o kv_path.o
+
+KV_SRC = kv.c
+KV_O = kv.o
+
+CFLAGS ?=
+CFLAGS += -W -Wall -Werror -ansi -pedantic -I.
+
+ifeq ($(DEBUG),)
+CFLAGS += -O2
+else
+CFLAGS += -DDEBUG -O0
+endif
+
+CLEANFILES =
+
+libkv: libkv.a
+
+libkv.a: ${LIBKV_O}
+ ar -cru libkv.a ${LIBKV_O}
+CLEANFILES += libkv.a
+
+.c.o:
+ ${CC} ${CPPFLAGS} ${CFLAGS} -c $<
+CLEANFILES += *.o
+
+kv: ${KV_O}
+ ${CC} ${CFLAGS} ${LDFLAGS} ${KV_O} -o kv
+CLEANFILES += kv
+
+clean:
+ rm -rf ${CLEANFILES}
+
+.PHONY: all build clean libkv
diff --git a/buffer.c b/buffer.c
new file mode 100644
index 0000000..945fd1a
--- /dev/null
+++ b/buffer.c
@@ -0,0 +1,136 @@
+/*
+ * kv - key value text file format
+ *
+ * Copyright 2022 Thomas de Grivel
+*/
+
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include "buffer.h"
+
+int buffer_init (s_buffer *b, FILE *fp, void *state)
+{
+ assert(b);
+ b->chars = calloc(BUFFER_SIZE, 1);
+ if (!b->chars)
+ return -1;
+ b->fp = fp;
+ b->pos = 0;
+ b->size = BUFFER_SIZE;
+ b->state = state;
+ b->wpos = 0;
+ return 0;
+}
+
+s_buffer * buffer_new (FILE *fp, void *state)
+{
+ s_buffer *b = calloc(1, sizeof(s_buffer));
+ if (!b)
+ return NULL;
+ if (buffer_init(b, fp, state))
+ return NULL;
+ return b;
+}
+
+int buffer_resize (s_buffer *b, size_t new_size)
+{
+ void *ptr;
+ assert(b);
+ assert(new_size >= b->size);
+ if (new_size == b->size)
+ return 0;
+ ptr = realloc(b->chars, new_size);
+ if (!ptr)
+ return -1;
+ b->chars = ptr;
+ b->size = new_size;
+ return 0;
+}
+
+int buffer_fill (s_buffer *b)
+{
+ size_t r;
+ assert(b);
+ if (b->wpos == b->size &&
+ buffer_resize(b, b->size + BUFFER_SIZE))
+ return -1;
+ assert(b->wpos < b->size);
+ r = fread(b->chars + b->wpos, 1, b->size - b->wpos, b->fp);
+ if (r <= 0)
+ return -1;
+ b->wpos += r;
+ return 0;
+}
+
+int buffer_peek (s_buffer *b)
+{
+ assert(b);
+ if (b->pos == b->wpos &&
+ buffer_fill(b))
+ return -1;
+ assert(b->pos < b->wpos);
+ return b->chars[b->pos];
+}
+
+int buffer_peek_n (s_buffer *b, size_t n)
+{
+ assert(b);
+ if (n == 0)
+ return 0;
+ while (b->pos + n >= b->wpos)
+ if (buffer_fill(b))
+ return -1;
+ assert(b->pos + n < b->wpos);
+ return 0;
+}
+
+int buffer_read (s_buffer *b)
+{
+ int c = buffer_peek(b);
+ if (c >= 0)
+ b->pos++;
+ return c;
+}
+
+int buffer_read_n (s_buffer *b, size_t n)
+{
+ if (buffer_peek_n(b, n))
+ return -1;
+ b->pos += n;
+ return 0;
+}
+
+int buffer_flush (s_buffer *b)
+{
+ assert(b);
+ if (b->pos == 0)
+ return 0;
+ memmove(b->chars, b->chars + b->pos, b->wpos - b->pos);
+ b->pos = 0;
+ b->wpos -= b->pos;
+ return 0;
+}
+
+int buffer_close (s_buffer *b)
+{
+ if (!b)
+ return 0;
+ fclose(b->fp);
+ free(b->chars);
+ b->chars = NULL;
+ b->pos = 0;
+ b->size = 0;
+ b->state = NULL;
+ b->wpos = 0;
+ return 0;
+}
+
+int buffer_delete (s_buffer *b)
+{
+ if (buffer_close(b))
+ return -1;
+ free(b);
+ return 0;
+}
diff --git a/buffer.h b/buffer.h
new file mode 100644
index 0000000..fd95a4d
--- /dev/null
+++ b/buffer.h
@@ -0,0 +1,38 @@
+/*
+ * kv - key value text file format
+ *
+ * Copyright 2022 Thomas de Grivel
+*/
+
+#ifndef BUFFER_H
+#define BUFFER_H
+
+#ifdef DEBUG
+# define BUFFER_SIZE 64
+#else
+# define BUFFER_SIZE (1024 * 1024)
+#endif
+
+typedef struct buffer
+{
+ char *chars;
+ FILE *fp;
+ size_t pos;
+ size_t size;
+ void *state;
+ size_t wpos;
+} s_buffer;
+
+int buffer_init (s_buffer *b, FILE *fp, void *state);
+s_buffer * buffer_new (FILE *fp, void *state);
+int buffer_resize (s_buffer *b, size_t new_size);
+int buffer_fill (s_buffer *b);
+int buffer_peek (s_buffer *b);
+int buffer_peek_n (s_buffer *b, size_t n);
+int buffer_read (s_buffer *b);
+int buffer_read_n (s_buffer *b, size_t n);
+int buffer_flush (s_buffer *b);
+int buffer_close (s_buffer *b);
+int buffer_delete (s_buffer *b);
+
+#endif
diff --git a/kv.c b/kv.c
new file mode 100644
index 0000000..9d67a5e
--- /dev/null
+++ b/kv.c
@@ -0,0 +1,36 @@
+/*
+ * kv - key value text file format
+ *
+ * Copyright 2022 Thomas de Grivel
+*/
+
+#include <stdio.h>
+#include <kv.h>
+
+int on_kv (s_buffer *b, const char *v, size_t vsz)
+{
+ (void) b;
+ (void) v;
+ (void) vsz;
+ printf("--- v: %s\n", v);
+ return 0;
+}
+
+int read_file (const char *path)
+{
+ FILE *fp = fopen(path, "rb");
+ s_buffer b;
+ if (buffer_init(&b, fp, NULL) ||
+ kv_parse(&b, on_kv) ||
+ buffer_close(&b))
+ return -1;
+ return 0;
+}
+
+
+int main (int argc, char **argv)
+{
+ while (--argc > 0)
+ read_file(*++argv);
+ return 0;
+}
diff --git a/kv.h b/kv.h
new file mode 100644
index 0000000..fd703db
--- /dev/null
+++ b/kv.h
@@ -0,0 +1,12 @@
+/*
+ * kv - key value text file format
+ *
+ * Copyright 2022 Thomas de Grivel
+*/
+
+#ifndef KV_H
+#define KV_H
+
+#include "buffer.h"
+
+#endif
diff --git a/kv_path.c b/kv_path.c
new file mode 100644
index 0000000..63e5ad4
--- /dev/null
+++ b/kv_path.c
@@ -0,0 +1,50 @@
+/*
+ * kv - key value text file format
+ *
+ * Copyright 2022 Thomas de Grivel
+*/
+
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include "kv_path.h"
+
+void kv_path_init (s_kv_path *p, const char *k, size_t ks,
+ s_kv_path *parent)
+{
+ assert(p);
+ p->k = k;
+ p->ks = ks;
+ p->parent = parent;
+}
+
+s_kv_path * kv_path_new (const char *k, size_t ks, s_kv_path *parent)
+{
+ s_kv_path *p = calloc(sizeof(s_kv_path), 1);
+ if (p)
+ kv_path_init(p, k, ks, parent);
+ return p;
+}
+
+void kv_path_delete (s_kv_path *p)
+{
+ free(p);
+}
+
+size_t kv_path_print (s_kv_path *p, FILE *fp)
+{
+ s_kv_path *acc;
+ size_t r = 0;
+ while (p) {
+ acc = kv_path_new(p->k, p->ks, acc);
+ p = p->parent;
+ }
+ while (acc) {
+ r += fprintf(fp, "%s", acc->k);
+ kv_path_delete(acc);
+ acc = acc->parent;
+ if (acc)
+ r += fprintf(fp, " ");
+ }
+ return r;
+}
diff --git a/kv_path.h b/kv_path.h
new file mode 100644
index 0000000..1895648
--- /dev/null
+++ b/kv_path.h
@@ -0,0 +1,24 @@
+/*
+ * kv - key value text file format
+ *
+ * Copyright 2022 Thomas de Grivel
+*/
+
+#ifndef KV_PATH_H
+#define KV_PATH_H
+
+typedef struct kv_path s_kv_path;
+
+struct kv_path {
+ const char *k;
+ size_t ks;
+ s_kv_path *parent;
+};
+
+void kv_path_init (s_kv_path *p, const char *k, size_t ks,
+ s_kv_path *parent);
+s_kv_path * kv_path_new (const char *k, size_t ks, s_kv_path *parent);
+void kv_path_delete (s_kv_path *p);
+size_t kv_path_print (s_kv_path *p, FILE *fp);
+
+#endif