package org.rascalmpl.library.experiments.Compiler.RVM.Interpreter.help; import java.io.IOException; import java.io.PrintWriter; import java.io.StringWriter; import java.io.UnsupportedEncodingException; import java.net.URI; import java.net.URISyntaxException; import java.net.URLEncoder; import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; import org.apache.lucene.analysis.Analyzer; import org.apache.lucene.document.Document; import org.apache.lucene.index.DirectoryReader; import org.apache.lucene.index.IndexReader; import org.apache.lucene.index.MultiReader; import org.apache.lucene.queryparser.classic.MultiFieldQueryParser; import org.apache.lucene.queryparser.classic.ParseException; import org.apache.lucene.queryparser.classic.QueryParser; import org.apache.lucene.search.IndexSearcher; import org.apache.lucene.search.Query; import org.apache.lucene.search.ScoreDoc; import org.apache.lucene.store.Directory; import org.apache.lucene.store.FSDirectory; import org.rascalmpl.library.experiments.Compiler.RVM.Interpreter.ideservices.IDEServices; import org.rascalmpl.library.experiments.tutor3.Concept; import org.rascalmpl.library.experiments.tutor3.Onthology; import org.rascalmpl.library.util.PathConfig; import org.rascalmpl.uri.URIResolverRegistry; import org.rascalmpl.uri.URIUtil; import org.rascalmpl.value.ISourceLocation; public class HelpManager { private ISourceLocation coursesDir; private PathConfig pcfg; private final int maxSearch = 25; private final PrintWriter stdout; private final PrintWriter stderr; private IndexSearcher indexSearcher; private final int port = 8000; private HelpServer helpServer; private final IDEServices ideServices; public HelpManager(PathConfig pcfg, PrintWriter stdout, PrintWriter stderr, IDEServices ideServices){ this.pcfg = pcfg; this.stdout = stdout; this.stderr = stderr; this.ideServices = ideServices; ISourceLocation binDir = pcfg.getBoot(); coursesDir = URIUtil.correctLocation(binDir.getScheme(), binDir.getAuthority(), binDir.getPath() + "/courses"); try { helpServer = new HelpServer(getPort(), this, coursesDir); indexSearcher = makeIndexSearcher(); } catch (IOException e) { System.err.println("HelpManager: " + e.getMessage()); } } PathConfig getPathConfig(){ return pcfg; } Path copyToTmp(ISourceLocation fromDir) throws IOException{ Path targetDir = Files.createTempDirectory(URIUtil.getLocationName(fromDir)); targetDir.toFile().deleteOnExit(); URIResolverRegistry reg = URIResolverRegistry.getInstance(); for(ISourceLocation file : reg.list(fromDir)){ if(!reg.isDirectory(file)){ String p = file.getPath(); int n = p.lastIndexOf("/"); String fileName = n >= 0 ? p.substring(n+1) : p; // Only copy _* (index files) and segments* (defines number of segments) if(fileName.startsWith("_") || fileName.startsWith("segments")){ Path targetFile = targetDir.resolve(fileName); //System.out.println("copy " + file + " to " + toDir.resolve(fileName)); Files.copy(reg.getInputStream(file), targetFile); targetFile.toFile().deleteOnExit(); } } } return targetDir; } private ArrayList<IndexReader> getReaders() throws IOException{ ArrayList<IndexReader> readers = new ArrayList<>(); URIResolverRegistry reg = URIResolverRegistry.getInstance(); for(ISourceLocation p : reg.list(coursesDir)){ if(reg.isDirectory(p) && URIUtil.getLocationName(p).toString().matches("^[A-Z].*")){ Path p1 = copyToTmp(p); Directory directory = FSDirectory.open(p1); try { DirectoryReader ireader = DirectoryReader.open(directory); readers.add(ireader); } catch (IOException e){ stderr.println("Skipping index " + directory); } } } return readers; } IndexSearcher makeIndexSearcher() throws IOException { ArrayList<IndexReader> readers = getReaders(); IndexReader[] ireaders = new IndexReader[readers.size()]; for(int i = 0; i < readers.size(); i++){ ireaders[i] = readers.get(i); } IndexReader ireader = new MultiReader(ireaders); return new IndexSearcher(ireader); } private boolean indexAvailable(){ if(indexSearcher != null){ return true; } stderr.println("No deployed courses found; they are needed for 'help' or 'apropos'"); return false; } void appendURL(StringWriter w, String conceptName){ String[] parts = conceptName.split("/"); int n = parts.length; String course = parts[0]; w.append("/").append(course).append("#").append(parts[n - (n > 1 ? 2 : 1)]).append("-").append(parts[n-1]); } String makeURL(String conceptName){ StringWriter w = new StringWriter(); appendURL(w, conceptName); return w.toString(); } void appendHyperlink(StringWriter w, String conceptName){ w.append("<a href=\"http://localhost:"); w.append(String.valueOf(getPort())); appendURL(w, conceptName); w.append("\">").append(conceptName).append("</a>"); } private String escapeForQuery(String s){ return s.toLowerCase().replaceAll("([+\\-!(){}\\[\\]\\^\"~*?:\\\\/]|(&&)|(\\|\\|))","\\\\$1"); } private String escapeHtml(String s){ // TODO: switch to StringEscapeUtils when compiled inside Eclipse return s; } private URI makeSearchURI(String[] words) throws URISyntaxException, UnsupportedEncodingException{ StringWriter w = new StringWriter(); for(int i = 1; i < words.length; i++){ w.append(words[i]); if(i < words.length - 1) w.append(" "); } String encoded = URLEncoder.encode(w.toString(), "UTF-8"); return new URI("http", "localhost:" + getPort() + "/Search?searchFor=" + encoded, null); } public void handleHelp(String[] words){ if(words[0].equals("help") && words.length > 1){ try { ideServices.browse(makeSearchURI(words)); } catch (URISyntaxException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (UnsupportedEncodingException e) { // TODO Auto-generated catch block e.printStackTrace(); } } else { stdout.println(giveHelp(words)); } } public String giveHelp(String[] words){ //TODO Add here for example credits, copyright, license if(words.length <= 1){ IntroHelp.print(stdout); return ""; } if(!indexAvailable()){ return ""; } Analyzer multiFieldAnalyzer = Onthology.multiFieldAnalyzer(); try { String searchFields[] = {"index", "synopsis", "doc"}; QueryParser parser = new MultiFieldQueryParser(searchFields, multiFieldAnalyzer); StringBuilder sb = new StringBuilder(); for(int i = 1; i < words.length; i++){ sb.append(" ").append(escapeForQuery(words[i])); } Query query; try { query = parser.parse(sb.toString()); } catch (ParseException e) { stderr.println("Cannot parse query: " + sb + ", " + e.getMessage()); return ""; } if(words[0].equals("help")){ return reportHelp(words, indexSearcher.search(query, maxSearch).scoreDocs); } else { return reportApropos(words, indexSearcher.search(query, maxSearch).scoreDocs); } } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } return ""; } String getField(Document hitDoc, String field){ String s = hitDoc.get(field); return (s == null || s.isEmpty()) ? "" : s; } void genPrelude(StringWriter w){ w.append("<head>\n"); w.append("<title>Rascal Help</title>"); w.append("<link rel=\"stylesheet\" href=\"css/style.css\"/>"); w.append("<link rel=\"stylesheet\" href=\"css/font-awesome.min.css\"/>"); w.append("<link rel=\"icon\" href=\"/favicon.ico\" type=\"image/x-icon\"/>"); w.append("</head>\n"); w.append("<body class=\"book toc2 toc-left\">"); w.append(Concept.getHomeLink()); w.append(Concept.getSearchForm()); w.append("<div id=\"toc\" class=\"toc2\">"); w.append("</div>"); } void genSearchTerms(String[] words, StringWriter w){ w.append("<i>"); for(int i = 1; i < words.length; i++){ w.append(words[i]).append(" "); } w.append("</i>\n"); } String reportHelp(String[] words, ScoreDoc[] hits) throws IOException{ int nhits = hits.length; StringWriter w = new StringWriter(); if(nhits == 0){ stdout.println("No info found"); genPrelude(w); w.append("<h1 class=\"search-sect0\">No help found for: "); genSearchTerms(words, w); w.append("</h1>\n"); w.append("<div class=\"search-ulist\">\n"); w.append("<ul><li>Perhaps try <i>help</i>, <i>further reading</i> or <i>introduction</i> as search terms</li>"); w.append("</ul>\n"); w.append("</div>"); w.append("</body>\n"); return w.toString(); } else { genPrelude(w); w.append("<h1 class=\"search-sect0\">Help for: "); genSearchTerms(words, w); w.append("</h1>\n"); w.append("<ul>\n"); for (int i = 0; i < Math.min(hits.length, maxSearch); i++) { Document hitDoc = indexSearcher.doc(hits[i].doc); w.append("<div class=\"search-ulist\">\n"); w.append("<li> "); String name = hitDoc.get("name"); appendHyperlink(w, name); w.append(": <em>").append(escapeHtml(getField(hitDoc, "synopsis"))).append("</em>"); String signature = getField(hitDoc, "signature"); if(!signature.isEmpty()){ w.append("<br>").append("<code>").append(escapeHtml(signature)).append("</code>"); } } w.append("</ul>\n"); w.append("</div>"); w.append("</body>\n"); return w.toString(); } } String reportApropos(String[] words, ScoreDoc[] hits) throws IOException{ StringWriter w = new StringWriter(); for (int i = 0; i < Math.min(hits.length, maxSearch); i++) { Document hitDoc = indexSearcher.doc(hits[i].doc); String name = hitDoc.get("name"); String signature = getField(hitDoc, "signature"); String synopsis = getField(hitDoc, "synopsis"); w.append(name).append(":\n\t").append(synopsis); if(!signature.isEmpty()){ w.append("\n\t").append(signature); } w.append("\n"); } return w.toString(); } public int getPort() { return port; } }