package org.rascalmpl.library.experiments.tutor3; import java.io.BufferedReader; import java.io.IOException; import java.io.StringReader; import java.io.StringWriter; import java.nio.file.Files; import java.nio.file.Path; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.rascalmpl.library.experiments.Compiler.RVM.Interpreter.repl.RascalShellExecutionException; public class Concept { private final Path name; private String text = null; private final Path destPath; private boolean remote; private boolean questions; private String title; private String synopsis; private String index; private Path libSrcPath; public Concept(Path name, String text, Path destPath, Path libSrcPath){ this.name = name; this.text = text; this.destPath = destPath; this.libSrcPath = libSrcPath; title = extract(titlePat); synopsis = extractSynopsis(); index = extractIndex(); } public void setRemote(){ remote = true; } public void setQuestions(){ questions = true; } public Path getName(){ return name; } public String getTitle(){ return title == null ? getName().toString() : title; } public String getSynopsis(){ return synopsis; } public String getText(){ return text; } public String getIndex(){ return index; } Pattern titlePat = Pattern.compile("(?ms)^# (.*?)$"); Pattern indexPat = Pattern.compile("(?ms)^\\.Index$\\n(.*?)$"); Pattern synopsisPat = Pattern.compile("(?ms)^\\.Synopsis$\\n(.*?)$"); private String extractSynopsis(){ String s = extract(synopsisPat); return s.isEmpty() ? "" : (s.endsWith(".") ? s : (s + ".")); } private String extractIndex(){ String s = extract(indexPat); if(!s.isEmpty()){ Matcher matcher = indexPat.matcher(text); text = matcher.replaceFirst(""); } return s; } private String extract(Pattern pat){ Matcher matcher = pat.matcher(text); return matcher.find() ? matcher.group(1).trim() : ""; } private String getConceptBaseName(){ return name.getFileName().toString(); } public String getAnchor(){ int n = name.getNameCount(); return n >= 2 ? name.getName(n-2) + "-" + name.getName(n-1) : name.getFileName().toString(); } private String getADocFileName(){ return destPath.toString() + "/" + name + (remote ? "" : ("/" + getConceptBaseName())) + ".adoc"; } public String genInclude(){ String baseName = getConceptBaseName(); return "include::" + baseName + "/" + baseName + ".adoc" + "[" + baseName + "]\n"; } private String complete(String line){ return line + (line.endsWith(";") ? "\n" : ";\n"); } String getAttr(String line, String attr, String defaultVal){ Pattern p = Pattern.compile(attr + "=([^,\\]])"); Matcher m = p.matcher(line); return m.find() ? m.group(1) : defaultVal; } String makeRenderSave(String line, String width, String height, String file){ Pattern p = Pattern.compile("render\\((.*)\\)"); Matcher m = p.matcher(line); if(m.find()){ String arg = m.group(1); if(height.isEmpty()){ return "renderSave(" + arg + "," + file + ");"; } else { return "renderSave(" + arg + ", |file://" + file + "|, width=" + width + ", height=" + height + ");"; } } return line; } String commonDefs = ":icons: font\n" + ":iconfont-remote!:\n" + //":iconfont-name: font-awesome.min\n" + ":images: ../images/\n" + ":table-caption!:\n" + ":prewrap!:\n" + ":docinfo1:\n" + ":experimental:\n"; public static String getSearchForm(){ return "<form class=\"search-form\" id=\"searchbox\" action=\"/Search\">\n" + "<input class=\"search-input\" id=\"search\" name=\"searchFor\" type=\"search\" placeholder=\"Search ...\">\n" + "<input class=\"search-submit\" id=\"submit\" type=\"submit\" value=\"➜\" onkeypress=\"if(event.keyCode==13) {javascript:form.submit();}\">\n" + "</form>\n"; } public static String getHomeLink(){ return "<a href=\"/TutorHome/index.html\"><img id=\"home\" src=\"/images/rascal-tutor-small.png\", alt=\"RascalTutor\" width=\"64\" height=\"64\"></a>"; } private final String prompt = "rascal>"; // "+++<span class=\"prompt\" data-value=\"rascal>\"></span>::after+++"; private final String continuation = ">>>>>>>"; //"<i class=\"continuation\">::before</i>"; private String makeRed(String result){ StringWriter sw = new StringWriter(result.length()+5); result.split("\n"); for(String s : result.split("\n")){ sw.append("[red]##").append(s).append("##\n"); } return sw.toString(); } public void preprocess(Onthology onthology, TutorCommandExecutor executor) throws IOException{ System.err.println("Preprocessing: " + name); BufferedReader reader = new BufferedReader(new StringReader(text)); StringWriter preprocessOut = new StringWriter(); String line = null; String[] details = new String[0]; int level = Onthology.level(name); while( (line = reader.readLine()) != null && !line.startsWith("#")){ preprocessOut.append(line).append("\n"); } if(line == null){ preprocessOut.append("# ").append(name.toString()).append("\n"); } else { title = line.substring(2).trim(); if(level > 0){ preprocessOut.append("\n[[").append(getAnchor()).append("]]\n"); preprocessOut.append(line).append("\n"); } else { line = line.replaceFirst("#", "="); preprocessOut.append(line).append("\n"); preprocessOut.append(commonDefs); preprocessOut.append(":LibDir: ").append(libSrcPath.toString()).append("/\n"); } preprocessOut.append(":concept: ").append(name.toString()).append("\n"); if(level == 0){ preprocessOut.append("\n++++\n"); preprocessOut.append(getHomeLink()); preprocessOut.append(getSearchForm()); preprocessOut.append("++++\n"); } while( (line = reader.readLine()) != null ) { if(line.startsWith(".Details")){ line = reader.readLine(); details = line.split("\\s"); } else if(line.startsWith("```rascal-shell") || line.startsWith("[source,rascal-shell") || line.startsWith("[source,rascal-figure")) { boolean isContinue = line.contains("continue"); boolean isFigure = line.contains("figure"); String width = "100"; String height = "100"; String file = "/tmp/fig.png"; if(isFigure){ height = getAttr(line, "height", height); width = getAttr(line, "width", width); file = getAttr(line,"file", file); } boolean mayHaveErrors = line.contains("error"); if(line.startsWith("[")){ line = reader.readLine(); // skip ---- if(line == null){ break; } } if(!isContinue){ executor.reset(); } executor.resetOutput(); preprocessOut.append("[source,rascal-shell"); if(mayHaveErrors){ preprocessOut.append("-error"); } if(mayHaveErrors){ // To enable [red] macro in generated output preprocessOut.append(",subs=\"verbatim,quotes\""); } preprocessOut.append("]\n").append("----\n"); boolean moreShellInput = true; while( moreShellInput && (line = reader.readLine()) != null ) { if(line.equals("```") || line.equals("----")){ break; } if(isFigure){ if(line.startsWith("render(")){ preprocessOut.append(prompt).append(line).append("\n"); line = makeRenderSave(line, height, width, file); } } else { preprocessOut.append(prompt).append(line).append("\n"); String continuationLine = ""; while(!executor.isStatementComplete(line)){ if((continuationLine = reader.readLine()) != null){ if(continuationLine.equals("```") || continuationLine.equals("----")){ moreShellInput = false; break; } preprocessOut.append(continuation).append(continuationLine).append("\n"); } else { break; } line += "\n" + continuationLine; } line += "\n"; } String resultOutput = ""; boolean errorFree = true; // System.err.println(line); try { // if(!isFigure){ resultOutput = executor.evalPrint(line); // } } catch (Exception e){ if(!mayHaveErrors){ String msg = "While executing '" + complete(line) + "': " + e.getMessage(); System.err.println(msg); executor.error("* __" + name + "__:"); executor.error(msg); } preprocessOut.append(e.getMessage() != null ? makeRed(e.getMessage()) : makeRed(e.toString()) ); errorFree = false; } String messages = executor.getMessages(); executor.resetOutput(); if(messages.isEmpty()){ preprocessOut.append(resultOutput.startsWith("Error") ? makeRed(resultOutput) : resultOutput); } else { preprocessOut.append(messages.startsWith("Error") ? makeRed(messages) : messages); errorFree = false; } // if(!isFigure){ // if(result == null){ // preprocessOut.append("ok\n"); // } else{ // preprocessOut.append(result.getType().toString()).append(": ").append(result.toString()).append("\n"); // } // } if(!mayHaveErrors && !errorFree){ //(messages.contains("[error]") || messages.contains("Exception")) ){ executor.error("* " + name + ":"); executor.error(messages.trim()); } } preprocessOut.append("----\n"); } else if(line.startsWith("```") || line.startsWith("[source")) { preprocessOut.append(line).append("\n"); boolean inCode = false; while((line = reader.readLine()) != null ) { preprocessOut.append(line).append("\n"); if(line.equals("```") || line.equals("----")){ if(inCode){ break; } else { inCode = true; } } } } else if(line.startsWith("loctoc::[")){ Pattern p = Pattern.compile("loctoc::\\[(\\d*)\\]"); Matcher m = p.matcher(line); int depth = 1; if(m.find()){ String intStr = m.group(1); depth = intStr.equals("") ? 1 : Integer.parseInt(intStr.substring(0,intStr.length())); } preprocessOut.append(onthology.genSubToc(name, depth, true, details)); } else if(line.contains("image:")){ Pattern p = Pattern.compile("(^.*)(image::?)([^\\[]+)(\\[.*$)"); Matcher m = p.matcher(line); if(m.find()){ String pre = m.group(1); String image = m.group(2); String link = m.group(3); String post = m.group(4); if(!link.contains("{") && !link.startsWith("/")){ link = "/{concept}/" + link; } preprocessOut.append(pre).append(image).append(link).append(post).append("\n"); } else { preprocessOut.append(line).append("\n"); } } else { preprocessOut.append(line).append("\n"); } } preprocessOut.append(onthology.genDetails(name, details)); } Path parent = destPath.resolve(name); if(!Files.exists(parent)){ Files.createDirectory(parent); } CourseCompiler.writeFile(getADocFileName(), preprocessOut.toString()); } }