OSDN Git Service

Allow iterating over sections in a config file and writing config to disk.
authorSharvil Nanavati <sharvil@google.com>
Mon, 28 Jul 2014 04:12:20 +0000 (21:12 -0700)
committerAndre Eisenbach <eisenbach@google.com>
Mon, 16 Mar 2015 23:51:28 +0000 (16:51 -0700)
These two additional operations will allow us to replace the existing
XML-based config files and switch to a single file format.

osi/include/config.h
osi/src/config.c
osi/test/config_test.cpp

index 5e45003..2694ca6 100644 (file)
@@ -14,6 +14,7 @@
 //   not exist. In other words, |config_has_section| will return false for
 //   empty sections.
 // - Duplicate keys in a section will overwrite previous values.
+// - All strings are case sensitive.
 
 #include <stdbool.h>
 
@@ -21,8 +22,8 @@
 // a section.
 #define CONFIG_DEFAULT_SECTION "Global"
 
-struct config_t;
 typedef struct config_t config_t;
+typedef struct config_section_node_t config_section_node_t;
 
 // Loads the specified file and returns a handle to the config file. If there
 // was a problem loading the file or allocating memory, this function returns
@@ -76,3 +77,46 @@ void config_set_bool(config_t *config, const char *section, const char *key, boo
 // not already exist, this function creates them. |config|, |section|, |key|, and
 // |value| must not be NULL.
 void config_set_string(config_t *config, const char *section, const char *key, const char *value);
+
+// Removes |section| from the |config| (and, as a result, all keys in the section).
+// Returns true if |section| was found and removed from |config|, false otherwise.
+// Neither |config| nor |section| may be NULL.
+bool config_remove_section(config_t *config, const char *section);
+
+// Removes one specific |key| residing in |section| of the |config|. Returns true
+// if the section and key were found and the key was removed, false otherwise.
+// None of |config|, |section|, or |key| may be NULL.
+bool config_remove_key(config_t *config, const char *section, const char *key);
+
+// Returns an iterator to the first section in the config file. If there are no
+// sections, the iterator will equal the return value of |config_section_end|.
+// The returned pointer must be treated as an opaque handle and must not be freed.
+// The iterator is invalidated on any config mutating operation. |config| may not
+// be NULL.
+const config_section_node_t *config_section_begin(const config_t *config);
+
+// Returns an iterator to one past the last section in the config file. It does not
+// represent a valid section, but can be used to determine if all sections have been
+// iterated over. The returned pointer must be treated as an opaque handle and must
+// not be freed and must not be iterated on (must not call |config_section_next| on
+// it). |config| may not be NULL.
+const config_section_node_t *config_section_end(const config_t *config);
+
+// Moves |iter| to the next section. If there are no more sections, |iter| will
+// equal the value of |config_section_end|. |iter| may not be NULL and must be
+// a pointer returned by either |config_section_begin| or |config_section_next|.
+const config_section_node_t *config_section_next(const config_section_node_t *iter);
+
+// Returns the name of the section referred to by |iter|. The returned pointer is
+// owned by the config module and must not be freed by the caller. The pointer will
+// remain valid until |config_free| is called. |iter| may not be NULL and must not
+// equal the value returned by |config_section_end|.
+const char *config_section_name(const config_section_node_t *iter);
+
+// Saves |config| to a file given by |filename|. Note that this could be a destructive
+// operation: if |filename| already exists, it will be overwritten. The config
+// module does not preserve comments or formatting so if a config file was opened
+// with |config_new| and subsequently overwritten with |config_save|, all comments
+// and special formatting in the original file will be lost. Neither |config| nor
+// |filename| may be NULL.
+bool config_save(const config_t *config, const char *filename);
index f38c7f0..5bf8513 100644 (file)
@@ -24,6 +24,9 @@ struct config_t {
   list_t *sections;
 };
 
+// Empty definition; this type is aliased to list_node_t.
+struct config_section_iter_t {};
+
 static void config_parse(FILE *fp, config_t *config);
 
 static section_t *section_new(const char *name);
@@ -37,25 +40,36 @@ static entry_t *entry_find(const config_t *config, const char *section, const ch
 config_t *config_new(const char *filename) {
   assert(filename != NULL);
 
-  FILE *fp = fopen(filename, "rt");
-  if (!fp) {
-    ALOGE("%s unable to open file '%s': %s", __func__, filename, strerror(errno));
-    return NULL;
-  }
-
   config_t *config = calloc(1, sizeof(config_t));
   if (!config) {
     ALOGE("%s unable to allocate memory for config_t.", __func__);
-    fclose(fp);
-    return NULL;
+    goto error;
   }
 
   config->sections = list_new(section_free);
-  config_parse(fp, config);
+  if (!config->sections) {
+    ALOGE("%s unable to allocate list for sections.", __func__);
+    goto error;
+  }
 
+  FILE *fp = fopen(filename, "rt");
+  if (!fp) {
+    ALOGE("%s unable to open file '%s': %s", __func__, filename, strerror(errno));
+    goto error;
+  }
+  config_parse(fp, config);
   fclose(fp);
-
   return config;
+
+error:;
+  if (config)
+    list_free(config->sections);
+  free(config);
+
+  if (fp)
+    fclose(fp);
+
+  return NULL;
 }
 
 void config_free(config_t *config) {
@@ -162,6 +176,82 @@ void config_set_string(config_t *config, const char *section, const char *key, c
   list_append(sec->entries, entry);
 }
 
+bool config_remove_section(config_t *config, const char *section) {
+  assert(config != NULL);
+  assert(section != NULL);
+
+  section_t *sec = section_find(config, section);
+  if (!sec)
+    return false;
+
+  return list_remove(config->sections, sec);
+}
+
+bool config_remove_key(config_t *config, const char *section, const char *key) {
+  assert(config != NULL);
+  assert(section != NULL);
+  assert(key != NULL);
+
+  section_t *sec = section_find(config, section);
+  entry_t *entry = entry_find(config, section, key);
+  if (!sec || !entry)
+    return false;
+
+  return list_remove(sec->entries, entry);
+}
+
+const config_section_node_t *config_section_begin(const config_t *config) {
+  assert(config != NULL);
+  return (const config_section_node_t *)list_begin(config->sections);
+}
+
+const config_section_node_t *config_section_end(const config_t *config) {
+  assert(config != NULL);
+  return (const config_section_node_t *)list_end(config->sections);
+}
+
+const config_section_node_t *config_section_next(const config_section_node_t *node) {
+  assert(node != NULL);
+  return (const config_section_node_t *)list_next((const list_node_t *)node);
+}
+
+const char *config_section_name(const config_section_node_t *node) {
+  assert(node != NULL);
+  const list_node_t *lnode = (const list_node_t *)node;
+  const section_t *section = (const section_t *)list_node(lnode);
+  return section->name;
+}
+
+bool config_save(const config_t *config, const char *filename) {
+  assert(config != NULL);
+  assert(filename != NULL);
+  assert(*filename != '\0');
+
+  FILE *fp = fopen(filename, "wt");
+  if (!fp) {
+    ALOGE("%s unable to write file '%s': %s", __func__, filename, strerror(errno));
+    return false;
+  }
+
+  for (const list_node_t *node = list_begin(config->sections); node != list_end(config->sections); node = list_next(node)) {
+    const section_t *section = (const section_t *)list_node(node);
+    fprintf(fp, "[%s]\n", section->name);
+
+    for (const list_node_t *enode = list_begin(section->entries); enode != list_end(section->entries); enode = list_next(enode)) {
+      const entry_t *entry = (const entry_t *)list_node(enode);
+      fprintf(fp, "%s = %s\n", entry->key, entry->value);
+    }
+
+    // Only add a separating newline if there are more sections.
+    if (list_next(node) != list_end(config->sections))
+      fputc('\n', fp);
+  }
+
+  fflush(fp);
+  fclose(fp);
+  return true;
+}
+
 static char *trim(char *str) {
   while (isspace(*str))
     ++str;
index 1ce199d..5d6073a 100644 (file)
@@ -111,3 +111,70 @@ TEST_F(ConfigTest, config_get_int_default) {
   EXPECT_EQ(config_get_int(config, "DID", "primaryRecord", 123), 123);
   config_free(config);
 }
+
+TEST_F(ConfigTest, config_remove_section) {
+  config_t *config = config_new(CONFIG_FILE);
+  EXPECT_TRUE(config_remove_section(config, "DID"));
+  EXPECT_FALSE(config_has_section(config, "DID"));
+  EXPECT_FALSE(config_has_key(config, "DID", "productId"));
+  config_free(config);
+}
+
+TEST_F(ConfigTest, config_remove_section_missing) {
+  config_t *config = config_new(CONFIG_FILE);
+  EXPECT_FALSE(config_remove_section(config, "not a section"));
+  config_free(config);
+}
+
+TEST_F(ConfigTest, config_remove_key) {
+  config_t *config = config_new(CONFIG_FILE);
+  EXPECT_EQ(config_get_int(config, "DID", "productId", 999), 0x1200);
+  EXPECT_TRUE(config_remove_key(config, "DID", "productId"));
+  EXPECT_FALSE(config_has_key(config, "DID", "productId"));
+  config_free(config);
+}
+
+TEST_F(ConfigTest, config_remove_key_missing) {
+  config_t *config = config_new(CONFIG_FILE);
+  EXPECT_EQ(config_get_int(config, "DID", "productId", 999), 0x1200);
+  EXPECT_TRUE(config_remove_key(config, "DID", "productId"));
+  EXPECT_EQ(config_get_int(config, "DID", "productId", 999), 999);
+  config_free(config);
+}
+
+TEST_F(ConfigTest, config_section_begin) {
+  config_t *config = config_new(CONFIG_FILE);
+  const config_section_node_t *section = config_section_begin(config);
+  EXPECT_TRUE(section != NULL);
+  const char *section_name = config_section_name(section);
+  EXPECT_TRUE(section != NULL);
+  EXPECT_TRUE(!strcmp(section_name, CONFIG_DEFAULT_SECTION));
+  config_free(config);
+}
+
+TEST_F(ConfigTest, config_section_next) {
+  config_t *config = config_new(CONFIG_FILE);
+  const config_section_node_t *section = config_section_begin(config);
+  EXPECT_TRUE(section != NULL);
+  section = config_section_next(section);
+  EXPECT_TRUE(section != NULL);
+  const char *section_name = config_section_name(section);
+  EXPECT_TRUE(section != NULL);
+  EXPECT_TRUE(!strcmp(section_name, "DID"));
+  config_free(config);
+}
+
+TEST_F(ConfigTest, config_section_end) {
+  config_t *config = config_new(CONFIG_FILE);
+  const config_section_node_t * section = config_section_begin(config);
+  section = config_section_next(section);
+  section = config_section_next(section);
+  EXPECT_EQ(section, config_section_end(config));
+  config_free(config);
+}
+
+TEST_F(ConfigTest, config_save_basic) {
+  config_t *config = config_new(CONFIG_FILE);
+  EXPECT_TRUE(config_save(config, CONFIG_FILE));
+  config_free(config);
+}