From 0c8ee7ad7085392fb59b1f33229a13ab21186875 Mon Sep 17 00:00:00 2001 From: Robert Panzer Date: Sat, 2 Dec 2023 19:41:40 +0100 Subject: [PATCH 1/4] Reuse Asciidoctor Ruby invoker --- .../main/java/org/asciidoctor/Options.java | 9 +- .../java/org/asciidoctor/OptionsBuilder.java | 19 +-- .../cli/AsciidoctorCliOptions.java | 123 +++++++++--------- .../cli/jruby/AsciidoctorInvoker.java | 76 +++-------- .../cli/WhenAsciidoctorAPICallIsCalled.java | 2 +- .../cli/WhenAsciidoctorIsCalledUsingCli.java | 22 +++- .../jruby/GlobDirectoryWalker.java | 10 +- .../jruby/internal/JRubyAsciidoctor.java | 7 +- .../jruby/internal/RubyGemsPreloader.java | 42 +++++- .../jruby/internal/asciidoctorclass.rb | 22 +++- .../WhenGlobExpressionIsUsedForScanning.java | 3 +- .../WhenJavaExtensionGroupIsRegistered.java | 11 +- .../WhenJavaExtensionIsRegistered.java | 15 ++- 13 files changed, 183 insertions(+), 178 deletions(-) diff --git a/asciidoctorj-api/src/main/java/org/asciidoctor/Options.java b/asciidoctorj-api/src/main/java/org/asciidoctor/Options.java index 70e045cc9..49734f069 100644 --- a/asciidoctorj-api/src/main/java/org/asciidoctor/Options.java +++ b/asciidoctorj-api/src/main/java/org/asciidoctor/Options.java @@ -6,12 +6,13 @@ /** * AsciidoctorJ conversion options. *

- * See https://docs.asciidoctor.org/asciidoctor/latest/api/options/ for further + * See https://docs.asciidoctor.org/asciidoctor/latest/api/options/ for further * details. */ public class Options { public static final String IN_PLACE = "in_place"; + public static final String WARNINGS = "warnings"; public static final String ATTRIBUTES = "attributes"; public static final String TEMPLATE_DIRS = "template_dirs"; public static final String TEMPLATE_ENGINE = "template_engine"; @@ -24,10 +25,10 @@ public class Options { public static final String ERUBY = "eruby"; public static final String CATALOG_ASSETS = "catalog_assets"; public static final String COMPACT = "compact"; - public static final String SOURCE_DIR = "source_dir"; public static final String BACKEND = "backend"; public static final String DOCTYPE = "doctype"; public static final String BASEDIR = "base_dir"; + public static final String TRACE = "trace"; public static final String TEMPLATE_CACHE = "template_cache"; public static final String SOURCE = "source"; public static final String PARSE = "parse"; @@ -160,10 +161,6 @@ public void setCompact(boolean compact) { this.options.put(COMPACT, compact); } - public void setSourceDir(String srcDir) { - this.options.put(SOURCE_DIR, srcDir); - } - public void setBackend(String backend) { this.options.put(BACKEND, backend); } diff --git a/asciidoctorj-api/src/main/java/org/asciidoctor/OptionsBuilder.java b/asciidoctorj-api/src/main/java/org/asciidoctor/OptionsBuilder.java index b8213942f..b25a1a4e4 100644 --- a/asciidoctorj-api/src/main/java/org/asciidoctor/OptionsBuilder.java +++ b/asciidoctorj-api/src/main/java/org/asciidoctor/OptionsBuilder.java @@ -2,7 +2,6 @@ import java.io.File; import java.io.OutputStream; -import java.util.Map; /** * Fluent Options API for AsciidoctorJ. @@ -160,7 +159,7 @@ public OptionsBuilder toStream(OutputStream toStream) { * @return this instance. */ public OptionsBuilder toDir(File directory) { - this.options.setToDir(directory.getAbsolutePath()); + this.options.setToDir(directory.getPath()); return this; } @@ -265,20 +264,6 @@ public OptionsBuilder parseHeaderOnly(boolean parseHeaderOnly) { return this; } - /** - * Source directory. - * - * This must be used alongside {@link #toDir(File)}. - * - * @param srcDir - * source directory. - * @return this instance. - */ - public OptionsBuilder sourceDir(File srcDir) { - this.options.setSourceDir(srcDir.getAbsolutePath()); - return this; - } - /** * Sets a custom or unlisted option. * @@ -301,7 +286,7 @@ public OptionsBuilder option(String option, Object value) { * @return this instance. */ public OptionsBuilder baseDir(File baseDir) { - this.options.setBaseDir(baseDir.getAbsolutePath()); + this.options.setBaseDir(baseDir.getPath()); return this; } diff --git a/asciidoctorj-cli/src/main/java/org/asciidoctor/cli/AsciidoctorCliOptions.java b/asciidoctorj-cli/src/main/java/org/asciidoctor/cli/AsciidoctorCliOptions.java index ea432b2a6..4a41fdf2b 100644 --- a/asciidoctorj-cli/src/main/java/org/asciidoctor/cli/AsciidoctorCliOptions.java +++ b/asciidoctorj-cli/src/main/java/org/asciidoctor/cli/AsciidoctorCliOptions.java @@ -1,14 +1,20 @@ package org.asciidoctor.cli; import com.beust.jcommander.Parameter; -import org.asciidoctor.*; +import org.asciidoctor.Attributes; +import org.asciidoctor.AttributesBuilder; +import org.asciidoctor.Options; +import org.asciidoctor.SafeMode; import org.asciidoctor.log.Severity; +import org.jruby.Ruby; +import org.jruby.RubyHash; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Map; import java.util.StringTokenizer; public class AsciidoctorCliOptions { @@ -16,6 +22,7 @@ public class AsciidoctorCliOptions { public static final String LOAD_PATHS = "-I"; public static final String REQUIRE = "-r"; public static final String QUIET = "-q"; + public static final String WARNINGS = "-w"; public static final String ATTRIBUTE = "-a"; public static final String HELP = "-h"; public static final String DESTINATION_DIR = "-D"; @@ -47,7 +54,7 @@ public class AsciidoctorCliOptions { private boolean version = false; @Parameter(names = {BACKEND, "--backend"}, description = "set output format backend") - private String backend = "html5"; + private String backend; @Parameter(names = {DOCTYPE, "--doctype"}, description = "document type to use when rendering output: [article, book, inline] (default: article)") private String doctype; @@ -70,8 +77,8 @@ public class AsciidoctorCliOptions { @Parameter(names = {SECTION_NUMBERS, "--section-numbers"}, description = "auto-number section titles; disabled by default") private boolean sectionNumbers = false; - @Parameter(names = {"--eruby"}, description = "specify eRuby implementation to render built-in templates: [erb, erubis]") - private String eruby = "erb"; + @Parameter(names = {"--eruby"}, description = "specify eRuby implementation to render built-in templates: [erb, erubis]; default erb") + private String eruby; @Parameter(names = {COMPACT, "--compact"}, description = "compact the output by removing blank lines") private boolean compact = false; @@ -103,6 +110,9 @@ public class AsciidoctorCliOptions { @Parameter(names = {QUIET, "--quiet"}, description = "suppress warnings") private boolean quiet = false; + @Parameter(names = {WARNINGS, "--warnings"}, description = "suppress warnings") + private boolean warnings = false; + @Parameter(names = {"--failure-level"}, converter = SeverityConverter.class, description = "set minimum log level that yields a non-zero exit code.") private Severity failureLevel = Severity.FATAL; @@ -266,80 +276,69 @@ private boolean isInPlaceRequired() { return !isOutFileOption() && !isDestinationDirOption() && !isOutputStdout(); } - public Options parse() throws IOException { - OptionsBuilder optionsBuilder = Options.builder() - .backend(this.backend) - .safe(this.safeMode) - .eruby(this.eruby) - .option(Options.STANDALONE, true); + public RubyHash parse(Ruby ruby) throws IOException { - if (isDoctypeOption()) { - optionsBuilder.docType(this.doctype); - } + RubyHash opts = new RubyHash(ruby); + Map attributes = buildAttributes(); - if (isInputStdin()) { - optionsBuilder.toStream(System.out); - if (outFile == null) { - outFile = "-"; - } - } + opts.put(ruby.newSymbol(Options.STANDALONE), true); + opts.put(ruby.newSymbol(Options.WARNINGS), false); - if (isOutFileOption() && !isOutputStdout()) { - optionsBuilder.toFile(new File(this.outFile)); + if (this.backend != null) { + attributes.put(Options.BACKEND, this.backend); } - - if (isOutputStdout()) { - optionsBuilder.toStream(System.out); + if (this.doctype != null) { + attributes.put(Options.DOCTYPE, this.doctype); + } + if (this.embedded) { + opts.put(ruby.newSymbol(Options.STANDALONE), false); + } + if (this.outFile != null) { + opts.put(ruby.newSymbol("output_file"), this.outFile); } - if (this.safe) { - optionsBuilder.safe(SafeMode.SAFE); + opts.put(ruby.newSymbol(Options.SAFE), SafeMode.SAFE.getLevel()); } - - if (this.embedded) { - optionsBuilder.option(Options.STANDALONE, false); + if (this.safeMode != null) { + opts.put(ruby.newSymbol("safe"), this.safeMode.getLevel()); } - if (this.noHeaderFooter) { - optionsBuilder.option(Options.STANDALONE, false); + opts.put(ruby.newSymbol(Options.STANDALONE), false); } - - if (this.compact) { - optionsBuilder.compact(this.compact); + if (this.sectionNumbers) { + attributes.put("sectnums", ""); } - - if (isBaseDirOption()) { - optionsBuilder.baseDir(new File(this.baseDir).getCanonicalFile()); + if (this.eruby != null) { + opts.put(ruby.newSymbol(Options.ERUBY), this.eruby); + } + if (isTemplateDirOption()) { + opts.put(ruby.newSymbol(Options.TEMPLATE_DIRS), this.templateDir.toArray(new String[0])); } - if (isTemplateEngineOption()) { - optionsBuilder.templateEngine(this.templateEngine); + opts.put(ruby.newSymbol(Options.TEMPLATE_ENGINE), this.templateEngine); } - - if (isTemplateDirOption()) { - for (String templateDir : this.templateDir) { - optionsBuilder.templateDirs(new File(templateDir).getCanonicalFile()); - } + if (this.baseDir != null) { + opts.put(ruby.newSymbol(Options.BASEDIR), this.baseDir); } - - if (isDestinationDirOption() && !isOutputStdout()) { - optionsBuilder.toDir(new File(this.destinationDir).getCanonicalFile()); - - if (isSourceDirOption()) { - optionsBuilder.sourceDir(new File(this.sourceDir).getCanonicalFile()); - } + if (this.destinationDir != null) { + opts.put(ruby.newSymbol("destination_dir"), this.destinationDir); } - - if (isInPlaceRequired()) { - optionsBuilder.inPlace(true); + if (this.trace) { + opts.put(ruby.newSymbol(Options.TRACE), true); } - - Attributes attributesBuilder = buildAttributes(); - if (this.sectionNumbers) { - attributesBuilder.setSectionNumbers(this.sectionNumbers); + if (this.timings) { + opts.put(ruby.newSymbol("timings"), true); + } + if (this.warnings) { + opts.put(ruby.newSymbol(Options.WARNINGS), true); + } + if (!attributes.isEmpty()) { + opts.put(ruby.newSymbol(Options.ATTRIBUTES), attributes); + } + if (this.isSourceDirOption()) { + opts.put(ruby.newSymbol("source_dir"), this.sourceDir); } - optionsBuilder.attributes(attributesBuilder); - return optionsBuilder.build(); + return opts; } /** @@ -347,7 +346,7 @@ public Options parse() throws IOException { * {@link Attributes} instance. */ // FIXME Should be private, made protected for testing. - Attributes buildAttributes() { + Map buildAttributes() { final AttributesBuilder attributesBuilder = Attributes.builder(); for (String attribute : attributes) { int separatorIndex = attribute.indexOf(ATTRIBUTE_SEPARATOR); @@ -359,7 +358,7 @@ Attributes buildAttributes() { attributesBuilder.attribute(attribute); } } - return attributesBuilder.build(); + return attributesBuilder.build().map(); } private List splitByPathSeparator(String path) { diff --git a/asciidoctorj-cli/src/main/java/org/asciidoctor/cli/jruby/AsciidoctorInvoker.java b/asciidoctorj-cli/src/main/java/org/asciidoctor/cli/jruby/AsciidoctorInvoker.java index 59254c83e..1a3edb8db 100644 --- a/asciidoctorj-cli/src/main/java/org/asciidoctor/cli/jruby/AsciidoctorInvoker.java +++ b/asciidoctorj-cli/src/main/java/org/asciidoctor/cli/jruby/AsciidoctorInvoker.java @@ -1,8 +1,6 @@ package org.asciidoctor.cli.jruby; import com.beust.jcommander.JCommander; -import org.asciidoctor.Asciidoctor; -import org.asciidoctor.Options; import org.asciidoctor.cli.AsciidoctorCliOptions; import org.asciidoctor.cli.MaxSeverityLogHandler; import org.asciidoctor.jruby.DirectoryWalker; @@ -10,8 +8,12 @@ import org.asciidoctor.jruby.internal.IOUtils; import org.asciidoctor.jruby.internal.JRubyAsciidoctor; import org.asciidoctor.jruby.internal.JRubyRuntimeContext; +import org.asciidoctor.jruby.internal.RubyGemsPreloader; import org.asciidoctor.jruby.internal.RubyUtils; import org.jruby.Main; +import org.jruby.Ruby; +import org.jruby.RubyClass; +import org.jruby.RubyHash; import org.jruby.runtime.builtin.IRubyObject; import java.io.File; @@ -19,11 +21,12 @@ import java.io.InputStream; import java.net.URL; import java.net.URLClassLoader; -import java.util.*; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.Properties; import java.util.stream.Collectors; -import static org.asciidoctor.cli.AsciidoctorCliOptions.TIMINGS_OPTION_NAME; - public class AsciidoctorInvoker { public int invoke(String... parameters) throws IOException { @@ -61,29 +64,20 @@ public int invoke(String... parameters) throws IOException { MaxSeverityLogHandler maxSeverityLogHandler = new MaxSeverityLogHandler(); asciidoctor.registerLogHandler(maxSeverityLogHandler); - Options options = asciidoctorCliOptions.parse(); - if (asciidoctorCliOptions.isRequire()) { for (String require : asciidoctorCliOptions.getRequire()) { RubyUtils.requireLibrary(asciidoctor.getRubyRuntime(), require); } } - setTimingsMode(asciidoctor, asciidoctorCliOptions, options); - setVerboseLevel(asciidoctor, asciidoctorCliOptions); - convertInput(asciidoctor, options, inputFiles); + convertInput(asciidoctor, asciidoctorCliOptions, inputFiles); if (asciidoctorCliOptions.getFailureLevel().compareTo(maxSeverityLogHandler.getMaxSeverity()) <= 0) { return 1; } - if (asciidoctorCliOptions.isTimings()) { - Map optionsMap = options.map(); - IRubyObject timings = (IRubyObject) optionsMap.get(TIMINGS_OPTION_NAME); - timings.callMethod(JRubyRuntimeContext.get(asciidoctor).getCurrentContext(), "print_report"); - } } return 0; } @@ -99,13 +93,6 @@ private String getAsciidoctorJVersion() { } } - private void setTimingsMode(Asciidoctor asciidoctor, AsciidoctorCliOptions asciidoctorCliOptions, Options options) { - if (asciidoctorCliOptions.isTimings()) { - options.setOption(TIMINGS_OPTION_NAME, - JRubyRuntimeContext.get(asciidoctor).evalScriptlet("Asciidoctor::Timings.new")); - } - } - private void setVerboseLevel(JRubyAsciidoctor asciidoctor, AsciidoctorCliOptions asciidoctorCliOptions) { if (asciidoctorCliOptions.isVerbose()) { RubyUtils.setGlobalVariable(asciidoctor.getRubyRuntime(), "VERBOSE", "true"); @@ -150,46 +137,17 @@ private URLClassLoader createUrlClassLoader(List classPaths) { return new URLClassLoader(cpUrls.toArray(new URL[cpUrls.size()])); } - private void convertInput(Asciidoctor asciidoctor, Options options, List inputFiles) { + private void convertInput(JRubyAsciidoctor asciidoctor, AsciidoctorCliOptions cliOptions, List inputFiles) throws IOException { + Ruby ruby = JRubyRuntimeContext.get(asciidoctor); + RubyHash opts = cliOptions.parse(ruby); - if (inputFiles.size() == 1 && "-".equals(inputFiles.get(0).getName())) { - asciidoctor.convert(readInputFromStdIn(), options); - return; - } + new RubyGemsPreloader(asciidoctor.getRubyRuntime()).preloadRequiredLibrariesCommandLine(opts); - options.setMkDirs(true); - - findInvalidInputFile(inputFiles) - .ifPresent(inputFile -> { - System.err.println("asciidoctor: FAILED: input file(s) '" - + inputFile.getAbsolutePath() - + "' missing or cannot be read"); - throw new IllegalArgumentException( - "asciidoctor: FAILED: input file(s) '" - + inputFile.getAbsolutePath() - + "' missing or cannot be read"); - }); - - final Optional toDir = getAbsolutePathFromOption(options, Options.TO_DIR); - final Optional srcDir = getAbsolutePathFromOption(options, Options.SOURCE_DIR); - - inputFiles.forEach(inputFile -> { - if (toDir.isPresent() && srcDir.isPresent()) { - if (inputFile.getAbsolutePath().startsWith(srcDir.get().getAbsolutePath())) { - String relativePath = srcDir.get().toURI().relativize(inputFile.getParentFile().getAbsoluteFile().toURI()).getPath(); - String absolutePath = new File(toDir.get(), relativePath).getAbsolutePath(); - options.setToDir(absolutePath); - } - } - asciidoctor.convertFile(inputFile, options); - }); - } + opts.put(ruby.newSymbol("input_files"), inputFiles.stream().map(f -> ruby.newString(f.getPath())).collect(Collectors.toList())); - private Optional getAbsolutePathFromOption(Options options, String name) { - return Optional.ofNullable(options.map().get(name)) - .filter(String.class::isInstance) - .map(String.class::cast) - .map(File::new); + RubyClass invokerClass = ruby.getModule("AsciidoctorJ").getModule("Cli").getClass("Invoker"); + IRubyObject invoker = invokerClass.newInstance(ruby.getCurrentContext(), opts); + invoker.callMethod(ruby.getCurrentContext(), "invoke!"); } private Optional findInvalidInputFile(List inputFiles) { diff --git a/asciidoctorj-cli/src/test/java/org/asciidoctor/cli/WhenAsciidoctorAPICallIsCalled.java b/asciidoctorj-cli/src/test/java/org/asciidoctor/cli/WhenAsciidoctorAPICallIsCalled.java index dc46be8c1..354b990a5 100644 --- a/asciidoctorj-cli/src/test/java/org/asciidoctor/cli/WhenAsciidoctorAPICallIsCalled.java +++ b/asciidoctorj-cli/src/test/java/org/asciidoctor/cli/WhenAsciidoctorAPICallIsCalled.java @@ -33,7 +33,7 @@ public void api_parameters_should_be_transformed_to_cli_command() { assertThat(asciidoctorCliOptions.getSafeMode()).isEqualTo(SafeMode.UNSAFE); assertThat(asciidoctorCliOptions.getBackend()).isEqualTo("docbook"); assertThat(asciidoctorCliOptions.getParameters()).containsExactly("file.adoc"); - assertThat(asciidoctorCliOptions.buildAttributes().map()) + assertThat(asciidoctorCliOptions.buildAttributes()) .containsEntry("myAttribute", "myValue") .containsEntry("sectnums", "") .containsEntry("copycss!", ""); diff --git a/asciidoctorj-cli/src/test/java/org/asciidoctor/cli/WhenAsciidoctorIsCalledUsingCli.java b/asciidoctorj-cli/src/test/java/org/asciidoctor/cli/WhenAsciidoctorIsCalledUsingCli.java index 14129a03a..eb19c37d4 100644 --- a/asciidoctorj-cli/src/test/java/org/asciidoctor/cli/WhenAsciidoctorIsCalledUsingCli.java +++ b/asciidoctorj-cli/src/test/java/org/asciidoctor/cli/WhenAsciidoctorIsCalledUsingCli.java @@ -16,6 +16,7 @@ import java.io.IOException; import java.io.PrintStream; import java.nio.charset.StandardCharsets; +import java.util.stream.Collectors; import static org.assertj.core.api.Assertions.catchThrowable; import static org.hamcrest.CoreMatchers.is; @@ -142,11 +143,13 @@ void version_flag_should_print_version_and_exit() throws IOException { } @Test - void invalid_input_file_should_throw_an_exception() { - Throwable throwable = catchThrowable(() -> new AsciidoctorInvoker().invoke("myunknown.adoc")); + void invalid_input_file_should_throw_an_exception() throws IOException { + final ByteArrayOutputStream output = redirectStderr(); + new AsciidoctorInvoker().invoke("myunknown.adoc"); - Assertions.assertThat(throwable) - .isInstanceOf(IllegalArgumentException.class); + String outputConsole = output.toString(); + Assertions.assertThat(outputConsole) + .startsWith("No such file or directory - myunknown.adoc"); } @Test @@ -213,13 +216,14 @@ void output_file_hyphen_symbol_should_render_output_to_stdout() throws IOExcepti @Test void verbose_option_should_fill_monitor_map() throws IOException { - final ByteArrayOutputStream output = redirectStdout(); + final ByteArrayOutputStream output = redirectStderr(); final String inputPath = renderSampleDocument.getPath().substring(pwd.length() + 1); new AsciidoctorInvoker().invoke("--timings", inputPath); String outputConsole = output.toString(); - assertThat(outputConsole, startsWith(" Time to read and parse source:")); + Assertions.assertThat(outputConsole.lines().collect(Collectors.toList())) + .anySatisfy(line -> Assertions.assertThat(line).startsWith(" Time to read and parse source:")); assertThat(outputConsole, not(containsString("null"))); } @@ -301,4 +305,10 @@ private ByteArrayOutputStream redirectStdout() { System.setOut(new PrintStream(output)); return output; } + + private ByteArrayOutputStream redirectStderr() { + ByteArrayOutputStream output = new ByteArrayOutputStream(); + System.setErr(new PrintStream(output)); + return output; + } } diff --git a/asciidoctorj-core/src/main/java/org/asciidoctor/jruby/GlobDirectoryWalker.java b/asciidoctorj-core/src/main/java/org/asciidoctor/jruby/GlobDirectoryWalker.java index ed0215919..a8653c9e7 100644 --- a/asciidoctorj-core/src/main/java/org/asciidoctor/jruby/GlobDirectoryWalker.java +++ b/asciidoctorj-core/src/main/java/org/asciidoctor/jruby/GlobDirectoryWalker.java @@ -6,6 +6,7 @@ import java.util.Arrays; import java.util.Iterator; import java.util.List; +import java.util.Optional; /** * @@ -31,10 +32,10 @@ public GlobDirectoryWalker(String globExpression) { String unglobbedPart = globExpression.substring(0, indexOfUnglobbedPart + 1); - this.rootDirectory = new File(unglobbedPart).getAbsoluteFile(); + this.rootDirectory = unglobbedPart.isEmpty() ? null : new File(unglobbedPart); this.globExpression = globExpression.substring(indexOfUnglobbedPart + 1); checkInput(rootDirectory); - this.canonicalRootDir = getCanonicalPath(rootDirectory); + this.canonicalRootDir = getCanonicalPath(Optional.ofNullable(rootDirectory).orElseGet(() -> new File("."))); } /** @@ -88,6 +89,9 @@ public List scan() { } private void checkInput(File rootDir) { + if (rootDir == null) { + return; + } if (!rootDir.exists()) { throw new IllegalArgumentException("Directory does not exist: " + rootDir); @@ -137,7 +141,7 @@ private void findFileInSpecificLocation(File dir, List includes) { private void findFilesThroughMatchingDirectories(File dir, List includes) { for (String fileName : dir.list()) { - List matchingIncludes = new ArrayList( + List matchingIncludes = new ArrayList<>( includes.size()); for (Pattern include : includes) { if (include.matches(fileName)) { diff --git a/asciidoctorj-core/src/main/java/org/asciidoctor/jruby/internal/JRubyAsciidoctor.java b/asciidoctorj-core/src/main/java/org/asciidoctor/jruby/internal/JRubyAsciidoctor.java index d672c1e62..e7faea011 100644 --- a/asciidoctorj-core/src/main/java/org/asciidoctor/jruby/internal/JRubyAsciidoctor.java +++ b/asciidoctorj-core/src/main/java/org/asciidoctor/jruby/internal/JRubyAsciidoctor.java @@ -39,7 +39,7 @@ public class JRubyAsciidoctor implements AsciidoctorJRuby, LogHandler { private RubyClass extensionGroupClass; - private List logHandlers = new ArrayList<>(); + private final List logHandlers = new ArrayList<>(); public JRubyAsciidoctor() { this(createRubyRuntime(Collections.singletonMap(GEM_PATH, null), new ArrayList<>(), null)); @@ -293,9 +293,6 @@ public T convertFile(File file, Map options, Class expect String currentDirectory = rubyRuntime.getCurrentDirectory(); - if (options.containsKey(Options.BASEDIR)) { - rubyRuntime.setCurrentDirectory((String) options.get(Options.BASEDIR)); - } final Object toFileOption = options.get(Options.TO_FILE); if (toFileOption instanceof OutputStream) { @@ -306,7 +303,7 @@ public T convertFile(File file, Map options, Class expect try { IRubyObject object = getAsciidoctorModule().callMethod("convert_file", - rubyRuntime.newString(file.getAbsolutePath()), rubyHash); + rubyRuntime.newString(file.toString()), rubyHash); return adaptReturn(object, expectedResult); } catch (RaiseException e) { logger.severe(e.getMessage()); diff --git a/asciidoctorj-core/src/main/java/org/asciidoctor/jruby/internal/RubyGemsPreloader.java b/asciidoctorj-core/src/main/java/org/asciidoctor/jruby/internal/RubyGemsPreloader.java index e57dfbb85..9edaf2d2f 100644 --- a/asciidoctorj-core/src/main/java/org/asciidoctor/jruby/internal/RubyGemsPreloader.java +++ b/asciidoctorj-core/src/main/java/org/asciidoctor/jruby/internal/RubyGemsPreloader.java @@ -3,6 +3,7 @@ import org.asciidoctor.Attributes; import org.asciidoctor.Options; import org.jruby.Ruby; +import org.jruby.RubyHash; import java.util.Map; @@ -25,7 +26,7 @@ public class RubyGemsPreloader { REVEALJS, "require 'asciidoctor-revealjs'" ); - private Ruby rubyRuntime; + private final Ruby rubyRuntime; public RubyGemsPreloader(Ruby rubyRuntime) { this.rubyRuntime = rubyRuntime; @@ -71,12 +72,49 @@ && isOptionWithValue(attributes, Attributes.SOURCE_HIGHLIGHTER, CODERAY)) { } } + public void preloadRequiredLibrariesCommandLine(RubyHash opts) { + Ruby ruby = opts.getRuntime(); + if (opts.containsKey(ruby.newSymbol(Options.ATTRIBUTES))) { + Map attributes = (Map) opts.get(ruby.newSymbol(Options.ATTRIBUTES)); + + if (CODERAY.equals(attributes.get(Attributes.SOURCE_HIGHLIGHTER))) { + preloadLibrary(Attributes.SOURCE_HIGHLIGHTER); + } + + if (attributes.containsKey(Attributes.CACHE_URI)) { + preloadLibrary(Attributes.CACHE_URI); + } + + if (attributes.containsKey(Attributes.DATA_URI)) { + preloadLibrary(Attributes.DATA_URI); + } + if ("epub3".equals(attributes.get(Options.BACKEND))) { + preloadLibrary(EPUB3); + } + if ("pdf".equals(attributes.get(Options.BACKEND))) { + preloadLibrary(PDF); + } + if ("revealjs".equals(attributes.get(Options.BACKEND))) { + preloadLibrary(REVEALJS); + } + } + + if (ERUBIS.equals(opts.get(ruby.newSymbol(Options.ERUBY)))) { + preloadLibrary(Options.ERUBY); + } + + if (opts.containsKey(ruby.newSymbol(Options.TEMPLATE_DIRS))) { + preloadLibrary(Options.TEMPLATE_DIRS); + } + + } + private void preloadLibrary(String option) { this.rubyRuntime.evalScriptlet(optionToRequiredGem.get(option)); } private boolean isOptionWithValue(Map attributes, String attribute, String value) { - return attributes.get(attribute).equals(value); + return value.equals(attributes.get(attribute)); } private boolean isOptionSet(Map attributes, String attribute) { diff --git a/asciidoctorj-core/src/main/resources/org/asciidoctor/jruby/internal/asciidoctorclass.rb b/asciidoctorj-core/src/main/resources/org/asciidoctor/jruby/internal/asciidoctorclass.rb index b626d96e7..16f04ca3e 100644 --- a/asciidoctorj-core/src/main/resources/org/asciidoctor/jruby/internal/asciidoctorclass.rb +++ b/asciidoctorj-core/src/main/resources/org/asciidoctor/jruby/internal/asciidoctorclass.rb @@ -1,3 +1,9 @@ +require 'java' +require 'asciidoctor' +require 'asciidoctor/cli' +require 'asciidoctor/extensions' + + module AsciidoctorJ include_package 'org.asciidoctor' module Extensions @@ -6,11 +12,19 @@ module Extensions # This is necessary to run against both Asciidoctor 1.5.5 and 1.5.6 TreeProcessor = Treeprocessor unless defined? TreeProcessor end -end -require 'java' -require 'asciidoctor' -require 'asciidoctor/extensions' + module Cli + class Invoker < Asciidoctor::Cli::Invoker + def initialize options + @documents = [] + @out = nil + @err = nil + @code = 0 + @options = options + end + end + end +end module AsciidoctorModule diff --git a/asciidoctorj-core/src/test/java/org/asciidoctor/WhenGlobExpressionIsUsedForScanning.java b/asciidoctorj-core/src/test/java/org/asciidoctor/WhenGlobExpressionIsUsedForScanning.java index 884ca150d..e9d4a8c55 100644 --- a/asciidoctorj-core/src/test/java/org/asciidoctor/WhenGlobExpressionIsUsedForScanning.java +++ b/asciidoctorj-core/src/test/java/org/asciidoctor/WhenGlobExpressionIsUsedForScanning.java @@ -64,6 +64,7 @@ public void should_not_fail_with_file_in_root_dir() { List asciidocFiles = globDirectoryWalker.scan(); assertThat(asciidocFiles) + .extracting(File::getAbsoluteFile) .contains(new File(fileNameInRootDir).getAbsoluteFile()); } @@ -76,6 +77,6 @@ public void should_not_fail_with_file_in_current_working_dir() { List asciidocFiles = globDirectoryWalker.scan(); assertThat(asciidocFiles) - .containsExactly(new File(fileNameInCurrentWorkingDir).getAbsoluteFile()); + .containsExactly(new File(fileNameInCurrentWorkingDir)); } } 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 c1478ad03..f6809396b 100644 --- a/asciidoctorj-core/src/test/java/org/asciidoctor/extension/WhenJavaExtensionGroupIsRegistered.java +++ b/asciidoctorj-core/src/test/java/org/asciidoctor/extension/WhenJavaExtensionGroupIsRegistered.java @@ -14,6 +14,7 @@ import org.asciidoctor.test.extension.AsciidoctorExtension; import org.asciidoctor.test.extension.ClasspathExtension; import org.asciidoctor.util.TestHttpServer; +import org.assertj.core.api.Assertions; import org.jsoup.Jsoup; import org.jsoup.nodes.Element; import org.jsoup.select.Elements; @@ -155,7 +156,7 @@ public void an_inner_class_should_be_registered() { Element contentElement = doc.getElementsByAttributeValue("class", "language-ruby").first(); - assertThat(contentElement.text(), startsWith(ASCIIDOCTORCLASS_PREFIX)); + Assertions.assertThat(contentElement.text()).contains(ASCIIDOCTORCLASS_PREFIX); } @@ -213,7 +214,7 @@ public boolean handles(String target) { Element contentElement = doc.getElementsByAttributeValue("class", "language-ruby").first(); - assertThat(contentElement.text(), startsWith(ASCIIDOCTORCLASS_PREFIX)); + Assertions.assertThat(contentElement.text()).contains(ASCIIDOCTORCLASS_PREFIX); } @@ -386,7 +387,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(ASCIIDOCTORCLASS_PREFIX)); + Assertions.assertThat(contentElement.text()).contains(ASCIIDOCTORCLASS_PREFIX); } @@ -407,7 +408,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(ASCIIDOCTORCLASS_PREFIX)); + Assertions.assertThat(contentElement.text()).contains(ASCIIDOCTORCLASS_PREFIX); } @@ -428,7 +429,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(ASCIIDOCTORCLASS_PREFIX)); + Assertions.assertThat(contentElement.text()).contains(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 1e95a73cc..888097fe9 100644 --- a/asciidoctorj-core/src/test/java/org/asciidoctor/extension/WhenJavaExtensionIsRegistered.java +++ b/asciidoctorj-core/src/test/java/org/asciidoctor/extension/WhenJavaExtensionIsRegistered.java @@ -15,6 +15,7 @@ import org.asciidoctor.test.extension.AsciidoctorExtension; import org.asciidoctor.test.extension.ClasspathExtension; import org.asciidoctor.util.TestHttpServer; +import org.assertj.core.api.Assertions; import org.jsoup.Jsoup; import org.jsoup.nodes.Element; import org.jsoup.select.Elements; @@ -157,7 +158,7 @@ public void an_inner_class_should_be_registered() { Element contentElement = doc.getElementsByAttributeValue("class", "language-ruby").first(); - assertThat(contentElement.text(), startsWith(ASCIIDOCTORCLASS_PREFIX)); + Assertions.assertThat(contentElement.text()).contains(ASCIIDOCTORCLASS_PREFIX); } @@ -215,7 +216,7 @@ public boolean handles(String target) { Element contentElement = doc.getElementsByAttributeValue("class", "language-ruby").first(); - assertThat(contentElement.text(), startsWith(ASCIIDOCTORCLASS_PREFIX)); + Assertions.assertThat(contentElement.text()).contains(ASCIIDOCTORCLASS_PREFIX); } @@ -389,7 +390,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(ASCIIDOCTORCLASS_PREFIX)); + Assertions.assertThat(contentElement.text()).contains(ASCIIDOCTORCLASS_PREFIX); } @@ -410,7 +411,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(ASCIIDOCTORCLASS_PREFIX)); + Assertions.assertThat(contentElement.text()).contains(ASCIIDOCTORCLASS_PREFIX); } @@ -431,7 +432,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(ASCIIDOCTORCLASS_PREFIX)); + Assertions.assertThat(contentElement.text()).contains(ASCIIDOCTORCLASS_PREFIX); } @@ -925,7 +926,7 @@ public void a_include_processor_class_should_be_executed_twice() { Element contentElement = doc.getElementsByAttributeValue("class", "language-ruby").first(); - assertThat(contentElement.text(), startsWith(ASCIIDOCTORCLASS_PREFIX)); + Assertions.assertThat(contentElement.text()).contains(ASCIIDOCTORCLASS_PREFIX); } } @@ -947,7 +948,7 @@ public void a_include_processor_instance_should_be_executed_twice() { Element contentElement = doc.getElementsByAttributeValue("class", "language-ruby").first(); - assertThat(contentElement.text(), startsWith(ASCIIDOCTORCLASS_PREFIX)); + Assertions.assertThat(contentElement.text()).contains(ASCIIDOCTORCLASS_PREFIX); } } From 21295a6bb9659877154ca21a5174deb5b5bd8f49 Mon Sep 17 00:00:00 2001 From: Robert Panzer Date: Sun, 10 Dec 2023 17:12:05 +0100 Subject: [PATCH 2/4] Factor out AsciidoctorRubyInvoker to clarify that the CLI bypasses the AsciidoctorJ API. Added more comments, minor code cleanup. --- .../main/java/org/asciidoctor/Options.java | 1 + .../cli/AsciidoctorCliOptions.java | 10 ++--- .../cli/jruby/AsciidoctorInvoker.java | 37 +++------------ .../cli/jruby/AsciidoctorRubyInvoker.java | 44 ++++++++++++++++++ .../jruby/internal/RubyGemsPreloader.java | 45 ++++++++++++------- 5 files changed, 85 insertions(+), 52 deletions(-) create mode 100644 asciidoctorj-cli/src/main/java/org/asciidoctor/cli/jruby/AsciidoctorRubyInvoker.java diff --git a/asciidoctorj-api/src/main/java/org/asciidoctor/Options.java b/asciidoctorj-api/src/main/java/org/asciidoctor/Options.java index 49734f069..0c26125c1 100644 --- a/asciidoctorj-api/src/main/java/org/asciidoctor/Options.java +++ b/asciidoctorj-api/src/main/java/org/asciidoctor/Options.java @@ -13,6 +13,7 @@ public class Options { public static final String IN_PLACE = "in_place"; public static final String WARNINGS = "warnings"; + public static final String TIMINGS = "timings"; public static final String ATTRIBUTES = "attributes"; public static final String TEMPLATE_DIRS = "template_dirs"; public static final String TEMPLATE_ENGINE = "template_engine"; diff --git a/asciidoctorj-cli/src/main/java/org/asciidoctor/cli/AsciidoctorCliOptions.java b/asciidoctorj-cli/src/main/java/org/asciidoctor/cli/AsciidoctorCliOptions.java index 4a41fdf2b..86ef2af1a 100644 --- a/asciidoctorj-cli/src/main/java/org/asciidoctor/cli/AsciidoctorCliOptions.java +++ b/asciidoctorj-cli/src/main/java/org/asciidoctor/cli/AsciidoctorCliOptions.java @@ -10,7 +10,6 @@ import org.jruby.RubyHash; import java.io.File; -import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -42,7 +41,6 @@ public class AsciidoctorCliOptions { public static final String VERBOSE = "-v"; public static final String TIMINGS = "-t"; public static final char ATTRIBUTE_SEPARATOR = '='; - public static final String TIMINGS_OPTION_NAME = "timings"; @Parameter(names = {VERBOSE, "--verbose"}, description = "enable verbose mode") private boolean verbose = false; @@ -276,7 +274,7 @@ private boolean isInPlaceRequired() { return !isOutFileOption() && !isDestinationDirOption() && !isOutputStdout(); } - public RubyHash parse(Ruby ruby) throws IOException { + public RubyHash parse(Ruby ruby) { RubyHash opts = new RubyHash(ruby); Map attributes = buildAttributes(); @@ -300,13 +298,13 @@ public RubyHash parse(Ruby ruby) throws IOException { opts.put(ruby.newSymbol(Options.SAFE), SafeMode.SAFE.getLevel()); } if (this.safeMode != null) { - opts.put(ruby.newSymbol("safe"), this.safeMode.getLevel()); + opts.put(ruby.newSymbol(Options.SAFE), this.safeMode.getLevel()); } if (this.noHeaderFooter) { opts.put(ruby.newSymbol(Options.STANDALONE), false); } if (this.sectionNumbers) { - attributes.put("sectnums", ""); + attributes.put(Attributes.SECTION_NUMBERS, ""); } if (this.eruby != null) { opts.put(ruby.newSymbol(Options.ERUBY), this.eruby); @@ -327,7 +325,7 @@ public RubyHash parse(Ruby ruby) throws IOException { opts.put(ruby.newSymbol(Options.TRACE), true); } if (this.timings) { - opts.put(ruby.newSymbol("timings"), true); + opts.put(ruby.newSymbol(Options.TIMINGS), true); } if (this.warnings) { opts.put(ruby.newSymbol(Options.WARNINGS), true); diff --git a/asciidoctorj-cli/src/main/java/org/asciidoctor/cli/jruby/AsciidoctorInvoker.java b/asciidoctorj-cli/src/main/java/org/asciidoctor/cli/jruby/AsciidoctorInvoker.java index 1a3edb8db..8e7e0f025 100644 --- a/asciidoctorj-cli/src/main/java/org/asciidoctor/cli/jruby/AsciidoctorInvoker.java +++ b/asciidoctorj-cli/src/main/java/org/asciidoctor/cli/jruby/AsciidoctorInvoker.java @@ -5,16 +5,10 @@ import org.asciidoctor.cli.MaxSeverityLogHandler; import org.asciidoctor.jruby.DirectoryWalker; import org.asciidoctor.jruby.GlobDirectoryWalker; -import org.asciidoctor.jruby.internal.IOUtils; import org.asciidoctor.jruby.internal.JRubyAsciidoctor; import org.asciidoctor.jruby.internal.JRubyRuntimeContext; -import org.asciidoctor.jruby.internal.RubyGemsPreloader; import org.asciidoctor.jruby.internal.RubyUtils; import org.jruby.Main; -import org.jruby.Ruby; -import org.jruby.RubyClass; -import org.jruby.RubyHash; -import org.jruby.runtime.builtin.IRubyObject; import java.io.File; import java.io.IOException; @@ -23,7 +17,6 @@ import java.net.URLClassLoader; import java.util.ArrayList; import java.util.List; -import java.util.Optional; import java.util.Properties; import java.util.stream.Collectors; @@ -131,33 +124,15 @@ private URLClassLoader createUrlClassLoader(List classPaths) { cpUrls.add(f.toURI().toURL()); } } catch (Exception e) { - System.err.println(String.format("asciidoctor: WARNING: Could not resolve classpath '%s': %s", cp, e.getMessage())); + System.err.printf("asciidoctor: WARNING: Could not resolve classpath '%s': %s%n", cp, e.getMessage()); } } - return new URLClassLoader(cpUrls.toArray(new URL[cpUrls.size()])); + return new URLClassLoader(cpUrls.toArray(new URL[0])); } private void convertInput(JRubyAsciidoctor asciidoctor, AsciidoctorCliOptions cliOptions, List inputFiles) throws IOException { - Ruby ruby = JRubyRuntimeContext.get(asciidoctor); - RubyHash opts = cliOptions.parse(ruby); - - new RubyGemsPreloader(asciidoctor.getRubyRuntime()).preloadRequiredLibrariesCommandLine(opts); - - opts.put(ruby.newSymbol("input_files"), inputFiles.stream().map(f -> ruby.newString(f.getPath())).collect(Collectors.toList())); - - RubyClass invokerClass = ruby.getModule("AsciidoctorJ").getModule("Cli").getClass("Invoker"); - IRubyObject invoker = invokerClass.newInstance(ruby.getCurrentContext(), opts); - invoker.callMethod(ruby.getCurrentContext(), "invoke!"); - } - - private Optional findInvalidInputFile(List inputFiles) { - return inputFiles.stream() - .filter(inputFile -> !inputFile.canRead()) - .findFirst(); - } - - private String readInputFromStdIn() { - return IOUtils.readFull(System.in); + AsciidoctorRubyInvoker invoker = new AsciidoctorRubyInvoker(asciidoctor); + invoker.invoke(inputFiles, cliOptions); } private List getInputFiles(AsciidoctorCliOptions asciidoctorCliOptions) { @@ -170,12 +145,12 @@ private List getInputFiles(AsciidoctorCliOptions asciidoctorCliOptions) { } return parameters.stream() - .map(globExpression -> new GlobDirectoryWalker(globExpression)) + .map(GlobDirectoryWalker::new) .flatMap(walker -> walker.scan().stream()) .collect(Collectors.toList()); } - public static void main(String args[]) throws IOException { + public static void main(String[] args) throws IOException { // Process .jrubyrc file Main.processDotfile(); diff --git a/asciidoctorj-cli/src/main/java/org/asciidoctor/cli/jruby/AsciidoctorRubyInvoker.java b/asciidoctorj-cli/src/main/java/org/asciidoctor/cli/jruby/AsciidoctorRubyInvoker.java new file mode 100644 index 000000000..5c647f956 --- /dev/null +++ b/asciidoctorj-cli/src/main/java/org/asciidoctor/cli/jruby/AsciidoctorRubyInvoker.java @@ -0,0 +1,44 @@ +package org.asciidoctor.cli.jruby; + +import org.asciidoctor.cli.AsciidoctorCliOptions; +import org.asciidoctor.jruby.internal.JRubyAsciidoctor; +import org.asciidoctor.jruby.internal.RubyGemsPreloader; +import org.jruby.Ruby; +import org.jruby.RubyClass; +import org.jruby.RubyHash; +import org.jruby.runtime.builtin.IRubyObject; + +import java.io.File; +import java.util.List; +import java.util.stream.Collectors; + +/** + * Converts AsciiDoc files using the Asciidoctor Ruby API. + * Note that this bypasses the AsciidoctorJ API and is only used for the CLI. + */ +public class AsciidoctorRubyInvoker { + + private final JRubyAsciidoctor asciidoctor; + + private final Ruby ruby; + + private final RubyClass invokerClass; + + public AsciidoctorRubyInvoker(JRubyAsciidoctor asciidoctor) { + this.asciidoctor = asciidoctor; + this.ruby = asciidoctor.getRubyRuntime(); + this.invokerClass = ruby.getModule("AsciidoctorJ").getModule("Cli").getClass("Invoker"); + } + + public void invoke(List inputFiles, AsciidoctorCliOptions cliOptions) { + RubyHash opts = cliOptions.parse(ruby); + + new RubyGemsPreloader(asciidoctor.getRubyRuntime()).preloadRequiredLibrariesCommandLine(opts); + + opts.put(ruby.newSymbol("input_files"), inputFiles.stream().map(f -> ruby.newString(f.getPath())).collect(Collectors.toList())); + + IRubyObject invoker = invokerClass.newInstance(ruby.getCurrentContext(), opts); + invoker.callMethod(ruby.getCurrentContext(), "invoke!"); + } + +} diff --git a/asciidoctorj-core/src/main/java/org/asciidoctor/jruby/internal/RubyGemsPreloader.java b/asciidoctorj-core/src/main/java/org/asciidoctor/jruby/internal/RubyGemsPreloader.java index 9edaf2d2f..1512f3d70 100644 --- a/asciidoctorj-core/src/main/java/org/asciidoctor/jruby/internal/RubyGemsPreloader.java +++ b/asciidoctorj-core/src/main/java/org/asciidoctor/jruby/internal/RubyGemsPreloader.java @@ -6,6 +6,9 @@ import org.jruby.RubyHash; import java.util.Map; +import java.util.Objects; + +import static java.util.Collections.emptyMap; public class RubyGemsPreloader { @@ -32,6 +35,13 @@ public RubyGemsPreloader(Ruby rubyRuntime) { this.rubyRuntime = rubyRuntime; } + /** + * Preload required libraries based on the options passed to the AsciidoctorJ API. + * This method should only be used if a document is converted via the AsciidoctorJ API. + * @param options The map with options that will be passed to the AsciidoctorJ API. + * This is usually obtained by calling Options.map() on an Options instance. + * The attributes are passed as a nested Map with String keys and String values. + */ public void preloadRequiredLibraries(Map options) { if (options.containsKey(Options.ATTRIBUTES)) { @@ -59,23 +69,29 @@ && isOptionWithValue(attributes, Attributes.SOURCE_HIGHLIGHTER, CODERAY)) { preloadLibrary(Options.TEMPLATE_DIRS); } - if (isOptionSet(options, Options.BACKEND) && "epub3".equalsIgnoreCase((String) options.get(Options.BACKEND))) { - preloadLibrary(EPUB3); - } - - if (isOptionSet(options, Options.BACKEND) && "pdf".equalsIgnoreCase((String) options.get(Options.BACKEND))) { - preloadLibrary(PDF); - } - - if (isOptionSet(options, Options.BACKEND) && "revealjs".equalsIgnoreCase((String) options.get(Options.BACKEND))) { - preloadLibrary(REVEALJS); + if (isOptionSet(options, Options.BACKEND)) { + if ("epub3".equalsIgnoreCase(Objects.toString(options.get(Options.BACKEND)))) { + preloadLibrary(EPUB3); + } else if ("pdf".equalsIgnoreCase(Objects.toString(options.get(Options.BACKEND)))) { + preloadLibrary(PDF); + } else if ("revealjs".equalsIgnoreCase(Objects.toString(options.get(Options.BACKEND)))) { + preloadLibrary(REVEALJS); + } } } + /** + * Preload required libraries based on the options passed in the command line. + * This method is only to be used from the CLI module, since it relies on how command line arguments map + * to attributes and options expected by the Asciidoctor::Cli::Invoker. + * @param opts The options that will be passed to the Asciidoctor::Cli::Invoker. + * Should be a Hash with RubySymbol keys and arbitrary values. + * The attributes are passed as a nested Hash with String keys and String values. + */ public void preloadRequiredLibrariesCommandLine(RubyHash opts) { Ruby ruby = opts.getRuntime(); if (opts.containsKey(ruby.newSymbol(Options.ATTRIBUTES))) { - Map attributes = (Map) opts.get(ruby.newSymbol(Options.ATTRIBUTES)); + Map attributes = (Map) opts.getOrDefault(ruby.newSymbol(Options.ATTRIBUTES), emptyMap()); if (CODERAY.equals(attributes.get(Attributes.SOURCE_HIGHLIGHTER))) { preloadLibrary(Attributes.SOURCE_HIGHLIGHTER); @@ -88,13 +104,12 @@ public void preloadRequiredLibrariesCommandLine(RubyHash opts) { if (attributes.containsKey(Attributes.DATA_URI)) { preloadLibrary(Attributes.DATA_URI); } + if ("epub3".equals(attributes.get(Options.BACKEND))) { preloadLibrary(EPUB3); - } - if ("pdf".equals(attributes.get(Options.BACKEND))) { + } else if ("pdf".equals(attributes.get(Options.BACKEND))) { preloadLibrary(PDF); - } - if ("revealjs".equals(attributes.get(Options.BACKEND))) { + } else if ("revealjs".equals(attributes.get(Options.BACKEND))) { preloadLibrary(REVEALJS); } } From 8960ef0777273c08f8cfaf994d51197dfbb1d73a Mon Sep 17 00:00:00 2001 From: Robert Panzer Date: Sat, 16 Dec 2023 15:40:34 +0100 Subject: [PATCH 3/4] Unify implementation of RubyGemsPreloader again --- .../cli/jruby/AsciidoctorRubyInvoker.java | 2 +- .../jruby/internal/RubyGemsPreloader.java | 86 +++++-------------- .../jruby/internal/RubyHashUtil.java | 10 +-- 3 files changed, 26 insertions(+), 72 deletions(-) diff --git a/asciidoctorj-cli/src/main/java/org/asciidoctor/cli/jruby/AsciidoctorRubyInvoker.java b/asciidoctorj-cli/src/main/java/org/asciidoctor/cli/jruby/AsciidoctorRubyInvoker.java index 5c647f956..a91af2363 100644 --- a/asciidoctorj-cli/src/main/java/org/asciidoctor/cli/jruby/AsciidoctorRubyInvoker.java +++ b/asciidoctorj-cli/src/main/java/org/asciidoctor/cli/jruby/AsciidoctorRubyInvoker.java @@ -33,7 +33,7 @@ public AsciidoctorRubyInvoker(JRubyAsciidoctor asciidoctor) { public void invoke(List inputFiles, AsciidoctorCliOptions cliOptions) { RubyHash opts = cliOptions.parse(ruby); - new RubyGemsPreloader(asciidoctor.getRubyRuntime()).preloadRequiredLibrariesCommandLine(opts); + new RubyGemsPreloader(asciidoctor.getRubyRuntime()).preloadRequiredLibraries(opts); opts.put(ruby.newSymbol("input_files"), inputFiles.stream().map(f -> ruby.newString(f.getPath())).collect(Collectors.toList())); diff --git a/asciidoctorj-core/src/main/java/org/asciidoctor/jruby/internal/RubyGemsPreloader.java b/asciidoctorj-core/src/main/java/org/asciidoctor/jruby/internal/RubyGemsPreloader.java index 1512f3d70..edc2c19f3 100644 --- a/asciidoctorj-core/src/main/java/org/asciidoctor/jruby/internal/RubyGemsPreloader.java +++ b/asciidoctorj-core/src/main/java/org/asciidoctor/jruby/internal/RubyGemsPreloader.java @@ -3,12 +3,9 @@ import org.asciidoctor.Attributes; import org.asciidoctor.Options; import org.jruby.Ruby; -import org.jruby.RubyHash; import java.util.Map; -import java.util.Objects; - -import static java.util.Collections.emptyMap; +import java.util.Optional; public class RubyGemsPreloader { @@ -42,11 +39,11 @@ public RubyGemsPreloader(Ruby rubyRuntime) { * This is usually obtained by calling Options.map() on an Options instance. * The attributes are passed as a nested Map with String keys and String values. */ - public void preloadRequiredLibraries(Map options) { - - if (options.containsKey(Options.ATTRIBUTES)) { - Map attributes = (Map) options.get(Options.ATTRIBUTES); + public void preloadRequiredLibraries(Map options) { + Map opts = RubyHashUtil.convertRubyHashMapToMap(options); + Map attributes = (Map) opts.get(Options.ATTRIBUTES); + if (attributes != null) { if (isOptionSet(attributes, Attributes.SOURCE_HIGHLIGHTER) && isOptionWithValue(attributes, Attributes.SOURCE_HIGHLIGHTER, CODERAY)) { preloadLibrary(Attributes.SOURCE_HIGHLIGHTER); @@ -61,78 +58,37 @@ && isOptionWithValue(attributes, Attributes.SOURCE_HIGHLIGHTER, CODERAY)) { } } - if (isOptionSet(options, Options.ERUBY) && isOptionWithValue(options, Options.ERUBY, ERUBIS)) { - preloadLibrary(Options.ERUBY); - } - - if (isOptionSet(options, Options.TEMPLATE_DIRS)) { - preloadLibrary(Options.TEMPLATE_DIRS); - } - - if (isOptionSet(options, Options.BACKEND)) { - if ("epub3".equalsIgnoreCase(Objects.toString(options.get(Options.BACKEND)))) { - preloadLibrary(EPUB3); - } else if ("pdf".equalsIgnoreCase(Objects.toString(options.get(Options.BACKEND)))) { - preloadLibrary(PDF); - } else if ("revealjs".equalsIgnoreCase(Objects.toString(options.get(Options.BACKEND)))) { - preloadLibrary(REVEALJS); - } - } - } - - /** - * Preload required libraries based on the options passed in the command line. - * This method is only to be used from the CLI module, since it relies on how command line arguments map - * to attributes and options expected by the Asciidoctor::Cli::Invoker. - * @param opts The options that will be passed to the Asciidoctor::Cli::Invoker. - * Should be a Hash with RubySymbol keys and arbitrary values. - * The attributes are passed as a nested Hash with String keys and String values. - */ - public void preloadRequiredLibrariesCommandLine(RubyHash opts) { - Ruby ruby = opts.getRuntime(); - if (opts.containsKey(ruby.newSymbol(Options.ATTRIBUTES))) { - Map attributes = (Map) opts.getOrDefault(ruby.newSymbol(Options.ATTRIBUTES), emptyMap()); - - if (CODERAY.equals(attributes.get(Attributes.SOURCE_HIGHLIGHTER))) { - preloadLibrary(Attributes.SOURCE_HIGHLIGHTER); - } - - if (attributes.containsKey(Attributes.CACHE_URI)) { - preloadLibrary(Attributes.CACHE_URI); - } - - if (attributes.containsKey(Attributes.DATA_URI)) { - preloadLibrary(Attributes.DATA_URI); - } - - if ("epub3".equals(attributes.get(Options.BACKEND))) { - preloadLibrary(EPUB3); - } else if ("pdf".equals(attributes.get(Options.BACKEND))) { - preloadLibrary(PDF); - } else if ("revealjs".equals(attributes.get(Options.BACKEND))) { - preloadLibrary(REVEALJS); - } - } - - if (ERUBIS.equals(opts.get(ruby.newSymbol(Options.ERUBY)))) { + if (isOptionSet(opts, Options.ERUBY) && isOptionWithValue(opts, Options.ERUBY, ERUBIS)) { preloadLibrary(Options.ERUBY); } - if (opts.containsKey(ruby.newSymbol(Options.TEMPLATE_DIRS))) { + if (isOptionSet(opts, Options.TEMPLATE_DIRS)) { preloadLibrary(Options.TEMPLATE_DIRS); } + Optional.ofNullable(opts.get(Options.BACKEND)) + .or(() -> Optional.ofNullable(attributes).map(a -> a.get(Attributes.BACKEND))) + .map(Object::toString) + .ifPresent(backend -> { + if ("epub3".equalsIgnoreCase(backend)) { + preloadLibrary(EPUB3); + } else if ("pdf".equalsIgnoreCase(backend)) { + preloadLibrary(PDF); + } else if ("revealjs".equalsIgnoreCase(backend)) { + preloadLibrary(REVEALJS); + } + }); } private void preloadLibrary(String option) { this.rubyRuntime.evalScriptlet(optionToRequiredGem.get(option)); } - private boolean isOptionWithValue(Map attributes, String attribute, String value) { + private boolean isOptionWithValue(Map attributes, String attribute, String value) { return value.equals(attributes.get(attribute)); } - private boolean isOptionSet(Map attributes, String attribute) { + private boolean isOptionSet(Map attributes, String attribute) { return attributes.containsKey(attribute); } diff --git a/asciidoctorj-core/src/main/java/org/asciidoctor/jruby/internal/RubyHashUtil.java b/asciidoctorj-core/src/main/java/org/asciidoctor/jruby/internal/RubyHashUtil.java index f0684c9a6..5817b6ea4 100644 --- a/asciidoctorj-core/src/main/java/org/asciidoctor/jruby/internal/RubyHashUtil.java +++ b/asciidoctorj-core/src/main/java/org/asciidoctor/jruby/internal/RubyHashUtil.java @@ -109,13 +109,11 @@ private static boolean isNotARubySymbol(Object keyType) { return keyType instanceof CharSequence; } - public static Map convertRubyHashMapToMap(Map rubyHashMap) { + public static Map convertRubyHashMapToMap(Map rubyHashMap) { - Map map = new HashMap(); + Map map = new HashMap<>(); - Set> elements = rubyHashMap.entrySet(); - - for (Entry element : elements) { + for (Entry element : rubyHashMap.entrySet()) { if(element.getKey() instanceof RubySymbol) { map.put(toJavaString((RubySymbol)element.getKey()), toJavaObject(element.getValue())); } else { @@ -129,7 +127,7 @@ public static Map convertRubyHashMapToMap(Map ru public static Map convertRubyHashMapToStringObjectMap(RubyHash rubyHashMap) { - Map map = new HashMap(); + Map map = new HashMap<>(); Set> elements = rubyHashMap.entrySet(); From 8f34fd9afe38590f41e38fa6109232efa42843b2 Mon Sep 17 00:00:00 2001 From: Robert Panzer Date: Sat, 16 Dec 2023 16:08:30 +0100 Subject: [PATCH 4/4] Implement review feedback --- .../src/main/java/org/asciidoctor/Attributes.java | 6 +++++- .../asciidoctor/cli/AsciidoctorCliOptions.java | 15 +++++++-------- .../cli/WhenAsciidoctorAPICallIsCalled.java | 2 +- 3 files changed, 13 insertions(+), 10 deletions(-) diff --git a/asciidoctorj-api/src/main/java/org/asciidoctor/Attributes.java b/asciidoctorj-api/src/main/java/org/asciidoctor/Attributes.java index 125a28473..0dddf8e25 100644 --- a/asciidoctorj-api/src/main/java/org/asciidoctor/Attributes.java +++ b/asciidoctorj-api/src/main/java/org/asciidoctor/Attributes.java @@ -593,7 +593,7 @@ private void addAttributes(String[] allAttributes) { private void extractAttributeNameAndValue(String attribute, int equalsIndex) { String attributeName = attribute.substring(0, equalsIndex); - String attributeValue = attribute.substring(equalsIndex + 1, attribute.length()); + String attributeValue = attribute.substring(equalsIndex + 1); this.attributes.put(attributeName, attributeValue); } @@ -620,6 +620,10 @@ private static String toTime(Date time) { return TIME_FORMAT.format(time); } + public boolean isEmpty() { + return this.attributes.isEmpty(); + } + /** * @deprecated For internal use only. */ diff --git a/asciidoctorj-cli/src/main/java/org/asciidoctor/cli/AsciidoctorCliOptions.java b/asciidoctorj-cli/src/main/java/org/asciidoctor/cli/AsciidoctorCliOptions.java index 86ef2af1a..72030665a 100644 --- a/asciidoctorj-cli/src/main/java/org/asciidoctor/cli/AsciidoctorCliOptions.java +++ b/asciidoctorj-cli/src/main/java/org/asciidoctor/cli/AsciidoctorCliOptions.java @@ -13,7 +13,6 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; -import java.util.Map; import java.util.StringTokenizer; public class AsciidoctorCliOptions { @@ -277,16 +276,16 @@ private boolean isInPlaceRequired() { public RubyHash parse(Ruby ruby) { RubyHash opts = new RubyHash(ruby); - Map attributes = buildAttributes(); + Attributes attributes = buildAttributes(); opts.put(ruby.newSymbol(Options.STANDALONE), true); opts.put(ruby.newSymbol(Options.WARNINGS), false); if (this.backend != null) { - attributes.put(Options.BACKEND, this.backend); + attributes.setAttribute(Options.BACKEND, this.backend); } if (this.doctype != null) { - attributes.put(Options.DOCTYPE, this.doctype); + attributes.setAttribute(Options.DOCTYPE, this.doctype); } if (this.embedded) { opts.put(ruby.newSymbol(Options.STANDALONE), false); @@ -304,7 +303,7 @@ public RubyHash parse(Ruby ruby) { opts.put(ruby.newSymbol(Options.STANDALONE), false); } if (this.sectionNumbers) { - attributes.put(Attributes.SECTION_NUMBERS, ""); + attributes.setAttribute(Attributes.SECTION_NUMBERS, ""); } if (this.eruby != null) { opts.put(ruby.newSymbol(Options.ERUBY), this.eruby); @@ -331,7 +330,7 @@ public RubyHash parse(Ruby ruby) { opts.put(ruby.newSymbol(Options.WARNINGS), true); } if (!attributes.isEmpty()) { - opts.put(ruby.newSymbol(Options.ATTRIBUTES), attributes); + opts.put(ruby.newSymbol(Options.ATTRIBUTES), attributes.map()); } if (this.isSourceDirOption()) { opts.put(ruby.newSymbol("source_dir"), this.sourceDir); @@ -344,7 +343,7 @@ public RubyHash parse(Ruby ruby) { * {@link Attributes} instance. */ // FIXME Should be private, made protected for testing. - Map buildAttributes() { + Attributes buildAttributes() { final AttributesBuilder attributesBuilder = Attributes.builder(); for (String attribute : attributes) { int separatorIndex = attribute.indexOf(ATTRIBUTE_SEPARATOR); @@ -356,7 +355,7 @@ Map buildAttributes() { attributesBuilder.attribute(attribute); } } - return attributesBuilder.build().map(); + return attributesBuilder.build(); } private List splitByPathSeparator(String path) { diff --git a/asciidoctorj-cli/src/test/java/org/asciidoctor/cli/WhenAsciidoctorAPICallIsCalled.java b/asciidoctorj-cli/src/test/java/org/asciidoctor/cli/WhenAsciidoctorAPICallIsCalled.java index 354b990a5..dc46be8c1 100644 --- a/asciidoctorj-cli/src/test/java/org/asciidoctor/cli/WhenAsciidoctorAPICallIsCalled.java +++ b/asciidoctorj-cli/src/test/java/org/asciidoctor/cli/WhenAsciidoctorAPICallIsCalled.java @@ -33,7 +33,7 @@ public void api_parameters_should_be_transformed_to_cli_command() { assertThat(asciidoctorCliOptions.getSafeMode()).isEqualTo(SafeMode.UNSAFE); assertThat(asciidoctorCliOptions.getBackend()).isEqualTo("docbook"); assertThat(asciidoctorCliOptions.getParameters()).containsExactly("file.adoc"); - assertThat(asciidoctorCliOptions.buildAttributes()) + assertThat(asciidoctorCliOptions.buildAttributes().map()) .containsEntry("myAttribute", "myValue") .containsEntry("sectnums", "") .containsEntry("copycss!", "");