Commit c5e944820ab50f6106ab4f86f37d087a74acc595

Vicent Martí 2012-03-01T00:52:21

config: Refactor & add `git_config_get_mapped` Sane API for real-world usage.

diff --git a/include/git2/config.h b/include/git2/config.h
index 8a0f589..acc45b0 100644
--- a/include/git2/config.h
+++ b/include/git2/config.h
@@ -37,6 +37,19 @@ struct git_config_file {
 	void (*free)(struct git_config_file *);
 };
 
+typedef enum {
+	GIT_CVAR_FALSE = 0,
+	GIT_CVAR_TRUE = 1,
+	GIT_CVAR_INT32,
+	GIT_CVAR_STRING
+} git_cvar_t;
+
+typedef struct {
+	git_cvar_t cvar_type;
+	const char *str_match;
+	int map_value;
+} git_cvar_map;
+
 /**
  * Locate the path to the global configuration file
  *
@@ -301,6 +314,43 @@ GIT_EXTERN(int) git_config_foreach(
 	int (*callback)(const char *var_name, const char *value, void *payload),
 	void *payload);
 
+
+/**
+ * Query the value of a config variable and return it mapped to
+ * an integer constant.
+ *
+ * This is a helper method to easily map different possible values
+ * to a variable to integer constants that easily identify them.
+ *
+ * A mapping array looks as follows:
+ *
+ *	git_cvar_map autocrlf_mapping[3] = {
+ *		{GIT_CVAR_FALSE, NULL, GIT_AUTO_CRLF_FALSE},
+ *		{GIT_CVAR_TRUE, NULL, GIT_AUTO_CRLF_TRUE},
+ *		{GIT_CVAR_STRING, "input", GIT_AUTO_CRLF_INPUT},
+ *		{GIT_CVAR_STRING, "default", GIT_AUTO_CRLF_DEFAULT}};
+ *
+ * On any "false" value for the variable (e.g. "false", "FALSE", "no"), the
+ * mapping will store `GIT_AUTO_CRLF_FALSE` in the `out` parameter.
+ *
+ * The same thing applies for any "true" value such as "true", "yes" or "1", storing
+ * the `GIT_AUTO_CRLF_TRUE` variable.
+ *
+ * Otherwise, if the value matches the string "input" (with case insensitive comparison),
+ * the given constant will be stored in `out`, and likewise for "default".
+ *
+ * If not a single match can be made to store in `out`, an error code will be
+ * returned.
+ *
+ * @param cfg config file to get the variables from
+ * @param name name of the config variable to lookup
+ * @param maps array of `git_cvar_map` objects specifying the possible mappings
+ * @param map_n number of mapping objects in `maps`
+ * @param out place to store the result of the mapping
+ * @return GIT_SUCCESS on success, error code otherwise
+ */
+GIT_EXTERN(int) git_config_get_mapped(git_config *cfg, const char *name, git_cvar_map *maps, size_t map_n, int *out);
+
 /** @} */
 GIT_END_DECL
 #endif
diff --git a/src/config.c b/src/config.c
index 4ff1b2e..9122241 100644
--- a/src/config.c
+++ b/src/config.c
@@ -209,23 +209,37 @@ int git_config_set_string(git_config *cfg, const char *name, const char *value)
 	return file->set(file, name, value);
 }
 
-/***********
- * Getters
- ***********/
+static int parse_bool(int *out, const char *value)
+{
+	/* A missing value means true */
+	if (value == NULL) {
+		*out = 1;
+		return GIT_SUCCESS;
+	}
 
-int git_config_get_int64(git_config *cfg, const char *name, int64_t *out)
+	if (!strcasecmp(value, "true") ||
+		!strcasecmp(value, "yes") ||
+		!strcasecmp(value, "on")) {
+		*out = 1;
+		return GIT_SUCCESS;
+	}
+	if (!strcasecmp(value, "false") ||
+		!strcasecmp(value, "no") ||
+		!strcasecmp(value, "off")) {
+		*out = 0;
+		return GIT_SUCCESS;
+	}
+
+	return GIT_EINVALIDTYPE;
+}
+
+static int parse_int64(int64_t *out, const char *value)
 {
-	const char *value, *num_end;
-	int ret;
+	const char *num_end;
 	int64_t num;
 
-	ret = git_config_get_string(cfg, name, &value);
-	if (ret < GIT_SUCCESS)
-		return git__rethrow(ret, "Failed to retrieve value for '%s'", name);
-
-	ret = git__strtol64(&num, value, &num_end, 0);
-	if (ret < GIT_SUCCESS)
-		return git__rethrow(ret, "Failed to convert value for '%s'", name);
+	if (git__strtol64(&num, value, &num_end, 0) < 0)
+		return GIT_EINVALIDTYPE;
 
 	switch (*num_end) {
 	case 'g':
@@ -245,38 +259,112 @@ int git_config_get_int64(git_config *cfg, const char *name, int64_t *out)
 		/* check that that there are no more characters after the
 		 * given modifier suffix */
 		if (num_end[1] != '\0')
-			return git__throw(GIT_EINVALIDTYPE,
-				"Failed to get value for '%s'. Invalid type suffix", name);
+			return GIT_EINVALIDTYPE;
 
 		/* fallthrough */
 
 	case '\0':
 		*out = num;
-		return GIT_SUCCESS;
+		return 0;
 
 	default:
-		return git__throw(GIT_EINVALIDTYPE,
-			"Failed to get value for '%s'. Value is of invalid type", name);
+		return GIT_EINVALIDTYPE;
 	}
 }
 
