From aea4e4bd70f495a429846947f4b0b0d23959bcc2 Mon Sep 17 00:00:00 2001
From: Garrett Beaty <gbeaty@google.com>
Date: Wed, 18 Sep 2024 13:59:19 -0700
Subject: [PATCH] Enable buildozer commands containing newlines in command
 files.

buildozer can accept an argument for the set command with appropriately
escaped newlines to force multiline values that would otherwise be
written out as a single line. When buildozer reads commands from a
commands file, it reads a single line and attempts to apply it as a
command, so newlines can't be used to force a multiline value.

To enable forcing multiline values when using a commands file, this
updates buildozer to continue reading lines when a line ends with an
escaped newline to match the behavior on the command line. This
additionally adds a test case for using whitespace on the command-line
so that there is consistent coverage for the two modes.
---
 buildozer/buildozer_test.sh | 25 ++++++++++++++++++++++++-
 edit/buildozer.go           | 22 +++++++++++++++-------
 2 files changed, 39 insertions(+), 8 deletions(-)

diff --git a/buildozer/buildozer_test.sh b/buildozer/buildozer_test.sh
index e8319e198..d66fd231b 100755
--- a/buildozer/buildozer_test.sh
+++ b/buildozer/buildozer_test.sh
@@ -1098,6 +1098,20 @@ function test_set_custom_code() {
 )'
 }
 
+function test_set_custom_code_with_whitespace() {
+  in='cc_test(name = "a")'
+
+  run "$in" 'set attr foo(\
+\ \ a\ =\ 1,\
+)' '//pkg:a'
+  assert_equals 'cc_test(
+    name = "a",
+    attr = foo(
+        a = 1,
+    ),
+)'
+}
+
 function assert_output() {
   echo "$1" > "expected"
   diff -u "expected" "$log" || fail "Output didn't match"
@@ -1658,7 +1672,12 @@ add deps x#|a/pkg2:foo
 add deps y|a/pkg2:bar|add deps c|a/pkg1:foo
 add deps z|a/pkg2:bar
 add deps a|//a/pkg1:bar
-add deps b|a/pkg1:foo" > commands
+add deps b|a/pkg1:foo
+
+set attr func(a=1)|a/pkg1:foo
+set attr func(\\
+\ \ a\ =\ 1\\
+)|a/pkg2:foo" > commands
 }
 
 function check_file_test() {
@@ -1666,6 +1685,7 @@ function check_file_test() {
   cat > expected_pkg_1 <<EOF
 cc_library(
     name = "foo",
+    attr = func(a = 1),
     deps = [
         "a",
         "b",
@@ -1689,6 +1709,9 @@ EOF
   cat > expected_pkg_2 <<EOF
 cc_library(
     name = "foo",
+    attr = func(
+        a = 1,
+    ),
     deps = ["x#"],
 )
 
diff --git a/edit/buildozer.go b/edit/buildozer.go
index 0e84f6b57..78728abc0 100644
--- a/edit/buildozer.go
+++ b/edit/buildozer.go
@@ -1367,14 +1367,22 @@ func appendCommandsFromReader(opts *Options, reader io.Reader, commandsByFile ma
 	r := bufio.NewReader(reader)
 	atEOF := false
 	for !atEOF {
-		line, err := r.ReadString('\n')
-		if err == io.EOF {
-			atEOF = true
-			err = nil
-		}
-		if err != nil {
-			return fmt.Errorf("error while reading commands file: %v", err)
+		lineBuilder := strings.Builder{}
+		for !atEOF {
+			linePart, err := r.ReadString('\n')
+			if err == io.EOF {
+				atEOF = true
+				err = nil
+			}
+			if err != nil {
+				return fmt.Errorf("error while reading commands file: %v", err)
+			}
+			lineBuilder.WriteString(linePart)
+			if !strings.HasSuffix(linePart, "\\\n") {
+				break
+			}
 		}
+		line := lineBuilder.String()
 		line = strings.TrimSpace(line)
 		if line == "" || line[0] == '#' {
 			continue