aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--Makefile2
-rw-r--r--README.md15
-rw-r--r--src/calp.c43
-rw-r--r--src/date.c12
-rw-r--r--src/draw.c50
-rw-r--r--src/draw.h48
-rw-r--r--src/ezini.c1033
-rw-r--r--src/ezini.h121
9 files changed, 1265 insertions, 60 deletions
diff --git a/.gitignore b/.gitignore
index 46f2a21..4916353 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,5 +2,6 @@ notes.md
calp
*.pdf
*.o
+template
assets
assets.backup
diff --git a/Makefile b/Makefile
index 7448222..91144ab 100644
--- a/Makefile
+++ b/Makefile
@@ -2,7 +2,7 @@ VERSION = 0.0
PREFIX = /usr/local
CC = cc
DIR = src
-OBJS = $(DIR)/calp.o $(DIR)/draw.o $(DIR)/date.o $(DIR)/image.o
+OBJS = $(DIR)/calp.o $(DIR)/draw.o $(DIR)/date.o $(DIR)/image.o $(DIR)/ezini.o
.PHONY: clean all install
TARGET = calp
diff --git a/README.md b/README.md
index 291f89f..21906dc 100644
--- a/README.md
+++ b/README.md
@@ -16,6 +16,21 @@ make install
make uninstall
```
+# Notes
+Month names are generated based on your set locale. This can be changed by setting `LANG` at runtime. Running `locale -a` provides a list of locales to choose from. Please select a `utf8` locale.
+
+```shell
+# Example: setting language to Dutch before generating the calendar
+$ LANG=nl_NL.utf8 calp
+```
+
# Known Issues
- Accuracy of dates deviates when approaching the beginning of A.D.
- Should be accurate enough for recent years
+
+# Planned Features
+- Sunrise/sunset calcuations
+- Moon phase calculations
+
+# Credits
+- ezini library by Michael Dipperstein
diff --git a/src/calp.c b/src/calp.c
index 3f81aff..8acef9b 100644
--- a/src/calp.c
+++ b/src/calp.c
@@ -3,6 +3,8 @@
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
+#include <langinfo.h>
+#include <locale.h>
#include <cairo/cairo-pdf.h>
#include <pango/pangocairo.h>
@@ -10,6 +12,7 @@
#include "date.h"
#include "color.h"
#include "image.h"
+#include "ezini.h"
// Metric
#define A4_WIDTH 210
@@ -33,10 +36,27 @@ int main (void) {
width = (width / 25.4) * 72.0;
height = (height / 25.4) * 72.0;
- extern char *months[];
+ //extern char *months[];
+
+
int num_of_months = 12;
+ char *months[num_of_months];
extern int days_in_month[12];
+ const nl_item nl_abmons[12] = {ABMON_1, ABMON_2, ABMON_3, ABMON_4,
+ ABMON_5, ABMON_6, ABMON_7, ABMON_8,
+ ABMON_9, ABMON_10, ABMON_11, ABMON_12};
+ const nl_item nl_months[12] = {MON_1, MON_2, MON_3, MON_4, MON_5, MON_6,
+ MON_7, MON_8, MON_9, MON_10, MON_11, MON_12};
+
+ int i;
+ setlocale(LC_ALL, "");
+ for (i = 0; i < 12; i++) {
+ printf("%d\t%s\t%s\n", i+1, nl_langinfo(nl_abmons[i]), nl_langinfo(nl_months[i]));
+ months[i] = nl_langinfo(nl_months[i]);
+ }
+
+
// Create dimensions struct to hold measurements
struct dimensions dim;
@@ -48,7 +68,7 @@ int main (void) {
// Leap year adjustment: increment February day count by one if
// it is a leap year.
- int year = 3043;
+ int year = 14;
if (isleap(year)) {
days_in_month[1]++;
}
@@ -92,20 +112,9 @@ int main (void) {
// Construction
// ------------
- // Print dimensions, for debugging
-
- if (verbose_output) {
- print_dimensions(&dim);
- }
-
-
- // Range of months to be rendered (Default: Full calendar year, 12 months)
- int first_month = 0;
- int end_month = num_of_months;
-
-
+
// Month generation loop
- for (int i = first_month; i < end_month; i++) {
+ for (int i = 0; i < num_of_months; i++) {
// Initialize month struct
month.first_day = day_of_week(1, i + 1, year);
month.num_of_days = days_in_month[i];
@@ -118,8 +127,8 @@ int main (void) {
cairo_surface_t *header = cairo_surface_create_similar(surface, CAIRO_CONTENT_COLOR_ALPHA, dim.header_width, dim.header_height);
cairo_t *ch = cairo_create(header);
- //set_color(ch, BLUE);
- //fill_bg(ch);
+
+
set_color(ch, BLACK);
draw_month_title(ch, &dim, "Sans", 40, months[i]);
diff --git a/src/date.c b/src/date.c
index 43250f1..784a5fd 100644
--- a/src/date.c
+++ b/src/date.c
@@ -1,13 +1,9 @@
-char *months[] = {
- "January", "February", "March", "April", "May",
- "June", "July", "August", "September", "October",
- "November", "December"
-};
-
// Februrary is altered in calp.c if year is a leap year. defaults to 28.
-int days_in_month[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
+int
+days_in_month[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
-int isleap(int year) {
+int
+isleap(int year) {
return (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0);
}
diff --git a/src/draw.c b/src/draw.c
index e475b32..051e12b 100644
--- a/src/draw.c
+++ b/src/draw.c
@@ -6,7 +6,8 @@
#include "color.h"
// struct for storing dimensions used for calculations, movement, etc.
-struct dimensions {
+struct
+dimensions {
// Multiplication factors
int row_count; /* rows */
int column_count; /* columns */
@@ -28,7 +29,8 @@ struct dimensions {
// struct to hold Month-specific info, like what day of the week the first
// day lands on, how many days are in the month, etc.
-struct month_info {
+struct
+month_info {
int month_position;
int first_day;
int num_of_days;
@@ -37,7 +39,8 @@ struct month_info {
// struct for defining RGB colors
-struct RGB {
+struct
+RGB {
double r;
double g;
double b;
@@ -48,7 +51,8 @@ struct RGB {
// Convert Hex color strings to their RGB counterparts.
// IMO, hex strings are easier to work with, so this is
// a nice helper function.
-struct RGB hex_to_rgb(char *str) {
+struct
+RGB hex_to_rgb(char *str) {
unsigned int r, g, b;
struct RGB color;
@@ -65,7 +69,8 @@ struct RGB hex_to_rgb(char *str) {
// Set a context's color using a HEX code. Color macros are defined
// in color.h
-int set_color(cairo_t *c, char *hex) {
+int
+set_color(cairo_t *c, char *hex) {
struct RGB color = hex_to_rgb(hex);
cairo_set_source_rgb (c, color.r, color.g, color.b);
return 0;
@@ -77,16 +82,16 @@ int set_color(cairo_t *c, char *hex) {
// Relative to "Su Mo Tu ... Sa" frame, not when Su and Sa are together
// Range is 0 -> 6
// https://wiki.c2.com/?PerpetualCalendarAlgorithm
-int day_of_week (int d, int m, int y) {
+int
+day_of_week (int d, int m, int y) {
return((d += m < 3 ? y-- : y - 2, 23*m/9 + d + 4 + y/4- y/100 + y/400)%7);
}
-
-
// Fill entire background with current color
// Function name is changed for clarity
-int fill_bg(cairo_t *c) {
+int
+fill_bg(cairo_t *c) {
cairo_paint(c);
return 0;
}
@@ -98,7 +103,8 @@ int fill_bg(cairo_t *c) {
// Requires Font Family (string) and Size (int)
// Creates and destroys layout within the scope of the function
// Is that performant when looped over 365 operations?
-int draw_text (cairo_t *c, double x, double y, char *font_family, int font_size, char *text) {
+int
+draw_text (cairo_t *c, double x, double y, char *font_family, int font_size, char *text) {
char font[100];
// Concat font string for pango_font_description_from_string
@@ -127,7 +133,8 @@ int draw_text (cairo_t *c, double x, double y, char *font_family, int font_size,
// Get the dimensions of a string of text
// Returns a PangoRectangle struct
-PangoRectangle get_logical_extents (cairo_t *c, char *font_family, int font_size, char *text) {
+PangoRectangle
+get_logical_extents (cairo_t *c, char *font_family, int font_size, char *text) {
char font[100];
// Concat font string for pango_font_description_from_string
@@ -155,7 +162,8 @@ PangoRectangle get_logical_extents (cairo_t *c, char *font_family, int font_size
// Calculate dimenions for one page and stores it in the dimensions struct
// Used for determining where elements should be drawn on the page
-int calculate_dimensions(double pw, double ph, struct dimensions *d) {
+int
+calculate_dimensions(double pw, double ph, struct dimensions *d) {
d->row_count = 6;
d->column_count = 7;
@@ -191,7 +199,8 @@ int calculate_dimensions(double pw, double ph, struct dimensions *d) {
// Print the contents of a dimensions struct.
// Use for debugging
-int print_dimensions(struct dimensions *d) {
+int
+print_dimensions(struct dimensions *d) {
printf("row count: %d\n", d->row_count);
printf("column count: %d\n", d->column_count);
printf("paper width: %.2f\n", d->paper_width);
@@ -213,7 +222,8 @@ int print_dimensions(struct dimensions *d) {
// Calculate minimum amount of rows a month can fit in.
// Most months can fit within 4 or 5 rows instead of 6.
// TBD: This can probably done in a more efficient way
-int calculate_minimum_rows(struct month_info *m, struct dimensions *d) {
+int
+calculate_minimum_rows(struct month_info *m, struct dimensions *d) {
int extra_rows = 0;
int days_in_first_week = (d->column_count - m->first_day);
@@ -242,7 +252,8 @@ int calculate_minimum_rows(struct month_info *m, struct dimensions *d) {
// Draws the grid lines for a month
-int draw_month_grid (cairo_t *c, struct month_info *m, struct dimensions *d) {
+int
+draw_month_grid (cairo_t *c, struct month_info *m, struct dimensions *d) {
double cursor_x = d->month_top_corner_x;
int row_offset = d->row_count - calculate_minimum_rows(m, d);
@@ -276,7 +287,8 @@ int draw_month_grid (cairo_t *c, struct month_info *m, struct dimensions *d) {
-int construct_day_box(cairo_t *cd, int month, int day, int week_day) {
+int
+construct_day_box(cairo_t *cd, int month, int day, int week_day) {
char str[20];
double padding = 8.0;
@@ -298,7 +310,8 @@ int construct_day_box(cairo_t *cd, int month, int day, int week_day) {
// Draws one month using daybox sub-surface & context, main context, and dimension struct
-int draw_month (cairo_t *c, cairo_t *cd, cairo_surface_t *s, struct month_info *m, struct dimensions *d) {
+int
+draw_month(cairo_t *c, cairo_t *cd, cairo_surface_t *s, struct month_info *m, struct dimensions *d) {
double cursor_x = d->month_top_corner_x;
if (m->first_day != 0) cursor_x = cursor_x + (d->day_width * m->first_day);
@@ -344,7 +357,8 @@ int draw_month (cairo_t *c, cairo_t *cd, cairo_surface_t *s, struct month_info *
// Draws the month being drawn at the top of the page
-int draw_month_title(cairo_t *c, struct dimensions *d, char *font_family, int font_size, char *name) {
+int
+draw_month_title(cairo_t *c, struct dimensions *d, char *font_family, int font_size, char *name) {
PangoRectangle rect;
diff --git a/src/draw.h b/src/draw.h
index 51855bf..9de1ed0 100644
--- a/src/draw.h
+++ b/src/draw.h
@@ -1,4 +1,5 @@
-struct dimensions {
+struct
+dimensions {
int row_count; /* rows */
int column_count; /* columns */
double paper_width; /* paper width */
@@ -14,41 +15,56 @@ struct dimensions {
double header_height;
};
-int fill_bg(cairo_t *c);
+int
+fill_bg(cairo_t *c);
-struct RGB {
+struct
+RGB {
double r;
double g;
double b;
};
-struct month_info {
+struct
+month_info {
int month_position;
int first_day;
int num_of_days;
int minimum_rows;
};
-struct RGB hex_to_rgb(char *str);
+struct
+RGB hex_to_rgb(char *str);
-int set_color(cairo_t *c, char *hex);
+int
+set_color(cairo_t *c, char *hex);
-int draw_text (cairo_t *c, double x, double y, char *font_family, int font_size, char *text);
+int
+draw_text(cairo_t *c, double x, double y, char *font_family, int font_size, char *text);
-int calculate_dimensions(double pw, double ph, struct dimensions *d);
+int
+calculate_dimensions(double pw, double ph, struct dimensions *d);
-int print_dimensions(struct dimensions *d);
+int
+print_dimensions(struct dimensions *d);
-int draw_month_grid (cairo_t *c, struct month_info *m, struct dimensions *d);
+int
+draw_month_grid(cairo_t *c, struct month_info *m, struct dimensions *d);
-int draw_month (cairo_t *c, cairo_t *cd, cairo_surface_t *s, struct month_info *m, struct dimensions *d);
+int
+draw_month(cairo_t *c, cairo_t *cd, cairo_surface_t *s, struct month_info *m, struct dimensions *d);
-int day_of_week (int d, int m, int y);
+int
+day_of_week(int d, int m, int y);
-int draw_month_title(cairo_t *c, struct dimensions *d, char *font_family, int font_size, char *name);
+int
+draw_month_title(cairo_t *c, struct dimensions *d, char *font_family, int font_size, char *name);
-PangoRectangle get_logical_extents (cairo_t *c, char *font_family, int font_size, char *text);
+PangoRectangle
+get_logical_extents(cairo_t *c, char *font_family, int font_size, char *text);
-int calculate_minimum_rows(struct month_info *m, struct dimensions *d);
+int
+calculate_minimum_rows(struct month_info *m, struct dimensions *d);
-int construct_day_box(cairo_t *cd, int month, int day, int week_day);
+int
+construct_day_box(cairo_t *cd, int month, int day, int week_day);
diff --git a/src/ezini.c b/src/ezini.c
new file mode 100644
index 0000000..ee1f79e
--- /dev/null
+++ b/src/ezini.c
@@ -0,0 +1,1033 @@
+/**
+ * \brief INI File handling library
+ * \file ezini.c
+ * \author Michael Dipperstein (mdipperstein@gmail.com)
+ * \date November 22, 2015
+ *
+ * This file implements a set of library functions that maybe be used
+ * to create, update, and/or parse INI files.
+ *
+ * \copyright Copyright (C) 2015, 2019 by Michael Dipperstein
+ * (mdipperstein@gmail.com)
+ *
+ * \par
+ * This file is part of the ezini library.
+ *
+ * \license
+ * The ezini library is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either version 3
+ * of the License, or (at your option) any later version.
+ *
+ * \par
+ * The ezini library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
+ * General Public License for more details.
+ *
+ * \par
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+/**
+ * \defgroup library Library Code
+ * \brief This module contains the code for the ezini INI file handling
+ * library
+ * @{
+ */
+
+/***************************************************************************
+* INCLUDED FILES
+***************************************************************************/
+#include <stdio.h>
+#include <ctype.h>
+#include <string.h>
+#include <stdlib.h>
+#include <errno.h>
+#include "ezini.h"
+
+/***************************************************************************
+* TYPE DEFINITIONS
+***************************************************************************/
+
+/**
+ * \struct ini_key_list_t
+ * \brief A structure used for creating linked lists of key/value pairs
+ * for a each section.
+ */
+
+/**
+ * \typedef struct ini_key_list_t
+ * \brief A shortcut for struct ini_key_list_t
+ */
+
+typedef struct ini_key_list_t
+{
+ char *key; /*!< pointer to a a NULL terminated string
+ containing key name for this entry */
+ char *value; /*!< pointer to a NULL terminated string
+ containing key value for this entry Use
+ ASCII strings to represent numbers */
+ struct ini_key_list_t *next;/*!< pointer to the next key/value pair in
+ in this section */
+
+} ini_key_list_t;
+
+
+/**
+ * \struct ini_section_list_t
+ * \brief A structure used for creating linked lists of sections, each
+ * maintaining its own list of key/value pairs.
+ */
+
+/**
+ * \typedef struct ini_section_list_t
+ * \brief A shortcut for struct ini_section_list_t
+ */
+
+typedef struct ini_section_list_t
+{
+ char *section; /*!< pointer to a NULL terminated string
+ containing the section name */
+ ini_key_list_t *members; /*!< pointer to the list of all key/value
+ pairs in this section */
+ struct ini_section_list_t *next; /*!< pointer to the next section in
+ the list of entries */
+
+} ini_section_list_t;
+
+
+/***************************************************************************
+* PROTOTYPES
+***************************************************************************/
+
+/* allocate */
+static ini_key_list_t *NewKeyList(const char *key, const char *value);
+static ini_section_list_t *NewSectionList(const char *section, const char *key,
+ const char *value);
+
+/* free */
+static void FreeKeyList(ini_key_list_t *list);
+static void FreeEntry(ini_entry_t *entry);
+
+/* utilities */
+static char *SkipWS(const char *str);
+static char *DupStr(const char *src);
+static char *GetLine(FILE *fp);
+
+/***************************************************************************
+* FUNCTIONS
+***************************************************************************/
+
+/**
+ * \fn int AddEntryToList(ini_entry_list_t *list, const char *section,
+ * const char *key, const char *value)
+ *
+ * \brief This function adds a (section, key, value) entry to an entry list.
+ *
+ * \param list A pointer to an ini_entry_list_t that points to the
+ * head of entry list being modified. Pass a pointer to an ini_entry_list_t
+ * pointing to NULL if the list needs to be created.
+ *
+ * \param section A NULL terminated string containing the name of the
+ * section for the entry being added.
+ *
+ * \param key A NULL terminated string containing the name of the key for
+ * the entry being added.
+ *
+ * \param value A NULL terminated string containing the value of the key for
+ * the entry being added. All values must be represented as strings. They may
+ * be converted to/from strings by the calling program.
+ *
+ * \effects
+ * Information used to generate an entry structure containing copies of
+ * the (section, key, value) entry is added to the list passed as a parameter.
+ * Memory will be dynamically allocated as needed.
+ *
+ * \returns 0 for success, Non-zero on error. Error type is contained in
+ * errno.
+ *
+ * This function adds information used to create a (section, key, value) entry
+ * to an entry list.
+ *
+ * If an entry containing the same section name and key already exists,
+ * the new value will overwrite the old value.
+ *
+ * If the entry is for an existing section, it will be added to the end of the
+ * list for that section.
+ *
+ * If the entry is for a new section, a new section will be added to the list of
+ * sections, and the key/value pair will be the first entry of the section.
+ */
+int AddEntryToList(ini_entry_list_t *list, const char *section,
+ const char *key, const char *value)
+{
+ ini_section_list_t *next;
+ int result;
+
+ /* handle empty list */
+ if (NULL == *list)
+ {
+ /* add the first entry to the list */
+ *list = NewSectionList(section, key, value);
+
+ if (NULL == *list)
+ {
+ return -1;
+ }
+
+ return 0;
+ }
+
+ next = *list;
+ result = 1;
+
+ while (1)
+ {
+ result = strcmp(section, next->section);
+
+ if (0 == result)
+ {
+ break; /* match, insert here */
+ }
+
+ if (NULL == next->next)
+ {
+ break; /* no match, create new section here */
+ }
+
+ next = next->next;
+ }
+
+ if (0 == result)
+ {
+ ini_key_list_t *member;
+
+ member = next->members;
+
+ while (1)
+ {
+ result = strcmp(key, member->key);
+
+ if (0 == result)
+ {
+ break; /* match, insert here */
+ }
+
+ if (NULL == member->next)
+ {
+ break; /* no match, create new section here */
+ }
+
+ member = member->next;
+ }
+
+ if (0 == result)
+ {
+ /* key exists, change value */
+ free(member->value);
+ member->value = DupStr(value);
+
+ if (NULL == member->value)
+ {
+ return -1;
+ }
+ }
+ else
+ {
+ /* new key, add to list */
+ member->next = NewKeyList(key, value);
+
+ if (NULL == member->next)
+ {
+ return -1;
+ }
+ }
+ }
+ else
+ {
+ /* add the section to the list with this key and value */
+ next->next = NewSectionList(section, key, value);
+ }
+
+ return 0;
+}
+
+
+/**
+ * \fn void FreeList(ini_entry_list_t list)
+ *
+ * \brief This function frees all of the members of an entry list.
+ *
+ * \param list A pointer to the head of an ini_entry_list_t
+ *
+ * \effects
+ * All of the memory allocated for all of the entries in an entry
+ * list will be freed.
+ *
+ * \returns Nothing
+ *
+ * This function uses recursion to step to the tail of the list and deletes
+ * section entries on the way back up.
+ */
+void FreeList(ini_entry_list_t list)
+{
+ /* recurse to the end of the list and free everything on the way back */
+ if (list->next != NULL)
+ {
+ FreeList(list->next);
+ }
+
+ if (list->section != NULL)
+ {
+ /* free the section name */
+ free(list->section);
+ }
+
+ if (list->members != NULL)
+ {
+ FreeKeyList(list->members);
+ }
+
+ free(list);
+}
+
+
+/**
+ * \fn int MakeINIFile(const char *iniFile, const ini_entry_list_t list)
+ *
+ * \brief This function creates the specified INI file from the list of
+ * entries passed as an argument.
+ *
+ * \param iniFile The name of the INI file to be created. stdout will be
+ * used if iniFile is NULL.
+ *
+ * \param list A pointer to a list of that will be used to construct
+ * (section, key, value) entries.
+ *
+ * \effects
+ * The specified file is created and the (section, key, value)
+ * triples generated from the entry list are written to the file. If the
+ * specified file already exists, it will be overwritten.
+ *
+ * \returns 0 for success, Non-zero on error. Error type is contained in
+ * errno.
+ *
+ * This function creates the specified INI file from the list of entries
+ * passed as an argument. Any existing INI file with the same name in the
+ * same path will be overwritten.
+ */
+int MakeINIFile(const char *iniFile, const ini_entry_list_t list)
+{
+ ini_entry_list_t section;
+ ini_key_list_t *members;
+ FILE *fp;
+
+ if (NULL == list)
+ {
+ errno = EINVAL;
+ return -1;
+ }
+
+ if (NULL == iniFile)
+ {
+ fp = stdout;
+ }
+ else
+ {
+
+ fp = fopen(iniFile, "w");
+
+ if (NULL == fp)
+ {
+ return -1;
+ }
+ }
+
+ section = list;
+
+ while (section != NULL)
+ {
+ fprintf(fp, "[%s]\n", section->section);
+
+ members = section->members;
+
+ while (members != NULL)
+ {
+ fprintf(fp, "%s = %s\n", members->key, members->value);
+ members = members->next;
+ }
+
+ fprintf(fp, "\n");
+ section = section->next;
+ }
+
+ if (fp != stdout)
+ {
+ fclose(fp);
+ }
+
+ return 0;
+}
+
+
+/**
+ * \fn int AddEntryToFile(const char *iniFile, const ini_entry_list_t list)
+ *
+ * \brief This function adds (section, key, value) entries in an entry list
+ * to an INI file.
+ *
+ * \param iniFile The name of the INI file to be modified.
+ *
+ * \param list A pointer to a list of entries to be added to the INI file.
+ *
+ * \effects
+ * The INI file will be re-written containing the results of adding
+ * the entries in the entry list to the entries already contained in the INI
+ * file.
+ *
+ * \returns 0 for success, Non-zero on error. Error type is contained in
+ * errno.
+ *
+ * This function adds (section, key, value) entries in an entry list to an
+ * INI file. Section order will be maintained with new sections added to the
+ * end of the INI file. If an entry containing the same section name and key
+ * already exists, the new value will overwrite the old value.
+ */
+int AddEntryToFile(const char *iniFile, const ini_entry_list_t list)
+{
+ ini_entry_t entry;
+ ini_entry_list_t merged;
+ ini_entry_list_t here;
+ int result;
+ FILE *fp;
+
+ if (NULL == iniFile)
+ {
+ errno = EINVAL;
+ return -1;
+ }
+
+ if (NULL == list)
+ {
+ errno = EINVAL;
+ return -1;
+ }
+
+ fp = fopen(iniFile, "r");
+
+ if (NULL == fp)
+ {
+ return -1;
+ }
+
+ merged = NULL;
+ entry.section = NULL;
+ entry.key = NULL;
+ entry.value = NULL;
+
+ /* read ini file back into an entry list */
+ while ((result = GetEntryFromFile(fp, &entry)) > 0)
+ {
+ AddEntryToList(&merged, entry.section, entry.key, entry.value);
+ }
+
+ fclose(fp);
+
+ if (result < 0)
+ {
+ FreeList(merged);
+ return -1;
+ }
+
+ /* add entries passed into this function to entries from INI file */
+ here = list;
+
+ while (here != NULL)
+ {
+ ini_key_list_t *members;
+
+ members = here->members;
+
+ while (members != NULL)
+ {
+ AddEntryToList(&merged, here->section, members->key,
+ members->value);
+ members = members->next;
+ }
+
+ here = here->next;
+ }
+
+ /* re-write INI file from merged entry list */
+ result = MakeINIFile(iniFile, merged);
+ FreeList(merged);
+
+ return result;
+}
+
+/**
+ * \fn int DeleteEntryFromFile(const char *iniFile, const char *section,
+ * const char *key)
+ *
+ * \brief This function deletes all entries from an INI file that match the
+ * section and key passed as an argument.
+ *
+ * \param iniFile The name of the INI file containing the entry to be
+ * deleted.
+ *
+ * \param section A pointer to a NULL terminated string containing the name
+ * of the section of the entry to be deleted.
+ *
+ * \param key A pointer to a NULL terminated string containing the name of
+ * the key of the entry to be deleted.
+ *
+ * \effects
+ * The INI file will be re-written without any entries that match
+ * the section and key to be deleted. Empty sections will not be deleted.
+ *
+ * \returns 0 for success, Non-zero on error. Error type is contained in
+ * errno.
+ *
+ * This function deletes all entries from an INI file that match the section
+ * and key passed as an argument. Empty sections will not be deleted.
+ *
+ * \note There will never be more than one matching entry in INI files
+ * created by this library.
+ */
+int DeleteEntryFromFile(const char *iniFile, const char *section,
+ const char *key)
+{
+ ini_entry_t entry;
+ ini_entry_list_t list;
+ int result;
+ FILE *fp;
+
+ if (NULL == iniFile)
+ {
+ errno = EINVAL;
+ return -1;
+ }
+
+ if (NULL == section)
+ {
+ errno = EINVAL;
+ return -1;
+ }
+
+ if (NULL == key)
+ {
+ errno = EINVAL;
+ return -1;
+ }
+
+ fp = fopen(iniFile, "r");
+
+ if (NULL == fp)
+ {
+ return -1;
+ }
+
+ list = NULL;
+ entry.section = NULL;
+ entry.key = NULL;
+ entry.value = NULL;
+
+ /* read ini file back into a structure */
+ while ((result = GetEntryFromFile(fp, &entry)) > 0)
+ {
+ if (0 != strcmp(entry.section, section))
+ {
+ /* this isn't one we're supposed to delete */
+ AddEntryToList(&list, entry.section, entry.key, entry.value);
+ }
+ else if (0 != strcmp(entry.key, key))
+ {
+ /* this isn't one we're supposed to delete */
+ AddEntryToList(&list, entry.section, entry.key, entry.value);
+ }
+ }
+
+ fclose(fp);
+
+ if (result < 0)
+ {
+ FreeList(list);
+ return -1;
+ }
+
+ result = MakeINIFile(iniFile, list);
+ FreeList(list);
+
+ return result;
+}
+
+/**
+ * \fn int GetEntryFromFile(FILE *iniFile, ini_entry_t *entry)
+ *
+ * \brief This function parses an INI file stream passed as an input,
+ * searching for the next (section, key, value) triple.
+ *
+ * \param iniFile A pointer to the INI file to be parsed. It must be
+ * opened for reading.
+ *
+ * \param entry A pointer to the entry structure used to store the discovered
+ * (section, key, value) triple.
+ *
+ * \effects
+ * The specified file is read until it discovers an entry.
+ *
+ * \returns 1 when an entry is found\n
+ * 0 when no more entries can be found\n
+ * -1 for an error. Error type is contained in errno.
+ *
+ * This function parses an INI file stream passed as an input, searching for
+ * the next (section, key, value) triple. The resulting triple will be used
+ * to populate the entry structure passed as a parameter.
+ */
+int GetEntryFromFile(FILE *iniFile, ini_entry_t *entry)
+{
+ char *line;
+ char *ptr;
+
+ if (NULL == iniFile)
+ {
+ errno = EINVAL;
+ return -1;
+ }
+
+ if (NULL == entry)
+ {
+ errno = EINVAL;
+ return -1;
+ }
+
+ /* handle section names, comments, and blank lines */
+ while ((line = GetLine(iniFile)) != NULL)
+ {
+ /* skip leading spaces and blank lines */
+ ptr = SkipWS(line);
+
+ /* skip blank lines and lines starting with ';' or '#' */
+ if (*ptr == '\0' || *ptr == ';' || *ptr == '#')
+ {
+ free(line);
+ continue;
+ }
+ else if (*ptr == '[')
+ {
+ /* possible new section */
+ char *end;
+
+ end = strchr(ptr, ']');
+
+ if (NULL == end)
+ {
+ FreeEntry(entry);
+ errno = EILSEQ;
+ return -1;
+ }
+
+ /* we have the full string for a new section, trim white space */
+ ptr = SkipWS(ptr + 1);
+
+ while (isspace(*(end - 1)))
+ {
+ end--;
+ }
+
+ *end = '\0';
+
+ free(entry->section);
+ entry->section = DupStr(ptr);
+ free(line);
+ }
+ else
+ {
+ /* this line should be key = value */
+ break;
+ }
+ }
+
+ /* we either have a non-section line or nothing left to get */
+ if (NULL == line)
+ {
+ /* nothing left to get */
+ FreeEntry(entry);
+ return 0;
+ }
+
+ /* the only other allowable lines are of the form key = value */
+ free(entry->key); /* free old key */
+ entry->key = ptr;
+ ptr++;
+
+ while (*ptr != '=')
+ {
+ if (*ptr == '\0')
+ {
+ /* didn't find '=' */
+ entry->key = NULL;
+ FreeEntry(entry);
+ free(line);
+ errno = EILSEQ;
+ return -1;
+ }
+
+ ptr++;
+ }
+
+ /* we found the '=' separating key and value trim white space */
+ free(entry->value); /* free old value */
+ entry->value = ptr + 1;
+ ptr--;
+
+ while (isspace(*ptr))
+ {
+ ptr--;
+ }
+
+ *(ptr + 1) = '\0';
+ entry->key = DupStr(entry->key);
+
+ ptr = entry->value;
+
+ /* now skip white space after '=' */
+ while (*ptr == ' ' || *ptr =='\t')
+ {
+ if (*ptr == '\0')
+ {
+ FreeEntry(entry);
+ free(line);
+ errno = EILSEQ;
+ return -1;
+ }
+
+ ptr++;
+ }
+
+ /* we found the start of value, trim trailing white space */
+ entry->value = ptr;
+ ptr = entry->value + strlen(entry->value) - 1;
+
+ while (isspace(*ptr))
+ {
+ ptr--;
+ }
+
+ *(ptr + 1) = '\0';
+ entry->value = DupStr(entry->value);
+
+ free(line);
+ return 1;
+}
+
+
+/**
+ * \fn ini_key_list_t *NewKeyList(const char *key, const char *value)
+ *
+ * \brief This function allocates memory for a new ini_key_list_t type
+ * variable and populates it with the data passed as parameters
+ *
+ * \param key A pointer to a NULL terminated string containing the key name
+ *
+ * \param key A pointer to a NULL terminated string containing the value of
+ * this key. Use ASCII strings to represent numbers.
+ *
+ * \effects
+ * Memory will be allocated for a new ini_key_list_t and copies
+ * of the key and value strings passed as a parameter. key and value are
+ * copied into the appropriate fields and the next pointer is set to NULL.
+ *
+ * \returns A pointer to the ini_key_list_t item that was allocated. The
+ * pointer will be NULL if an error occurs.
+ *
+ * This function allocates memory for a new ini_key_list_t and copies
+ * of the key and value strings passed as a parameter. The next pointer
+ * will be set to NULL.
+ */
+static ini_key_list_t *NewKeyList(const char *key, const char *value)
+{
+ ini_key_list_t *item;
+
+ item = (ini_key_list_t *)malloc(sizeof(ini_key_list_t));
+
+ if (NULL == item)
+ {
+ return NULL;
+ }
+
+ /* allocation succeeded copy key and value */
+ item->next = NULL;
+
+ item->key = DupStr(key);
+
+ if (NULL == item->key)
+ {
+ free(item);
+ return NULL;
+ }
+
+ item->value = DupStr(value);
+
+ if (NULL == item->value)
+ {
+ free(item->key);
+ free(item);
+ return NULL;
+ }
+
+ return item;
+}
+
+
+/**
+ * \fn ini_section_list_t *NewSectionList(const char *section, const char *key,
+ * const char *value)
+ *
+ * \brief This function allocates memory for a new ini_section_list_t type
+ * variable and populates it with the data passed as parameters
+ *
+ * \param section A pointer to a NULL terminated string containing the section
+ * name
+
+ * \param key A pointer to a NULL terminated string containing the key name
+ *
+ * \param key A pointer to a NULL terminated string containing the value of
+ * this key. Use ASCII strings to represent numbers.
+ *
+ * \effects
+ * Memory will be allocated for a new ini_section_list_t and copies
+ * of the section, key, value strings passed as a parameter. section is
+ * copied to the appropriate field and a new key list items is created fo
+ * the key and value strings. The next pointer will be set to NULL.
+ *
+ * \returns A pointer to the ini_section_list_t item that was allocated. The
+ * pointer will be NULL if an error occurs.
+ *
+ * This function allocates memory for a new ini_section_list_t and copies
+ * of the section, key, value strings passed as a parameter. A ini_key_list_t
+ * is allocated for the key and value strings. The next pointer is set to NULL.
+ */
+static ini_section_list_t *NewSectionList(const char *section, const char *key,
+ const char *value)
+{
+ ini_section_list_t *item;
+
+ item = (ini_section_list_t *)malloc(sizeof(ini_section_list_t));
+
+ if (NULL == item)
+ {
+ return NULL;
+ }
+
+ /* now populate item */
+ item->next = NULL;
+ item->section = DupStr(section);
+
+ if (NULL == item->section)
+ {
+ free(item);
+ return NULL;
+ }
+
+ /* start a member list with the current key and value */
+ item->members = NewKeyList(key, value);
+
+ if (NULL == item->members)
+ {
+ free(item->section);
+ free(item);
+ return NULL;
+ }
+
+ return item;
+}
+
+
+/**
+ * \fn void FreeKeyList(ini_key_list_t *list)
+ *
+ * \brief This function frees all of the members of a key list.
+ *
+ * \param list A pointer to the head of an ini_key_list_t
+ *
+ * \effects All of the memory allocated for all of the key/value pairs in
+ * a key list will be freed.
+ *
+ * \returns Nothing
+ *
+ * This function uses recursion to step to the tail of the list and deletes
+ * key/value entries on the way back up.
+ */
+static void FreeKeyList(ini_key_list_t *list)
+{
+ /* recurse to the end of the list and free everything on the way back */
+ if (list->next != NULL)
+ {
+ FreeKeyList(list->next);
+ }
+
+ free(list->key);
+ free(list->value);
+ free(list);
+}
+
+
+/**
+ * \fn static void FreeEntry(ini_entry_t *entry)
+ *
+ * \brief This function frees the allocated memory that is pointed to by the
+ * members of an ini_entry_t structure.
+ *
+ * \param entry A pointer to the entry structure containing the members
+ * pointing to the memory to be freed.
+ *
+ * \effects Dynamically allocated memory is freed and entry data is set
+ * to NULL.
+ *
+ * \returns Nothing
+ */
+static void FreeEntry(ini_entry_t *entry)
+{
+ free(entry->section);
+ free(entry->key);
+ free(entry->value);
+
+ entry->section = NULL;
+ entry->key = NULL;
+ entry->value = NULL;
+}
+
+/**
+ * \fn static char *SkipWS(const char *str)
+ *
+ * \brief This function returns a pointer to the first non-space in the
+ * string passed as a parameter.
+ *
+ * \param str A pointer to the string being searched.
+ *
+ * \effects None
+ *
+ * \returns A pointer to the first non-space in str.
+ */
+static char *SkipWS(const char *str)
+{
+ char *c;
+
+ c = (char *)str;
+ while(isspace(*c))
+ {
+ c++;
+ }
+
+ return c;
+}
+
+/**
+ * \fn static char *DupStr(const char *src)
+ *
+ * \brief This function returns a copy of the string passed as a parameter.
+ *
+ * \param str A pointer to the string being being copied.
+ *
+ * \effects Memory is dynamically allocated to hold a duplicate of the
+ * input string.
+ *
+ * \returns A copy of str in malloced memory is returned on success. NULL
+ * is returned on failure.
+ *
+ * This function returns a copy of the string passed as a parameter. The
+ * memory for the copy is allocated by malloc() and must be freed by the caller.
+ */
+static char *DupStr(const char *src)
+{
+ char *dest;
+
+ if (NULL == src)
+ {
+ return NULL;
+ }
+
+ dest = (char *)malloc(strlen(src) + 1);
+
+ if (NULL != dest)
+ {
+ strcpy(dest, src);
+ }
+
+ return dest;
+}
+
+/**
+ * \fn static char *GetLine(FILE *fp)
+ *
+ * \brief This function returns a NULL terminated array of char containing the
+ * next line in the file passed as an argument.
+ *
+ * \param fp A pointer to the file being read.
+ *
+ * \effects One line is read from fp and copied into a dynamically allocated
+ * string.
+ *
+ * \returns A NULL terminated array of char containing the next line in fp
+ * is retured. The array must be free by the caller. NULL is returned at end
+ * of file.
+ *
+ * This function returns a NULL terminated array of char containing the next
+ * line in the file passed as an argument. The memory for the string returned
+ * is allocated by malloc() and must be freed by the caller.
+ */
+static char *GetLine(FILE *fp)
+{
+ char *line; /* string to read line into */
+ char *next; /* where to write the next characters into */
+ const size_t chunkSize = 32;
+ size_t lineSize;
+
+ if ((NULL == fp) || feof(fp))
+ {
+ return NULL;
+ }
+
+ lineSize = chunkSize;
+ line = (char *)malloc(lineSize * sizeof(char));
+
+ if (NULL == line)
+ {
+ /* allocation failed */
+ return NULL;
+ }
+
+ line[0] = '\0';
+ next = line;
+
+ while (NULL != fgets(next, lineSize - strlen(line), fp))
+ {
+ if ('\n' == line[strlen(line) - 1])
+ {
+ /* we got to the EOL strip off the trailing '\n' and exit */
+ line[strlen(line) - 1] = '\0';
+ break;
+ }
+ else
+ {
+ /* there's still more on this line */
+ lineSize += chunkSize;
+ line = (char *)realloc(line, lineSize);
+
+ if (NULL == line)
+ {
+ return NULL;
+ }
+
+ next = line + strlen(line);
+ }
+ }
+
+ return line;
+}
+
+/**@}*/
diff --git a/src/ezini.h b/src/ezini.h
new file mode 100644
index 0000000..e563c7c
--- /dev/null
+++ b/src/ezini.h
@@ -0,0 +1,121 @@
+/**
+ * \brief INI File handling library header
+ * \file ezini.h
+ * \author Michael Dipperstein (mdipperstein@gmail.com)
+ * \date November 22, 2015
+ *
+ * This file implements a set of library functions that maybe be used
+ * to create, update, and/or parse INI files.
+ *
+ * \copyright Copyright (C) 2015, 2019 by Michael Dipperstein
+ * (mdipperstein@gmail.com)
+ *
+ * \par
+ * This file is part of the ezini library.
+ *
+ * \license The ezini library is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either version 3
+ * of the License, or (at your option) any later version.
+ *
+ * \par
+ * The ezini library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
+ * General Public License for more details.
+ *
+ * \par
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+#ifndef __EZINI_H
+#define __EZINI_H
+
+/**
+ * \mainpage EZIni - INI File handling library
+ *
+ * These pages provide documentation for EZIni, an INI File handling library.
+ *
+ * \copyright Copyright (C) 2015, 2019 by Michael Dipperstein
+ * (mdipperstein@gmail.com)
+ *
+ * \license
+ * The ezini library is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either version 3
+ * of the License, or (at your option) any later version.
+ *
+ * \par
+ * The ezini library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
+ * General Public License for more details.
+ *
+ * \par
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/***************************************************************************
+* TYPE DEFINITIONS
+***************************************************************************/
+
+/**
+ * \struct ini_entry_t
+ * \brief A structure containing the section, key, and value of INI.
+ * file entry
+ */
+typedef struct
+{
+ char *section; /*!< pointer to a NULL terminated string containing the
+ section name */
+ char *key; /*!< pointer to a a NULL terminated string containing
+ the key name */
+ char *value; /*!< pointer to a NULL terminated string with entry
+ value. Use ASCII strings to represent numbers */
+} ini_entry_t;
+
+/**
+ * \typedef ini_entry_list_t
+ * \brief A shortcut for forward referenced struct ini_section_list_t*
+ */
+typedef struct ini_section_list_t* ini_entry_list_t;
+
+/***************************************************************************
+* PROTOTYPES
+***************************************************************************/
+
+/* add (section, key, value) to a list of INI entries */
+int AddEntryToList(ini_entry_list_t *list, const char *section,
+ const char *key, const char *value);
+
+/* free all of the entries in an entry list */
+void FreeList(ini_entry_list_t list);
+
+/* create/add entries to an INI file from a sorted entry list */
+int MakeINIFile(const char *iniFile, const ini_entry_list_t list);
+int AddEntryToFile(const char *iniFile, const ini_entry_list_t list);
+
+/* remove a single entry from an INI file */
+int DeleteEntryFromFile(const char *iniFile, const char *section,
+ const char *key);
+
+/***************************************************************************
+* get the next entry in INI file.
+* returns: 1 if an entry is found
+* 0 if there are no more entries
+* -1 on error
+***************************************************************************/
+int GetEntryFromFile(FILE *iniFile, ini_entry_t *entry);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* ndef __EZINI_H */