heredoc << support

git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@1736058 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/Parser.java b/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/Parser.java
index 862bef6..f6fb81a 100644
--- a/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/Parser.java
+++ b/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/Parser.java
@@ -308,6 +308,7 @@
 
     private static final Pattern redirNoArg = Pattern.compile("[0-9]?>&[0-9-]|[0-9-]?<&[0-9-]");
     private static final Pattern redirArg = Pattern.compile("[0-9&]?>|[0-9]?>>|[0-9]?<|[0-9]?<>|<<<");
+    private static final Pattern redirHereDoc = Pattern.compile("<<-?");
 
     public Statement statement()
     {
@@ -366,6 +367,11 @@
                 redirs.add(t);
                 needRedirArg = true;
             }
+            else if (redirHereDoc.matcher(t).matches())
+            {
+                redirs.add(t);
+                redirs.add(tz.readHereDoc(t.charAt(t.length() - 1) == '-'));
+            }
             else
             {
                 tokens.add(t);
diff --git a/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/Pipe.java b/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/Pipe.java
index fc9bc67..91b459d 100644
--- a/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/Pipe.java
+++ b/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/Pipe.java
@@ -338,6 +338,37 @@
                         setStream(ch, fd, READ + (output ? WRITE : 0));
                     }
                 }
+                else if ((m = Pattern.compile("<<-?").matcher(t)).matches())
+                {
+                    Token hereDoc = tokens.get(++i);
+                    boolean stripLeadingTabs = t.charAt(t.length() - 1) == '-';
+                    InputStream doc = new InputStream()
+                    {
+                        final byte[] bytes = hereDoc.toString().getBytes();
+                        int index = 0;
+                        boolean nl = true;
+                        @Override
+                        public int read() throws IOException
+                        {
+                            if (nl && stripLeadingTabs)
+                            {
+                                while (index < bytes.length && bytes[index] == '\t')
+                                {
+                                    index++;
+                                }
+                            }
+                            if (index < bytes.length)
+                            {
+                                int ch = bytes[index++];
+                                nl = ch == '\n';
+                                return ch;
+                            }
+                            return -1;
+                        }
+                    };
+                    Channel ch = Channels.newChannel(doc);
+                    setStream(ch, 0, READ);
+                }
                 else if (Token.eq("<<<", t))
                 {
                     Token word = tokens.get(++i);
diff --git a/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/Tokenizer.java b/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/Tokenizer.java
index 549cb5c..e9cec78 100644
--- a/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/Tokenizer.java
+++ b/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/Tokenizer.java
@@ -23,7 +23,7 @@
 public class Tokenizer extends BaseTokenizer
 {
 
-    private final Pattern redir = Pattern.compile("[0-9&]?>|[0-9]?>>|[0-9]?>&|[0-9]?<|[0-9]?<>|<<<");
+    private final Pattern redir = Pattern.compile("[0-9&]?>|[0-9]?>>|[0-9]?>&|[0-9]?<|[0-9]?<>|<<<|<<\\-?");
 
     protected boolean inArray;
     protected int word = 0;
@@ -119,6 +119,17 @@
                     }
                     word = 0;
                     return token(start);
+                case '-':
+                    t = text.subSequence(start, index);
+                    if (redir.matcher(t).matches())
+                    {
+                        getch();
+                        return token(start);
+                    }
+                    else {
+                        getch();
+                        break;
+                    }
                 case '&':
                     // beginning of token
                     if (start == index - 1) {
@@ -211,4 +222,58 @@
         this.pushed = token;
     }
 
+    public Token readHereDoc(boolean ignoreLeadingTabs)
+    {
+        final short sLine = line;
+        final short sCol = column;
+        int start;
+        int nlIndex;
+        boolean nl;
+        // Find word
+        skipSpace();
+        start = index - 1;
+        while (ch != '\n' && ch != EOT) {
+            getch();
+        }
+        if (ch == EOT) {
+            throw new EOFError(sLine, sCol, "expected here-doc start", "heredoc", "foo");
+        }
+        Token token = text.subSequence(start, index - 1);
+        getch();
+        start = index - 1;
+        nlIndex = start;
+        nl = true;
+        // Get heredoc
+        while (true)
+        {
+            if (nl)
+            {
+                if (ignoreLeadingTabs && ch == '\t')
+                {
+                    nlIndex++;
+                }
+                else
+                {
+                    nl = false;
+                }
+            }
+            if (ch == '\n' || ch == EOT)
+            {
+                Token s = text.subSequence(nlIndex, index - 1);
+                if (Token.eq(s, token))
+                {
+                    Token hd = text.subSequence(start, s.start());
+                    getch();
+                    return hd;
+                }
+                nlIndex = index;
+                nl = true;
+            }
+            if (ch == EOT)
+            {
+                throw new EOFError(sLine, sCol, "unexpected eof found in here-doc", "heredoc", token.toString());
+            }
+            getch();
+        }
+    }
 }
diff --git a/gogo/runtime/src/test/java/org/apache/felix/gogo/runtime/TestParser4.java b/gogo/runtime/src/test/java/org/apache/felix/gogo/runtime/TestParser4.java
index ec69734..5bcb6a3 100644
--- a/gogo/runtime/src/test/java/org/apache/felix/gogo/runtime/TestParser4.java
+++ b/gogo/runtime/src/test/java/org/apache/felix/gogo/runtime/TestParser4.java
@@ -62,6 +62,9 @@
         Files.deleteIfExists(path.resolve("foo"));
         assertEquals("hello\n", c.execute("echo hello>foo | tac"));
         assertEquals("hello\n", new String(Files.readAllBytes(path.resolve("foo"))));
+
+        Files.deleteIfExists(path.resolve("foo2"));
+        assertEquals("hello\n", c.execute("echo hello>\\\nfoo2 | tac"));
     }
 
     @Test
@@ -152,6 +155,18 @@
         assertEquals("ar1 ar2\n", c.execute("cat <<<$c | tac"));
     }
 
+    @Test
+    public void testHereDoc() throws Exception
+    {
+        Context c = new Context();
+        c.addCommand("echo", this);
+        c.addCommand("tac", this);
+        c.addCommand("cat", this);
+
+        assertEquals("bar\nbaz\n", c.execute("cat <<foo\nbar\nbaz\nfoo\n| tac"));
+        assertEquals("bar\nbaz\n", c.execute("cat <<-foo\n\tbar\n\tbaz\n\tfoo\n| tac"));
+    }
+
     public void echo(String msg)
     {
         System.out.println(msg);