package org.fushihara.github.markdown;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.URISyntaxException;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.StandardOpenOption;
import java.util.Locale;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.commons.lang3.StringEscapeUtils;
import com.squareup.okhttp.MediaType;
import com.squareup.okhttp.OkHttpClient;
import com.squareup.okhttp.Request;
import com.squareup.okhttp.Request.Builder;
import com.squareup.okhttp.RequestBody;
import com.squareup.okhttp.Response;
public class GithubMarkdown {
static final Charset utf8 = Charset.forName("utf-8");
final String tempReplaceKey;
OkHttpClient httpClient = new OkHttpClient();
public static void main(String[] args) throws FileNotFoundException, IOException, URISyntaxException {
String markdownTextPath = "";
String saveDirectory = "";
String savePath = null;
String templeteTxtPath = GithubMarkdown.getDefaultTempleteFilePath();
for (String string : args) {
if (string == null || string.equals("")) {
continue;
} else if (string.startsWith("-d:")) {
saveDirectory = string.substring(3);
} else if (string.startsWith("-t:")) {
templeteTxtPath = string.substring(3);
} else {
markdownTextPath = string;
}
}
if (markdownTextPath.equals("")) {
System.out.println("GithubMarkdown.jar [-d:保存フォルダ] [-t:テンプレートテキスト] markdownテキストのパス");
return;
}
if (saveDirectory.equals("")) {
savePath = new File(new File(markdownTextPath).getParentFile(), new File(markdownTextPath).getName() + ".html").getPath();
} else {
savePath = new File(saveDirectory, new File(markdownTextPath).getName() + ".html").getPath();
}
System.out.println("version:" + System.getProperty("java.version"));
System.out.println("load:" + markdownTextPath);
System.out.println("save:" + savePath);
GithubMarkdown ghm = new GithubMarkdown();
String markdownText = GithubMarkdown.loadText(markdownTextPath);
System.out.println("rawText:" + markdownText.length() + " chars");
markdownText = ghm.syntaxHighlightKeepFileName(markdownText);
String markdownHtmlRaw = ghm.convertMarkdown(markdownText);
markdownHtmlRaw = ghm.syntaxHighlightRestoreFileName(markdownHtmlRaw);
System.out.println("markDownText:" + markdownHtmlRaw.length() + " chars");
String headText = GithubMarkdown.loadText(templeteTxtPath);
String finalText = headText;
finalText = finalText.replace("/*title*/", new File(markdownTextPath).getName());
finalText = finalText.replace("/*body*/", markdownHtmlRaw);
finalText = finalText.replace("/*menu*/", ghm.getMenuHtml(markdownText));
ghm.saveText(savePath, finalText);
}
public GithubMarkdown() {
this.tempReplaceKey = String.format("x%x", System.identityHashCode(this));
}
/**
* 自分自身のjarファイル名の拡張子をtxtにしたパスを返す
*
* @throws URISyntaxException
* @throws IOException
*/
private static String getDefaultTempleteFilePath() throws URISyntaxException, IOException {
String myFile = new File(GithubMarkdown.class.getProtectionDomain().getCodeSource().getLocation().toURI().getPath()).getAbsolutePath();
if (!myFile.endsWith(".jar")) {
throw new IOException(myFile);
}
String tempPath = myFile.substring(0, myFile.length() - 4) + ".txt";
return tempPath;
}
/** ファイル名の構文があるqiita独自のシンタックスハイライトの構文からファイル名の部分を一旦移動させる */
private String syntaxHighlightKeepFileName(String markdownRaw) {
final Pattern pat = Pattern.compile("^(```[a-zA-Z0-9_]+?):(.+)", Pattern.MULTILINE);
final Matcher matcher = pat.matcher(markdownRaw);
final StringBuffer sb = new StringBuffer();
while (matcher.find()) {
// 一致したグループは matcher.group(n) で取得できる。
// ここで replacement を加工する。
String replacement = this.tempReplaceKey + ":" + matcher.group(2) + System.lineSeparator() + matcher.group(1);
matcher.appendReplacement(sb, replacement);
}
matcher.appendTail(sb);
return sb.toString();
}
private String syntaxHighlightRestoreFileName(String markdownHtml) {
final Pattern pat = Pattern.compile("<p>" + this.tempReplaceKey + ":(.+?)</p>\\s\\s(<div[^\\>]+?>)([\\s\\S]+?)</div>");
final Matcher matcher = pat.matcher(markdownHtml);
final StringBuffer sb = new StringBuffer();
while (matcher.find()) {
// 一致したグループは matcher.group(n) で取得できる。
// ここで replacement を加工する。
String replacement = String.format("%2$s<div class=\"code-lang\">%1$s</div><div class=\"highlight-pre\">%3$s</div></div>", matcher.group(1).toString(), matcher.group(2).toString(), matcher.group(3).toString());
matcher.appendReplacement(sb, replacement);
}
matcher.appendTail(sb);
return sb.toString();
}
private static String loadText(String path) throws IOException {
try {
byte[] bytes = Files.readAllBytes(new File(path).toPath());
return new String(bytes, utf8);
} catch (FileNotFoundException e) {
return "";
}
}
private String getMenuHtml(String rawText) {
StringBuilder sb = new StringBuilder();
Pattern pat = Pattern.compile("\\s*(\\#+)\\s*(.+)");
Matcher mat = pat.matcher(rawText);
while (mat.find()) {
String sharps = mat.group(1).toString().trim();
String title = mat.group(2).toString().trim();
sb.append(createMenuHtmlOneItem(sharps.length(), title));
sb.append(System.lineSeparator());
}
return sb.toString();
}
private String createMenuHtmlOneItem(int level, String label) {
return String.format(Locale.US, "<a href=\"%3$s\"><h%1$d>%2$s</h%1$d></a>", level, StringEscapeUtils.escapeHtml4(label), "#" + StringEscapeUtils.escapeXml11("user-content-" + label));
}
private void saveText(String path, String content) throws IOException {
Files.write(new File(path).toPath(), content.getBytes(utf8), StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.CREATE);
}
private String convertMarkdown(String rawText) throws IOException {
Builder rb = new Request.Builder();
// rb.addHeader("key", "value");
rb.url("https://api.github.com/markdown/raw");
RequestBody rbody = RequestBody.create(MediaType.parse("text/plain"), rawText);// 第二引数はbyte[]やfileの場合もある
rb.post(rbody);
Request request = rb.build();
OkHttpClient client = new OkHttpClient();
String result;
Response response = client.newCall(request).execute();
result = response.body().string();
return result;
}
}