Le 16/12/2012 01:47, Colomban Wendling a écrit :
> [...]
> 
> OK, to chose appropriate quoting you may argue one needs to understand
> sub-command syntax.  Expanding the command:
> 
>       cmd "$(cmd2 %f)"
> 
> to
> 
>       cmd "$(cmd2 "'xxx.c'")"
> 
> is incorrect -- and that's what my patch would do, because it doesn't
> understand sub-shells.  We may or may not want to make sure to support
> sub-shells -- that's not that hard, but may also not be that useful, but
> whatever.

Attached an updated version of my patch to deal with `` and $()
sub-shell syntax.

Cheers,
Colomban
diff --git a/src/build.c b/src/build.c
index 7a2d84a..db198d7 100644
--- a/src/build.c
+++ b/src/build.c
@@ -706,60 +706,141 @@ static void parse_build_output(const gchar **output, gint status)
 #endif
 
 
-/* Replaces occurences of %e and %p with the appropriate filenames,
- * %d and %p replacements should be in UTF8 */
+/* Replaces %f, %d, %e and %p placeholders in a shell-style string.
+ * 
+ * This functions reads @p src as a shell-style input, understanding quoting (with "" and '')
+ * as well as nested sub-commands (with `` and $(), up to 16 levels).  It makes sure the
+ * placeholder replacements are properly quoted.
+ * 
+ * Returns: an UTF-8 string with placeholders replaced. */
 static gchar *build_replace_placeholder(const GeanyDocument *doc, const gchar *src)
 {
 	GString *stack;
-	gchar *filename = NULL;
-	gchar *replacement;
-	gchar *executable = NULL;
-	gchar *ret_str; /* to be freed when not in use anymore */
+	struct {
+		gchar quote;
+		gchar terminator;
+	} nesting[16] = {{ 0, 0 }};
+	guint level = 0;
+	/* replacements, %<letter> */
+	gchar *f = NULL; /* %f: basename */
+	gchar *d = NULL; /* %d: dirname */
+	gchar *e = NULL; /* %e: basename without extension */
+	gchar *p = NULL; /* %p: project (absolute) base directory */
+
+	if (src == NULL)
+		return NULL;
 
-	stack = g_string_new(src);
 	if (doc != NULL && doc->file_name != NULL)
 	{
-		filename = utils_get_utf8_from_locale(doc->file_name);
-
-		/* replace %f with the filename (including extension) */
-		replacement = g_path_get_basename(filename);
-		utils_string_replace_all(stack, "%f", replacement);
-		g_free(replacement);
+		gchar *filename = utils_get_utf8_from_locale(doc->file_name);
 
-		/* replace %d with the absolute path of the dir of the current file */
-		replacement = g_path_get_dirname(filename);
-		utils_string_replace_all(stack, "%d", replacement);
-		g_free(replacement);
+		f = g_path_get_basename(filename);
+		d = g_path_get_dirname(filename);
+		e = utils_remove_ext_from_filename(f);
 
-		/* replace %e with the filename (excluding extension) */
-		executable = utils_remove_ext_from_filename(filename);
-		replacement = g_path_get_basename(executable);
-		utils_string_replace_all(stack, "%e", replacement);
-		g_free(replacement);
+		g_free(filename);
 	}
-
-	/* replace %p with the current project's (absolute) base directory */
-	replacement = NULL; /* prevent double free if no replacement found */
 	if (app->project)
+		p = project_get_base_path();
+
+	/* quote the replacements */
+	if (f) SETPTR(f, g_shell_quote(f));
+	if (d) SETPTR(d, g_shell_quote(d));
+	if (e) SETPTR(e, g_shell_quote(e));
+	if (p) SETPTR(p, g_shell_quote(p));
+
+	stack = g_string_new(NULL);
+	for (; *src; src++)
 	{
-		replacement = project_get_base_path();
-	}
-	else if (strstr(stack->str, "%p"))
-	{   /* fall back to %d */
-		ui_set_statusbar(FALSE, _("failed to substitute %%p, no project active"));
-		if (doc != NULL && filename != NULL)
-			replacement = g_path_get_dirname(filename);
-	}
+		if (*src == nesting[level].terminator)
+		{
+			g_string_append_c(stack, *src);
+			level--;
+			continue;
+		}
 
-	utils_string_replace_all(stack, "%p", replacement);
-	g_free(replacement);
+		switch (*src)
+		{
+			case '`':
+				g_string_append_c(stack, *src);
+				if (nesting[level].quote != '\'' &&
+					level < G_N_ELEMENTS(nesting))
+				{
+					level ++;
+					nesting[level].quote = 0;
+					nesting[level].terminator = *src;
+				}
+				break;
 
-	ret_str = utils_get_utf8_from_locale(stack->str);
-	g_free(executable);
-	g_free(filename);
-	g_string_free(stack, TRUE);
+			case '$':
+				g_string_append_c(stack, *src);
+				if (nesting[level].quote != '\'' &&
+					level < G_N_ELEMENTS(nesting) &&
+					src[1] == '(')
+				{
+					src++;
+					g_string_append_c(stack, *src);
+					level ++;
+					nesting[level].quote = 0;
+					nesting[level].terminator = ')';
+				}
+				break;
+
+			case '"':
+			case '\'':
+				if (! nesting[level].quote)
+					nesting[level].quote = *src;
+				else if (nesting[level].quote == *src)
+					nesting[level].quote = 0;
+				g_string_append_c(stack, *src);
+				break;
+
+			case '%':
+				src++;
+				if (nesting[level].quote) /* close the quote */
+					g_string_append_c(stack, nesting[level].quote);
+				if (*src == 'f' && f)
+					g_string_append(stack, f);
+				else if (*src == 'd' && d)
+					g_string_append(stack, d);
+				else if (*src == 'e' && e)
+					g_string_append(stack, e);
+				else if (*src == 'p')
+				{
+					if (p)
+						g_string_append(stack, p);
+					else
+					{   /* fallback to %d */
+						ui_set_statusbar(FALSE, _("failed to substitute %%p, no project active"));
+						if (d)
+							g_string_append(stack, d);
+					}
+				}
+				else
+				{   /* just leave the placeholder */
+					g_string_append_c(stack, '%');
+					g_string_append_c(stack, *src);
+				}
+				if (nesting[level].quote) /* re-open quote */
+					g_string_append_c(stack, nesting[level].quote);
+				break;
+
+			case '\\':
+				g_string_append_c(stack, *src);
+				src++;
+			/* fallthrough */
+			default:
+				g_string_append_c(stack, *src);
+				break;
+		}
+	}
+
+	g_free (f);
+	g_free (d);
+	g_free (e);
+	g_free (p);
 
-	return ret_str; /* don't forget to free src also if needed */
+	return g_string_free(stack, FALSE);
 }
 
 
_______________________________________________
Devel mailing list
Devel@lists.geany.org
https://lists.geany.org/cgi-bin/mailman/listinfo/devel

Reply via email to