Add pandoc-plugin and conversion to latex
This commit is contained in:
parent
7cccb96023
commit
5659bd2f93
@ -45,4 +45,5 @@ include(
|
|||||||
":snark-storage-driver",
|
":snark-storage-driver",
|
||||||
":snark-document-builder",
|
":snark-document-builder",
|
||||||
":snark-main",
|
":snark-main",
|
||||||
|
":snark-pandoc-plugin",
|
||||||
)
|
)
|
@ -14,6 +14,8 @@ dependencies {
|
|||||||
api("io.ktor:ktor-server-html-builder:$ktorVersion")
|
api("io.ktor:ktor-server-html-builder:$ktorVersion")
|
||||||
|
|
||||||
implementation(project(":snark-storage-driver"))
|
implementation(project(":snark-storage-driver"))
|
||||||
|
implementation(project(":snark-pandoc-plugin"))
|
||||||
|
|
||||||
implementation("com.fasterxml.jackson.module:jackson-module-kotlin:$jacksonVersion")
|
implementation("com.fasterxml.jackson.module:jackson-module-kotlin:$jacksonVersion")
|
||||||
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.3")
|
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.3")
|
||||||
}
|
}
|
||||||
|
@ -1,17 +1,15 @@
|
|||||||
package documentBuilder
|
package documentBuilder
|
||||||
|
|
||||||
import com.fasterxml.jackson.core.io.BigDecimalParser
|
|
||||||
import space.kscience.snark.storage.*
|
|
||||||
import java.nio.file.Path
|
|
||||||
import java.nio.file.Paths
|
|
||||||
import kotlinx.html.*
|
|
||||||
import kotlinx.html.dom.createHTMLDocument
|
|
||||||
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
|
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
|
||||||
import com.fasterxml.jackson.module.kotlin.readValue
|
import space.kscience.snark.pandoc.PandocCommandBuilder
|
||||||
import kotlinx.serialization.json.Json
|
import space.kscience.snark.pandoc.PandocWrapper
|
||||||
|
import space.kscience.snark.storage.*
|
||||||
|
import java.nio.file.Files
|
||||||
|
import java.nio.file.Path
|
||||||
import kotlin.io.path.*
|
import kotlin.io.path.*
|
||||||
|
|
||||||
private val SNARK_HTML_RENDER = "snark-document-builder/src/main/nodejs/HtmlRenderer.js"
|
private val SNARK_HTML_RENDER = "snark-document-builder/src/main/nodejs/HtmlRenderer.js"
|
||||||
|
private val SNARK_MD_RENDERER = "snark-document-builder/src/main/nodejs/MdRenderer.js"
|
||||||
fun getHtml(ast_string: String): String
|
fun getHtml(ast_string: String): String
|
||||||
{
|
{
|
||||||
return ProcessBuilder("node", SNARK_HTML_RENDER, ast_string)
|
return ProcessBuilder("node", SNARK_HTML_RENDER, ast_string)
|
||||||
@ -20,6 +18,30 @@ fun getHtml(ast_string: String): String
|
|||||||
.start().inputStream.bufferedReader().readText()
|
.start().inputStream.bufferedReader().readText()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getLatex(ast_string: String) : Path
|
||||||
|
{
|
||||||
|
val outputMd = Files.createTempFile(Path.of("./data/"), "output", ".md")
|
||||||
|
|
||||||
|
val output = ProcessBuilder("node", SNARK_MD_RENDERER, ast_string)
|
||||||
|
.redirectOutput(ProcessBuilder.Redirect.PIPE)
|
||||||
|
.redirectError(ProcessBuilder.Redirect.INHERIT)
|
||||||
|
.start().inputStream.bufferedReader().readText()
|
||||||
|
outputMd.writeText(output)
|
||||||
|
|
||||||
|
val outputTex = Files.createTempFile(Path.of("./data/"), "output", ".tex")
|
||||||
|
|
||||||
|
val pandocWrapper = PandocWrapper()
|
||||||
|
pandocWrapper.use { p: PandocWrapper? ->
|
||||||
|
val command = PandocCommandBuilder(
|
||||||
|
listOf<Path>(outputMd),
|
||||||
|
outputTex
|
||||||
|
)
|
||||||
|
PandocWrapper.execute(command)
|
||||||
|
}
|
||||||
|
|
||||||
|
return outputTex
|
||||||
|
}
|
||||||
|
|
||||||
private val DEFAULT_DOCUMENT_ROOT = "main.md"
|
private val DEFAULT_DOCUMENT_ROOT = "main.md"
|
||||||
|
|
||||||
public suspend fun buildDocument(root: Directory, path: Path): String {
|
public suspend fun buildDocument(root: Directory, path: Path): String {
|
||||||
@ -40,6 +62,18 @@ public suspend fun buildDocument(root: Directory, path: Path): String {
|
|||||||
return getHtml(jacksonObjectMapper().writeValueAsString(root))
|
return getHtml(jacksonObjectMapper().writeValueAsString(root))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public suspend fun buildLatex(root: Directory, path: Path) : Path {
|
||||||
|
val dependencyGraph = buildDependencyGraph(root, path)
|
||||||
|
|
||||||
|
val graphManage = GraphManager(dependencyGraph)
|
||||||
|
|
||||||
|
graphManage.buildDocument(path.toString())
|
||||||
|
|
||||||
|
val root: MdAstRoot = dependencyGraph.nodes[path.toString()]!!.mdAst
|
||||||
|
|
||||||
|
return getLatex(jacksonObjectMapper().writeValueAsString(root))
|
||||||
|
}
|
||||||
|
|
||||||
public suspend fun buildDependencyGraph(root: Directory, path: Path): DependencyGraph {
|
public suspend fun buildDependencyGraph(root: Directory, path: Path): DependencyGraph {
|
||||||
val nodes = HashMap<FileName, DependencyGraphNode>()
|
val nodes = HashMap<FileName, DependencyGraphNode>()
|
||||||
|
|
||||||
|
14
snark-document-builder/src/main/nodejs/MdRenderer.js
Normal file
14
snark-document-builder/src/main/nodejs/MdRenderer.js
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import {toMarkdown} from 'mdast-util-to-markdown'
|
||||||
|
|
||||||
|
main()
|
||||||
|
|
||||||
|
function main()
|
||||||
|
{
|
||||||
|
if (process.argv.length < 3)
|
||||||
|
throw "No input"
|
||||||
|
|
||||||
|
const md_ast = JSON.parse(process.argv[2])
|
||||||
|
const markdown = toMarkdown(md_ast)
|
||||||
|
|
||||||
|
console.log(markdown)
|
||||||
|
}
|
@ -14,6 +14,8 @@ dependencies {
|
|||||||
api("io.ktor:ktor-server-host-common:$ktorVersion")
|
api("io.ktor:ktor-server-host-common:$ktorVersion")
|
||||||
implementation("io.ktor:ktor-server-netty:2.3.0")
|
implementation("io.ktor:ktor-server-netty:2.3.0")
|
||||||
implementation(project(":snark-storage-driver"))
|
implementation(project(":snark-storage-driver"))
|
||||||
|
implementation("io.ktor:ktor-server-partial-content:$ktorVersion")
|
||||||
|
implementation("io.ktor:ktor-server-auto-head-response:$ktorVersion")
|
||||||
|
|
||||||
testApi("io.ktor:ktor-server-tests:$ktorVersion")
|
testApi("io.ktor:ktor-server-tests:$ktorVersion")
|
||||||
}
|
}
|
@ -1,18 +1,17 @@
|
|||||||
package space.kscience.snark.ktor
|
package space.kscience.snark.ktor
|
||||||
|
|
||||||
import io.ktor.http.*
|
import io.ktor.http.*
|
||||||
|
import io.ktor.server.application.*
|
||||||
|
import io.ktor.server.response.*
|
||||||
|
import io.ktor.server.routing.*
|
||||||
import io.ktor.http.content.*
|
import io.ktor.http.content.*
|
||||||
import io.ktor.server.engine.*
|
import io.ktor.server.engine.*
|
||||||
import io.ktor.server.netty.*
|
import io.ktor.server.netty.*
|
||||||
import io.ktor.server.application.*
|
|
||||||
import io.ktor.server.html.*
|
import io.ktor.server.html.*
|
||||||
import io.ktor.server.request.*
|
import io.ktor.server.request.*
|
||||||
import io.ktor.server.response.*
|
|
||||||
import kotlinx.html.*
|
import kotlinx.html.*
|
||||||
import io.ktor.server.routing.*
|
|
||||||
import space.kscience.snark.storage.Directory
|
import space.kscience.snark.storage.Directory
|
||||||
import space.kscience.snark.storage.unzip.unzip
|
import space.kscience.snark.storage.unzip.unzip
|
||||||
import java.io.File
|
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
import kotlin.io.createTempFile
|
import kotlin.io.createTempFile
|
||||||
import kotlin.io.writeBytes
|
import kotlin.io.writeBytes
|
||||||
@ -22,6 +21,7 @@ public interface DataHolder {
|
|||||||
public suspend fun init(relativePath: Path) : Directory
|
public suspend fun init(relativePath: Path) : Directory
|
||||||
|
|
||||||
public suspend fun represent(relativePath: Path): String
|
public suspend fun represent(relativePath: Path): String
|
||||||
|
public suspend fun toPdf(relativePath: Path) : Path
|
||||||
}
|
}
|
||||||
|
|
||||||
public class SNARKServer(private val dataHolder: DataHolder, private val port: Int): Runnable {
|
public class SNARKServer(private val dataHolder: DataHolder, private val port: Int): Runnable {
|
||||||
@ -35,6 +35,16 @@ public class SNARKServer(private val dataHolder: DataHolder, private val port: I
|
|||||||
private suspend fun renderGet(call: ApplicationCall) {
|
private suspend fun renderGet(call: ApplicationCall) {
|
||||||
call.respondText(dataHolder.represent(relativePath), ContentType.Text.Html)
|
call.respondText(dataHolder.represent(relativePath), ContentType.Text.Html)
|
||||||
}
|
}
|
||||||
|
private suspend fun renderFile(call: ApplicationCall) {
|
||||||
|
call.response.header(
|
||||||
|
HttpHeaders.ContentDisposition,
|
||||||
|
ContentDisposition.Attachment.withParameter(ContentDisposition.Parameters.FileName, "output.tex")
|
||||||
|
.toString()
|
||||||
|
)
|
||||||
|
call.respondFile(dataHolder.toPdf(relativePath).toFile())
|
||||||
|
call.respondRedirect("/")
|
||||||
|
|
||||||
|
}
|
||||||
private suspend fun renderUpload(call: ApplicationCall) {
|
private suspend fun renderUpload(call: ApplicationCall) {
|
||||||
val multipartData = call.receiveMultipart()
|
val multipartData = call.receiveMultipart()
|
||||||
val tmp = createTempFile(suffix=".zip")
|
val tmp = createTempFile(suffix=".zip")
|
||||||
@ -89,6 +99,11 @@ public class SNARKServer(private val dataHolder: DataHolder, private val port: I
|
|||||||
a("/data") {
|
a("/data") {
|
||||||
+"Show data\n"
|
+"Show data\n"
|
||||||
}
|
}
|
||||||
|
getForm (action = "/download") {
|
||||||
|
button {
|
||||||
|
+"Download latex\n"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -107,6 +122,9 @@ public class SNARKServer(private val dataHolder: DataHolder, private val port: I
|
|||||||
get("/data") {
|
get("/data") {
|
||||||
renderGet(call)
|
renderGet(call)
|
||||||
}
|
}
|
||||||
|
get("/download") {
|
||||||
|
renderFile(call)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}.start(wait = true)
|
}.start(wait = true)
|
||||||
}
|
}
|
||||||
|
@ -13,4 +13,8 @@ internal class ServerDataHolder(private val directory: Directory): DataHolder {
|
|||||||
override suspend fun represent(relativePath: Path): String {
|
override suspend fun represent(relativePath: Path): String {
|
||||||
return buildDocument(directory, relativePath)
|
return buildDocument(directory, relativePath)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun toPdf(relativePath: Path) : Path {
|
||||||
|
return buildLatex(directory, relativePath)
|
||||||
|
}
|
||||||
}
|
}
|
2
snark-pandoc-plugin/.gitignore
vendored
Normal file
2
snark-pandoc-plugin/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
.pandoc/
|
||||||
|
|
25
snark-pandoc-plugin/build.gradle.kts
Normal file
25
snark-pandoc-plugin/build.gradle.kts
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
plugins {
|
||||||
|
id("java")
|
||||||
|
}
|
||||||
|
|
||||||
|
repositories {
|
||||||
|
mavenCentral()
|
||||||
|
}
|
||||||
|
|
||||||
|
java.sourceCompatibility = JavaVersion.VERSION_11
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation("commons-io:commons-io:2.7")
|
||||||
|
implementation("org.slf4j:slf4j-simple:2.0.6")
|
||||||
|
implementation("org.slf4j:slf4j-api:2.0.6")
|
||||||
|
implementation("org.apache.commons:commons-exec:1.3")
|
||||||
|
implementation("org.apache.commons:commons-compress:1.2")
|
||||||
|
implementation("org.apache.ant:ant:1.10.13")
|
||||||
|
implementation("com.fasterxml.jackson.core:jackson-databind:2.14.2")
|
||||||
|
testImplementation("org.junit.jupiter:junit-jupiter-api:5.8.1")
|
||||||
|
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.8.1")
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.getByName<Test>("test") {
|
||||||
|
useJUnitPlatform()
|
||||||
|
}
|
@ -0,0 +1,367 @@
|
|||||||
|
package space.kscience.snark.pandoc;
|
||||||
|
|
||||||
|
import java.io.BufferedInputStream;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.FileNotFoundException;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.HttpURLConnection;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.net.http.HttpClient;
|
||||||
|
import java.net.http.HttpRequest;
|
||||||
|
import java.net.http.HttpResponse;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.util.Enumeration;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Properties;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.DeserializationFeature;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import org.apache.commons.compress.archivers.ArchiveEntry;
|
||||||
|
import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
|
||||||
|
import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
|
||||||
|
import org.apache.commons.compress.archivers.zip.ZipFile;
|
||||||
|
import org.apache.commons.compress.compressors.gzip.GzipCompressorInputStream;
|
||||||
|
import org.apache.commons.exec.OS;
|
||||||
|
import org.apache.commons.io.FileUtils;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
|
||||||
|
public class Installer {
|
||||||
|
|
||||||
|
private static final Logger log
|
||||||
|
= LoggerFactory.getLogger(Installer.class);
|
||||||
|
|
||||||
|
public enum OSType {
|
||||||
|
WINDOWS("windows-x86_64.zip", "windows"),
|
||||||
|
MAC_OS_AMD("x86_64-macOS.zip", "mac.os.amd"),
|
||||||
|
MAC_OS_ARM("arm64-macOS.zip", "mac.os.arm"),
|
||||||
|
LINUX_ARM("linux-arm64", "linux.arm"),
|
||||||
|
LINUX_AMD("linux-amd64", "linux.amd");
|
||||||
|
|
||||||
|
private final String assetSuffix;
|
||||||
|
private final String propertySuffix;
|
||||||
|
OSType(String assetSuf, String propertySuf) {
|
||||||
|
assetSuffix = assetSuf;
|
||||||
|
propertySuffix = propertySuf;
|
||||||
|
}
|
||||||
|
public String getAssetSuffix() {
|
||||||
|
return assetSuffix;
|
||||||
|
}
|
||||||
|
public String getPropertySuffix() {
|
||||||
|
return propertySuffix;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class OSData {
|
||||||
|
private URL urlForInstalling;
|
||||||
|
private Path fileToInstall;
|
||||||
|
private Path pathToPandoc;
|
||||||
|
public OSData() {}
|
||||||
|
public void setUrlForInstalling(URL urlForInstalling) {
|
||||||
|
this.urlForInstalling = urlForInstalling;
|
||||||
|
}
|
||||||
|
public URL getUrlForInstalling() {
|
||||||
|
return urlForInstalling;
|
||||||
|
}
|
||||||
|
public void setFileToInstall(Path fileToInstall) {
|
||||||
|
this.fileToInstall = fileToInstall;
|
||||||
|
}
|
||||||
|
public Path getFileToInstall() {
|
||||||
|
return fileToInstall;
|
||||||
|
}
|
||||||
|
public Path getPathToPandoc() {
|
||||||
|
return pathToPandoc;
|
||||||
|
}
|
||||||
|
public void setPathToPandoc(Path pathToPandoc) {
|
||||||
|
this.pathToPandoc = pathToPandoc;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Map<OSType, OSData> dataForInstalling = new HashMap<>(OSType.values().length);
|
||||||
|
private static final Properties properties = new Properties();
|
||||||
|
private static Path pandocDir = Path.of("./pandoc").toAbsolutePath();
|
||||||
|
private static final int TIMEOUT_SECONDS = 2;
|
||||||
|
private static final int ATTEMPTS = 3;
|
||||||
|
|
||||||
|
Installer() throws IOException, InterruptedException {
|
||||||
|
try {
|
||||||
|
properties.load(new FileInputStream(
|
||||||
|
Thread.currentThread().getContextClassLoader().getResource("installer.properties").getPath()
|
||||||
|
));
|
||||||
|
} catch (Exception ex) {
|
||||||
|
log.error("Error during download properties, ex: {}", ex.getMessage(), ex);
|
||||||
|
throw ex;
|
||||||
|
}
|
||||||
|
|
||||||
|
initFiles();
|
||||||
|
var resp = getGithubUrls();
|
||||||
|
initUrls(resp);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initFiles() {
|
||||||
|
for (var os : OSType.values()) {
|
||||||
|
dataForInstalling.put(os, new OSData());
|
||||||
|
switch (os) {
|
||||||
|
case LINUX_AMD :
|
||||||
|
case LINUX_ARM :
|
||||||
|
dataForInstalling.get(os)
|
||||||
|
.setFileToInstall(Path.of(pandocDir.toString() + "/pandoc.tar.gz"));
|
||||||
|
break;
|
||||||
|
default :
|
||||||
|
dataForInstalling.get(os)
|
||||||
|
.setFileToInstall(Path.of(pandocDir.toString() + "/pandoc.zip"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initUrls(ResponseDto responseDto) throws IOException {
|
||||||
|
|
||||||
|
for (var os : OSType.values()) {
|
||||||
|
var asset = responseDto.getAssetByOsSuffix(os.getAssetSuffix());
|
||||||
|
var currUrl = asset.getBrowserDownloadUrl();
|
||||||
|
|
||||||
|
var currPath = properties.getProperty("path.to.pandoc." + os.getPropertySuffix()).replace("{version}",
|
||||||
|
responseDto.getTagName());
|
||||||
|
|
||||||
|
dataForInstalling.get(os).setUrlForInstalling(URI.create(currUrl).toURL());
|
||||||
|
dataForInstalling.get(os).setPathToPandoc(Path.of(pandocDir.toString() + currPath));
|
||||||
|
log.info("Init {} url : {}, path to pandoc: {}", os, currUrl, dataForInstalling.get(os).getPathToPandoc());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Install last released pandoc from github
|
||||||
|
* @return path to executable pandoc
|
||||||
|
* @throws IOException in case incorrect github url or path of installation directory
|
||||||
|
*/
|
||||||
|
Path installPandoc() throws IOException {
|
||||||
|
log.info("Start install");
|
||||||
|
Path res;
|
||||||
|
if (OS.isFamilyMac()) {
|
||||||
|
if (OS.isArch("aarch64")) {
|
||||||
|
res = installPandoc(OSType.MAC_OS_ARM);
|
||||||
|
} else {
|
||||||
|
res = installPandoc(OSType.MAC_OS_AMD);
|
||||||
|
}
|
||||||
|
} else if (OS.isFamilyUnix()) {
|
||||||
|
if (OS.isArch("aarch64")) {
|
||||||
|
res = installPandoc(OSType.LINUX_ARM);
|
||||||
|
} else {
|
||||||
|
res = installPandoc(OSType.LINUX_AMD);
|
||||||
|
}
|
||||||
|
} else if (OS.isFamilyWindows()) {
|
||||||
|
res = installPandoc(OSType.WINDOWS);
|
||||||
|
} else {
|
||||||
|
throw new RuntimeException("Got unexpected os, could not install pandoc");
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Path installPandoc(OSType os) throws IOException {
|
||||||
|
log.info(
|
||||||
|
"Start installing pandoc os: {}, url: {}, file: {}",
|
||||||
|
os,
|
||||||
|
dataForInstalling.get(os).getUrlForInstalling(),
|
||||||
|
dataForInstalling.get(os).getFileToInstall()
|
||||||
|
);
|
||||||
|
|
||||||
|
clearInstallingDirectory();
|
||||||
|
|
||||||
|
if (!handleSaving(os)) {
|
||||||
|
throw new RuntimeException("Could not save file from github");
|
||||||
|
}
|
||||||
|
if (!unarchive(os)) {
|
||||||
|
throw new RuntimeException("Could not unzip file");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!dataForInstalling.get(os).getPathToPandoc().toFile().setExecutable(true)) {
|
||||||
|
throw new RuntimeException("Could not make pandoc executable");
|
||||||
|
}
|
||||||
|
|
||||||
|
return dataForInstalling.get(os).getPathToPandoc();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Downloads from a (http/https) URL and saves to a file.
|
||||||
|
* @param file File to write. Parent directory will be created if necessary
|
||||||
|
* @param url http/https url to connect
|
||||||
|
* @param secsConnectTimeout Seconds to wait for connection establishment
|
||||||
|
* @param secsReadTimeout Read timeout in seconds - trasmission will abort if it freezes more than this
|
||||||
|
* @return true if successfully save file and false if:
|
||||||
|
* connection interrupted, timeout (but something was read)
|
||||||
|
* server error (500...)
|
||||||
|
* could not connect: connection timeout java.net.SocketTimeoutException
|
||||||
|
* could not connect: java.net.ConnectException
|
||||||
|
* could not resolve host (bad host, or no internet - no dns)
|
||||||
|
* @throws IOException Only if URL is malformed or if could not create the file
|
||||||
|
* @throws FileNotFoundException if did not find file for save
|
||||||
|
*/
|
||||||
|
private boolean saveUrl(final Path file, final URL url,
|
||||||
|
int secsConnectTimeout, int secsReadTimeout) throws IOException {
|
||||||
|
Files.createDirectories(file.getParent()); // make sure parent dir exists , this can throw exception
|
||||||
|
var conn = url.openConnection(); // can throw exception if bad url
|
||||||
|
if (secsConnectTimeout > 0) {
|
||||||
|
conn.setConnectTimeout(secsConnectTimeout * 1000);
|
||||||
|
}
|
||||||
|
if (secsReadTimeout > 0) {
|
||||||
|
conn.setReadTimeout(secsReadTimeout * 1000);
|
||||||
|
}
|
||||||
|
var ret = true;
|
||||||
|
boolean somethingRead = false;
|
||||||
|
try (var is = conn.getInputStream()) {
|
||||||
|
try (var in = new BufferedInputStream(is);
|
||||||
|
var fout = Files.newOutputStream(file)) {
|
||||||
|
final byte data[] = new byte[8192];
|
||||||
|
int count;
|
||||||
|
while ((count = in.read(data)) > 0) {
|
||||||
|
somethingRead = true;
|
||||||
|
fout.write(data, 0, count);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (java.io.IOException e) {
|
||||||
|
int httpcode = 999;
|
||||||
|
try {
|
||||||
|
httpcode = ((HttpURLConnection) conn).getResponseCode();
|
||||||
|
} catch (Exception ee) {}
|
||||||
|
|
||||||
|
if (e instanceof FileNotFoundException) {
|
||||||
|
throw new FileNotFoundException("Did not found file for install");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (somethingRead && e instanceof java.net.SocketTimeoutException) {
|
||||||
|
log.error("Read something, but connection interrupted: {}", e.getMessage(), e);
|
||||||
|
ret = false;
|
||||||
|
} else if (httpcode >= 400 && httpcode < 600 ) {
|
||||||
|
log.error("Got server error, httpcode: {}", httpcode);
|
||||||
|
ret = false;
|
||||||
|
} else if (e instanceof java.net.SocketTimeoutException) {
|
||||||
|
log.error("Connection timeout: {}", e.getMessage(), e);
|
||||||
|
ret = false;
|
||||||
|
} else if (e instanceof java.net.ConnectException) {
|
||||||
|
log.error("Could not connect: {}", e.getMessage(), e);
|
||||||
|
ret = false;
|
||||||
|
} else if (e instanceof java.net.UnknownHostException ) {
|
||||||
|
log.error("Could not resolve host: {}", e.getMessage(), e);
|
||||||
|
ret = false;
|
||||||
|
} else {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean handleSaving(OSType os) throws IOException {
|
||||||
|
var attempt = 0;
|
||||||
|
var saveFile = false;
|
||||||
|
|
||||||
|
while (attempt < ATTEMPTS && !saveFile) {
|
||||||
|
++attempt;
|
||||||
|
saveFile = saveUrl(
|
||||||
|
dataForInstalling.get(os).getFileToInstall(),
|
||||||
|
dataForInstalling.get(os).getUrlForInstalling(),
|
||||||
|
TIMEOUT_SECONDS,
|
||||||
|
TIMEOUT_SECONDS);
|
||||||
|
}
|
||||||
|
|
||||||
|
return saveFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean unarchive(OSType os) {
|
||||||
|
try {
|
||||||
|
switch (os) {
|
||||||
|
case LINUX_AMD:
|
||||||
|
case LINUX_ARM :
|
||||||
|
unTarGz(dataForInstalling.get(os).getFileToInstall(), pandocDir);
|
||||||
|
break;
|
||||||
|
default :
|
||||||
|
unZip(dataForInstalling.get(os).getFileToInstall(), pandocDir);
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
log.error("Could not perform unarchiving: {}", e.getMessage(), e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
private void unTarGz(Path pathInput, Path targetDir) throws IOException {
|
||||||
|
try (var tarIn =
|
||||||
|
new TarArchiveInputStream(
|
||||||
|
new GzipCompressorInputStream(
|
||||||
|
new BufferedInputStream(Files.newInputStream(pathInput))))) {
|
||||||
|
ArchiveEntry archiveEntry;
|
||||||
|
while ((archiveEntry = tarIn.getNextEntry()) != null) {
|
||||||
|
var pathEntryOutput = targetDir.resolve(archiveEntry.getName());
|
||||||
|
if (archiveEntry.isDirectory()) {
|
||||||
|
Files.createDirectory(pathEntryOutput);
|
||||||
|
} else {
|
||||||
|
Files.copy(tarIn, pathEntryOutput);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void unZip(Path pathInput, Path targetDir) throws IOException {
|
||||||
|
ZipFile zipFile = new ZipFile(pathInput.toFile());
|
||||||
|
try {
|
||||||
|
Enumeration<ZipArchiveEntry> entries = zipFile.getEntries();
|
||||||
|
while (entries.hasMoreElements()) {
|
||||||
|
ZipArchiveEntry zipEntry = entries.nextElement();
|
||||||
|
var pathEntryOutput = targetDir.resolve(zipEntry.getName());
|
||||||
|
if (zipEntry.isDirectory()) {
|
||||||
|
Files.createDirectories(pathEntryOutput);
|
||||||
|
} else {
|
||||||
|
Files.createDirectories(pathEntryOutput.getParent());
|
||||||
|
Files.copy(zipFile.getInputStream(zipEntry), pathEntryOutput);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
zipFile.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear installing directory
|
||||||
|
*/
|
||||||
|
public static void clearInstallingDirectory() {
|
||||||
|
try {
|
||||||
|
FileUtils.cleanDirectory(pandocDir.toFile());
|
||||||
|
} catch (IOException e) {
|
||||||
|
log.error("Could not clean installing directory");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set directory to install pandoc
|
||||||
|
* @param newDir
|
||||||
|
*/
|
||||||
|
public void setInstallingDirectory(Path newDir) {
|
||||||
|
pandocDir = newDir.toAbsolutePath();
|
||||||
|
}
|
||||||
|
|
||||||
|
private ResponseDto getGithubUrls() throws IOException, InterruptedException {
|
||||||
|
var uri = URI.create(properties.getProperty("github.url"));
|
||||||
|
var client = HttpClient.newHttpClient();
|
||||||
|
var request = HttpRequest
|
||||||
|
.newBuilder()
|
||||||
|
.uri(uri)
|
||||||
|
.version(HttpClient.Version.HTTP_2)
|
||||||
|
.timeout(Duration.ofMinutes(1))
|
||||||
|
.header("Accept", "application/vnd.github+json")
|
||||||
|
.GET()
|
||||||
|
.build();
|
||||||
|
var response = client.send(request, HttpResponse.BodyHandlers.ofString());
|
||||||
|
log.info("Got response from github, status: {}", response.statusCode());
|
||||||
|
|
||||||
|
var objectMapper = new ObjectMapper();
|
||||||
|
objectMapper.configure(DeserializationFeature.USE_JAVA_ARRAY_FOR_JSON_ARRAY, true);
|
||||||
|
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
|
||||||
|
|
||||||
|
return objectMapper.readValue(response.body(), ResponseDto.class);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,142 @@
|
|||||||
|
package space.kscience.snark.pandoc;
|
||||||
|
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.StandardOpenOption;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
|
||||||
|
public class PandocWrapper {
|
||||||
|
|
||||||
|
private static final Logger log
|
||||||
|
= LoggerFactory.getLogger(PandocWrapper.class);
|
||||||
|
|
||||||
|
private static final Installer installer;
|
||||||
|
|
||||||
|
static {
|
||||||
|
try {
|
||||||
|
installer = new Installer();
|
||||||
|
} catch (IOException | InterruptedException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private static String pandocPath = "pandoc"; // got pandoc at PATH
|
||||||
|
|
||||||
|
public static String getPandocPath() {
|
||||||
|
return pandocPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Install pandoc if needed then perform block
|
||||||
|
* @param block
|
||||||
|
* @return block's return value
|
||||||
|
* @param <T>
|
||||||
|
*/
|
||||||
|
public <T> Object use(Function<PandocWrapper, T> block) {
|
||||||
|
if (!isPandocInstalled()) {
|
||||||
|
installPandoc();
|
||||||
|
}
|
||||||
|
return block.apply(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if pandoc is installed
|
||||||
|
* @return true if installed false otherwise
|
||||||
|
*/
|
||||||
|
public static boolean isPandocInstalled() {
|
||||||
|
var pb = new PandocCommandBuilder().getVersion();
|
||||||
|
return execute(pb);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Call pandoc with options described by commandBuilder.
|
||||||
|
* @param commandBuilder
|
||||||
|
* @return true if successfully false otherwise
|
||||||
|
*/
|
||||||
|
public static boolean execute(PandocCommandBuilder commandBuilder) {
|
||||||
|
return execute(commandBuilder, null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Call pandoc with options described by commandBuilder and log output to outputFile
|
||||||
|
* @param commandBuilder
|
||||||
|
* @return true if successfully false otherwise
|
||||||
|
*/
|
||||||
|
public static boolean execute(PandocCommandBuilder commandBuilder, Path outputFile) {
|
||||||
|
return execute(commandBuilder, outputFile, null);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Call pandoc with options described by commandBuilder and log output to outputFile and error to errorFile.
|
||||||
|
* In case errors write exit code to errorFile
|
||||||
|
* @param commandBuilder
|
||||||
|
* @return true if successfully false otherwise
|
||||||
|
*/
|
||||||
|
public static boolean execute(PandocCommandBuilder commandBuilder, Path outputFile, Path errorFile) {
|
||||||
|
try {
|
||||||
|
Process pandoc = new ProcessBuilder(commandBuilder.build()).start();
|
||||||
|
pandoc.waitFor(1, TimeUnit.SECONDS);
|
||||||
|
|
||||||
|
BufferedReader inp = new BufferedReader(new InputStreamReader(pandoc.getInputStream()));
|
||||||
|
String currLine = inp.readLine();
|
||||||
|
|
||||||
|
log.info("log output from pandoc to: {}", outputFile);
|
||||||
|
do {
|
||||||
|
if (outputFile == null) {
|
||||||
|
log.info(currLine);
|
||||||
|
} else {
|
||||||
|
Files.writeString(outputFile, currLine + "\n", StandardOpenOption.CREATE, StandardOpenOption.APPEND);
|
||||||
|
}
|
||||||
|
} while ((currLine = inp.readLine()) != null);
|
||||||
|
inp.close();
|
||||||
|
|
||||||
|
if (pandoc.exitValue() == 0) {
|
||||||
|
log.info("Successfully execute");
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
log.error("Got problems with executing, pandoc exit error: {}", pandoc.exitValue());
|
||||||
|
|
||||||
|
BufferedReader input = new BufferedReader(new InputStreamReader(pandoc.getErrorStream()));
|
||||||
|
String line = input.readLine();
|
||||||
|
log.info("log error stream from pandoc to: {}", errorFile);
|
||||||
|
|
||||||
|
if (errorFile != null) {
|
||||||
|
Files.writeString(errorFile, "exit code: " + pandoc.exitValue());
|
||||||
|
}
|
||||||
|
do {
|
||||||
|
if (errorFile == null) {
|
||||||
|
log.info(line);
|
||||||
|
} else {
|
||||||
|
Files.writeString(errorFile, currLine + "\n", StandardOpenOption.CREATE, StandardOpenOption.APPEND);
|
||||||
|
}
|
||||||
|
} while ((line = input.readLine()) != null);
|
||||||
|
input.close();
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("Got problems with executing: " + e.getMessage());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Install pandoc and set executable path.
|
||||||
|
* @return true if success false otherwise
|
||||||
|
*/
|
||||||
|
public static boolean installPandoc() {
|
||||||
|
try {
|
||||||
|
pandocPath = installer.installPandoc().toString();
|
||||||
|
return true;
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("Got error: {}", e.getMessage(), e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,70 @@
|
|||||||
|
package space.kscience.snark.pandoc;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Response from github/releases/latest
|
||||||
|
*/
|
||||||
|
public class ResponseDto {
|
||||||
|
|
||||||
|
public AssetDto[] getAssets() {
|
||||||
|
return assets;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAssets(AssetDto[] assets) {
|
||||||
|
this.assets = assets;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param osSuffix
|
||||||
|
* @return asset appropriate to os
|
||||||
|
*/
|
||||||
|
public AssetDto getAssetByOsSuffix(String osSuffix) {
|
||||||
|
for (var asset : assets) {
|
||||||
|
if (asset.getName().contains(osSuffix)) {
|
||||||
|
return asset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new IllegalArgumentException("Unexpected osSuffix");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static class AssetDto {
|
||||||
|
|
||||||
|
@JsonProperty("browser_download_url")
|
||||||
|
private String browserDownloadUrl;
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
public String getBrowserDownloadUrl() {
|
||||||
|
return browserDownloadUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBrowserDownloadUrl(String browserDownloadUrl) {
|
||||||
|
this.browserDownloadUrl = browserDownloadUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AssetDto() {}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setName(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private AssetDto[] assets;
|
||||||
|
|
||||||
|
public String getTagName() {
|
||||||
|
return tagName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTagName(String tagName) {
|
||||||
|
this.tagName = tagName;
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonProperty("tag_name")
|
||||||
|
private String tagName;
|
||||||
|
|
||||||
|
public ResponseDto() {}
|
||||||
|
}
|
@ -0,0 +1,8 @@
|
|||||||
|
path.to.pandoc.mac.os.arm=/pandoc-{version}-arm64/bin/pandoc
|
||||||
|
path.to.pandoc.mac.os.amd=/pandoc-{version}-x86_64/bin/pandoc
|
||||||
|
path.to.pandoc.windows=/pandoc-{version}/pandoc.exe
|
||||||
|
path.to.pandoc.linux.amd=/pandoc-{version}/bin/pandoc
|
||||||
|
path.to.pandoc.linux.arm=/pandoc-{version}/bin/pandoc
|
||||||
|
|
||||||
|
github.url=https://api.github.com/repos/jgm/pandoc/releases/latest
|
||||||
|
|
123
snark-pandoc-plugin/src/test/java/PandocWrapperTest.java
Normal file
123
snark-pandoc-plugin/src/test/java/PandocWrapperTest.java
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.FileReader;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import space.kscience.snark.pandoc.Installer;
|
||||||
|
import space.kscience.snark.pandoc.PandocCommandBuilder;
|
||||||
|
import space.kscience.snark.pandoc.PandocWrapper;
|
||||||
|
import org.junit.jupiter.api.AfterAll;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
import static org.junit.jupiter.api.Assertions.fail;
|
||||||
|
|
||||||
|
public class PandocWrapperTest {
|
||||||
|
|
||||||
|
private static final Path CORRECT_MD = Path.of("./src/test/testing_directory/first_test.md");
|
||||||
|
private static final Path TEX_PATH_TO = Path.of("./src/test/testing_directory/output1.tex");
|
||||||
|
private static final Path TESTING_DIRECTORY = Path.of("./src/test/testing_directory");
|
||||||
|
private final PandocWrapper pandocWrapper = new PandocWrapper();
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void when_gotPandocAndCorrectArgs_doConverting() {
|
||||||
|
try {
|
||||||
|
var res = pandocWrapper.use(p -> {
|
||||||
|
var command = new PandocCommandBuilder(List.of(CORRECT_MD), TEX_PATH_TO);
|
||||||
|
return PandocWrapper.execute(command);
|
||||||
|
});
|
||||||
|
assertTrue((Boolean) res);
|
||||||
|
assertTrue(TEX_PATH_TO.toFile().exists());
|
||||||
|
|
||||||
|
var reader = new BufferedReader(new FileReader(TEX_PATH_TO.toFile()));
|
||||||
|
String fileString = reader.lines().collect(Collectors.joining());
|
||||||
|
|
||||||
|
assertTrue(fileString.contains("Some simple text"));
|
||||||
|
assertTrue(fileString.contains("\\subsection{Copy elision}"));
|
||||||
|
assertTrue(fileString.contains("return"));
|
||||||
|
|
||||||
|
Files.delete(TEX_PATH_TO);
|
||||||
|
|
||||||
|
} catch (Exception ex) {
|
||||||
|
fail("Unexpected exception during test when_gotPandocAndCorrectArgs_doConverting()", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void when_gotPandocAndNotExistsFromFile_then_error() {
|
||||||
|
var notExistsFile = Path.of("./src/test/testing_directory/non_exists_test.md");
|
||||||
|
assertFalse(notExistsFile.toFile().exists());
|
||||||
|
var res = pandocWrapper.use(p -> {
|
||||||
|
var command = new PandocCommandBuilder(List.of(notExistsFile), TEX_PATH_TO);
|
||||||
|
return PandocWrapper.execute(command);
|
||||||
|
});
|
||||||
|
assertFalse((Boolean) res);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void when_gotPandocAndPassDirectory_then_error() {
|
||||||
|
assertTrue(TESTING_DIRECTORY.toFile().isDirectory());
|
||||||
|
var res = pandocWrapper.use(p -> {
|
||||||
|
var command = new PandocCommandBuilder(List.of(TESTING_DIRECTORY), TEX_PATH_TO);
|
||||||
|
return PandocWrapper.execute(command);
|
||||||
|
});
|
||||||
|
assertFalse((Boolean) res);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void when_askVersionToFile_then_Ok() throws IOException {
|
||||||
|
Path outputFile = Files.createTempFile(TESTING_DIRECTORY, "output", ".txt");
|
||||||
|
|
||||||
|
var res = pandocWrapper.use(p -> {
|
||||||
|
var command = new PandocCommandBuilder();
|
||||||
|
command.getVersion();
|
||||||
|
return PandocWrapper.execute(command, outputFile);
|
||||||
|
});
|
||||||
|
|
||||||
|
var reader = new BufferedReader(new FileReader(outputFile.toFile()));
|
||||||
|
String fileString = reader.lines().collect(Collectors.joining());
|
||||||
|
assertTrue(fileString.contains("pandoc"));
|
||||||
|
assertTrue(fileString.contains("This is free software"));
|
||||||
|
assertTrue((Boolean) res);
|
||||||
|
|
||||||
|
Files.delete(outputFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void when_error_then_writeToErrorStream() throws IOException {
|
||||||
|
Path outputFile = Files.createTempFile(TESTING_DIRECTORY, "output", ".txt");
|
||||||
|
Path errorFile = Files.createTempFile(TESTING_DIRECTORY, "error", ".txt");
|
||||||
|
|
||||||
|
var res = pandocWrapper.use(p -> {
|
||||||
|
var command = new PandocCommandBuilder(List.of(Path.of("./simple.txt")), TEX_PATH_TO);
|
||||||
|
command.formatFrom("txt");
|
||||||
|
return PandocWrapper.execute(command, outputFile, errorFile);
|
||||||
|
});
|
||||||
|
|
||||||
|
var reader = new BufferedReader(new FileReader(errorFile.toFile()));
|
||||||
|
String fileString = reader.lines().collect(Collectors.joining());
|
||||||
|
assertFalse((Boolean) res);
|
||||||
|
assertTrue(fileString.contains("21"));
|
||||||
|
|
||||||
|
Files.delete(outputFile);
|
||||||
|
Files.delete(errorFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void when_installPandoc_thenFindIt() {
|
||||||
|
Installer.clearInstallingDirectory();
|
||||||
|
assertTrue(PandocWrapper.installPandoc());
|
||||||
|
assertTrue(PandocWrapper.isPandocInstalled());
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterAll
|
||||||
|
public static void clear() {
|
||||||
|
Installer.clearInstallingDirectory();
|
||||||
|
}
|
||||||
|
}
|
15
snark-pandoc-plugin/src/test/testing_directory/first_test.md
Normal file
15
snark-pandoc-plugin/src/test/testing_directory/first_test.md
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
|
||||||
|
|
||||||
|
## Copy elision
|
||||||
|
### RVO/NRVO
|
||||||
|
Some simple text
|
||||||
|
```c++
|
||||||
|
A f() {
|
||||||
|
return {5};
|
||||||
|
}
|
||||||
|
|
||||||
|
A g() {
|
||||||
|
A a(5);
|
||||||
|
return a;
|
||||||
|
}
|
||||||
|
```
|
@ -0,0 +1 @@
|
|||||||
|
hello
|
Loading…
Reference in New Issue
Block a user