From 7504acdb4ac70819bfe8c1c3bcbf914999b57cd1 Mon Sep 17 00:00:00 2001 From: Ryan Kurtz Date: Wed, 2 Oct 2024 07:24:15 -0400 Subject: [PATCH] GP-4987: Generating HTML from Markdown --- .../GhidraDev/GhidraDevPlugin/build.gradle | 6 +- GhidraBuild/MarkdownSupport/build.gradle | 23 +++++ .../MarkdownSupport/certification.manifest | 2 + .../java/ghidra/markdown/MarkdownToHtml.java | 95 +++++++++++++++++++ GhidraDocs/build.gradle | 4 +- gradle/root/distribution.gradle | 26 +++++ licenses/BSD-2-ATLASSIAN.txt | 23 +++++ licenses/certification.manifest | 1 + settings.gradle | 1 + 9 files changed, 177 insertions(+), 4 deletions(-) create mode 100644 GhidraBuild/MarkdownSupport/build.gradle create mode 100644 GhidraBuild/MarkdownSupport/certification.manifest create mode 100644 GhidraBuild/MarkdownSupport/src/main/java/ghidra/markdown/MarkdownToHtml.java create mode 100644 licenses/BSD-2-ATLASSIAN.txt diff --git a/GhidraBuild/EclipsePlugins/GhidraDev/GhidraDevPlugin/build.gradle b/GhidraBuild/EclipsePlugins/GhidraDev/GhidraDevPlugin/build.gradle index 83b3539a41..e04ec6715b 100644 --- a/GhidraBuild/EclipsePlugins/GhidraDev/GhidraDevPlugin/build.gradle +++ b/GhidraBuild/EclipsePlugins/GhidraDev/GhidraDevPlugin/build.gradle @@ -122,11 +122,13 @@ rootProject.assembleDistribution { include 'GhidraDev*.zip' into "Extensions/Eclipse/GhidraDev/" } +} + +rootProject.assembleMarkdownToHtml { from ("${this.projectDir}/README.md") { into "Extensions/Eclipse/GhidraDev/" - rename 'README.md', 'GhidraDev_README.md' } -} +} // PrepDev dependencies rootProject.prepDev.dependsOn utilityJar diff --git a/GhidraBuild/MarkdownSupport/build.gradle b/GhidraBuild/MarkdownSupport/build.gradle new file mode 100644 index 0000000000..6ca736dcce --- /dev/null +++ b/GhidraBuild/MarkdownSupport/build.gradle @@ -0,0 +1,23 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +apply from: "$rootProject.projectDir/gradle/javaProject.gradle" +apply plugin: 'eclipse' +eclipse.project.name = '_MarkdownSupport' + +dependencies { + implementation 'org.commonmark:commonmark:0.23.0' + implementation 'org.commonmark:commonmark-ext-heading-anchor:0.23.0' +} diff --git a/GhidraBuild/MarkdownSupport/certification.manifest b/GhidraBuild/MarkdownSupport/certification.manifest new file mode 100644 index 0000000000..2e93ba7ea4 --- /dev/null +++ b/GhidraBuild/MarkdownSupport/certification.manifest @@ -0,0 +1,2 @@ +##VERSION: 2.0 +##MODULE IP: BSD-2-ATLASSIAN diff --git a/GhidraBuild/MarkdownSupport/src/main/java/ghidra/markdown/MarkdownToHtml.java b/GhidraBuild/MarkdownSupport/src/main/java/ghidra/markdown/MarkdownToHtml.java new file mode 100644 index 0000000000..47cbfdacf8 --- /dev/null +++ b/GhidraBuild/MarkdownSupport/src/main/java/ghidra/markdown/MarkdownToHtml.java @@ -0,0 +1,95 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.markdown; + +import java.io.*; +import java.util.List; +import java.util.Map; + +import org.commonmark.Extension; +import org.commonmark.ext.heading.anchor.HeadingAnchorExtension; +import org.commonmark.node.Link; +import org.commonmark.node.Node; +import org.commonmark.parser.Parser; +import org.commonmark.renderer.html.*; + +/** + * Program to convert a Markdown file to an HTML file + */ +public class MarkdownToHtml { + + /** + * Converts a Markdown file to an HTML file + * + * @param args An array of 2 arguments: The path of the markdown file to convert, and the path + * to save the new HTML file to + * @throws Exception If invalid arguments are passed in, or if there is an issue writing the + * new HTML file + */ + public static void main(String[] args) throws Exception { + + // Validate input + if (args.length != 2) { + throw new Exception("Expected 2 arguments, got " + args.length); + } + if (!args[0].toLowerCase().endsWith(".md")) { + throw new Exception("First argument doesn't not end with .md"); + } + + // Setup the CommonMark Library with the needed "anchor extension" library + List extensions = List.of(HeadingAnchorExtension.create()); + Parser parser = Parser.builder().extensions(extensions).build(); + HtmlRenderer renderer = HtmlRenderer.builder() + .extensions(extensions) + .attributeProviderFactory(new LinkAttributeProvider()) + .build(); + + // Create output directory (if necessary) + File inFile = new File(args[0]); + File outFile = new File(args[1]); + if (!outFile.getParentFile().isDirectory() && !outFile.getParentFile().mkdirs()) { + throw new Exception("Failed to create: " + outFile.getParent()); + } + + // Generate and write the HTML + String html = renderer.render(parser.parseReader(new FileReader(inFile))); + try (PrintWriter out = new PrintWriter(outFile)) { + out.write(html); + } + } + + /** + * Class to help adjust links to Markdown files to instead become links to HTML files + */ + private static class LinkAttributeProvider + implements AttributeProvider, AttributeProviderFactory { + + @Override + public AttributeProvider create(AttributeProviderContext attributeProviderContext) { + return new LinkAttributeProvider(); + } + + @Override + public void setAttributes(Node node, String tagName, Map attributes) { + if (node instanceof Link) { + String href = attributes.get("href"); + if (href != null && !href.startsWith("#") && href.toLowerCase().endsWith(".md")) { + attributes.put("href", href.substring(0, href.length() - 2) + "html"); + } + } + } + } +} diff --git a/GhidraDocs/build.gradle b/GhidraDocs/build.gradle index 7a6427e73a..2243c7eb90 100644 --- a/GhidraDocs/build.gradle +++ b/GhidraDocs/build.gradle @@ -4,9 +4,9 @@ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. diff --git a/gradle/root/distribution.gradle b/gradle/root/distribution.gradle index 6ce7172fef..6c0653bf61 100644 --- a/gradle/root/distribution.gradle +++ b/gradle/root/distribution.gradle @@ -500,6 +500,31 @@ task assembleSource (type: Copy) { } +/************************************************************************************ + * + * Copies a markdown file and a generaterated html file into the distribution folder. + * + ************************************************************************************/ +task assembleMarkdownToHtml (type: Copy) { + group 'private' + description "Copies a markdown file and a generaterated html file into the distribution folder" + + dependsOn ':MarkdownSupport:classes' + outputs.upToDateWhen {false} + destinationDir file(DISTRIBUTION_DIR.getPath() + "/" + ZIP_DIR_PREFIX) + + eachFile { f -> + def htmlName = f.sourceName[0..-3] + "html" + def htmlPath = f.relativePath.replaceLastName(htmlName).pathString + + javaexec { + classpath = project(':MarkdownSupport').sourceSets.main.runtimeClasspath + mainClass = 'ghidra.markdown.MarkdownToHtml' + args f.file + args file("${destinationDir.path}/${htmlPath}") + } + } +} /********************************************************************************* * @@ -581,6 +606,7 @@ task assembleAll() { dependsOn assembleDistribution dependsOn assembleSource + dependsOn assembleMarkdownToHtml dependsOn "assembleDistribution_$currentPlatform" if (project.hasProperty("allPlatforms")) { diff --git a/licenses/BSD-2-ATLASSIAN.txt b/licenses/BSD-2-ATLASSIAN.txt new file mode 100644 index 0000000000..b09e367cef --- /dev/null +++ b/licenses/BSD-2-ATLASSIAN.txt @@ -0,0 +1,23 @@ +Copyright (c) 2015, Atlassian Pty Ltd +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/licenses/certification.manifest b/licenses/certification.manifest index 7a4ca7cbb6..dae15d303f 100644 --- a/licenses/certification.manifest +++ b/licenses/certification.manifest @@ -1,6 +1,7 @@ ##VERSION: 2.0 Apache_License_2.0.txt||LICENSE||||END| Apache_License_2.0_with_LLVM_Exceptions.txt||LICENSE||||END| +BSD-2-ATLASSIAN.txt||LICENSE||||END| BSD-2-ORACLE.txt||LICENSE||||END| BSD-3-APPLE.txt||LICENSE||||END| BSD-3-CAPSTONE.txt||LICENSE||||END| diff --git a/settings.gradle b/settings.gradle index 599e2c7c34..ed753c9816 100644 --- a/settings.gradle +++ b/settings.gradle @@ -35,6 +35,7 @@ includeProjects('GPL') *******************************************************************************************/ includeProject('Doclets', 'GhidraBuild/BuildFiles', true) includeProject('LaunchSupport', 'GhidraBuild', true) +includeProject('MarkdownSupport', 'GhidraBuild', true) includeProject('Skeleton', 'GhidraBuild', true) includeProject('BuildFiles', 'GhidraBuild', true) includeProject('decompile', 'Ghidra/Features/Decompiler/src', true)