Commit 039fc4067989a14a09784ed16ce7126ac75461cb

Russell Belfer 2012-07-10T15:10:14

Add a couple of useful git_buf utilities * `git_buf_rfind` (with tests and tests for `git_buf_rfind_next`) * `git_buf_puts_escaped` and `git_buf_puts_escaped_regex` (with tests) to copy strings into a buffer while injecting an escape sequence (e.g. '\') in front of particular characters.

diff --git a/src/buffer.c b/src/buffer.c
index 04aaec3..d16b171 100644
--- a/src/buffer.c
+++ b/src/buffer.c
@@ -141,6 +141,40 @@ int git_buf_puts(git_buf *buf, const char *string)
 	return git_buf_put(buf, string, strlen(string));
 }
 
+int git_buf_puts_escaped(
+	git_buf *buf, const char *string, const char *esc_chars, const char *esc_with)
+{
+	const char *scan = string;
+	size_t total = 0, esc_with_len = strlen(esc_with);
+
+	while (*scan) {
+		size_t count = strcspn(scan, esc_chars);
+		total += count + 1 + esc_with_len;
+		scan += count + 1;
+	}
+
+	ENSURE_SIZE(buf, buf->size + total + 1);
+
+	for (scan = string; *scan; ) {
+		size_t count = strcspn(scan, esc_chars);
+
+		memmove(buf->ptr + buf->size, scan, count);
+		scan += count;
+		buf->size += count;
+
+		if (*scan) {
+			memmove(buf->ptr + buf->size, esc_with, esc_with_len);
+			buf->size += esc_with_len;
+
+			memmove(buf->ptr + buf->size, scan, 1);
+			scan += 1;
+			buf->size += 1;
+		}
+	}
+
+	return 0;
+}
+
 int git_buf_vprintf(git_buf *buf, const char *format, va_list ap)
 {
 	int len;
diff --git a/src/buffer.h b/src/buffer.h
index 50c75f6..75f3b0e 100644
--- a/src/buffer.h
+++ b/src/buffer.h
@@ -91,6 +91,18 @@ int git_buf_join_n(git_buf *buf, char separator, int nbuf, ...);
 int git_buf_join(git_buf *buf, char separator, const char *str_a, const char *str_b);
 
 /**
+ * Copy string into buf prefixing every character that is contained in the
+ * esc_chars string with the esc_with string.
+ */
+int git_buf_puts_escaped(
+	git_buf *buf, const char *string, const char *esc_chars, const char *esc_with);
+
+GIT_INLINE(int) git_buf_puts_escape_regex(git_buf *buf, const char *string)
+{
+	return git_buf_puts_escaped(buf, string, "^.[]$()|*+?{}\\", "\\");
+}
+
+/**
  * Join two strings as paths, inserting a slash between as needed.
  * @return 0 on success, -1 on failure
  */
@@ -121,6 +133,13 @@ GIT_INLINE(ssize_t) git_buf_rfind_next(git_buf *buf, char ch)
 	return idx;
 }
 
+GIT_INLINE(ssize_t) git_buf_rfind(git_buf *buf, char ch)
+{
+	ssize_t idx = (ssize_t)buf->size - 1;
+	while (idx >= 0 && buf->ptr[idx] != ch) idx--;
+	return idx;
+}
+
 /* Remove whitespace from the end of the buffer */
 void git_buf_rtrim(git_buf *buf);
 
diff --git a/tests-clar/core/buffer.c b/tests-clar/core/buffer.c
index 6a718f4..996cb7b 100644
--- a/tests-clar/core/buffer.c
+++ b/tests-clar/core/buffer.c
@@ -611,3 +611,50 @@ void test_core_buffer__11(void)
 
 	git_buf_free(&a);
 }
+
+void test_core_buffer__rfind_variants(void)
+{
+	git_buf a = GIT_BUF_INIT;
+	ssize_t len;
+
+	cl_git_pass(git_buf_sets(&a, "/this/is/it/"));
+
+	len = (ssize_t)git_buf_len(&a);
+
+	cl_assert(git_buf_rfind(&a, '/') == len - 1);
+	cl_assert(git_buf_rfind_next(&a, '/') == len - 4);
+
+	cl_assert(git_buf_rfind(&a, 'i') == len - 3);
+	cl_assert(git_buf_rfind_next(&a, 'i') == len - 3);
+
+	cl_assert(git_buf_rfind(&a, 'h') == 2);
+	cl_assert(git_buf_rfind_next(&a, 'h') == 2);
+
+	cl_assert(git_buf_rfind(&a, 'q') == -1);
+	cl_assert(git_buf_rfind_next(&a, 'q') == -1);
+
+	git_buf_clear(&a);
+}
+
+void test_core_buffer__puts_escaped(void)
+{
+	git_buf a = GIT_BUF_INIT;
+
+	git_buf_clear(&a);
+	cl_git_pass(git_buf_puts_escaped(&a, "this is a test", "", ""));
+	cl_assert_equal_s("this is a test", a.ptr);
+
+	git_buf_clear(&a);
+	cl_git_pass(git_buf_puts_escaped(&a, "this is a test", "t", "\\"));
+	cl_assert_equal_s("\\this is a \\tes\\t", a.ptr);
+
+	git_buf_clear(&a);
+	cl_git_pass(git_buf_puts_escaped(&a, "this is a test", "i ", "__"));
+	cl_assert_equal_s("th__is__ __is__ a__ test", a.ptr);
+
+	git_buf_clear(&a);
+	cl_git_pass(git_buf_puts_escape_regex(&a, "^match\\s*[A-Z]+.*"));
+	cl_assert_equal_s("\\^match\\\\s\\*\\[A-Z\\]\\+\\.\\*", a.ptr);
+
+	git_buf_free(&a);
+}