-int git_config_get_int32(git_config *cfg, const char *name, int32_t *out)
+static int parse_int32(int32_t *out, const char *value)
 {
-	int64_t tmp_long;
-	int32_t tmp_int;
+	int64_t tmp;
+	int32_t truncate;
+
+	if (parse_int64(&tmp, value) < 0)
+		return GIT_EINVALIDTYPE;
+
+	truncate = tmp & 0xFFFFFFFF;
+	if (truncate != tmp)
+		return GIT_EOVERFLOW;
+
+	*out = truncate;
+	return 0;
+}
+
+/***********
+ * Getters
+ ***********/
+int git_config_get_mapped(git_config *cfg, const char *name, git_cvar_map *maps, size_t map_n, int *out)
+{
+	size_t i;
+	const char *value;
+	int error;
+
+	error = git_config_get_string(cfg, name, &value);
+	if (error < GIT_SUCCESS)
+		return error;
+
+	for (i = 0; i < map_n; ++i) {
+		git_cvar_map *m = maps + i;
+
+		switch (m->cvar_type) {
+			case GIT_CVAR_FALSE:
+			case GIT_CVAR_TRUE: {
+				int bool_val;
+
+				if (parse_bool(&bool_val, value) == 0 && 
+					bool_val == (int)m->cvar_type) {
+					*out = m->map_value;
+					return 0;
+				}
+
+				break;
+			}
+
+			case GIT_CVAR_INT32:
+				if (parse_int32(out, value) == 0)
+					return 0;
+
+				break;
+
+			case GIT_CVAR_STRING:
+				if (strcasecmp(value, m->str_match) == 0) {
+					*out = m->map_value;
+					return 0;
+				}
+		}
+	}
+
+	return git__throw(GIT_ENOTFOUND,
+		"Failed to map the '%s' config variable with a valid value", name);
+}
+
+int git_config_get_int64(git_config *cfg, const char *name, int64_t *out)
+{
+	const char *value;
 	int ret;
 
-	ret = git_config_get_int64(cfg, name, &tmp_long);
+	ret = git_config_get_string(cfg, name, &value);
 	if (ret < GIT_SUCCESS)
-		return git__rethrow(ret, "Failed to convert value for '%s'", name);
-	
-	tmp_int = tmp_long & 0xFFFFFFFF;
-	if (tmp_int != tmp_long)
-		return git__throw(GIT_EOVERFLOW, "Value for '%s' is too large", name);
+		return git__rethrow(ret, "Failed to retrieve value for '%s'", name);
 
-	*out = tmp_int;
+	if (parse_int64(out, value) < 0)
+		return git__throw(GIT_EINVALIDTYPE, "Failed to parse '%s' as an integer", value);
 
-	return ret;
+	return GIT_SUCCESS;
+}
+
+int git_config_get_int32(git_config *cfg, const char *name, int32_t *out)
+{
+	const char *value;
+	int error;
+
+	error = git_config_get_string(cfg, name, &value);
+	if (error < GIT_SUCCESS)
+		return git__rethrow(error, "Failed to get value for %s", name);
+
+	error = parse_int32(out, value);
+	if (error < GIT_SUCCESS)
+		return git__throw(GIT_EINVALIDTYPE, "Failed to parse '%s' as a 32-bit integer", value);
+	
+	return GIT_SUCCESS;
 }
 
 int git_config_get_bool(git_config *cfg, const char *name, int *out)
@@ -288,33 +376,15 @@ int git_config_get_bool(git_config *cfg, const char *name, int *out)
 	if (error < GIT_SUCCESS)
 		return git__rethrow(error, "Failed to get value for %s", name);
 
-	/* A missing value means true */
-	if (value == NULL) {
-		*out = 1;
+	if (parse_bool(out, value) == 0)
 		return GIT_SUCCESS;
-	}
 
-	if (!strcasecmp(value, "true") ||
-		!strcasecmp(value, "yes") ||
-		!strcasecmp(value, "on")) {
-		*out = 1;
-		return GIT_SUCCESS;
-	}
-	if (!strcasecmp(value, "false") ||
-		!strcasecmp(value, "no") ||
-		!strcasecmp(value, "off")) {
-		*out = 0;
+	if (parse_int32(out, value) == 0) {
+		*out = !!(*out);
 		return GIT_SUCCESS;
 	}
 
-	/* Try to parse it as an integer */
-	error = git_config_get_int32(cfg, name, out);
-	if (error == GIT_SUCCESS)
-		*out = !!(*out);
-
-	if (error < GIT_SUCCESS)
-		return git__rethrow(error, "Failed to get value for %s", name);
-	return error;
+	return git__throw(GIT_EINVALIDTYPE, "Failed to parse '%s' as a boolean value", value);
 }
 
 int git_config_get_string(git_config *cfg, const char *name, const char **out)