/** * Authors: Frederik Leyvraz, David Degenhardt * License: GNU General Public License v3.0 only * Version: 1.0.0 */ package ch.bfh.ti.latexindexer; import java.io.File; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.*; public class UI { //Use to colour text: static final String RESET = "\u001B[0m"; static final String WARNING = "\u001B[31m"; static final String SUCCESS = "\u001B[32m"; private Parser parser; private Plotter plotter; private IndexWriter indexWriter; private String workingDirectory; private List words; private String texFile; /** * @param texFile The .tex file for which an index is being created. */ public void start(String texFile) { Scanner scanner = new Scanner(System.in); boolean running = true; workingDirectory = System.getProperty("user.dir"); if (!getExtension(workingDirectory, texFile)){ return; } parser = new PandocParser(this.texFile); // TODO decide which parser to use parseDocument(); //initial document parse System.out.println("> LaTeX-indexer >>> Welcome to the LaTeX-indexer. Press 'h' for a list of available commands."); while (running) { System.out.print("> LaTeX-indexer >>> "); String input = scanner.nextLine().trim(); String[] command = input.split("\\s+"); switch (command[0].toLowerCase()) { case "h", "help" -> printHelp(); case "p", "parse" -> parseDocument(); case "l", "list" -> listWords(getArgs(command)); case "g", "generate" -> generateFrequencyPlot(getArgs(command)); case "s", "subvariants", "subvariant" -> setSubVariant(command); case "v", "var", "variation", "variations" -> setVariation(command); case "a", "add" -> addToIndex(command); case "i", "interactive" -> addToIndexInteractive(command); case "q", "quit" -> running = false; default -> printUsage(input); } } } /** * A helper that makes sure the path name includes the file extension (.tex) and makes sure it exists. * @param workingDirectory The path to the directory that contains the file. * @param texFile The name of the .tex file (With or without the extension). * @return The full qualified path to the .tex file with the extension. */ private boolean getExtension(String workingDirectory, String texFile){ String[] filePath = texFile.split("/"); String filename = filePath[filePath.length-1]; if (filename.codePoints().anyMatch(c -> c == '.')){ int dotIndex = filename.indexOf('.'); if (!filename.substring(dotIndex).equals(".tex")){ System.out.println(WARNING + "Unknown file extension '" + texFile.substring(dotIndex) + "'. Please provide a '.tex' file." + RESET); return false; } } else { texFile += ".tex"; } texFile = workingDirectory + "/" + texFile; if (new File(texFile).isFile()){ this.texFile = texFile; return true; } System.out.println(WARNING + "Couldn't find '" + texFile + "' Please verify the file name and try again."); return false; } /** * @param command The array with the individual strings with which a command was entered (including the command name itself as the first array element). * @return An array of Argument objects. */ private Argument[] getArgs(String[] command) { LinkedList argsList = new LinkedList<>(); for (int i = 1; i < command.length; i++) { if (command[i].equals("-h")) { argsList.add(new Argument("-h", null)); break; } else if (i + 1 < command.length) { if (command[i].charAt(0) == '-' && command[i + 1].charAt(0) != '-') { argsList.add(new Argument(command[i], command[++i])); } else { System.out.println(WARNING + "Dropped parameter '" + command[i] + "' as it was either missing a dash (-) or not followed by a value." + RESET); } } else { System.out.println(WARNING + "Dropped parameter '" + command[i] + "' as it wasn't followed by a value." + RESET); } } return argsList.toArray(new Argument[0]); } /** * Prints a list of available commands. */ private void printHelp() { System.out.println(); System.out.println("\th;\thelp\t\t\t–\tlist all available commands."); System.out.println("\tp;\tparse\t\t\t–\t(re-)parse the words contained in the .tex document. This is automatically done at the start of the program."); System.out.println("\t*****Any of the following commands can be executed with the parameter '-h' to get more detailed information about how it works*****"); System.out.println("\tl;\tlist\t\t\t–\tlist the parsed words. Specify the number of words you want to see by typing it after the command."); System.out.println("\tg;\tgenerate\t\t–\tgenerate a plot of the frequencies of the parsed words. Specify the number of words you want to see by typing it after the command."); System.out.println("\ts;\tsubvariants\t\t–\tdefine some words to be sub variants of other words."); System.out.println("\tv;\tvariations\t\t–\tdefine variations of a word."); System.out.println("\ta;\tadd\t\t\t–\tadd words to the index."); System.out.println("\ti;\tinteractive\t\t–\tadd words to the index interactively."); System.out.println("\tq;\tquit\t\t\t–\tquit."); System.out.println(); } /** * Parses the document. */ private void parseDocument() { //TODO: Temporary implementation: try { words = parser.parseDocument(); plotter = new Plotter(words); } catch (IOException e) { e.printStackTrace(); } } /** * @param args An Array containing the arguments passed to the list command. */ private void listWords(Argument[] args) { if (Arrays.stream(args).anyMatch(argument -> argument.getName().equals("-h"))) { System.out.println("\n\n\tThe 'list' command may be used with the following parameters:\n" + "\t\t-n (default: 20).\n" + "\t\t-c for listing words alphabetically or by frequency (default: frequency).\n" + "\t\t-p for only listing words starting with a certain prefix (default: none).\n" + "\t\t-r list the words in reverse order (default: false).\n\n"); } else { System.out.println(plotter.print(args)); } } /** * @param args An Array containing the arguments passed to the generate command. */ private void generateFrequencyPlot(Argument[] args) { if (Arrays.stream(args).anyMatch(argument -> argument.getName().equals("-h"))) { System.out.println("\n\n\tThe 'generate' command generates a separate .tex file containing latex code to generate a frequency plot of the selected words using the PGFplots package and renders it with PDFlatex.\n" + "\t\tThe command may be used with the following parameters:\n" + "\t\t-f define a custom file name for the plot\n" + "\t\t-n (default: 20).\n" + "\t\t-c for plotting words alphabetically or by frequency (default: frequency).\n" + "\t\t-p for only listing words starting with a certain prefix (default: none).\n" + "\t\t-r plot the words in reverse order (default: false).\n\n"); } else { try { plotter.generatePlot(workingDirectory, args); } catch (IOException e) { System.out.println(WARNING + "Something went wrong trying to write the plot .tex or trying to render it:" + RESET); e.printStackTrace(); } catch (NumberFormatException e) { System.out.println(WARNING + "'-n' must be followed by an integer value." + RESET); } catch (InterruptedException e) { System.out.println(WARNING + "Undefined error. Was not able to render plot." + RESET); } } } /** * @param input Prints an error message and a help message when an unknown command is run. */ private void printUsage(String input) { System.out.println(); System.out.println(WARNING + "Command " + input + " unknown. Usage:" + RESET); printHelp(); } /** * @param command An array containing the arguments passed to the command. */ private void addToIndex(String[] command) { if (Arrays.asList(command).contains("-h")) { System.out.println("\n\n\tThe 'add' command adds the specified words to the index.\n" + "\t\tTo specify the words you want to add to the index, simply append them to the 'add' command separated by single spaces as follows:\n" + "\t\t$ a ...\n\n"); } else { IndexWriter writer = new IndexWriter(texFile); if (command.length > 1) { for (int i = 1; i < command.length; i++) { String c = command[i]; Optional w = words.stream().filter(word -> word.getValue().equals(c)).findFirst(); Word word = w.orElse(null); if (word == null) { System.out.println(WARNING + "'" + command[1] + "' not found in the given document." + RESET); return; } try { writer.replaceWordInFiles(word); System.out.println(SUCCESS + "Successfully added '" + word.getValue() + "' to index." + RESET); } catch (IOException e) { System.out.println(WARNING + "Something went wrong trying to modify the output file."); System.out.print(e.getMessage() + RESET); } } } else { System.out.println(WARNING + "Please specify a word which you want to add to the index." + RESET); } } } /** * * @param command An array containing the arguments passed to the command. */ private void setSubVariant(String[] command) { if (Arrays.asList(command).contains("-h")) { System.out.println("\n\n\tThe 'subvariant' command let's you define words to be sub variants of other words. This means that these sub variants will be indexed under their respective super variant and not on their own alphabetically.\n" + "\t\tTo specify the words you want to define sub variants for, simply append them to the 'subvariant' command separated by single spaces as follows:\n" + "\t\t$ s ...\n\n"); } else { if (command.length > 1) { Scanner scanner = new Scanner(System.in); for (int i = 1; i < command.length; i++) { String s = command[i]; Optional oSupV = words.stream().filter(word -> word.getValue().equals(s.trim())).findFirst(); Word superVariant = oSupV.orElse(null); if (superVariant == null) { System.out.println(WARNING + "'" + s + "' was not found in the document." + RESET); } else { System.out.println("Please enter the words you want to define as sub variants of '" + s + "' separated by spaces."); String input = scanner.nextLine().trim(); String[] subVariants = input.split("\\s+"); for (String subV : subVariants) { Optional oSubV = words.stream().filter(word -> word.getValue().equals(subV.trim())).findFirst(); Word subVariant = oSubV.orElse(null); if (subVariant == null) { System.out.println(WARNING + "'" + subV + "' was not found in the document." + RESET); } else { subVariant.setSuperVariant(superVariant); System.out.println(SUCCESS + "Successfully defined '" + subV + "' to be a sub variant of '" + s + "'." + RESET); } } } } } else { System.out.println("\n\n\tThe 'subvariant' command let's you define words to be sub variants of other words. This means that these sub variants will indexed under their respective super variant and not on their own alphabetically.\n" + "\t\tTo specify the words you want to define sub variants for, simply append them to the 'subvariant' command separated by single spaces as follows:\n" + "\t\t$ s ...\n\n"); } } } /** * Reads user input from the command line and defines specified words to be sub variations of a (specified) word. * * @param command An array containing the word the user wants to define variations of. */ private void setVariation(String[] command) { if (Arrays.asList(command).contains("-h")) { System.out.println("\n\n\tThe 'variation' command let's you define words to be variations of other words. This means that instances of these sub variants will be indexed under their respective super variation. These words will however not appear in the index on their own.\n" + "\t\tTo specify the words you want to define variations for, simply append them to the 'variation' command separated by single spaces as follows:\n" + "\t\t$ v ...\n\n"); } else { if (command.length < 2) { System.out.println("\n\n\tThe 'variation' command let's you define words to be variations of other words. This means that instances of these sub variants will be indexed under their respective super variation. These words will however not appear in the index on their own." + "\t\tTo specify the words you want to define variations for, simply append them to the 'variation' command separated by single spaces as follows:\n" + "\t\t$ v ...\n\n"); } else { Scanner scanner = new Scanner(System.in); for (int i = 1; i < command.length; i++) { String s = command[i]; Optional w = words.stream().filter(word -> word.getValue().equals(s)).findFirst(); Word word = w.orElse(null); if (word == null) { System.out.println(WARNING + "'" + s + "' not found in the given document." + RESET); } else { System.out.println("Enter the words you want to define as variations of '" + word.getValue() + "' separated by a single space each."); String input = scanner.nextLine().trim(); String[] variations = input.split("\\s+"); for (String var : variations) { if (words.stream().anyMatch(wor -> wor.getValue().equals(var))) { word.setVariation(var); System.out.println(SUCCESS + "Successfully added '" + var + "' as a variation of '" + word.getValue() + "'." + RESET); } else { System.out.println(WARNING + "'" + var + "' not found in the given document." + RESET); } } } } } } } /** * Adds the words interactively (i.e. occurrence by occurrence). * @param command An array containing the arguments passed to the command. */ private void addToIndexInteractive(String[] command) { if (command.length < 2) { System.out.println(WARNING + "Please provide the word you want to add" + RESET); } else { if (Arrays.asList(command).contains("-h")) { System.out.println("\n\n\tThe 'interactive' command lets you add the specified word to the index interactively, asking you at every occurrence of the word whether you want to index it or not.\n" + "\t\tTo specify the word you want to add to the index, simply append it to the 'interactive' command as follows:\n" + "\t\t$ i \n\n"); return; } Optional w = words.stream().filter(word -> word.getValue().equals(command[1])).findFirst(); Word word = w.orElse(null); if (word == null) { System.out.println(WARNING + "'" + command[1] + "' not found in the given document." + RESET); return; } indexWriter = new IndexWriter(texFile); Map> occurrences = null; try { occurrences = indexWriter.getOccurrences(word); } catch (IOException e) { System.out.println(e.getMessage()); throw new RuntimeException(e); } Scanner scanner = new Scanner(System.in); // for each file for (Map.Entry> entry : occurrences.entrySet()) { Path filePath = Paths.get(entry.getKey()); List lines = null; try { lines = Files.readAllLines(filePath); } catch (IOException e) { System.out.println(e.getMessage()); throw new RuntimeException(e); } System.out.println("Current file: " + filePath.toAbsolutePath().normalize()); String previousLine = "\n"; String nextLine = "\n"; List toBeReplaced = new ArrayList<>(); // for each line in that file for (int lineNumber : entry.getValue()) { if (lineNumber > 1) { previousLine = lines.get(lineNumber - 1); } if (lineNumber < lines.size() - 1) { nextLine = lines.get(lineNumber + 1); } String content = lines.get(lineNumber); System.out.println("Replace word at line number: " + lineNumber); System.out.println((lineNumber - 1) + ": " + previousLine); System.out.println(lineNumber + ": " + content); System.out.println((lineNumber + 1) + ": " + nextLine); System.out.println("[Y]es [N]o [A]bort"); String input = scanner.nextLine().trim().toUpperCase(); if (input.equals("A")) { System.out.println("Aborting."); return; } else if (input.equals("N")) { System.out.println("Skipping to next."); } else if (input.equals("Y")) { toBeReplaced.add(lineNumber); } else { System.out.println("Invalid Input. Added instance of word to index."); } } occurrences.replace(entry.getKey(), toBeReplaced); } try { indexWriter.addToIndexByOccurrence(occurrences, word); } catch (IOException e) { throw new RuntimeException(e); } } } /** * Argument class to more easily handle additional parameters a user might pass to a command. */ static class Argument { private final String name; private final String value; public Argument(String name, String value) { this.name = name; this.value = value; } public String getValue() { return this.value; } public String getName() { return this.name; } } }