From 62bb720ea707765e2a33a03ee5dfa91b753123e8 Mon Sep 17 00:00:00 2001 From: Eduardo Pedroni Date: Fri, 5 Jun 2015 21:50:36 +0200 Subject: Tidied up README and help, sorted out a neater Ant build script --- src/eu/equalparts/cardbase/data/CardBase.java | 24 -- .../equalparts/cardbase/data/CardBaseManager.java | 4 +- src/eu/equalparts/cardbase/data/Cardbase.java | 24 ++ src/eu/equalparts/cardbase/query/IO.java | 8 +- src/eu/equalparts/cardbase/query/Test.java | 70 ----- .../cardbase/standalone/CardBaseCLI.java | 275 ----------------- .../cardbase/standalone/CardbaseCLI.java | 341 +++++++++++++++++++++ 7 files changed, 371 insertions(+), 375 deletions(-) delete mode 100644 src/eu/equalparts/cardbase/data/CardBase.java create mode 100644 src/eu/equalparts/cardbase/data/Cardbase.java delete mode 100644 src/eu/equalparts/cardbase/query/Test.java delete mode 100644 src/eu/equalparts/cardbase/standalone/CardBaseCLI.java create mode 100644 src/eu/equalparts/cardbase/standalone/CardbaseCLI.java (limited to 'src') diff --git a/src/eu/equalparts/cardbase/data/CardBase.java b/src/eu/equalparts/cardbase/data/CardBase.java deleted file mode 100644 index abdd423..0000000 --- a/src/eu/equalparts/cardbase/data/CardBase.java +++ /dev/null @@ -1,24 +0,0 @@ -package eu.equalparts.cardbase.data; - -import java.util.ArrayList; - -public class CardBase { - - public ArrayList cards = new ArrayList<>(); - public ArrayList decks = new ArrayList<>(); - - /** - * @param setCode - * @param number - * @return the card if found, else null. - */ - public Card getCardByNumber(String setCode, String number) { - for (Card card : cards) { - if (card.setCode.equals(setCode) && card.number.equals(number)) - return card; - } - - return null; - } - -} diff --git a/src/eu/equalparts/cardbase/data/CardBaseManager.java b/src/eu/equalparts/cardbase/data/CardBaseManager.java index 0baf9d7..d6a63dc 100644 --- a/src/eu/equalparts/cardbase/data/CardBaseManager.java +++ b/src/eu/equalparts/cardbase/data/CardBaseManager.java @@ -12,7 +12,7 @@ import eu.equalparts.cardbase.query.IO; public class CardBaseManager { private ArrayList metaSets; - public CardBase cardBase; + public Cardbase cardBase; /** * Parse a cardbase file and create an associated CardBase object. @@ -33,7 +33,7 @@ public class CardBaseManager { */ public CardBaseManager() throws JsonParseException, JsonMappingException, IOException { metaSets = IO.getAllMetaSets(); - cardBase = new CardBase(); + cardBase = new Cardbase(); } public ArrayList getAllMetaSets() { diff --git a/src/eu/equalparts/cardbase/data/Cardbase.java b/src/eu/equalparts/cardbase/data/Cardbase.java new file mode 100644 index 0000000..1c2aa12 --- /dev/null +++ b/src/eu/equalparts/cardbase/data/Cardbase.java @@ -0,0 +1,24 @@ +package eu.equalparts.cardbase.data; + +import java.util.ArrayList; + +public class Cardbase { + + public ArrayList cards = new ArrayList<>(); + public ArrayList decks = new ArrayList<>(); + + /** + * @param setCode + * @param number + * @return the card if found, else null. + */ + public Card getCardByNumber(String setCode, String number) { + for (Card card : cards) { + if (card.setCode.equals(setCode) && card.number.equals(number)) + return card; + } + + return null; + } + +} diff --git a/src/eu/equalparts/cardbase/query/IO.java b/src/eu/equalparts/cardbase/query/IO.java index 9b81b20..e913966 100644 --- a/src/eu/equalparts/cardbase/query/IO.java +++ b/src/eu/equalparts/cardbase/query/IO.java @@ -13,7 +13,7 @@ import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.JsonMappingException; import com.fasterxml.jackson.databind.ObjectMapper; -import eu.equalparts.cardbase.data.CardBase; +import eu.equalparts.cardbase.data.Cardbase; import eu.equalparts.cardbase.data.CardSet; import eu.equalparts.cardbase.data.MetaCardSet; @@ -59,8 +59,8 @@ public class IO { * @throws JsonMappingException * @throws IOException */ - public static CardBase readCardBase(File file) throws JsonParseException, JsonMappingException, IOException { - return mapper.readValue(file, CardBase.class); + public static Cardbase readCardBase(File file) throws JsonParseException, JsonMappingException, IOException { + return mapper.readValue(file, Cardbase.class); } /** @@ -72,7 +72,7 @@ public class IO { * @throws JsonMappingException * @throws IOException */ - public static void writeCardBase(File file, CardBase cardBase) throws JsonGenerationException, JsonMappingException, IOException { + public static void writeCardBase(File file, Cardbase cardBase) throws JsonGenerationException, JsonMappingException, IOException { mapper.writeValue(file, cardBase); } diff --git a/src/eu/equalparts/cardbase/query/Test.java b/src/eu/equalparts/cardbase/query/Test.java deleted file mode 100644 index e466c55..0000000 --- a/src/eu/equalparts/cardbase/query/Test.java +++ /dev/null @@ -1,70 +0,0 @@ -package eu.equalparts.cardbase.query; - -import java.io.File; -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; - -import com.fasterxml.jackson.core.JsonParseException; -import com.fasterxml.jackson.databind.DeserializationFeature; -import com.fasterxml.jackson.databind.JsonMappingException; -import com.fasterxml.jackson.databind.ObjectMapper; - -import eu.equalparts.cardbase.data.Card; -import eu.equalparts.cardbase.data.CardSet; - -public class Test { - -// public static void main(String[] args) throws JsonParseException, JsonMappingException, IOException { -// ObjectMapper mapper = new ObjectMapper(); -// mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); -// -// @SuppressWarnings("unchecked") -// List allCards = getAllCards((Map) mapper -// .readValue(new File("AllSets.json"), new TypeReference>() {})); -// -// System.out.println("Number of cards: " + allCards.size()); -// } -// -// public static List getAllCards(Map sets) { -// List allCards = new ArrayList(); -// -// for (CardSet set : sets.values()) { -// for (Card card : set.getCards()) { -// card.setSetCode(set.getCode()); -// card.setSetName(set.getName()); -// // System.out.println(set.getName() + ": " + card.getName()); -// -// allCards.add(card); -// } -// } -// -// return allCards; -// } - - public static void main(String[] args) throws JsonParseException, JsonMappingException, IOException { - ObjectMapper mapper = new ObjectMapper(); - mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); - -// @SuppressWarnings("unchecked") - CardSet set = mapper.readValue(new File("M15.json"), CardSet.class); - System.out.println(set.getCardByNumber("281").name); - - } - - public static List getAllCards(Map sets) { - List allCards = new ArrayList(); - - for (CardSet set : sets.values()) { - for (Card card : set.cards) { - card.setCode = set.code; - // System.out.println(set.getName() + ": " + card.getName()); - - allCards.add(card); - } - } - - return allCards; - } -} diff --git a/src/eu/equalparts/cardbase/standalone/CardBaseCLI.java b/src/eu/equalparts/cardbase/standalone/CardBaseCLI.java deleted file mode 100644 index d0503ac..0000000 --- a/src/eu/equalparts/cardbase/standalone/CardBaseCLI.java +++ /dev/null @@ -1,275 +0,0 @@ -package eu.equalparts.cardbase.standalone; - -import java.io.BufferedReader; -import java.io.File; -import java.io.IOException; -import java.io.InputStreamReader; -import java.net.MalformedURLException; -import java.util.HashMap; -import java.util.Iterator; -import java.util.Scanner; - -import com.fasterxml.jackson.core.JsonParseException; -import com.fasterxml.jackson.databind.JsonMappingException; - -import eu.equalparts.cardbase.data.Card; -import eu.equalparts.cardbase.data.CardBaseManager; -import eu.equalparts.cardbase.data.CardSet; -import eu.equalparts.cardbase.data.MetaCardSet; -import eu.equalparts.cardbase.query.IO; - -/** - * This provides a lightweight CLI for interacting with cardbase files. - * - */ -public class CardBaseCLI { - - private static CardSet selectedSet = null; - private static HashMap setCache = new HashMap(); - private static CardBaseManager cbm; - private static boolean exit = false; - private static String help = "No help file was found"; - private static File cardBaseFile; - - /** - * Execute the interface. - * - * @param args the first argument is the cardbase file. Further arguments are ignored. - */ - public static void main(String... args) { - - System.out.println("Welcome to cardbase"); - - try { - // construct the cardbase - if (args.length > 0) { - cardBaseFile = new File(args[0]); - if (cardBaseFile.exists() && cardBaseFile.isFile() && cardBaseFile.canRead()) { - System.out.println("Loading cardbase from " + args[0]); - cbm = new CardBaseManager(cardBaseFile); - } else { - System.out.println(args[0] + " appears to be invalid"); - System.exit(0); - } - } else { - //System.out.println("No cardbase file was provided, initialising a clean cardbase"); - System.out.println("Loading testbase for debugging purposes"); - cardBaseFile = new File("testbase"); - cbm = new CardBaseManager(cardBaseFile); - } - - // initialise necessary components - System.out.println("Fetching card sets from upstream"); - BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); - - System.out.println("Loading externals"); - File helpFile = new File("help"); - if (helpFile.exists() && helpFile.canRead()) { - help = new Scanner(helpFile).useDelimiter("\\Z").next(); - } else { - System.out.println("Help file is not available, I hope you know how to use the program!"); - } - - while (!exit) { - System.out.print(selectedSet == null ? "> " : selectedSet.code + " > "); - String rawInput = br.readLine().trim().toLowerCase(); - String[] commands = rawInput.split("[ \t]+"); - - parse(commands); - } - - } catch (JsonParseException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } catch (JsonMappingException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } catch (IOException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - - } - - private static void parse(String[] commands) throws JsonParseException, JsonMappingException, MalformedURLException, IOException { - if (commands.length > 0) { - switch (commands[0]) { - /* - * Show help - */ - case "help": - System.out.println(help); - break; - - /* - * Write current CardBase to file - */ - case "write": - if (commands.length > 1) { - File output = new File(commands[1]); - if (output.exists()) { - if(output.isFile()) { - if (output.canWrite()) { - IO.writeCardBase(output, cbm.cardBase); - cardBaseFile = output; - System.out.println("Cardbase saved to " + output.getAbsolutePath()); - } else { - System.out.println(commands[1] + " cannot be written to, nothing was written"); - } - } else { - System.out.println(commands[1] + " is not a file, nothing was written"); - } - } else { - System.out.println(commands[1] + " does not exist, nothing was written"); - } - } else if (cardBaseFile != null) { - System.out.println("Writing to " + cardBaseFile.getAbsolutePath()); - } else { - System.out.println("Please provide a file name"); - } - break; - - /* - * Exit procedures - */ - case "exit": - exit = true; - break; - - /* - * Print a list of valid set codes - */ - case "sets": - for (MetaCardSet mcs : cbm.getAllMetaSets()) { - System.out.println(mcs); - } - break; - - /* - * Select a set, any card numbers provided will be fetched from that set - */ - case "set": - for (MetaCardSet mcs : cbm.getAllMetaSets()) { - if (mcs.code.equalsIgnoreCase(commands[1])) { - // if the set is cached, use that - if (setCache.containsKey(mcs.code)) { - selectedSet = setCache.get(mcs.code); - } else { - selectedSet = IO.getCardSet(mcs.code); - setCache.put(mcs.code, selectedSet); - } - System.out.println("Selected set: " + mcs.name); - return; - } - } - System.out.println(commands[1] + " does not correspond to any set (use \"sets\" to see all valid set codes)"); - break; - - /* - * Print a brief list of the complete cardbase. - */ - case "glance": - for (Iterator i = cbm.cardIterator(); i.hasNext();) { - printGlance(i.next()); - } - break; - - /* - * Print a detailed information of a single card or the whole cardbase. - */ - case "peruse": - if (commands.length > 1) { - if (selectedSet != null) { - Card card = cbm.getCard(selectedSet.code, commands[1]); - 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 { - for (Iterator i = cbm.cardIterator(); i.hasNext();) { - printPerusal(i.next()); - } - } - break; - - /* - * Remove one or more cards - */ - case "remove": - if (selectedSet != null) { - if (commands.length > 1) { - Card remove = selectedSet.getCardByNumber(commands[1]); - if (remove != null) { - Integer count = 1; - if (commands.length > 2 && commands[2].matches("[0-9]+")) { - count = Integer.valueOf(commands[2]); - if (count <= 0) { - System.out.println("Can't remove " + count + " cards"); - return; - } - } - cbm.removeCard(remove, count); - System.out.println("Removed " + count + "x " + remove.name); - } else { - System.out.println(commands[1] + " 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."); - } - break; - - /* - * Add one or more cards - */ - default: - if (selectedSet != null) { - Card newCard = selectedSet.getCardByNumber(commands[0]); - if (newCard != null) { - Integer count = 1; - if (commands.length > 1 && commands[1].matches("[0-9]+")) { - count = Integer.valueOf(commands[1]); - if (count <= 0) { - System.out.println("Can't add " + count + " cards"); - return; - } - } - newCard.setCode = selectedSet.code; - cbm.addCard(newCard, count); - System.out.println("Added " + count + "x " + newCard.name); - } else { - System.out.println(commands[0] + " does not correspond to a card in " + selectedSet.name); - } - } else { - System.out.println("Select a set before adding cards."); - } - break; - } - } - } - - private static 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); - } - - private static 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)); - } -} diff --git a/src/eu/equalparts/cardbase/standalone/CardbaseCLI.java b/src/eu/equalparts/cardbase/standalone/CardbaseCLI.java new file mode 100644 index 0000000..c19bc08 --- /dev/null +++ b/src/eu/equalparts/cardbase/standalone/CardbaseCLI.java @@ -0,0 +1,341 @@ +package eu.equalparts.cardbase.standalone; + +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.net.MalformedURLException; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Scanner; + +import com.fasterxml.jackson.core.JsonParseException; +import com.fasterxml.jackson.databind.JsonMappingException; + +import eu.equalparts.cardbase.data.Card; +import eu.equalparts.cardbase.data.CardBaseManager; +import eu.equalparts.cardbase.data.CardSet; +import eu.equalparts.cardbase.data.MetaCardSet; +import eu.equalparts.cardbase.query.IO; + +/** + * This provides a lightweight CLI for interacting with cardbase files. + * + */ +public class CardbaseCLI { + + private enum LastAction { + ADD, REMOVE; + public Integer count; + public Card card; + + public void set(Card card, Integer count) { + this.card = card; + this.count = count; + } + + } + private static LastAction lastAction = null; + + private static CardSet selectedSet = null; + private static HashMap setCache = new HashMap(); + private static CardBaseManager cbm; + private static boolean exit = false; + private static String help = "No help file was found"; + private static File cardBaseFile = null; + private static boolean savePrompt = false; + + /** + * Execute the interface. + * + * @param args the first argument is the cardbase file. Further arguments are ignored. + */ + public static void main(String... args) { + + System.out.println("Welcome to cardbase"); + + try { + // construct the cardbase + if (args.length > 0) { + cardBaseFile = new File(args[0]); + if (cardBaseFile.exists() && cardBaseFile.isFile() && cardBaseFile.canRead()) { + System.out.println("Loading cardbase from " + args[0]); + cbm = new CardBaseManager(cardBaseFile); + } else { + System.out.println(args[0] + " appears to be invalid"); + System.exit(0); + } + } else { + System.out.println("No cardbase file was provided, initialising a clean cardbase"); + cbm = new CardBaseManager(); + } + + // initialise necessary components + System.out.println("Fetching card sets from upstream"); + BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); + + System.out.println("Loading externals"); + InputStream is = CardbaseCLI.class.getResourceAsStream("/help"); + if (is != null) { + help = new Scanner(is).useDelimiter("\\Z").next(); + } else { + System.out.println("Help file is not available, I hope you know how to use the program!"); + } + + while (!exit) { + System.out.print(selectedSet == null ? "> " : selectedSet.code + " > "); + String rawInput = br.readLine().trim().toLowerCase(); + String[] commands = rawInput.split("[ \t]+"); + + parse(commands); + } + + } catch (JsonParseException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (JsonMappingException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + + } + + private static void parse(String[] commands) throws JsonParseException, JsonMappingException, MalformedURLException, IOException { + if (commands.length > 0) { + switch (commands[0]) { + /* + * Show help + */ + case "help": + System.out.println(help); + break; + + /* + * Write current CardBase to file + */ + case "write": + File output; + + if (commands.length > 1) { + output = new File(sanitiseFileName(commands[1])); + } else { + output = cardBaseFile; + } + + if (output != null) { + if (output.exists() && (!output.isFile() || !output.canWrite())) { + System.out.println("Could not write to " + output.getAbsolutePath()); + return; + } + + IO.writeCardBase(output, cbm.cardBase); + cardBaseFile = output; + System.out.println("Cardbase was saved to " + output.getAbsolutePath()); + savePrompt = false; + } else { + System.out.println("Please provide a file name"); + } + break; + + /* + * Exit procedures + */ + case "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; + } + break; + + /* + * Print a list of valid set codes + */ + case "sets": + for (MetaCardSet mcs : cbm.getAllMetaSets()) { + System.out.println(mcs); + } + break; + + /* + * Select a set, any card numbers provided will be fetched from that set + */ + case "set": + for (MetaCardSet mcs : cbm.getAllMetaSets()) { + if (mcs.code.equalsIgnoreCase(commands[1])) { + // if the set is cached, use that + if (setCache.containsKey(mcs.code)) { + selectedSet = setCache.get(mcs.code); + } else { + selectedSet = IO.getCardSet(mcs.code); + setCache.put(mcs.code, selectedSet); + } + System.out.println("Selected set: " + mcs.name); + lastAction = null; + return; + } + } + System.out.println(commands[1] + " does not correspond to any set (use \"sets\" to see all valid set codes)"); + break; + + /* + * Print a brief list of the complete cardbase. + */ + case "glance": + Card current; + int total = 0; + for (Iterator i = cbm.cardIterator(); i.hasNext();) { + current = i.next(); + printGlance(current); + total += current.count; + } + System.out.println("Total: " + total); + break; + + /* + * Print a detailed information of a single card or the whole cardbase. + */ + case "peruse": + if (commands.length > 1) { + if (selectedSet != null) { + Card card = cbm.getCard(selectedSet.code, commands[1]); + 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 { + for (Iterator i = cbm.cardIterator(); i.hasNext();) { + printPerusal(i.next()); + } + } + break; + + /* + * Undo previous action. + */ + case "undo": + if (lastAction != null) { + if (lastAction == LastAction.ADD) { + remove(lastAction.card, lastAction.count); + } else if (lastAction == LastAction.REMOVE) { + add(lastAction.card, lastAction.count); + } + lastAction = null; + } else { + System.out.println("Nothing to undo"); + } + break; + + /* + * Remove one or more cards + */ + case "remove": + if (selectedSet != null) { + if (commands.length > 1) { + Card remove = selectedSet.getCardByNumber(commands[1]); + if (remove != null) { + Integer count = 1; + if (commands.length > 2 && commands[2].matches("[0-9]+")) { + count = Integer.valueOf(commands[2]); + if (count <= 0) { + System.out.println("Can't remove " + count + " cards"); + return; + } + } + remove(remove, count); + } else { + System.out.println(commands[1] + " 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."); + } + break; + + /* + * Add one or more cards + */ + default: + if (selectedSet != null) { + if (commands.length == 1 && commands[0].isEmpty()) { + if (lastAction == LastAction.ADD) + add(lastAction.card, lastAction.count); + } else { + Card newCard = selectedSet.getCardByNumber(commands[0]); + if (newCard != null) { + Integer count = 1; + if (commands.length > 1 && commands[1].matches("[0-9]+")) { + count = Integer.valueOf(commands[1]); + if (count <= 0) { + System.out.println("Can't add " + count + " cards"); + return; + } + } + add(newCard, count); + } else { + System.out.println(commands[0] + " does not correspond to a card in " + selectedSet.name); + } + } + } else { + System.out.println("Select a set before adding cards."); + } + break; + } + } + } + + private static void add(Card card, Integer count) { + card.setCode = selectedSet.code; + cbm.addCard(card, count); + System.out.println("Added " + count + "x " + card.name); + savePrompt = true; + lastAction = LastAction.ADD; + lastAction.set(card, count); + } + + private static void remove(Card card, Integer count) { + cbm.removeCard(card, count); + System.out.println("Removed " + count + "x " + card.name); + savePrompt = true; + lastAction = LastAction.REMOVE; + lastAction.set(card, count); + } + + private static String sanitiseFileName(String name) { + name = name.replaceAll("[^-_.A-Za-z0-9]", ""); + if (!name.endsWith(".cb")) { + name = name.concat(".cb"); + } + return name; + } + + private static 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); + } + + private static 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)); + } +} -- cgit v1.2.3