From 6e20bab8f5fcfbe4fe816425704f9edd8ad88444 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Yaroslav=20de=20la=20Pe=C3=B1a=20Smirnov?=
 <yps@yaroslavps.com>
Date: Thu, 19 Aug 2021 01:42:49 +0300
Subject: Example and new helper funcion

* Added an example program an ini file to example/
* Added a new helper function `parcini_value_handle` to handle and copy
  parsed values, depending on the expected value type.
---
 Makefile            |  8 ++++-
 README.md           | 11 +++++-
 example/example.c   | 99 +++++++++++++++++++++++++++++++++++++++++++++++++++++
 example/example.ini |  8 +++++
 include/parcini.h   | 17 ++++++++-
 src/parcini.c       | 27 +++++++++++++++
 6 files changed, 167 insertions(+), 3 deletions(-)
 create mode 100644 example/example.c
 create mode 100644 example/example.ini

diff --git a/Makefile b/Makefile
index 8917d8e..a21748b 100644
--- a/Makefile
+++ b/Makefile
@@ -12,16 +12,22 @@ BUILD_DIR:=build
 test: CFLAGS := -std=c99 -O0 -g -Wall -DDEBUG
 test: $(BUILD_DIR) tests/parcini
 
-tests/%: $(OBJ_DIR)/src/tests/%.o $(OBJ_DIR)/src/%.o $(PARCINI_OBJECTS)
+tests/%: $(OBJ_DIR)/src/tests/%.o $(PARCINI_OBJECTS)
 	$(CC) $(LDFLAGS) -o $(BUILD_DIR)/$@ $^
 	$(BUILD_DIR)/$@
 
+example: CFLAGS := -std=c99 -O2 -Wall
+example: $(OBJ_DIR)/example/example.o $(PARCINI_OBJECTS)
+	$(CC) $(LDFLAGS) -o $(BUILD_DIR)/example/$@ $^
+
 $(OBJ_DIR)/%.o: %.c
 	$(CC) -c $(CFLAGS) $(INC_DIRS) -o $@ $<
 
 $(BUILD_DIR):
 	mkdir -p $(BUILD_DIR)/tests
+	mkdir -p $(BUILD_DIR)/example
 	mkdir -p $(OBJ_DIR)/src/tests
+	mkdir -p $(OBJ_DIR)/example
 
 clean:
 	$(RM) $(OBJ_DIR)
diff --git a/README.md b/README.md
index 44986f5..27ced63 100644
--- a/README.md
+++ b/README.md
@@ -67,4 +67,13 @@ key="value" # another comment
 
 # Example program
 
