From ff08a196fe98790d78ee7f5b26b9f367f9fa5a68 Mon Sep 17 00:00:00 2001 From: Eduardo Pedroni Date: Thu, 11 Jun 2015 18:12:24 +0200 Subject: Started work on the GUI, and added imageCode field to card, now need to update own collection --- src/eu/equalparts/cardbase/cli/CardbaseCLI.java | 493 ++++++++++++++++++++++++ 1 file changed, 493 insertions(+) create mode 100644 src/eu/equalparts/cardbase/cli/CardbaseCLI.java (limited to 'src/eu/equalparts/cardbase/cli') diff --git a/src/eu/equalparts/cardbase/cli/CardbaseCLI.java b/src/eu/equalparts/cardbase/cli/CardbaseCLI.java new file mode 100644 index 0000000..7a2e581 --- /dev/null +++ b/src/eu/equalparts/cardbase/cli/CardbaseCLI.java @@ -0,0 +1,493 @@ +package eu.equalparts.cardbase.cli; + +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.Arrays; +import java.util.Scanner; + +import com.fasterxml.jackson.core.JsonGenerationException; +import com.fasterxml.jackson.core.JsonParseException; +import com.fasterxml.jackson.databind.JsonMappingException; + +import eu.equalparts.cardbase.Cardbase; +import eu.equalparts.cardbase.data.Card; +import eu.equalparts.cardbase.data.FullCardSet; +import eu.equalparts.cardbase.data.CardSetInformation; +import eu.equalparts.cardbase.utils.MTGUniverse; + +/** + * This provides a lightweight CLI for interacting with cardbase files. + * + * @author Eduardo Pedroni + */ +public class CardbaseCLI { + + /** + * Enum type to store actions. + * + * @author Eduardo Pedroni + */ + private enum Action { + ADD, REMOVE; + public Card card; + public Integer count; + /** + * Sets both fields at once. + * + * @param card the card last modified. + * @param count the amount that was added or removed. + */ + public void set(Card card, Integer count) { + this.card = card; + this.count = count; + } + } + + /** + * Location of the help file. + */ + private static final String HELP_FILE_PATH = "/help"; + /** + * The last action performed by the user. + */ + private Action lastAction = null; + /** + * The currently selected set, from which new cards are added. + */ + private FullCardSet selectedSet = null; + /** + * The actual cardbase being interfaced with. + */ + private Cardbase cardbase; + /** + * Printed to the console when the user enters the help command. + */ + private String help = "Not available, check project page on GitHub."; + /** + * The cardbase file off which we are currently working, if any. + */ + private File cardbaseFile = null; + /** + * Save flag is raised when cards are added or removed and causes a prompt to be shown + * if the user tries to exit with unsaved changed. + */ + private boolean savePrompt = false; + /** + * Exit flag, program breaks out of the main loop when true. + */ + private boolean exit = false; + + /** + * Execute the interface. + * + * @param args the first argument is the cardbase file. Further arguments are ignored. + */ + public static void main(String... args) { + new CardbaseCLI(args).startInterface(); + } + + /** + * Reads in an optional cardbase JSON and initialises other necessary components. + * This does not actually produce the CLI, for that use {@code startInterface()} + * on the constructed object. + * + * @param args a list of arguments. Only the first argument is used, as a cardbase JSON. + */ + private CardbaseCLI(String... args) { + System.out.println("Welcome to Cardbase CLI!"); + + // set debug flag if we are debugging + if (Cardbase.DEBUG) System.out.println("Debug mode is on."); + + // make the CardbaseManager + if (args.length > 0) { + File cardbaseFile = new File(args[0]); + if (cardbaseFile.exists() && cardbaseFile.isFile() && cardbaseFile.canRead()) { + System.out.println("Loading cardbase from \"" + args[0] + "\"."); + try { + cardbase = new Cardbase(cardbaseFile); + } catch (JsonParseException e) { + System.out.println("Error: poorly formatted cardbase, check the syntax and try again."); + // although the problem could also be with the upstream CardSetList json. + if (Cardbase.DEBUG) e.printStackTrace(); + System.exit(1); + } catch (JsonMappingException e) { + System.out.println("Error: unexpected fields found in cardbase, it may be from an old version?"); + if (Cardbase.DEBUG) e.printStackTrace(); + System.exit(1); + } catch (IOException e) { + System.out.println("Error: something went wrong reading cardbase file, abort..."); + if (Cardbase.DEBUG) e.printStackTrace(); + System.exit(1); + } + } else { + System.out.println(args[0] + " appears to be invalid."); + System.exit(0); + } + } else { + System.out.println("No cardbase file was provided, creating a clean cardbase."); + cardbase = new Cardbase(); + } + + // load help information + InputStream is = CardbaseCLI.class.getResourceAsStream(HELP_FILE_PATH); + if (is != null) { + help = new Scanner(is).useDelimiter("\\Z").next(); + } else { + System.out.println("Help file was not found, check the project page on GitHub for help instead."); + } + + } + + /** + * Read stdin for user input, sanitise and interpret any commands entered. + */ + private void startInterface() { + BufferedReader consoleReader = new BufferedReader(new InputStreamReader(System.in)); + try { + // the main loop + while (!exit) { + // print prompt + System.out.print(selectedSet == null ? "> " : selectedSet.code + " > "); + // condition input and interpret + String[] raw = consoleReader.readLine().trim().split("[ \t]+"); + String command = raw[0]; + String[] args = Arrays.copyOfRange(raw, 1, raw.length); + + if (command.equalsIgnoreCase("help")) { + help(); + } else if (command.equalsIgnoreCase("write") + || command.equalsIgnoreCase("save")) { + write(args); + } else if (command.equalsIgnoreCase("exit")) { + exit(); + } else if (command.equalsIgnoreCase("sets")) { + sets(); + } else if (command.equalsIgnoreCase("set")) { + set(args); + } else if (command.equalsIgnoreCase("glance")) { + glance(); + } else if (command.equalsIgnoreCase("peruse")) { + peruse(args); + } else if (command.equalsIgnoreCase("undo")) { + undo(); + } else if (command.equalsIgnoreCase("remove") + || command.equalsIgnoreCase("rm")) { + remove(args); + } else { + add(command, args); + } + } + } catch (IOException e) { + System.out.println("Error: something went wrong with stdin, exiting..."); + if (Cardbase.DEBUG) e.printStackTrace(); + } + } + + /** + * Print help to console. + */ + public void help() { + System.out.println(help); + } + + /** + * Write current cardbase to file. + * + * @param args optionally the file to which to write. + */ + public void write(String[] args) { + File outputFile; + // user-provided file overrides everything else + if (args.length > 0) { + outputFile = new File(sanitiseFileName(args[0])); + } else { + outputFile = cardbaseFile; + } + + if (outputFile != null) { + if (outputFile.exists() && (!outputFile.isFile() || !outputFile.canWrite())) { + System.out.println("Could not write to \"" + outputFile.getAbsolutePath() + "\"."); + } else { + // handle these exceptions locally - they don't necessarily mean the program should exit + try { + cardbase.writeCardbase(outputFile); + // we are now working off outputFile, which may or may not be the same as cardbaseFile at this point + cardbaseFile = outputFile; + System.out.println("Cardbase was saved to \"" + outputFile.getAbsolutePath() + "\". " + + "Subsequent writes will be done to this same file unless otherwise requested."); + savePrompt = false; + } catch (JsonGenerationException | JsonMappingException e) { + System.out.println("Error: something terrible happened to the internal cardbase data structure. Oops."); + if (Cardbase.DEBUG) e.printStackTrace(); + } catch (IOException e) { + System.out.println("Error: lost contact with the output file, try again?"); + if (Cardbase.DEBUG) e.printStackTrace(); + } + } + } else { + System.out.println("Please provide a file name."); + } + } + + /** + * Exit procedure. + */ + public void exit() { + if (savePrompt) { + System.out.println("Don't forget to save. If you really wish to quit without saving, type \"exit\" again."); + savePrompt = false; + } else { + exit = true; + } + } + + /** + * Print a list of valid set codes. + */ + public void sets() { + for (CardSetInformation set : MTGUniverse.getCardSetList()) { + // CardSet has an overridden toString() + System.out.println(set); + } + } + + /** + * Select a set. + * + * @param args the code of the chosen set. + */ + public void set(String[] args) { + if (args.length > 0) { + try { + selectedSet = MTGUniverse.getFullCardSet(args[0]); + // if the set code is invalid, null is returned + if (selectedSet != null) { + System.out.println("Selected set: " + selectedSet.name + "."); + // undoing is not allowed if the set is changed - it would get tricky + lastAction = null; + } else { + System.out.println("\"" + args[0] + "\" does not correspond to any set (use \"sets\" to see all valid set codes)."); + } + } catch (JsonParseException e) { + System.out.println("Error: JSON fetched from upstream was not formatted properly."); + if (Cardbase.DEBUG) e.printStackTrace(); + } catch (JsonMappingException e) { + System.out.println("Error: JSON fetched from upstream does not match the data structure used internally."); + if (Cardbase.DEBUG) e.printStackTrace(); + } catch (IOException e) { + System.out.println("Error: JSON could not be fetched from upstream."); + if (Cardbase.DEBUG) e.printStackTrace(); + } + } else { + System.out.println("Please enter a set code (use \"sets\" to see all valid set codes)."); + } + } + + /** + * Print a brief list of the whole cardbase. + */ + public void glance() { + int total = 0; + for (Card card : cardbase.getCards()) { + printGlance(card); + total += card.count; + } + System.out.println("Total: " + total); + } + + /** + * Print detailed information of a single card or the whole cardbase. + * + * @param args optionally a card within the set (by number) to peruse. + */ + public void peruse(String[] args) { + // if a card is specified, peruse only that + if (args.length > 0) { + if (selectedSet != null) { + Card card = cardbase.getCard(selectedSet.code, args[0]); + if (card != null) { + printPerusal(card); + } else { + System.out.println("Card not in cardbase."); + } + } else { + System.out.println("Please select a set before perusing a specific card."); + } + } else { + // peruse all cards in cardbase + int total = 0; + for (Card card : cardbase.getCards()) { + printPerusal(card); + total += card.count; + } + System.out.println("Total: " + total); + } + } + + /** + * Undo previous action. + */ + public void undo() { + if (lastAction != null) { + if (lastAction == Action.ADD) { + removeCard(lastAction.card, lastAction.count); + } else if (lastAction == Action.REMOVE) { + addCard(lastAction.card, lastAction.count); + } + // can only undo once + lastAction = null; + } else { + System.out.println("Nothing to undo."); + } + } + + /** + * Remove one or more of a card. + * + * @param args the set number of the card to remove and optionally the count to be removed. + */ + public void remove(String[] args) { + if (selectedSet != null) { + if (args.length > 0) { + Card cardToRemove = selectedSet.getCardByNumber(args[0]); + if (cardToRemove != null) { + Integer count = 1; + if (args.length > 1 && args[1].matches("[0-9]+")) { + count = Integer.valueOf(args[1]); + if (count <= 0) { + System.out.println("Can't remove " + count + " cards."); + return; + } + } + removeCard(cardToRemove, count); + } else { + System.out.println(args[0] + " does not correspond to a card in " + selectedSet.name + "."); + } + } else { + System.out.println("Please specify a card number to remove."); + } + } else { + System.out.println("Select a set before removing cards."); + } + } + + /** + * Add one or more of a card. + * + * @param number the number of the card to add. + * @param args optionally the count to add. + */ + public void add(String number, String[] args) { + if (selectedSet != null) { + // a blank line after adding a card repeats the addition unlimitedly + if (number.isEmpty()) { + if (lastAction == Action.ADD) + addCard(lastAction.card, lastAction.count); + } else { + Card cardToAdd = selectedSet.getCardByNumber(number); + if (cardToAdd != null) { + Integer count = 1; + if (args.length > 0 && args[0].matches("[0-9]+")) { + count = Integer.valueOf(args[0]); + if (count <= 0) { + System.out.println("Can't add " + count + " cards."); + return; + } + } + addCard(cardToAdd, count); + } else { + System.out.println(number + " does not correspond to a card in " + selectedSet.name + "."); + } + } + } else { + System.out.println("Select a set before adding cards."); + } + } + + /** + * Add the specified count of the specified card + * to the cardbase. + * + * @param card the card to add. + * @param count the number of times to add it. + */ + private void addCard(Card card, Integer count) { + cardbase.addCard(card, count); + System.out.println("Added " + count + "x " + card.name + "."); + savePrompt = true; + lastAction = Action.ADD; + lastAction.set(card, count); + } + + /** + * Remove the specified count of the specified card + * from the cardbase. + * + * @param card the card to remove. + * @param count the number of times to remove it. + */ + private void removeCard(Card card, Integer count) { + Integer removed = cardbase.removeCard(card, count); + if (removed > 0) { + System.out.println("Removed " + removed + "x " + card.name + "."); + savePrompt = true; + lastAction = Action.REMOVE; + lastAction.set(card, removed); + } else { + System.out.println(card.name + " is not in the cardbase."); + } + + } + + /** + * Return a {@code String} that is guaranteed to be a legal file name. + * + * @param name the file name candidate to sanitise. + * @return the sanitised name. + */ + private String sanitiseFileName(String name) { + // POSIX-compliant valid filename characters + name = name.replaceAll("[^-_.A-Za-z0-9]", ""); + // extension is not indispensable, but good practice + if (!name.endsWith(".cb")) { + name = name.concat(".cb"); + } + return name; + } + + /** + * Prints a glance of the specified card. A glance contains simply + * the card count, name, set code and set number. + * + * @param card the card to glance. + */ + private void printGlance(Card card) { + System.out.println(String.format("%1$-4d %2$s (%3$s, %4$s)", card.count, card.name, card.setCode, card.number)); + } + + /** + * Prints a perusal of the specified card. A perusal contains more + * information than a glance, but not every single field the card has + * as that would be too verbose while adding little value. + * + * @param card the card to peruse. + */ + private void printPerusal(Card card) { + printGlance(card); + if (card.type != null) System.out.println("\t" + card.type); + if (card.manaCost != null) System.out.println("\tCost: " + card.manaCost); + if (card.power != null && card.toughness != null) System.out.println("\t" + card.power + "/" + card.toughness); + if (card.loyalty != null) System.out.println("\tLoyalty: " + card.loyalty); + + if (card.text != null) System.out.println("\t" + card.text.replaceAll("\n", "\n\t")); + if (card.flavor != null) System.out.println("\t" + card.flavor.replaceAll("\n", "\n\t")); + + if (card.rarity != null) System.out.println("\t" + card.rarity); + if (card.multiverseid != null) System.out.println("\tMID: " + card.multiverseid); + if (card.artist != null) System.out.println("\tIllus. " + card.artist); + } +} -- cgit v1.2.3