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-document-builder",
|
||||
":snark-main",
|
||||
":snark-pandoc-plugin",
|
||||
)
|
@ -14,6 +14,8 @@ dependencies {
|
||||
api("io.ktor:ktor-server-html-builder:$ktorVersion")
|
||||
|
||||
implementation(project(":snark-storage-driver"))
|
||||
implementation(project(":snark-pandoc-plugin"))
|
||||
|
||||
implementation("com.fasterxml.jackson.module:jackson-module-kotlin:$jacksonVersion")
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.3")
|
||||
}
|
||||
|
@ -1,17 +1,15 @@
|
||||
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.readValue
|
||||
import kotlinx.serialization.json.Json
|
||||
import space.kscience.snark.pandoc.PandocCommandBuilder
|
||||
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.*
|
||||
|
||||
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
|
||||
{
|
||||
return ProcessBuilder("node", SNARK_HTML_RENDER, ast_string)
|
||||
@ -20,6 +18,30 @@ fun getHtml(ast_string: String): String
|
||||
.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"
|
||||
|
||||
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))
|
||||
}
|
||||
|
||||
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 {
|
||||
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")
|
||||
implementation("io.ktor:ktor-server-netty:2.3.0")
|
||||
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")
|
||||
}
|
@ -1,18 +1,17 @@
|
||||
package space.kscience.snark.ktor
|
||||
|
||||
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.server.engine.*
|
||||
import io.ktor.server.netty.*
|
||||
import io.ktor.server.application.*
|
||||
import io.ktor.server.html.*
|
||||
import io.ktor.server.request.*
|
||||
import io.ktor.server.response.*
|
||||
import kotlinx.html.*
|
||||
import io.ktor.server.routing.*
|
||||
import space.kscience.snark.storage.Directory
|
||||
import space.kscience.snark.storage.unzip.unzip
|
||||
import java.io.File
|
||||
import java.nio.file.Path
|
||||
import kotlin.io.createTempFile
|
||||
import kotlin.io.writeBytes
|
||||
@ -22,6 +21,7 @@ public interface DataHolder {
|
||||
public suspend fun init(relativePath: Path) : Directory
|
||||
|
||||
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 {
|
||||
@ -35,6 +35,16 @@ public class SNARKServer(private val dataHolder: DataHolder, private val port: I
|
||||
private suspend fun renderGet(call: ApplicationCall) {
|
||||
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) {
|
||||
val multipartData = call.receiveMultipart()
|
||||
val tmp = createTempFile(suffix=".zip")
|
||||
@ -89,6 +99,11 @@ public class SNARKServer(private val dataHolder: DataHolder, private val port: I
|
||||
a("/data") {
|
||||
+"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") {
|
||||
renderGet(call)
|
||||
}
|
||||
get("/download") {
|
||||
renderFile(call)
|
||||
}
|
||||
}
|
||||
}.start(wait = true)
|
||||
}
|
||||
|
@ -13,4 +13,8 @@ internal class ServerDataHolder(private val directory: Directory): DataHolder {
|
||||
override suspend fun represent(relativePath: Path): String {
|
||||
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