-*TODO*
+Check out `example/example.c` for an example of a program using parcini. You can
+compile it by running `make example`, it will be compiled to
+`build/example/example`.
+
+# Questions
+
+I don't expect this piece of code to be used by a lot of people (if any), but if
+you have any questions, or maybe even patches, send me an email. You can find my
+address and my PGP key on my homepage 
+[www.yaroslavps.com](http://www.yaroslavps.com).
diff --git a/example/example.c b/example/example.c
new file mode 100644
index 0000000..1036845
--- /dev/null
+++ b/example/example.c
@@ -0,0 +1,99 @@
+#define _POSIX_C_SOURCE 200809L
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "parcini.h"
+
+#define EXAMPLE_INI "example/example.ini"
+
+struct {
+	struct {
+		char *name;
+		long int age;
+	} personal;
+	struct {
+		char *email;
+		bool visible;
+	} contact;
+} example = {
+	.personal.name = NULL,
+	.contact.email = NULL,
+};
+
+static bool
+handle_keyvalue(struct parcini_line *parsed)
+{
+#define MATCHES(s, k) !strcmp(parsed->section, s) && !strcmp(parsed->key, k)
+
+	if (MATCHES("personal", "name")) {
+		return parcini_value_handle(&parsed->value, PARCINI_VALUE_STRING,
+				&example.personal.name);
+	}
+	if (MATCHES("personal", "age")) {
+		return parcini_value_handle(&parsed->value, PARCINI_VALUE_INTEGER,
+				&example.personal.age);
+	}
+	if (MATCHES("contact", "email")) {
+		return parcini_value_handle(&parsed->value, PARCINI_VALUE_STRING,
+				&example.contact.email);
+	}
+	if (MATCHES("contact", "visible")) {
+		return parcini_value_handle(&parsed->value, PARCINI_VALUE_BOOLEAN, 
+				&example.contact.visible);
+	}
+
+	return false;
+}
+
+int
+main(void)
+{
+	parcini_t *parser = parcini_from_file(EXAMPLE_INI);
+	if (parser == NULL) {
+		fprintf(stderr, "error opening file: %s\n", strerror(errno));
+		return 1;
+	}
+
+	struct parcini_line parsed;
+	enum parcini_result res;
+	while ((res = parcini_parse_next_line(parser, &parsed)) != PARCINI_EOF) {
+		switch (res) {
+		case PARCINI_EMPTY_LINE:
+		case PARCINI_SECTION:
+			continue;
+
+		case PARCINI_KEYVALUE:
+			if (!handle_keyvalue(&parsed)) {
+				fprintf(stderr, 
+						"line %ld: error: unexpected section '%s'/key '%s' or "
+						"invalid value type\n",
+						parsed.lineno, parsed.section, parsed.key);
+				goto cleanup;
+			}
+			continue;
+
+		case PARCINI_MEMORY_ERROR:
+		case PARCINI_STREAM_ERROR:
+			fprintf(stderr, 
+					"An error occurred while trying to read the next line\n");
+			goto cleanup;
+
+		default:
+			fprintf(stderr, "line %ld: parse error\n", parsed.lineno);
+			goto cleanup;
+		};
+	}
+
+	printf("parsed example.ini:\n");
+	printf("name:\t%s\n", example.personal.name);
+	printf("age:\t%ld\n", example.personal.age);
+	printf("email:\t%s\n", example.contact.email);
+	printf("visible:\t%s\n", example.contact.visible ? "true" : "false");
+
+cleanup:
+	free(example.personal.name);
+	free(example.contact.email);
+	parcini_destroy(parser);
+}
diff --git a/example/example.ini b/example/example.ini
new file mode 100644
index 0000000..9dd5a0e
--- /dev/null
+++ b/example/example.ini
@@ -0,0 +1,8 @@
+# An example ini file
+[personal]
+name = "Ivan Ivanovich Ivanov"
+age = 69
+
+[contact]
+email = "ivan@mail.xyz"
+visible = yes
diff --git a/include/parcini.h b/include/parcini.h
index 3bcaf59..d485ffd 100644
--- a/include/parcini.h
+++ b/include/parcini.h
@@ -93,6 +93,21 @@ struct parcini_line {
 	struct parcini_value value;
 };
 
+/*
+ * Helper function to handle a value that was parsed by parcini. Pass a pointer
+ * of a parcini_value, and the expected type. If the parcini_value typematches
+ * the expected type, then *dst is set to the provided value casting it to the
+ * correct type. 
+ *
+ * IMPORTANT: in the case of "string" values, you need to pass a pointer to a
+ * char pointer (i.e. char **), and the memory in the string value is copied
+ * over to the char *. That means, that if some memory was already allocated in
+ * the passed pointer, there could be a memory leak if you don't free that
+ * memory before, unless you have another pointer to that same memory elsewhere.
+ */
+bool parcini_value_handle(const struct parcini_value *,
+		const enum parcini_value_type expected, void *dst);
+
 /*
  * Parse the next line in the stream returning the kind of result/error of
  * parsing and filling the parcini_line structure with the information that was
@@ -115,7 +130,7 @@ parcini_t *parcini_init(FILE *stream);
 
 /*
  * Helper function to open a file and then initialize a parcini_t object with
- * said as the source stream.
+ * said as the source stream. Returns NULL if the file couldn't be opened.
  */
 parcini_t *parcini_from_file(const char *fpath);
 
diff --git a/src/parcini.c b/src/parcini.c
index 6c19d7a..83b5de4 100644
--- a/src/parcini.c
+++ b/src/parcini.c
@@ -74,6 +74,33 @@ slicecpy(char *start, char *end, char **dst, size_t *dstn)
 	return *dst;
 }
 
+bool
+parcini_value_handle(const struct parcini_value *value,
+		const enum parcini_value_type expected, void *dst)
+{
+
+	if (value->type != expected) {
+		return false;
+	}
+	if(expected == PARCINI_VALUE_STRING) {
+		char **string = (char **)dst;
+		*string = strdup(value->value.string);
+		return true;
+	}
+	if(expected == PARCINI_VALUE_INTEGER) {
+		long int *integer = (long int *)dst;
+		*integer = value->value.integer;
+		return true;
+	}
+	if(expected == PARCINI_VALUE_BOOLEAN) {
+		bool *boolean = (bool *)dst;
+		*boolean = value->value.boolean;
+		return true;
+	}
+
+	return false;
+}
+
 enum parcini_result
 parcini_parse_next_line(parcini_t *parser, struct parcini_line *parsed)
 {
-- 
cgit v1.2.3