This is an automated email from the ASF dual-hosted git repository.
ddekany pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/freemarker-docgen.git
The following commit(s) were added to refs/heads/master by this push:
new 8f290de Fixed several glitches in new features. Renamed insertOutput
to insertWithOutput, and made insertCommand=true the default. Added
docgen.verifyCommand, that doesn't insert output, just ensures that the command
doesn't fail.
8f290de is described below
commit 8f290de844ce74c3bdcc91af5038fb14a431f43b
Author: ddekany <[email protected]>
AuthorDate: Sun Feb 14 21:09:18 2021 +0100
Fixed several glitches in new features. Renamed insertOutput to
insertWithOutput, and made insertCommand=true the default. Added
docgen.verifyCommand, that doesn't insert output, just ensures that the command
doesn't fail.
---
.../docgen/core/BashCommandLineArgsParser.java | 44 ++++-
.../docgen/core/ChopLinebreakDirective.java | 48 ++++++
.../docgen/core/ChopLinebreakWriter.java | 99 ++++++++++++
.../freemarker/docgen/core/FilterDirective.java | 52 ++++++
.../PrintTextWithDocgenSubstitutionsDirective.java | 180 ++++++++++++++++-----
.../org/freemarker/docgen/core/SettingName.java | 44 ++++-
.../org/freemarker/docgen/core/SettingUtils.java | 1 -
.../java/org/freemarker/docgen/core/Transform.java | 11 +-
.../docgen/core/templates/node-handlers.ftlh | 3 +-
.../docgen/core/BashCommandLineArgsParserTest.java | 9 +-
.../docgen/core/ChopLinebreakWriterTest.java | 146 +++++++++++++++++
.../docgen/core/EscapeHtmlAndXmlWriterTest.java | 75 +++++++++
.../freemarker/docgen/core/SettingNameTest.java | 2 +
13 files changed, 656 insertions(+), 58 deletions(-)
diff --git
a/freemarker-docgen-core/src/main/java/org/freemarker/docgen/core/BashCommandLineArgsParser.java
b/freemarker-docgen-core/src/main/java/org/freemarker/docgen/core/BashCommandLineArgsParser.java
index c464b34..662cfb2 100644
---
a/freemarker-docgen-core/src/main/java/org/freemarker/docgen/core/BashCommandLineArgsParser.java
+++
b/freemarker-docgen-core/src/main/java/org/freemarker/docgen/core/BashCommandLineArgsParser.java
@@ -47,7 +47,7 @@ public class BashCommandLineArgsParser {
}
private String skipWSAndFetchArg() {
- skipWS();
+ skipBashWS();
return fetchArg();
}
@@ -58,11 +58,24 @@ public class BashCommandLineArgsParser {
boolean escaped = false;
while (pos < src.length()) {
char c = src.charAt(pos);
+
+ // Get rid of Windows and Mac line-breaks:
+ if (c == '\r') {
+ if (pos + 1 < src.length()) {
+ if (src.charAt(pos + 1) == '\n') {
+ pos++;
+ }
+ }
+ c = '\n';
+ }
+
if (escaped) {
- if (openedQuote == '"' && !(c == '"' || c == '\\' || c ==
'$')) {
+ if (openedQuote == '"' && !(c == '"' || c == '\\' || c == '$'
|| c == '\n')) {
arg.append('\\');
+ arg.append(c);
+ } else if (c != '\n') { // Otherwise it's an escaped
line-break, so we just drop it to join lines.
+ arg.append(c);
}
- arg.append(c);
escaped = false;
} else {
if (c == '"' || c == '\'') {
@@ -86,13 +99,32 @@ public class BashCommandLineArgsParser {
return startPos != pos ? arg.toString() : null;
}
- private void skipWS() {
- while (pos < src.length() && isWS(src.charAt(pos))) {
+ private void skipBashWS() {
+ while (pos < src.length()) {
+ char c = src.charAt(pos);
+
+ if (c == '\\') {
+ if (pos + 1 < src.length() && isLinebreak(src.charAt(pos +
1))) {
+ // Skip escaped linebreak as whitespace
+ if (src.charAt(pos + 1) == '\r' && pos + 2 < src.length()
&& src.charAt(pos + 2) == '\n') {
+ pos++;
+ }
+ } else {
+ break;
+ }
+ } else if (!isWS(c)) {
+ break;
+ }
pos++;
}
}
private boolean isWS(char c) {
- return c == ' ' || c == '\n' || c == '\r' || c == '\t';
+ return c == ' ' || c == '\t' || isLinebreak(c);
+ }
+
+ private boolean isLinebreak(char c) {
+ return c == '\n' || c == '\r';
}
+
}
diff --git
a/freemarker-docgen-core/src/main/java/org/freemarker/docgen/core/ChopLinebreakDirective.java
b/freemarker-docgen-core/src/main/java/org/freemarker/docgen/core/ChopLinebreakDirective.java
new file mode 100644
index 0000000..599b1a2
--- /dev/null
+++
b/freemarker-docgen-core/src/main/java/org/freemarker/docgen/core/ChopLinebreakDirective.java
@@ -0,0 +1,48 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.freemarker.docgen.core;
+
+import java.io.IOException;
+import java.io.Writer;
+import java.util.Map;
+
+import freemarker.core.Environment;
+import freemarker.template.TemplateDirectiveBody;
+import freemarker.template.TemplateDirectiveModel;
+import freemarker.template.TemplateException;
+import freemarker.template.TemplateModel;
+import freemarker.template.TemplateModelException;
+import freemarker.template.TemplateTransformModel;
+
+/**
+ * Similar to <code>${capturedContent?chopLinebreak}</code>, but it's
"streaming", which is important if we want the
+ * partial output when there's an exception in the mid of generating the
content.
+ */
+class ChopLinebreakDirective extends FilterDirective {
+ static final ChopLinebreakDirective INSTANCE = new
ChopLinebreakDirective();
+
+ private ChopLinebreakDirective() {
+ }
+
+ @Override
+ protected Writer wrapWriter(Writer out) {
+ return new ChopLinebreakWriter(out);
+ }
+}
diff --git
a/freemarker-docgen-core/src/main/java/org/freemarker/docgen/core/ChopLinebreakWriter.java
b/freemarker-docgen-core/src/main/java/org/freemarker/docgen/core/ChopLinebreakWriter.java
new file mode 100644
index 0000000..2020e96
--- /dev/null
+++
b/freemarker-docgen-core/src/main/java/org/freemarker/docgen/core/ChopLinebreakWriter.java
@@ -0,0 +1,99 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.freemarker.docgen.core;
+
+import java.io.FilterWriter;
+import java.io.IOException;
+import java.io.Writer;
+
+/**
+ * Writer that passes content through to a wrapped {@link Writer}, but will
remove the line-break from the end of what
+ * was written so far.
+ */
+class ChopLinebreakWriter extends FilterWriter {
+ private String pendingLinebreak;
+
+ protected ChopLinebreakWriter(Writer out) {
+ super(out);
+ }
+
+ @Override
+ public void write(int c) throws IOException {
+ if (isLinebreakChar(c)) {
+ if (pendingLinebreak == null) {
+ pendingLinebreak = Character.toString((char) c);
+ } else {
+ if (c == '\n' && pendingLinebreak.equals("\r")) {
+ pendingLinebreak = "\r\n";
+ } else {
+ commitPendingLinebreak();
+ pendingLinebreak = Character.toString((char) c);
+ }
+ }
+ } else {
+ commitPendingLinebreak();
+ out.write(c);
+ }
+ }
+
+ private static boolean isLinebreakChar(int c) {
+ return c == '\n' || c == '\r';
+ }
+
+ @Override
+ public void write(char[] cbuf, int off, int len) throws IOException {
+ int endOff = off + len;
+ int lastNonBrIndex = endOff - 1;
+ while (lastNonBrIndex >= off && isLinebreakChar(cbuf[lastNonBrIndex]))
{
+ lastNonBrIndex--;
+ }
+ if (lastNonBrIndex >= off) {
+ commitPendingLinebreak();
+ out.write(cbuf, off, lastNonBrIndex + 1 - off);
+ }
+ for (int i = lastNonBrIndex + 1; i < endOff; i++) {
+ write(cbuf[i]);
+ }
+ }
+
+ @Override
+ public void write(String str, int off, int len) throws IOException {
+ int endOff = off + len;
+ int lastNonBrIndex = endOff - 1;
+ while (lastNonBrIndex >= off &&
isLinebreakChar(str.charAt(lastNonBrIndex))) {
+ lastNonBrIndex--;
+ }
+ if (lastNonBrIndex >= off) {
+ commitPendingLinebreak();
+ out.write(str, off, lastNonBrIndex + 1 - off);
+ }
+ for (int i = lastNonBrIndex + 1; i < endOff; i++) {
+ write(str.charAt(i));
+ }
+ }
+
+ private void commitPendingLinebreak() throws IOException {
+ if (pendingLinebreak != null) {
+ out.write(pendingLinebreak);
+ pendingLinebreak = null;
+ }
+ }
+
+}
diff --git
a/freemarker-docgen-core/src/main/java/org/freemarker/docgen/core/FilterDirective.java
b/freemarker-docgen-core/src/main/java/org/freemarker/docgen/core/FilterDirective.java
new file mode 100644
index 0000000..a319216
--- /dev/null
+++
b/freemarker-docgen-core/src/main/java/org/freemarker/docgen/core/FilterDirective.java
@@ -0,0 +1,52 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.freemarker.docgen.core;
+
+import java.io.IOException;
+import java.io.Writer;
+import java.util.Map;
+
+import freemarker.core.Environment;
+import freemarker.template.TemplateDirectiveBody;
+import freemarker.template.TemplateDirectiveModel;
+import freemarker.template.TemplateException;
+import freemarker.template.TemplateModel;
+
+abstract class FilterDirective implements TemplateDirectiveModel {
+ protected FilterDirective() {
+ }
+
+ @Override
+ public void execute(Environment env, Map params, TemplateModel[] loopVars,
TemplateDirectiveBody body) throws
+ TemplateException, IOException {
+ if (!params.isEmpty()) {
+ throw new TemplateException("This directive doesn't support any
parameters", env);
+ }
+ if (loopVars.length != 0) {
+ throw new TemplateException("This directive doesn't support any
loop variables", env);
+ }
+ if (body == null) {
+ return;
+ }
+ body.render(wrapWriter(env.getOut()));
+ }
+
+ protected abstract Writer wrapWriter(Writer out);
+}
diff --git
a/freemarker-docgen-core/src/main/java/org/freemarker/docgen/core/PrintTextWithDocgenSubstitutionsDirective.java
b/freemarker-docgen-core/src/main/java/org/freemarker/docgen/core/PrintTextWithDocgenSubstitutionsDirective.java
index b245e94..d2eb333 100644
---
a/freemarker-docgen-core/src/main/java/org/freemarker/docgen/core/PrintTextWithDocgenSubstitutionsDirective.java
+++
b/freemarker-docgen-core/src/main/java/org/freemarker/docgen/core/PrintTextWithDocgenSubstitutionsDirective.java
@@ -24,10 +24,8 @@ import static
org.freemarker.docgen.core.PrintTextWithDocgenSubstitutionsDirecti
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintStream;
-import java.io.PrintWriter;
import java.io.StringWriter;
import java.io.Writer;
-import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.nio.charset.Charset;
@@ -48,6 +46,7 @@ import java.util.stream.Collectors;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.IOUtils;
+import org.apache.commons.io.input.ClosedInputStream;
import org.apache.commons.io.output.WriterOutputStream;
import org.apache.commons.text.StringEscapeUtils;
@@ -68,6 +67,7 @@ import freemarker.template.TemplateModel;
import freemarker.template.TemplateNumberModel;
import freemarker.template.TemplateScalarModel;
import freemarker.template.utility.ClassUtil;
+import freemarker.template.utility.NullWriter;
import freemarker.template.utility.StringUtil;
public class PrintTextWithDocgenSubstitutionsDirective implements
TemplateDirectiveModel {
@@ -79,7 +79,8 @@ public class PrintTextWithDocgenSubstitutionsDirective
implements TemplateDirect
enum InsertDirectiveType {
INSERT_FILE("insertFile"),
- INSERT_OUTPUT("insertOutput");
+ INSERT_WITH_OUTPUT("insertWithOutput"),
+ CHECK_COMMAND("checkCommand");
private final String directiveName;
@@ -127,7 +128,8 @@ public class PrintTextWithDocgenSubstitutionsDirective
implements TemplateDirect
new DocgenSubstitutionInterpreter(text, env).execute();
}
- private static final String DOCGEN_WD_TAG = "[docgen.wd]";
+ private static final String WD = "wd";
+ private static final String DOCGEN_WD_TAG = "[docgen." + WD + "]";
private static final Pattern DOCGEN_WD_TAG_AND_SLASH_PATTERN =
Pattern.compile(Pattern.quote(DOCGEN_WD_TAG) + "/?");
private class DocgenSubstitutionInterpreter {
@@ -171,10 +173,18 @@ public class PrintTextWithDocgenSubstitutionsDirective
implements TemplateDirect
InsertDirectiveArgs args =
fetchInsertDirectiveArgs(subvarName, INSERT_FILE);
lastUnprintedIdx = cursor;
insertFile(args);
- } else if (INSERT_OUTPUT.directiveName.equals(subvarName)) {
- InsertDirectiveArgs args =
fetchInsertDirectiveArgs(subvarName, INSERT_OUTPUT);
+ } else if
(INSERT_WITH_OUTPUT.directiveName.equals(subvarName)) {
+ InsertDirectiveArgs args =
fetchInsertDirectiveArgs(subvarName, INSERT_WITH_OUTPUT);
lastUnprintedIdx = cursor;
- insertOutput(args);
+ insertCommandAndOutput(INSERT_WITH_OUTPUT, args);
+ } else if (CHECK_COMMAND.directiveName.equals(subvarName)) {
+ InsertDirectiveArgs args =
fetchInsertDirectiveArgs(subvarName, CHECK_COMMAND);
+ lastUnprintedIdx = cursor;
+ insertCommandAndOutput(CHECK_COMMAND, args);
+ } else if (subvarName.equals(WD)) {
+ throw new TemplateException(
+ "The " + WD + " docgen subvariable can only be
used in the nested content of Docgen "
+ + "directives that specify a command to
run.", env);
} else {
throw new TemplateException(
"Unsupported docgen subvariable " +
StringUtil.jQuote(subvarName) + ".", env);
@@ -286,8 +296,9 @@ public class PrintTextWithDocgenSubstitutionsDirective
implements TemplateDirect
}
}
- private void insertOutput(InsertDirectiveArgs args) throws
TemplateException, IOException {
- if (args.printCommand) {
+ private void insertCommandAndOutput(InsertDirectiveType
insertDirectiveType, InsertDirectiveArgs args)
+ throws TemplateException, IOException {
+ if (args.printCommand || insertDirectiveType == CHECK_COMMAND) {
out.write("> ");
out.write(DOCGEN_WD_TAG_AND_SLASH_PATTERN.matcher(StringUtil.chomp(args.body)).replaceAll(""));
out.write("\n");
@@ -313,14 +324,20 @@ public class PrintTextWithDocgenSubstitutionsDirective
implements TemplateDirect
Method mainMethod = getMainMethod(cmdKey, cmdProps);
- StringWriter stdOutCapturer;
+ String cmdRunExceptionShortMessage;
+ TemplateException cmdRunException;
+ Writer outCapturer;
PrintStream prevOut = System.out;
+ PrintStream prevErr = System.err;
+ InputStream prevIn = System.in;
Map<String, String> prevSystemProperties = new HashMap<>();
try {
- stdOutCapturer = new StringWriter();
- PrintStream stdOutCapturerPrintStream = new PrintStream(
- new WriterOutputStream(stdOutCapturer,
Charset.defaultCharset()));
- System.setOut(stdOutCapturerPrintStream);
+ outCapturer = insertDirectiveType != CHECK_COMMAND ? new
StringWriter() : NullWriter.INSTANCE;
+ PrintStream outCapturerPrintStream = new PrintStream(
+ new WriterOutputStream(outCapturer,
Charset.defaultCharset()));
+ System.setOut(outCapturerPrintStream);
+ System.setErr(outCapturerPrintStream);
+ System.setIn(ClosedInputStream.CLOSED_INPUT_STREAM);
cmdProps.getSystemProperties().forEach((k, v) -> {
String prevValue = setOrClearSystemProperty(k, v);
@@ -342,28 +359,70 @@ public class PrintTextWithDocgenSubstitutionsDirective
implements TemplateDirect
})
.collect(Collectors.toList());
- Object returnValue;
+ Object cmdExitCode;
try {
- returnValue = mainMethod.invoke(null, (Object)
cmdArgs.toArray(new String[0]));
+ cmdExitCode = mainMethod.invoke(null, (Object)
cmdArgs.toArray(new String[0]));
+ if (cmdExitCode instanceof Integer && ((Integer)
cmdExitCode) != 0) {
+ cmdRunExceptionShortMessage = "Command execution has
returned with non-0 exit code " + cmdExitCode + ".";
+ cmdRunException =
newErrorInInsertOutputCommandException(
+ cmdRunExceptionShortMessage,
+ cmdProps, cmdArgs,
+ null);
+ } else {
+ cmdRunExceptionShortMessage = null;
+ cmdRunException = null;
+ }
} catch (Exception e) {
- throw newErrorInDocgenTag("Error when executing command
with "
- + cmdProps.getMainClassName() + "." +
cmdProps.getMainMethodName()
- + ", and arguments " + cmdArgs + ".",
+ cmdRunExceptionShortMessage = "The main method has thrown
this exception:\n" + e;
+ cmdRunException = newErrorInInsertOutputCommandException(
+ cmdRunExceptionShortMessage,
+ cmdProps, cmdArgs,
e);
}
- if (returnValue instanceof Integer && ((Integer) returnValue)
!= 0) {
- throw newErrorInDocgenTag(
- "Command execution has returned with non-0 exit
code " + returnValue
- + ", from " + cmdProps.getMainClassName()
+ "." + cmdProps.getMainMethodName()
- + ", called with arguments " + cmdArgs +
".");
- }
- stdOutCapturerPrintStream.flush();
+ outCapturerPrintStream.flush();
} finally {
prevSystemProperties.forEach(PrintTextWithDocgenSubstitutionsDirective::setOrClearSystemProperty);
+ System.setIn(prevIn);
+ System.setErr(prevErr);
System.setOut(prevOut);
}
- cutAndInsertContent(args, stdOutCapturer.toString());
+ if (cmdRunException == null) {
+ if (insertDirectiveType != CHECK_COMMAND) {
+ cutAndInsertContent(args, outCapturer.toString());
+ }
+ } else {
+ out.write(
+ "--------------------\n" +
+ "Docgen " + INSERT_WITH_OUTPUT.directiveName + "
directive failed: "
+ + cmdRunExceptionShortMessage + "\n"
+ + "The command was:\n"
+ + StringUtil.chomp(args.body) + "\n\n"
+ + "The output of the command (if any) until it
failed:\n\n");
+ HTMLOutputFormat.INSTANCE.output(outCapturer.toString(), out);
+ throw cmdRunException;
+ }
+ }
+
+ private TemplateException newErrorInInsertOutputCommandException(
+ String specificMessage,
+ Transform.InsertableOutputCommandProperties cmdProps,
List<String> cmdArgs,
+ Throwable e) {
+ String outputFileName =
transform.getCurrentFileTOCNode().getOutputFileName();
+ return newErrorInDocgenTag(
+ specificMessage
+ + "\nCommand main method: "
+ + cmdProps.getMainClassName() + "." +
cmdProps.getMainMethodName()
+ + "\nCommand arguments:"
+ + (cmdArgs.size() != 0
+ ? "\n "
+ +
cmdArgs.stream().map(StringUtil::jQuote)
+
.collect(Collectors.joining("\n "))
+ + "\n"
+ : " None")
+ + "\nThe error message printed by the command
itself, if any, can be found at the end of "
+ + (outputFileName != null ? "\"" + outputFileName
+ "\"" : "the output file") + ".",
+ e);
}
private void cutAndInsertContent(InsertDirectiveArgs args, String
content)
@@ -485,6 +544,37 @@ public class PrintTextWithDocgenSubstitutionsDirective
implements TemplateDirect
return found;
}
+ private boolean skipLineBreak() {
+ int savedCursor = cursor;
+
+ // Skip horizontal whitespace
+ while (cursor < text.length()) {
+ char c = text.charAt(cursor);
+ if (c != ' ' && c != '\t' && c != '\u00A0') {
+ break;
+ }
+ cursor++;
+ }
+
+ // Skip line-break:
+ if (cursor < text.length()) {
+ char c = text.charAt(cursor);
+ if (c == '\n') {
+ cursor++;
+ return true;
+ }
+ if (c == '\r') {
+ cursor++;
+ if (cursor < text.length() && text.charAt(cursor) == '\n')
{
+ cursor++;
+ }
+ return true;
+ }
+ }
+ cursor = savedCursor;
+ return false;
+ }
+
private void skipRequiredToken(String token) throws TemplateException {
if (!skipOptionalToken(token)) {
throw newUnexpectedTokenException(StringUtil.jQuote(token),
env);
@@ -492,16 +582,17 @@ public class PrintTextWithDocgenSubstitutionsDirective
implements TemplateDirect
}
private boolean skipOptionalToken(String token) throws
TemplateException {
+ int savedCursor = cursor;
skipWS();
for (int i = 0; i < token.length(); i++) {
char expectedChar = token.charAt(i);
int lookAheadCursor = cursor + i;
if (charAt(lookAheadCursor) != expectedChar) {
+ cursor = savedCursor;
return false;
}
}
cursor += token.length();
- skipWS();
return true;
}
@@ -514,7 +605,10 @@ public class PrintTextWithDocgenSubstitutionsDirective
implements TemplateDirect
}
private String fetchOptionalVariableName() {
+ int savedCursor = cursor;
+ skipWS();
if (!Character.isJavaIdentifierStart(charAt(cursor))) {
+ cursor = savedCursor;
return null;
}
int varNameStart = cursor;
@@ -534,6 +628,8 @@ public class PrintTextWithDocgenSubstitutionsDirective
implements TemplateDirect
}
private String fetchOptionalString() throws TemplateException {
+ int savedCursor = cursor;
+ skipWS();
char quoteChar = charAt(cursor);
boolean rawString = quoteChar == 'r';
if (rawString) {
@@ -542,6 +638,7 @@ public class PrintTextWithDocgenSubstitutionsDirective
implements TemplateDirect
}
}
if (quoteChar != '"' && quoteChar != '\'') {
+ cursor = savedCursor;
return null;
}
cursor += rawString ? 2 : 1;
@@ -617,6 +714,7 @@ public class PrintTextWithDocgenSubstitutionsDirective
implements TemplateDirect
TemplateException {
InsertDirectiveArgs args = new InsertDirectiveArgs();
args.toOptional = true;
+ args.printCommand = true;
if (insertDirectiveType == INSERT_FILE) {
skipWS();
@@ -632,18 +730,19 @@ public class PrintTextWithDocgenSubstitutionsDirective
implements TemplateDirect
"Duplicate docgen." + subvarName + " parameter "
+ StringUtil.jQuote(paramName) + ".",
env);
}
- if (insertDirectiveType == INSERT_FILE &&
paramName.equals("charset")) {
- args.charset =
StringEscapeUtils.unescapeXml(fetchRequiredString());
- } else if (paramName.equals("from")) {
- args.from = parseRegularExpressionParam(paramName,
StringEscapeUtils.unescapeXml(fetchRequiredString()));
- } else if (paramName.equals("to")) {
- args.to = parseRegularExpressionParam(paramName,
StringEscapeUtils.unescapeXml(fetchRequiredString()));
- } else if (paramName.equals("fromOptional")) {
+ boolean insertFileOrOutput = insertDirectiveType ==
INSERT_FILE || insertDirectiveType ==
+ INSERT_WITH_OUTPUT;
+ if (insertFileOrOutput && paramName.equals("charset")) {
+ args.charset = fetchRequiredString();
+ } else if (insertFileOrOutput && paramName.equals("from")) {
+ args.from = parseRegularExpressionParam(paramName,
fetchRequiredString());
+ } else if (insertFileOrOutput && paramName.equals("to")) {
+ args.to = parseRegularExpressionParam(paramName,
fetchRequiredString());
+ } else if (insertFileOrOutput &&
paramName.equals("fromOptional")) {
args.fromOptional = fetchRequiredBoolean();
- } else if (paramName.equals("toOptional")) {
+ } else if (insertFileOrOutput &&
paramName.equals("toOptional")) {
args.toOptional = fetchRequiredBoolean();
- } else if (insertDirectiveType == INSERT_OUTPUT
- && paramName.equals("printCommand")) {
+ } else if (insertDirectiveType == INSERT_WITH_OUTPUT &&
paramName.equals("printCommand")) {
args.printCommand = fetchRequiredBoolean();
} else {
throw new DocgenTagException(
@@ -653,9 +752,10 @@ public class PrintTextWithDocgenSubstitutionsDirective
implements TemplateDirect
}
skipRequiredToken(DOCGEN_TAG_END);
+ skipLineBreak();
int indexAfterStartTag = cursor;
- if (insertDirectiveType == INSERT_OUTPUT) {
+ if (insertDirectiveType == INSERT_WITH_OUTPUT ||
insertDirectiveType == CHECK_COMMAND) {
int endTagIndex = findNextDocgenEndTag(cursor);
if (endTagIndex == -1) {
throw new DocgenTagException(
@@ -663,7 +763,7 @@ public class PrintTextWithDocgenSubstitutionsDirective
implements TemplateDirect
}
lastDocgenTagStart = endTagIndex;
- args.body =
StringEscapeUtils.unescapeXml(text.substring(indexAfterStartTag, endTagIndex));
+ args.body =
StringUtil.chomp(text.substring(indexAfterStartTag, endTagIndex));
cursor = endTagIndex + DOCGEN_END_TAG_START.length();
skipRequiredToken(".");
diff --git
a/freemarker-docgen-core/src/main/java/org/freemarker/docgen/core/SettingName.java
b/freemarker-docgen-core/src/main/java/org/freemarker/docgen/core/SettingName.java
index 78bb341..952df37 100644
---
a/freemarker-docgen-core/src/main/java/org/freemarker/docgen/core/SettingName.java
+++
b/freemarker-docgen-core/src/main/java/org/freemarker/docgen/core/SettingName.java
@@ -22,8 +22,12 @@ package org.freemarker.docgen.core;
import java.io.File;
import java.util.Arrays;
import java.util.List;
+import java.util.Objects;
import java.util.Optional;
+import freemarker.ext.beans.NumberModel;
+import freemarker.template.utility.StringUtil;
+
final class SettingName {
private final File parentFile;
private final SettingName parent;
@@ -32,7 +36,11 @@ final class SettingName {
public SettingName(File parentFile, SettingName parent, Object key) {
this.parentFile = parentFile;
this.parent = parent;
- this.key = key;
+ this.key = Objects.requireNonNull(key);
+ if (!(key instanceof String) && !(key instanceof Number)) {
+ throw new IllegalArgumentException(
+ "Key must be String or Number, but it was: " +
key.getClass().getName());
+ }
}
static SettingName topLevel(File parentFile, String simpleName) {
@@ -44,7 +52,7 @@ final class SettingName {
}
SettingName subKey(Object... keys) {
- return new SettingName(null,this, subKey(Arrays.asList(keys)));
+ return subKey(Arrays.asList(keys));
}
SettingName subKey(List<Object> keys) {
@@ -71,12 +79,36 @@ final class SettingName {
parent.appendName(sb);
}
if (key instanceof String) {
- if (sb.length() != 0) {
- sb.append('.');
+ String strKey = (String) key;
+ if (isIdentifierLike(strKey)) {
+ if (sb.length() != 0) {
+ sb.append('.');
+ }
+ sb.append(key);
+ } else {
+ if (sb.length() == 0) {
+ sb.append("#ROOT");
+ }
+ sb.append('[').append(StringUtil.jQuote(key)).append(']');
}
- sb.append(key);
- } else {
+ } else if (key instanceof Number) {
sb.append('[').append(key).append(']');
}
}
+
+ private boolean isIdentifierLike(String s) {
+ if (s.length() == 0) {
+ return false;
+ }
+ if (!Character.isJavaIdentifierStart(s.charAt(0))) {
+ return false;
+ }
+ for (int i = 1; i < s.length(); i++) {
+ char c = s.charAt(i);
+ if (!Character.isJavaIdentifierPart(c) && c != '-') {
+ return false;
+ }
+ }
+ return true;
+ }
}
diff --git
a/freemarker-docgen-core/src/main/java/org/freemarker/docgen/core/SettingUtils.java
b/freemarker-docgen-core/src/main/java/org/freemarker/docgen/core/SettingUtils.java
index 39c1f59..30e6a84 100644
---
a/freemarker-docgen-core/src/main/java/org/freemarker/docgen/core/SettingUtils.java
+++
b/freemarker-docgen-core/src/main/java/org/freemarker/docgen/core/SettingUtils.java
@@ -114,7 +114,6 @@ final class SettingUtils {
throw newNullSettingValueException(settingName);
}
if (!valueType.isInstance(settingValue)) {
- System.out.println("BAD VALUE: " + settingValue); //!!T
throw newBadSettingValueTypeException(settingName, valueType,
settingValue);
}
diff --git
a/freemarker-docgen-core/src/main/java/org/freemarker/docgen/core/Transform.java
b/freemarker-docgen-core/src/main/java/org/freemarker/docgen/core/Transform.java
index ed3c6fc..f860012 100644
---
a/freemarker-docgen-core/src/main/java/org/freemarker/docgen/core/Transform.java
+++
b/freemarker-docgen-core/src/main/java/org/freemarker/docgen/core/Transform.java
@@ -1056,6 +1056,9 @@ public final class Transform {
fmConfig.setSharedVariable(
"printTextWithDocgenSubstitutions",
new PrintTextWithDocgenSubstitutionsDirective(this));
+ fmConfig.setSharedVariable(
+ "chopLinebreak",
+ ChopLinebreakDirective.INSTANCE);
// Calculated data:
{
@@ -2573,12 +2576,16 @@ public final class Transform {
return insertableFiles;
}
+ TOCNode getCurrentFileTOCNode() {
+ return currentFileTOCNode;
+ }
+
+ //
-------------------------------------------------------------------------
+
public Map<String, InsertableOutputCommandProperties>
getInsertableOutputCommands() {
return insertableOutputCommands;
}
-// -------------------------------------------------------------------------
-
public File getDestinationDirectory() {
return destDir;
}
diff --git
a/freemarker-docgen-core/src/main/resources/org/freemarker/docgen/core/templates/node-handlers.ftlh
b/freemarker-docgen-core/src/main/resources/org/freemarker/docgen/core/templates/node-handlers.ftlh
index 3be163b..4075ae6 100644
---
a/freemarker-docgen-core/src/main/resources/org/freemarker/docgen/core/templates/node-handlers.ftlh
+++
b/freemarker-docgen-core/src/main/resources/org/freemarker/docgen/core/templates/node-handlers.ftlh
@@ -391,8 +391,7 @@
<#if roleLabel != ''><div
class="code-block-label">${roleLabel}</div></#if><#t>
<pre class="code-block-body"><@Anchor/><#t>
<#-- XXE and usual FO-stylesheet-compatible interpretation of inital
line-breaks -->
- <#local content><#recurse></#local>
- ${content?markupString?chopLinebreak?noEsc}<#t>
+ <@chopLinebreak><#recurse></@><#t>
</pre><#t>
</div>
</@CantBeNestedIntoP>
diff --git
a/freemarker-docgen-core/src/test/java/org/freemarker/docgen/core/BashCommandLineArgsParserTest.java
b/freemarker-docgen-core/src/test/java/org/freemarker/docgen/core/BashCommandLineArgsParserTest.java
index 8a9db4a..6692bfe 100644
---
a/freemarker-docgen-core/src/test/java/org/freemarker/docgen/core/BashCommandLineArgsParserTest.java
+++
b/freemarker-docgen-core/src/test/java/org/freemarker/docgen/core/BashCommandLineArgsParserTest.java
@@ -28,7 +28,7 @@ import org.junit.jupiter.api.Test;
class BashCommandLineArgsParserTest {
@Test
- void parse() {
+ void test1() {
assertEquals(Arrays.asList(), BashCommandLineArgsParser.parse(""));
assertEquals(Arrays.asList(), BashCommandLineArgsParser.parse( " "));
assertEquals(Arrays.asList("cmd", "1", "2", "3"),
BashCommandLineArgsParser.parse("cmd 1\t2\r\n3"));
@@ -40,4 +40,11 @@ class BashCommandLineArgsParserTest {
assertEquals(Arrays.asList("a\\b\\\\c"),
BashCommandLineArgsParser.parse("'a\\b\\\\c'"));
}
+ @Test
+ void testBackslashLineBreak() {
+ assertEquals(Arrays.asList("ab", "c"),
BashCommandLineArgsParser.parse("a\\\nb\\\n c"));
+ assertEquals(Arrays.asList("ab", "c"),
BashCommandLineArgsParser.parse("a\\\r\nb\\\r\n c"));
+ assertEquals(Arrays.asList("a ab", "c"),
BashCommandLineArgsParser.parse("a\\ a\\\r\nb \\\r\n c"));
+ }
+
}
\ No newline at end of file
diff --git
a/freemarker-docgen-core/src/test/java/org/freemarker/docgen/core/ChopLinebreakWriterTest.java
b/freemarker-docgen-core/src/test/java/org/freemarker/docgen/core/ChopLinebreakWriterTest.java
new file mode 100644
index 0000000..9f3216d
--- /dev/null
+++
b/freemarker-docgen-core/src/test/java/org/freemarker/docgen/core/ChopLinebreakWriterTest.java
@@ -0,0 +1,146 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.freemarker.docgen.core;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+import java.io.IOException;
+import java.io.StringWriter;
+import java.io.Writer;
+
+import org.junit.jupiter.api.Test;
+
+class ChopLinebreakWriterTest {
+
+ @Test
+ public void testCharArg1() throws IOException {
+ StringWriter sw = new StringWriter();
+ Writer w = new ChopLinebreakWriter(sw);
+
+ w.write('a');
+ w.write('\n');
+ w.write('b');
+ w.write('\n');
+ assertEquals("a\nb", sw.toString());
+ }
+
+ @Test
+ public void testStringArg1() throws IOException {
+ StringWriter sw = new StringWriter();
+ Writer w = new ChopLinebreakWriter(sw);
+
+ w.write("a");
+ assertEquals("a", sw.toString());
+ w.write("b");
+ assertEquals("ab", sw.toString());
+ w.write("\r");
+ assertEquals("ab", sw.toString());
+ w.write("\n");
+ assertEquals("ab", sw.toString());
+ w.write("c");
+ assertEquals("ab\r\nc", sw.toString());
+ w.write("\ndef\r");
+ assertEquals("ab\r\nc\ndef", sw.toString());
+ w.write("g\n");
+ assertEquals("ab\r\nc\ndef\rg", sw.toString());
+ w.write("\nh");
+ assertEquals("ab\r\nc\ndef\rg\n\nh", sw.toString());
+ }
+
+ @Test
+ public void testStrignArg3() throws IOException {
+ StringWriter sw = new StringWriter();
+ Writer w = new ChopLinebreakWriter(sw);
+
+ w.write("\n\n\n\r");
+ assertEquals("\n\n\n", sw.toString());
+ w.write("\n");
+ assertEquals("\n\n\n", sw.toString());
+ w.write("\r");
+ assertEquals("\n\n\n\r\n", sw.toString());
+ w.write("\n");
+ assertEquals("\n\n\n\r\n", sw.toString());
+ w.write("c");
+ assertEquals("\n\n\n\r\n\r\nc", sw.toString());
+ }
+
+ @Test
+ public void testStringArg2() throws IOException {
+ StringWriter sw = new StringWriter();
+ Writer w = new ChopLinebreakWriter(sw);
+
+ w.write("a\n\n");
+ assertEquals("a\n", sw.toString());
+ w.write("b");
+ assertEquals("a\n\nb", sw.toString());
+ }
+
+ @Test
+ public void testCharArray() throws IOException {
+ StringWriter sw = new StringWriter();
+ Writer w = new ChopLinebreakWriter(sw);
+
+ w.write("a".toCharArray());
+ assertEquals("a", sw.toString());
+ w.write("\nb\n".toCharArray());
+ assertEquals("a\nb", sw.toString());
+ w.write("\nc".toCharArray());
+ assertEquals("a\nb\n\nc", sw.toString());
+ w.write("def\n\nghi\n\n".toCharArray());
+ assertEquals("a\nb\n\ncdef\n\nghi\n", sw.toString());
+ }
+
+ @Test
+ public void testSubstringWrites() throws IOException {
+ StringWriter sw = new StringWriter();
+ Writer w = new ChopLinebreakWriter(sw);
+
+ String s = "0123\n567\n\nA";
+ w.write(s, 1, 3);
+ assertEquals("123", sw.toString());
+ w.write(s, 4, 1);
+ assertEquals("123", sw.toString());
+ w.write(s, 5, 2);
+ assertEquals("123\n56", sw.toString());
+ w.write(s, 7, 3);
+ assertEquals("123\n567\n", sw.toString());
+ w.write(s, 10, 1);
+ assertEquals("123\n567\n\nA", sw.toString());
+ }
+
+ @Test
+ public void testSubArrayWrites() throws IOException {
+ StringWriter sw = new StringWriter();
+ Writer w = new ChopLinebreakWriter(sw);
+
+ String s = "0123\n567\n\nA";
+ w.write(s.toCharArray(), 1, 3);
+ assertEquals("123", sw.toString());
+ w.write(s.toCharArray(), 4, 1);
+ assertEquals("123", sw.toString());
+ w.write(s.toCharArray(), 5, 2);
+ assertEquals("123\n56", sw.toString());
+ w.write(s.toCharArray(), 7, 3);
+ assertEquals("123\n567\n", sw.toString());
+ w.write(s.toCharArray(), 10, 1);
+ assertEquals("123\n567\n\nA", sw.toString());
+ }
+
+}
\ No newline at end of file
diff --git
a/freemarker-docgen-core/src/test/java/org/freemarker/docgen/core/EscapeHtmlAndXmlWriterTest.java
b/freemarker-docgen-core/src/test/java/org/freemarker/docgen/core/EscapeHtmlAndXmlWriterTest.java
new file mode 100644
index 0000000..555888f
--- /dev/null
+++
b/freemarker-docgen-core/src/test/java/org/freemarker/docgen/core/EscapeHtmlAndXmlWriterTest.java
@@ -0,0 +1,75 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.freemarker.docgen.core;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+import java.io.IOException;
+import java.io.StringWriter;
+import java.io.Writer;
+
+import org.junit.jupiter.api.Test;
+
+class EscapeHtmlAndXmlWriterTest {
+
+ @Test
+ public void testCharArg1() throws IOException {
+ StringWriter sw = new StringWriter();
+ Writer w = new EscapeHtmlAndXmlWriter(sw);
+
+ w.write('a');
+ w.write('<');
+ w.write('>');
+ w.write('b');
+ w.write('&');
+ assertEquals("a<>b&", sw.toString());
+ }
+
+ @Test
+ public void testStringArg1() throws IOException {
+ StringWriter sw = new StringWriter();
+ Writer w = new EscapeHtmlAndXmlWriter(sw);
+
+ w.write("");
+ w.write("a");
+ w.write("b<cd>ef&g");
+ assertEquals("ab<cd>ef&g", sw.toString());
+ w.write("<>");
+ assertEquals("ab<cd>ef&g<>", sw.toString());
+ w.write("<");
+ assertEquals("ab<cd>ef&g<><", sw.toString());
+ }
+
+ @Test
+ public void testArrayArg1() throws IOException {
+ StringWriter sw = new StringWriter();
+ Writer w = new EscapeHtmlAndXmlWriter(sw);
+
+ w.write("".toCharArray());
+ w.write("a".toCharArray());
+ w.write("b<cd>ef&g".toCharArray());
+ assertEquals("ab<cd>ef&g", sw.toString());
+ w.write("<>".toCharArray());
+ assertEquals("ab<cd>ef&g<>", sw.toString());
+ w.write("<".toCharArray());
+ assertEquals("ab<cd>ef&g<><", sw.toString());
+ }
+
+}
\ No newline at end of file
diff --git
a/freemarker-docgen-core/src/test/java/org/freemarker/docgen/core/SettingNameTest.java
b/freemarker-docgen-core/src/test/java/org/freemarker/docgen/core/SettingNameTest.java
index a0eab82..c399ea5 100644
---
a/freemarker-docgen-core/src/test/java/org/freemarker/docgen/core/SettingNameTest.java
+++
b/freemarker-docgen-core/src/test/java/org/freemarker/docgen/core/SettingNameTest.java
@@ -28,5 +28,7 @@ public class SettingNameTest {
public void toStringTest() {
assertEquals("a", SettingName.topLevel(null, "a").toString());
assertEquals("a.b[1]", SettingName.topLevel(null,
"a").subKey("b").subKey(1).toString());
+ assertEquals("a.b[1].c.d", SettingName.topLevel(null, "a").subKey("b",
1, "c", "d").toString());
+ assertEquals("a[\"a b\"].b1", SettingName.topLevel(null,
"a").subKey("a b").subKey("b1").toString());
}
}