diff --git a/asciidoctorj-core/src/main/java/org/asciidoctor/Asciidoctor.java b/asciidoctorj-core/src/main/java/org/asciidoctor/Asciidoctor.java index 213a66dc1..d3ba6e2d9 100644 --- a/asciidoctorj-core/src/main/java/org/asciidoctor/Asciidoctor.java +++ b/asciidoctorj-core/src/main/java/org/asciidoctor/Asciidoctor.java @@ -16,6 +16,7 @@ import org.asciidoctor.extension.JavaExtensionRegistry; import org.asciidoctor.extension.RubyExtensionRegistry; import org.asciidoctor.internal.JRubyAsciidoctor; +import org.asciidoctor.log.LogHandler; /** * @@ -910,5 +911,8 @@ public static Asciidoctor create(List loadPaths, String gemPath) { */ Document loadFile(File file, Map options); + void registerLogHandler(LogHandler logHandler); + + void unregisterLogHandler(LogHandler logHandler); } diff --git a/asciidoctorj-core/src/main/java/org/asciidoctor/ast/impl/CursorImpl.java b/asciidoctorj-core/src/main/java/org/asciidoctor/ast/impl/CursorImpl.java index c5c181771..0977e981f 100644 --- a/asciidoctorj-core/src/main/java/org/asciidoctor/ast/impl/CursorImpl.java +++ b/asciidoctorj-core/src/main/java/org/asciidoctor/ast/impl/CursorImpl.java @@ -17,17 +17,24 @@ public int getLineNumber() { @Override public String getPath() { - return getString("path"); + final Object result = getProperty("path"); + return result == null ? null : result.toString(); } @Override public String getDir() { - return getString("dir"); + final Object result = getProperty("dir"); + return result == null ? null : result.toString(); } @Override public String getFile() { - return getString("file"); + final Object result = getProperty("file"); + return result == null ? null : result.toString(); } + @Override + public String toString() { + return getString("to_s"); + } } diff --git a/asciidoctorj-core/src/main/java/org/asciidoctor/extension/Contexts.java b/asciidoctorj-core/src/main/java/org/asciidoctor/extension/Contexts.java index b4212810e..1e9f4e109 100644 --- a/asciidoctorj-core/src/main/java/org/asciidoctor/extension/Contexts.java +++ b/asciidoctorj-core/src/main/java/org/asciidoctor/extension/Contexts.java @@ -18,9 +18,9 @@ * super(blockName); * } * - * public Object process(StructuralNode parent, Reader reader, Map attributes) { - * List lines = reader.readLines(); - * List newLines = new ArrayList<>(); + * public Object process(StructuralNode parent, Reader reader, Map<String, Object> attributes) { + * List<String> lines = reader.readLines(); + * List<String> newLines = new ArrayList<>(); * for (String line: lines) { * newLines.add(line.toUpperCase()); * } diff --git a/asciidoctorj-core/src/main/java/org/asciidoctor/internal/JRubyAsciidoctor.java b/asciidoctorj-core/src/main/java/org/asciidoctor/internal/JRubyAsciidoctor.java index aa5b2b83e..0037f5142 100644 --- a/asciidoctorj-core/src/main/java/org/asciidoctor/internal/JRubyAsciidoctor.java +++ b/asciidoctorj-core/src/main/java/org/asciidoctor/internal/JRubyAsciidoctor.java @@ -11,7 +11,6 @@ import org.asciidoctor.ast.NodeConverter; import org.asciidoctor.ast.StructuralNode; import org.asciidoctor.ast.StructuredDocument; -import org.asciidoctor.ast.Title; import org.asciidoctor.ast.impl.ContentPartImpl; import org.asciidoctor.ast.impl.DocumentHeaderImpl; import org.asciidoctor.ast.impl.StructuredDocumentImpl; @@ -21,6 +20,11 @@ import org.asciidoctor.extension.JavaExtensionRegistry; import org.asciidoctor.extension.RubyExtensionRegistry; import org.asciidoctor.extension.internal.ExtensionRegistryExecutor; +import org.asciidoctor.log.LogHandler; +import org.asciidoctor.log.LogRecord; +import org.asciidoctor.log.internal.JULLogHandler; +import org.asciidoctor.log.internal.JavaLogger; +import org.asciidoctor.log.internal.LogHandlerRegistryExecutor; import org.jruby.Ruby; import org.jruby.RubyClass; import org.jruby.RubyHash; @@ -44,9 +48,10 @@ import java.util.List; import java.util.Map; import java.util.UUID; +import java.util.logging.Level; import java.util.logging.Logger; -public class JRubyAsciidoctor implements Asciidoctor { +public class JRubyAsciidoctor implements Asciidoctor, LogHandler { private static final Logger logger = Logger.getLogger(JRubyAsciidoctor.class.getName()); @@ -59,6 +64,10 @@ public class JRubyAsciidoctor implements Asciidoctor { private RubyClass extensionGroupClass; + private List logHandlers = new ArrayList<>(); + + private final static Logger LOGGER = Logger.getLogger("asciidoctorj"); + private JRubyAsciidoctor(final Ruby rubyRuntime) { this.rubyRuntime = rubyRuntime; @@ -67,6 +76,7 @@ private JRubyAsciidoctor(final Ruby rubyRuntime) { this.rubyRuntime.evalScriptlet(script); this.rubyGemsPreloader = new RubyGemsPreloader(this.rubyRuntime); + this.logHandlers.add(new JULLogHandler()); } public static JRubyAsciidoctor create() { @@ -96,6 +106,7 @@ public static JRubyAsciidoctor create(List loadPaths, String gemPath) { private static JRubyAsciidoctor processRegistrations(JRubyAsciidoctor asciidoctor) { registerExtensions(asciidoctor); registerConverters(asciidoctor); + registerLogHandlers(asciidoctor); return asciidoctor; } @@ -107,6 +118,10 @@ private static void registerExtensions(Asciidoctor asciidoctor) { new ExtensionRegistryExecutor(asciidoctor).registerAllExtensions(); } + private static void registerLogHandlers(Asciidoctor asciidoctor) { + new LogHandlerRegistryExecutor(asciidoctor).registerAllLogHandlers(); + } + private static JRubyAsciidoctor createJRubyAsciidoctorInstance(Map environmentVars, List loadPaths, ClassLoader classloader) { Map env = environmentVars != null ? @@ -120,7 +135,11 @@ private static JRubyAsciidoctor createJRubyAsciidoctorInstance(Map environmentVars) { @@ -132,6 +151,18 @@ private static RubyInstanceConfig createOptimizedConfiguration() { return new RubyInstanceConfig(); } + @Override + public void registerLogHandler(final LogHandler logHandler) { + if (!this.logHandlers.contains(logHandler)) { + this.logHandlers.add(logHandler); + } + } + + @Override + public void unregisterLogHandler(final LogHandler logHandler) { + this.logHandlers.remove(logHandler); + } + public Ruby getRubyRuntime() { return rubyRuntime; } @@ -140,7 +171,7 @@ private DocumentHeader toDocumentHeader(Document document) { Document documentImpl = (Document) NodeConverter.createASTNode(document); - return DocumentHeaderImpl.createDocumentHeader((Title) documentImpl.getStructuredDoctitle(), documentImpl.getDoctitle(), + return DocumentHeaderImpl.createDocumentHeader(documentImpl.getStructuredDoctitle(), documentImpl.getDoctitle(), documentImpl.getAttributes()); } @@ -642,4 +673,15 @@ private RubyClass getExtensionGroupClass() { } return extensionGroupClass; } + + @Override + public void log(LogRecord logRecord) { + for (LogHandler logHandler: logHandlers) { + try { + logHandler.log(logRecord); + } catch (Exception e) { + LOGGER.log(Level.WARNING, "Unexpected exception while logging Asciidoctor log entry", e); + } + } + } } diff --git a/asciidoctorj-core/src/main/java/org/asciidoctor/log/LogHandler.java b/asciidoctorj-core/src/main/java/org/asciidoctor/log/LogHandler.java new file mode 100644 index 000000000..ea3fa6ab7 --- /dev/null +++ b/asciidoctorj-core/src/main/java/org/asciidoctor/log/LogHandler.java @@ -0,0 +1,7 @@ +package org.asciidoctor.log; + +public interface LogHandler { + + void log(LogRecord logRecord); + +} \ No newline at end of file diff --git a/asciidoctorj-core/src/main/java/org/asciidoctor/log/LogRecord.java b/asciidoctorj-core/src/main/java/org/asciidoctor/log/LogRecord.java new file mode 100644 index 000000000..4b9b14d83 --- /dev/null +++ b/asciidoctorj-core/src/main/java/org/asciidoctor/log/LogRecord.java @@ -0,0 +1,44 @@ +package org.asciidoctor.log; + +import org.asciidoctor.ast.Cursor; + +public class LogRecord { + + private final Severity severity; + + private final Cursor cursor; + + private final String message; + + private final String sourceFileName; + + private final String sourceMethodName; + + public LogRecord(Severity severity, Cursor cursor, String message, String sourceFileName, String sourceMethodName) { + this.severity = severity; + this.cursor = cursor; + this.message = message; + this.sourceFileName = sourceFileName; + this.sourceMethodName = sourceMethodName; + } + + public Severity getSeverity() { + return severity; + } + + public Cursor getCursor() { + return cursor; + } + + public String getMessage() { + return message; + } + + public String getSourceFileName() { + return sourceFileName; + } + + public String getSourceMethodName() { + return sourceMethodName; + } +} \ No newline at end of file diff --git a/asciidoctorj-core/src/main/java/org/asciidoctor/log/Severity.java b/asciidoctorj-core/src/main/java/org/asciidoctor/log/Severity.java new file mode 100644 index 000000000..ac2e8a4fd --- /dev/null +++ b/asciidoctorj-core/src/main/java/org/asciidoctor/log/Severity.java @@ -0,0 +1,20 @@ +package org.asciidoctor.log; + +public enum Severity { + DEBUG(0), + INFO(1), + WARN(2), + ERROR(3), + FATAL(4), + UNKNOWN(5); + + private int rubyId; + + private Severity(final int rubyId) { + this.rubyId = rubyId; + } + + public int getRubyId() { + return rubyId; + } +} \ No newline at end of file diff --git a/asciidoctorj-core/src/main/java/org/asciidoctor/log/internal/JULLogHandler.java b/asciidoctorj-core/src/main/java/org/asciidoctor/log/internal/JULLogHandler.java new file mode 100644 index 000000000..89ea94e98 --- /dev/null +++ b/asciidoctorj-core/src/main/java/org/asciidoctor/log/internal/JULLogHandler.java @@ -0,0 +1,45 @@ +package org.asciidoctor.log.internal; + +import org.asciidoctor.log.LogHandler; +import org.asciidoctor.log.LogRecord; +import org.asciidoctor.log.Severity; + +import java.util.logging.Level; +import java.util.logging.Logger; + +public class JULLogHandler implements LogHandler { + + private final static Logger LOGGER = Logger.getLogger("asciidoctor"); + + @Override + public void log(LogRecord logRecord) { + final java.util.logging.LogRecord julLogRecord = + new java.util.logging.LogRecord( + mapSeverity(logRecord.getSeverity()), + logRecord.getCursor() != null ? logRecord.getCursor().toString() + ": " + logRecord.getMessage() : logRecord.getMessage()); + + julLogRecord.setSourceClassName(logRecord.getSourceFileName()); + julLogRecord.setSourceMethodName(logRecord.getSourceMethodName()); + julLogRecord.setParameters(new Object[] { logRecord.getCursor() }); + LOGGER.log(julLogRecord); + } + + private static Level mapSeverity(Severity severity) { + switch (severity) { + case DEBUG: + return Level.FINEST; + case INFO: + return Level.INFO; + case WARN: + return Level.WARNING; + case ERROR: + return Level.SEVERE; + case FATAL: + return Level.SEVERE; + case UNKNOWN: + default: + return Level.INFO; + } + } + +} \ No newline at end of file diff --git a/asciidoctorj-core/src/main/java/org/asciidoctor/log/internal/JavaLogger.java b/asciidoctorj-core/src/main/java/org/asciidoctor/log/internal/JavaLogger.java new file mode 100644 index 000000000..f5e75f1ef --- /dev/null +++ b/asciidoctorj-core/src/main/java/org/asciidoctor/log/internal/JavaLogger.java @@ -0,0 +1,132 @@ +package org.asciidoctor.log.internal; + +import org.asciidoctor.log.LogHandler; +import org.asciidoctor.ast.Cursor; +import org.asciidoctor.ast.impl.CursorImpl; +import org.asciidoctor.log.LogRecord; +import org.asciidoctor.log.Severity; +import org.jruby.Ruby; +import org.jruby.RubyClass; +import org.jruby.RubyHash; +import org.jruby.RubyModule; +import org.jruby.RubyObject; +import org.jruby.anno.JRubyMethod; +import org.jruby.runtime.Block; +import org.jruby.runtime.Helpers; +import org.jruby.runtime.ObjectAllocator; +import org.jruby.runtime.ThreadContext; +import org.jruby.runtime.backtrace.BacktraceElement; +import org.jruby.runtime.builtin.IRubyObject; + +import java.util.Objects; + +public class JavaLogger extends RubyObject { + + private final LogHandler rootLogHandler; + + private static final String LOG_PROPERTY_SOURCE_LOCATION = "source_location"; + private static final String LOG_PROPERTY_TEXT = "text"; + + public static void install(final Ruby runtime, final LogHandler logHandler) { + + final RubyModule asciidoctorModule = runtime.getModule("Asciidoctor"); + final RubyModule loggerManager = asciidoctorModule.defineOrGetModuleUnder("LoggerManager"); + final RubyClass loggerBaseClass = asciidoctorModule.getClass("Logger"); + + final RubyClass loggerClass = asciidoctorModule + .defineOrGetModuleUnder("LoggerManager") + .defineClassUnder("JavaLogger", loggerBaseClass, new ObjectAllocator() { + @Override + public IRubyObject allocate(Ruby runtime, RubyClass klazz) { + return new JavaLogger(runtime, klazz, logHandler); + } + }); + + loggerClass.defineAnnotatedMethods(JavaLogger.class); + + final IRubyObject logger = loggerClass.allocate(); + logger.callMethod(runtime.getCurrentContext(), "initialize", runtime.getNil()); + loggerManager.callMethod("logger=", logger); + + } + + private JavaLogger(final Ruby runtime, final RubyClass metaClass, final LogHandler rootLogHandler) { + super(runtime, metaClass); + this.rootLogHandler = rootLogHandler; + } + + @JRubyMethod(name = "initialize", required = 1, optional = 2) + public IRubyObject initialize(final ThreadContext threadContext, final IRubyObject[] args) { + return Helpers.invokeSuper( + threadContext, + this, + getMetaClass(), + "initialize", + args, + Block.NULL_BLOCK); + } + + /** + * @param threadContext + * @param args + */ + @JRubyMethod(name = "add", required = 1, optional = 2) + public IRubyObject add(final ThreadContext threadContext, final IRubyObject[] args, Block block) { + final IRubyObject rubyMessage; + if (args.length >= 2 && !args[1].isNil()) { + rubyMessage = args[1]; + } else if (block.isGiven()) { + rubyMessage = block.yield(threadContext, getRuntime().getNil()); + } else { + rubyMessage = args[2]; + } + final Cursor cursor = getSourceLocation(rubyMessage); + final String message = formatMessage(rubyMessage); + final Severity severity = mapRubyLogLevel(args[0]); + + final LogRecord record = createLogRecord(threadContext, severity, cursor, message); + + rootLogHandler.log(record); + return getRuntime().getNil(); + } + + private LogRecord createLogRecord(final ThreadContext threadContext, + final Severity severity, + final Cursor cursor, + final String message) { + BacktraceElement[] backtrace = threadContext.getBacktrace(); + final String sourceFileName = backtrace[2].getFilename(); + final String sourceMethodName = backtrace[2].getMethod(); + final LogRecord record = new LogRecord(severity, cursor, message, sourceFileName, sourceMethodName); + return record; + } + + private Severity mapRubyLogLevel(IRubyObject arg) { + final int rubyId = arg.convertToInteger().getIntValue(); + for (Severity severity: Severity.values()) { + if (severity.getRubyId() == rubyId) { + return severity; + } + } + return Severity.UNKNOWN; + } + + private String formatMessage(final IRubyObject msg) { + if (getRuntime().getString().equals(msg.getType())) { + return msg.asJavaString(); + } else if (getRuntime().getHash().equals(msg.getType())) { + final RubyHash hash = (RubyHash) msg; + return Objects.toString(hash.get(getRuntime().newSymbol(LOG_PROPERTY_TEXT))); + } + throw new IllegalArgumentException(Objects.toString(msg)); + } + + private Cursor getSourceLocation(IRubyObject msg) { + if (getRuntime().getHash().equals(msg.getType())) { + final RubyHash hash = (RubyHash) msg; + final Object sourceLocation = hash.get(getRuntime().newSymbol(LOG_PROPERTY_SOURCE_LOCATION)); + return new CursorImpl((IRubyObject) sourceLocation); + } + return null; + } +} \ No newline at end of file diff --git a/asciidoctorj-core/src/main/java/org/asciidoctor/log/internal/LogHandlerRegistryExecutor.java b/asciidoctorj-core/src/main/java/org/asciidoctor/log/internal/LogHandlerRegistryExecutor.java new file mode 100644 index 000000000..2187d676c --- /dev/null +++ b/asciidoctorj-core/src/main/java/org/asciidoctor/log/internal/LogHandlerRegistryExecutor.java @@ -0,0 +1,24 @@ +package org.asciidoctor.log.internal; + +import org.asciidoctor.Asciidoctor; +import org.asciidoctor.log.LogHandler; + +import java.util.ServiceLoader; + +public class LogHandlerRegistryExecutor { + private static ServiceLoader logHandlerServiceLoader = ServiceLoader + .load(LogHandler.class); + + private Asciidoctor asciidoctor; + + public LogHandlerRegistryExecutor(Asciidoctor asciidoctor) { + this.asciidoctor = asciidoctor; + } + + public void registerAllLogHandlers() { + for (LogHandler logHandler: logHandlerServiceLoader) { + asciidoctor.registerLogHandler(logHandler); + } + } + +} \ No newline at end of file diff --git a/asciidoctorj-core/src/main/resources/org/asciidoctor/internal/asciidoctorclass.rb b/asciidoctorj-core/src/main/resources/org/asciidoctor/internal/asciidoctorclass.rb index b626d96e7..44440a936 100644 --- a/asciidoctorj-core/src/main/resources/org/asciidoctor/internal/asciidoctorclass.rb +++ b/asciidoctorj-core/src/main/resources/org/asciidoctor/internal/asciidoctorclass.rb @@ -1,3 +1,6 @@ +# Set the global variable VERBOSE to true to get invalid refs into the log +$VERBOSE=true + module AsciidoctorJ include_package 'org.asciidoctor' module Extensions diff --git a/asciidoctorj-core/src/test/groovy/org/asciidoctor/internal/RubyAttributesMapDecoratorSpecification.groovy b/asciidoctorj-core/src/test/groovy/org/asciidoctor/internal/RubyAttributesMapDecoratorSpecification.groovy index 2ef60c96a..c6bb3f3ba 100644 --- a/asciidoctorj-core/src/test/groovy/org/asciidoctor/internal/RubyAttributesMapDecoratorSpecification.groovy +++ b/asciidoctorj-core/src/test/groovy/org/asciidoctor/internal/RubyAttributesMapDecoratorSpecification.groovy @@ -39,7 +39,8 @@ Lorem ipsum dolor !attributes.containsKey(1L) attributes.containsKey(ATTR_ONE) attributes.keySet().contains(ATTR_ONE) - attributes.get(ATTR_ONE).startsWith(blockStyle) + attributes.get(ATTR_ONE) == blockStyle + } @@ -58,7 +59,7 @@ Lorem ipsum dolor !attributes.containsKey(ATTR_ONE) and: 'remove returned the previous attribute value' - oldValue.startsWith(blockStyle) + oldValue == blockStyle and: 'The attributes derived from the positional attribute are still there' attributes.containsKey(ATTR_NAME_ROLE) diff --git a/asciidoctorj-core/src/test/java/org/asciidoctor/MemoryLogHandler.java b/asciidoctorj-core/src/test/java/org/asciidoctor/MemoryLogHandler.java new file mode 100644 index 000000000..12037335c --- /dev/null +++ b/asciidoctorj-core/src/test/java/org/asciidoctor/MemoryLogHandler.java @@ -0,0 +1,29 @@ +package org.asciidoctor; + +import java.util.ArrayList; +import java.util.List; +import java.util.logging.Handler; +import java.util.logging.LogRecord; + +public class MemoryLogHandler extends Handler { + + private List logRecords = new ArrayList<>(); + + @Override + public void publish(LogRecord record) { + logRecords.add(record); + } + + @Override + public void flush() { + } + + @Override + public void close() throws SecurityException { + logRecords.clear(); + } + + public List getLogRecords() { + return logRecords; + } +} diff --git a/asciidoctorj-core/src/test/java/org/asciidoctor/WhenAsciidoctorLogsToConsole.java b/asciidoctorj-core/src/test/java/org/asciidoctor/WhenAsciidoctorLogsToConsole.java index 6f704dd5d..c2d83011d 100644 --- a/asciidoctorj-core/src/test/java/org/asciidoctor/WhenAsciidoctorLogsToConsole.java +++ b/asciidoctorj-core/src/test/java/org/asciidoctor/WhenAsciidoctorLogsToConsole.java @@ -1,16 +1,37 @@ package org.asciidoctor; import org.asciidoctor.arquillian.api.Unshared; +import org.asciidoctor.ast.Cursor; +import org.asciidoctor.internal.JRubyAsciidoctor; +import org.asciidoctor.log.LogHandler; +import org.asciidoctor.log.LogRecord; +import org.asciidoctor.log.TestLogHandlerService; import org.asciidoctor.util.ClasspathResources; +import org.hamcrest.Matchers; import org.jboss.arquillian.junit.Arquillian; import org.jboss.arquillian.test.api.ArquillianResource; +import org.junit.After; +import org.junit.Before; import org.junit.Test; import org.junit.rules.TemporaryFolder; import org.junit.runner.RunWith; import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.logging.LogManager; +import java.util.logging.Logger; import static org.asciidoctor.OptionsBuilder.options; +import static org.hamcrest.Matchers.both; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.hasSize; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.not; +import static org.hamcrest.core.IsNull.nullValue; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; @RunWith(Arquillian.class) public class WhenAsciidoctorLogsToConsole { @@ -24,15 +45,242 @@ public class WhenAsciidoctorLogsToConsole { @ArquillianResource(Unshared.class) private Asciidoctor asciidoctor; + @Before + public void before() { + asciidoctor = JRubyAsciidoctor.create(); + } + + @After + public void cleanup() throws IOException { + LogManager.getLogManager().readConfiguration(); + TestLogHandlerService.clear(); + } + + @Test + public void shouldRedirectToJUL() throws Exception { + final MemoryLogHandler memoryLogHandler = registerMemoryLogHandler(); + + File inputFile = classpath.getResource("documentwithnotexistingfile.adoc"); + String renderContent = asciidoctor.renderFile(inputFile, + options() + .inPlace(true) + .safe(SafeMode.SERVER) + .attributes( + AttributesBuilder.attributes().allowUriRead(true)) + .asMap()); + + File expectedFile = new File(inputFile.getParent(), "documentwithnotexistingfile.html"); + expectedFile.delete(); + + assertEquals(4, memoryLogHandler.getLogRecords().size()); + assertThat(memoryLogHandler.getLogRecords().get(0).getMessage(), + both(containsString("include file not found")) + .and(containsString("documentwithnotexistingfile.adoc: line 3"))); + } + + private MemoryLogHandler registerMemoryLogHandler() { + final Logger logger = Logger.getLogger("asciidoctor"); + final MemoryLogHandler handler = new MemoryLogHandler(); + logger.addHandler(handler); + return handler; + } + + @Test + public void shouldNotifyLogHandler() throws Exception { + + final List logRecords = new ArrayList<>(); + + + final LogHandler logHandler = new LogHandler() { + @Override + public void log(LogRecord logRecord) { + logRecords.add(logRecord); + } + }; + asciidoctor.registerLogHandler(logHandler); + + File inputFile = classpath.getResource("documentwithnotexistingfile.adoc"); + String renderContent = asciidoctor.renderFile(inputFile, + options() + .inPlace(true) + .safe(SafeMode.SERVER) + .attributes( + AttributesBuilder.attributes().allowUriRead(true)) + .asMap()); + + File expectedFile = new File(inputFile.getParent(), "documentwithnotexistingfile.html"); + expectedFile.delete(); + + assertEquals(4, logRecords.size()); + assertThat(logRecords.get(0).getMessage(), containsString("include file not found")); + final Cursor cursor = logRecords.get(0).getCursor(); + assertThat(cursor.getDir().replace('\\', '/'), is(inputFile.getParent().replace('\\', '/'))); + assertThat(cursor.getFile(), is(inputFile.getName())); + assertThat(cursor.getLineNumber(), is(3)); + + for (LogRecord logRecord: logRecords) { + assertThat(logRecord.getCursor(), not(nullValue())); + assertThat(logRecord.getCursor().getFile(), not(nullValue())); + assertThat(logRecord.getCursor().getDir(), not(nullValue())); + } + + } + + @Test + public void shouldLogInvalidRefs() throws Exception { + + final List logRecords = new ArrayList<>(); + + final LogHandler logHandler = new LogHandler() { + @Override + public void log(LogRecord logRecord) { + logRecords.add(logRecord); + } + }; + asciidoctor.registerLogHandler(logHandler); + + File inputFile = classpath.getResource("documentwithinvalidrefs.adoc"); + String renderContent = asciidoctor.renderFile(inputFile, + options() + .inPlace(true) + .safe(SafeMode.SERVER) + .toFile(false) + .attributes( + AttributesBuilder.attributes().allowUriRead(true)) + .asMap()); + + assertThat(logRecords, hasSize(1)); + assertThat(logRecords.get(0).getMessage(), containsString("invalid reference: invalidref")); + final Cursor cursor = logRecords.get(0).getCursor(); + assertThat(cursor, is(nullValue())); + } + @Test - public void shouldBeRedirectToAsciidoctorJLoggerSystem() { + public void shouldOnlyNotifyFromRegisteredAsciidoctor() throws Exception { + + final List logRecords = new ArrayList<>(); + + final Asciidoctor secondInstance = Asciidoctor.Factory.create(); + final LogHandler logHandler = new LogHandler() { + @Override + public void log(LogRecord logRecord) { + logRecords.add(logRecord); + } + }; + // Register at first instance! + asciidoctor.registerLogHandler(logHandler); + + // Now render via second instance and check that there is no notification File inputFile = classpath.getResource("documentwithnotexistingfile.adoc"); - String renderContent = asciidoctor.renderFile(inputFile, options() - .inPlace(true).safe(SafeMode.SERVER).asMap()); + String renderContent1 = secondInstance.renderFile(inputFile, + options() + .inPlace(true) + .safe(SafeMode.SERVER) + .attributes( + AttributesBuilder.attributes().allowUriRead(true)) + .asMap()); + + File expectedFile1 = new File(inputFile.getParent(), "documentwithnotexistingfile.html"); + expectedFile1.delete(); + + assertEquals(0, logRecords.size()); + + // Now render via first instance and check that notifications appeared. + String renderContent = asciidoctor.renderFile(inputFile, + options() + .inPlace(true) + .safe(SafeMode.SERVER) + .attributes( + AttributesBuilder.attributes().allowUriRead(true)) + .asMap()); + + File expectedFile2 = new File(inputFile.getParent(), "documentwithnotexistingfile.html"); + expectedFile2.delete(); + + assertEquals(4, logRecords.size()); + assertThat(logRecords.get(0).getMessage(), containsString("include file not found")); + final Cursor cursor = (Cursor) logRecords.get(0).getCursor(); + assertThat(cursor.getDir().replace('\\', '/'), is(inputFile.getParent().replace('\\', '/'))); + assertThat(cursor.getFile(), is(inputFile.getName())); + assertThat(cursor.getLineNumber(), is(3)); + } + + @Test + public void shouldNoLongerNotifyAfterUnregisterOnlyNotifyFromRegisteredAsciidoctor() throws Exception { + + final List logRecords = new ArrayList<>(); + + final LogHandler logHandler = new LogHandler() { + @Override + public void log(LogRecord logRecord) { + logRecords.add(logRecord); + } + }; + asciidoctor.registerLogHandler(logHandler); + + File inputFile = classpath.getResource("documentwithnotexistingfile.adoc"); + String renderContent = asciidoctor.renderFile(inputFile, + options() + .inPlace(true) + .safe(SafeMode.SERVER) + .attributes( + AttributesBuilder.attributes().allowUriRead(true)) + .asMap()); File expectedFile = new File(inputFile.getParent(), "documentwithnotexistingfile.html"); expectedFile.delete(); + + assertEquals(4, logRecords.size()); + logRecords.clear(); + + asciidoctor.unregisterLogHandler(logHandler); + + asciidoctor.renderFile(inputFile, + options() + .inPlace(true) + .safe(SafeMode.SERVER) + .attributes( + AttributesBuilder.attributes().allowUriRead(true)) + .asMap()); + + File expectedFile2 = new File(inputFile.getParent(), "documentwithnotexistingfile.html"); + expectedFile2.delete(); + assertEquals(0, logRecords.size()); + } + @Test + public void shouldNotifyLogHandlerService() throws Exception { + + File inputFile = classpath.getResource("documentwithnotexistingfile.adoc"); + String renderContent = asciidoctor.renderFile(inputFile, + options() + .inPlace(true) + .safe(SafeMode.SERVER) + .attributes( + AttributesBuilder.attributes().allowUriRead(true)) + .asMap()); + + File expectedFile = new File(inputFile.getParent(), "documentwithnotexistingfile.html"); + expectedFile.delete(); + + final List logRecords = TestLogHandlerService.getLogRecords(); + for (LogRecord logRecord : logRecords) { + System.err.println(">> " + logRecord.getMessage()); + } + assertThat(logRecords, hasSize(4)); + assertThat(logRecords.get(0).getMessage(), containsString("include file not found")); + final Cursor cursor = logRecords.get(0).getCursor(); + assertThat(cursor.getDir().replace('\\', '/'), is(inputFile.getParent().replace('\\', '/'))); + assertThat(cursor.getFile(), is(inputFile.getName())); + assertThat(cursor.getLineNumber(), is(3)); + + for (LogRecord logRecord: logRecords) { + assertThat(logRecord.getCursor(), not(Matchers.nullValue())); + assertThat(logRecord.getCursor().getFile(), not(Matchers.nullValue())); + assertThat(logRecord.getCursor().getDir(), not(Matchers.nullValue())); + } + } } + diff --git a/asciidoctorj-core/src/test/java/org/asciidoctor/extension/WhenJavaExtensionGroupIsRegistered.java b/asciidoctorj-core/src/test/java/org/asciidoctor/extension/WhenJavaExtensionGroupIsRegistered.java index 48a412a4c..35cad6049 100644 --- a/asciidoctorj-core/src/test/java/org/asciidoctor/extension/WhenJavaExtensionGroupIsRegistered.java +++ b/asciidoctorj-core/src/test/java/org/asciidoctor/extension/WhenJavaExtensionGroupIsRegistered.java @@ -51,6 +51,8 @@ @RunWith(Arquillian.class) public class WhenJavaExtensionGroupIsRegistered { + public static final String ASCIIDOCTORCLASS_PREFIX = "# Set the global variable VERBOSE to true to get invalid refs into the log"; + @ArquillianResource private ClasspathResources classpath; @@ -130,7 +132,7 @@ public void an_inner_class_should_be_registered() { Element contentElement = doc.getElementsByAttributeValue("class", "language-ruby").first(); - assertThat(contentElement.text(), startsWith("module AsciidoctorJ")); + assertThat(contentElement.text(), startsWith(ASCIIDOCTORCLASS_PREFIX)); } @@ -190,7 +192,7 @@ public boolean handles(String target) { Element contentElement = doc.getElementsByAttributeValue("class", "language-ruby").first(); - assertThat(contentElement.text(), startsWith("module AsciidoctorJ")); + assertThat(contentElement.text(), startsWith(ASCIIDOCTORCLASS_PREFIX)); } @@ -358,7 +360,7 @@ public void a_include_processor_as_string_should_be_executed_when_include_macro_ Element contentElement = doc.getElementsByAttributeValue("class", "language-ruby").first(); - assertThat(contentElement.text(), startsWith("module AsciidoctorJ")); + assertThat(contentElement.text(), startsWith(ASCIIDOCTORCLASS_PREFIX)); } @@ -379,7 +381,7 @@ public void a_include_processor_should_be_executed_when_include_macro_is_found() Element contentElement = doc.getElementsByAttributeValue("class", "language-ruby").first(); - assertThat(contentElement.text(), startsWith("module AsciidoctorJ")); + assertThat(contentElement.text(), startsWith(ASCIIDOCTORCLASS_PREFIX)); } @@ -400,7 +402,7 @@ public void a_include_instance_processor_should_be_executed_when_include_macro_i Element contentElement = doc.getElementsByAttributeValue("class", "language-ruby").first(); - assertThat(contentElement.text(), startsWith("module AsciidoctorJ")); + assertThat(contentElement.text(), startsWith(ASCIIDOCTORCLASS_PREFIX)); } diff --git a/asciidoctorj-core/src/test/java/org/asciidoctor/extension/WhenJavaExtensionIsRegistered.java b/asciidoctorj-core/src/test/java/org/asciidoctor/extension/WhenJavaExtensionIsRegistered.java index bc655ba11..c3474e064 100644 --- a/asciidoctorj-core/src/test/java/org/asciidoctor/extension/WhenJavaExtensionIsRegistered.java +++ b/asciidoctorj-core/src/test/java/org/asciidoctor/extension/WhenJavaExtensionIsRegistered.java @@ -51,6 +51,8 @@ @RunWith(Arquillian.class) public class WhenJavaExtensionIsRegistered { + public static final String ASCIIDOCTORCLASS_PREFIX = "# Set the global variable VERBOSE to true to get invalid refs into the log"; + @ArquillianResource private ClasspathResources classpath; @@ -130,7 +132,7 @@ public void an_inner_class_should_be_registered() { Element contentElement = doc.getElementsByAttributeValue("class", "language-ruby").first(); - assertThat(contentElement.text(), startsWith("module AsciidoctorJ")); + assertThat(contentElement.text(), startsWith(ASCIIDOCTORCLASS_PREFIX)); } @@ -190,7 +192,7 @@ public boolean handles(String target) { Element contentElement = doc.getElementsByAttributeValue("class", "language-ruby").first(); - assertThat(contentElement.text(), startsWith("module AsciidoctorJ")); + assertThat(contentElement.text(), startsWith(ASCIIDOCTORCLASS_PREFIX)); } @@ -356,7 +358,7 @@ public void a_include_processor_as_string_should_be_executed_when_include_macro_ Element contentElement = doc.getElementsByAttributeValue("class", "language-ruby").first(); - assertThat(contentElement.text(), startsWith("module AsciidoctorJ")); + assertThat(contentElement.text(), startsWith(ASCIIDOCTORCLASS_PREFIX)); } @@ -377,7 +379,7 @@ public void a_include_processor_should_be_executed_when_include_macro_is_found() Element contentElement = doc.getElementsByAttributeValue("class", "language-ruby").first(); - assertThat(contentElement.text(), startsWith("module AsciidoctorJ")); + assertThat(contentElement.text(), startsWith(ASCIIDOCTORCLASS_PREFIX)); } @@ -398,7 +400,7 @@ public void a_include_instance_processor_should_be_executed_when_include_macro_i Element contentElement = doc.getElementsByAttributeValue("class", "language-ruby").first(); - assertThat(contentElement.text(), startsWith("module AsciidoctorJ")); + assertThat(contentElement.text(), startsWith(ASCIIDOCTORCLASS_PREFIX)); } @@ -857,7 +859,7 @@ public void a_include_processor_class_should_be_executed_twice() Element contentElement = doc.getElementsByAttributeValue("class", "language-ruby").first(); - assertThat(contentElement.text(), startsWith("module AsciidoctorJ")); + assertThat(contentElement.text(), startsWith(ASCIIDOCTORCLASS_PREFIX)); } } @@ -880,7 +882,7 @@ public void a_include_processor_instance_should_be_executed_twice() Element contentElement = doc.getElementsByAttributeValue("class", "language-ruby").first(); - assertThat(contentElement.text(), startsWith("module AsciidoctorJ")); + assertThat(contentElement.text(), startsWith(ASCIIDOCTORCLASS_PREFIX)); } } diff --git a/asciidoctorj-core/src/test/java/org/asciidoctor/log/TestLogHandlerService.java b/asciidoctorj-core/src/test/java/org/asciidoctor/log/TestLogHandlerService.java new file mode 100644 index 000000000..4f0f855dc --- /dev/null +++ b/asciidoctorj-core/src/test/java/org/asciidoctor/log/TestLogHandlerService.java @@ -0,0 +1,22 @@ +package org.asciidoctor.log; + +import java.util.ArrayList; +import java.util.List; + +public class TestLogHandlerService implements LogHandler { + + private static List logRecords = new ArrayList<>(); + + public static List getLogRecords() { + return logRecords; + } + + public static void clear() { + logRecords.clear(); + } + + @Override + public void log(LogRecord logRecord) { + logRecords.add(logRecord); + } +} \ No newline at end of file diff --git a/asciidoctorj-core/src/test/resources/META-INF/services/org.asciidoctor.log.LogHandler b/asciidoctorj-core/src/test/resources/META-INF/services/org.asciidoctor.log.LogHandler new file mode 100644 index 000000000..75b880b11 --- /dev/null +++ b/asciidoctorj-core/src/test/resources/META-INF/services/org.asciidoctor.log.LogHandler @@ -0,0 +1 @@ +org.asciidoctor.log.TestLogHandlerService \ No newline at end of file diff --git a/asciidoctorj-core/src/test/resources/documentwithinvalidrefs.adoc b/asciidoctorj-core/src/test/resources/documentwithinvalidrefs.adoc new file mode 100644 index 000000000..971e042b8 --- /dev/null +++ b/asciidoctorj-core/src/test/resources/documentwithinvalidrefs.adoc @@ -0,0 +1,3 @@ += Test + +This is an <> \ No newline at end of file diff --git a/asciidoctorj-core/src/test/resources/documentwithnotexistingfile.adoc b/asciidoctorj-core/src/test/resources/documentwithnotexistingfile.adoc index 075c94b60..f12e37c20 100644 --- a/asciidoctorj-core/src/test/resources/documentwithnotexistingfile.adoc +++ b/asciidoctorj-core/src/test/resources/documentwithnotexistingfile.adoc @@ -1,3 +1,9 @@ = My Document +include::unexistingdoc.adoc[] + +include::unexistingdoc.adoc[] + +include::https://raw.githubusercontent.com/asciidoctor/asciidoctorj/e47a2c01c563fa624ca61e1bc58e264a114f7fb5/asciidoctorj-core/src/test/resources/rendersample.asciidoc[] + include::unexistingdoc.adoc[] \ No newline at end of file diff --git a/build.gradle b/build.gradle index c467049db..3c0ee3ad7 100644 --- a/build.gradle +++ b/build.gradle @@ -95,7 +95,7 @@ subprojects { // NOTE sourceCompatibility & targetCompatibility are set in gradle.properties to meet requirements of Gradle // Must redefine here to work around a bug in the Eclipse plugin - sourceCompatibility = targetCompatibility = JavaVersion.VERSION_1_7 + sourceCompatibility = targetCompatibility = JavaVersion.VERSION_1_8 plugins.withType(JavaPlugin) { project.tasks.withType(JavaCompile) { task -> diff --git a/gradle.properties b/gradle.properties index 0eef8c6ca..3299b5f73 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,3 +1,3 @@ version=1.6.0-SNAPSHOT -sourceCompatibility=1.6 -targetCompatibility=1.6 +sourceCompatibility=1.8 +targetCompatibility=1.8