Add pandoc-plugin and conversion to latex

This commit is contained in:
Neledova Elizaveta 2023-06-11 21:55:45 +03:00
parent 7cccb96023
commit 5659bd2f93
17 changed files with 2116 additions and 12 deletions

View File

@ -45,4 +45,5 @@ include(
":snark-storage-driver", ":snark-storage-driver",
":snark-document-builder", ":snark-document-builder",
":snark-main", ":snark-main",
":snark-pandoc-plugin",
) )

View File

@ -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")
} }

View File

@ -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>()

View 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)
}

View File

@ -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")
} }

View File

@ -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)
} }

View File

@ -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
View File

@ -0,0 +1,2 @@
.pandoc/

View 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()
}

View File

@ -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);
}
}

View File

@ -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;
}
}
}

View File

@ -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() {}
}

View File

@ -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

View 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();
}
}

View File

@ -0,0 +1,15 @@
## Copy elision
### RVO/NRVO
Some simple text
```c++
A f() {
return {5};
}
A g() {
A a(5);
return a;
}
```

View File

@ -0,0 +1 @@
hello