diff options
| author | foswret | 2026-05-20 12:21:51 -0500 |
|---|---|---|
| committer | foswret | 2026-05-20 12:21:51 -0500 |
| commit | 04fc01ab521abf48cede1e64563fd6276e781b24 (patch) | |
| tree | 2b41708c80466e20d220171cd087a8b5c48e93ef /src | |
| parent | 879425cba4cc5ba85b15a01423ad6e50f526fec8 (diff) | |
add LANG support
Diffstat (limited to 'src')
| -rw-r--r-- | src/calp.c | 43 | ||||
| -rw-r--r-- | src/date.c | 12 | ||||
| -rw-r--r-- | src/draw.c | 50 | ||||
| -rw-r--r-- | src/draw.h | 48 | ||||
| -rw-r--r-- | src/ezini.c | 1033 | ||||
| -rw-r--r-- | src/ezini.h | 121 |
6 files changed, 1248 insertions, 59 deletions
@@ -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]); @@ -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); } @@ -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; @@ -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 */ |
