From: Niki Roo Date: Tue, 5 May 2020 18:42:37 +0000 (+0200) Subject: Merge master X-Git-Url: https://git.nikiroo.be/?a=commitdiff_plain;h=9a553563269f93bae59cf45e43933c81fdec07e8;p=nikiroo-utils.git Merge master --- diff --git a/Main.java b/Main.java index 8d72803..b49cf0f 100644 --- a/Main.java +++ b/Main.java @@ -85,6 +85,22 @@ public class Main { * see method description */ public static void main(String[] args) { + new Main().start(args); + } + + /** + * Start the default handling for the application. + *

+ * If specific actions were asked (with correct parameters), they will be + * forwarded to the different protected methods that you can override. + *

+ * At the end of the method, {@link Main#exit(int)} will be called; by + * default, it calls {@link System#exit(int)} if the status is not 0. + * + * @param args + * the arguments received from the system + */ + public void start(String [] args) { // Only one line, but very important: Instance.init(); @@ -373,38 +389,52 @@ public class Main { Progress pg = new Progress(); mainProgress.addProgress(pg, mainProgress.getMax()); - VersionCheck updates = VersionCheck.check(); - if (updates.isNewVersionAvailable()) { - // Sent to syserr so not to cause problem if one tries to capture a - // story content in text mode - System.err - .println("A new version of the program is available at https://github.com/nikiroo/fanfix/releases"); - System.err.println(""); - for (Version v : updates.getNewer()) { - System.err.println("\tVersion " + v); - System.err.println("\t-------------"); - System.err.println(""); - for (String it : updates.getChanges().get(v)) { - System.err.println("\t- " + it); - } - System.err.println(""); - } - } + VersionCheck updates = checkUpdates(); if (exitCode == 0) { switch (action) { case IMPORT: - exitCode = imprt(urlString, pg); - updates.ok(); // we consider it read + if (updates != null) + updates.ok(); // we consider it read + + try { + exitCode = imprt(BasicReader.getUrl(urlString), pg); + } catch (MalformedURLException e) { + Instance.getInstance().getTraceHandler().error(e); + exitCode = 1; + } + break; case EXPORT: - exitCode = export(luid, sourceString, target, pg); - updates.ok(); // we consider it read + if (updates != null) + updates.ok(); // we consider it read + + OutputType exportType = OutputType.valueOfNullOkUC(sourceString, null); + if (exportType == null) { + Instance.getInstance().getTraceHandler().error(new Exception(trans(StringId.OUTPUT_DESC, sourceString))); + exitCode = 1; + break; + } + + exitCode = export(luid, exportType, target, pg); + break; case CONVERT: - exitCode = convert(urlString, sourceString, target, + if (updates != null) + updates.ok(); // we consider it read + + OutputType convertType = OutputType.valueOfAllOkUC(sourceString, null); + if (convertType == null) { + Instance.getInstance().getTraceHandler() + .error(new IOException(trans(StringId.ERR_BAD_OUTPUT_TYPE, sourceString))); + + exitCode = 2; + break; + } + + exitCode = convert(urlString, convertType, target, plusInfo == null ? false : plusInfo, pg); - updates.ok(); // we consider it read + break; case LIST: exitCode = list(sourceString); @@ -441,8 +471,20 @@ public class Main { } try { + Integer chap = null; + if (chapString != null) { + try { + chap = Integer.parseInt(chapString); + } catch (NumberFormatException e) { + Instance.getInstance().getTraceHandler().error(new IOException( + "Chapter number cannot be parsed: " + chapString, e)); + exitCode = 2; + break; + } + } + BasicLibrary lib = Instance.getInstance().getLibrary(); - exitCode = read(lib.getStory(luid, null), chapString); + exitCode = read(lib.getStory(luid, null), chap); } catch (IOException e) { Instance.getInstance().getTraceHandler() .error(new IOException("Failed to read book", e)); @@ -458,6 +500,18 @@ public class Main { } try { + Integer chap = null; + if (chapString != null) { + try { + chap = Integer.parseInt(chapString); + } catch (NumberFormatException e) { + Instance.getInstance().getTraceHandler().error(new IOException( + "Chapter number cannot be parsed: " + chapString, e)); + exitCode = 2; + break; + } + } + BasicSupport support = BasicSupport .getSupport(BasicReader.getUrl(urlString)); if (support == null) { @@ -467,7 +521,7 @@ public class Main { break; } - exitCode = read(support.process(null), chapString); + exitCode = read(support.process(null), chap); } catch (IOException e) { Instance.getInstance().getTraceHandler() .error(new IOException("Failed to read book", e)); @@ -490,19 +544,22 @@ public class Main { break; } - try { - if (searchOn == null) { - new CliReader().listSearchables(); - } else if (search != null) { - - new CliReader().searchBooksByKeyword(searchOn, search, page, - item); - } else { - exitCode = 255; + if (searchOn == null) { + try { + search(); + } catch (IOException e) { + Instance.getInstance().getTraceHandler().error(e); + exitCode = 1; } - } catch (IOException e1) { - Instance.getInstance().getTraceHandler().error(e1); - exitCode = 20; + } else if (search != null) { + try { + searchKeywords(searchOn, search, page, item); + } catch (IOException e) { + Instance.getInstance().getTraceHandler().error(e); + exitCode = 20; + } + } else { + exitCode = 255; } break; @@ -527,10 +584,10 @@ public class Main { } try { - new CliReader().searchBooksByTag(searchOn, page, item, - tags.toArray(new Integer[] {})); - } catch (IOException e1) { - Instance.getInstance().getTraceHandler().error(e1); + searchTags(searchOn, page, item, + tags.toArray(new Integer[] {})); + } catch (IOException e) { + Instance.getInstance().getTraceHandler().error(e); } break; @@ -539,16 +596,18 @@ public class Main { exitCode = 0; break; case VERSION: + if (updates != null) + updates.ok(); // we consider it read + System.out .println(String.format("Fanfix version %s" + "%nhttps://github.com/nikiroo/fanfix/" + "%n\tWritten by Nikiroo", Version.getCurrentVersion())); - updates.ok(); // we consider it read break; case START: try { - new CliReader().listBooks(null); + start(); } catch (IOException e) { Instance.getInstance().getTraceHandler().error(e); exitCode = 66; @@ -563,13 +622,12 @@ public class Main { break; } try { - ServerObject server = new RemoteLibraryServer(key, port); - server.setTraceHandler(Instance.getInstance().getTraceHandler()); - server.run(); + startServer(key, port); } catch (IOException e) { Instance.getInstance().getTraceHandler().error(e); } - return; + + break; case STOP_SERVER: // Can be given via "--remote XX XX XX" if (key == null) @@ -583,7 +641,7 @@ public class Main { break; } try { - new RemoteLibrary(key, host, port).exit(); + stopServer(key, host, port); } catch (SSLException e) { Instance.getInstance().getTraceHandler().error( "Bad access key for remote library"); @@ -603,29 +661,106 @@ public class Main { try { Instance.getInstance().getTempFiles().close(); } catch (IOException e) { - Instance.getInstance().getTraceHandler().error(new IOException("Cannot dispose of the temporary files", e)); + Instance.getInstance().getTraceHandler().error(new IOException( + "Cannot dispose of the temporary files", e)); } if (exitCode == 255) { syntax(false); } - System.exit(exitCode); + exit(exitCode); + } + + /** + * A normal invocation of the program (without parameters or at least + * without "action" parameters). + *

+ * You will probably want to override that one if you offer a user + * interface. + * + * @throws IOException + * in case of I/O error + */ + protected void start() throws IOException { + new CliReader().listBooks(null); + } + + /** + * Will check if updates are available. + *

+ * For this, it will simply forward the call to + * {@link Main#checkUpdates(String)} with a value of "nikiroo/fanfix". + * + * @return the newer version information or NULL if nothing new + */ + protected VersionCheck checkUpdates() { + return checkUpdates("nikiroo/fanfix"); + } + + /** + * Will check if updates are available on a specific GitHub project. + *

+ * Will be called by {@link Main#checkUpdates()}, but if you override that + * one you mall call it with another project. + * + * @param githubProject + * the GitHub project, for instance "nikiroo/fanfix" + * + * @return the newer version information or NULL if nothing new + */ + protected VersionCheck checkUpdates(String githubProject) { + VersionCheck updates = VersionCheck.check(githubProject); + if (updates.isNewVersionAvailable()) { + notifyUpdates(updates); + return updates; + } + + return null; } + /** + * Notify the user about available updates. + *

+ * Will only be called when a version is available. + *

+ * Note that you can call {@link VersionCheck#ok()} on it if the user has + * read the information (by default, it is marked read only on certain other + * actions). + * + * @param updates + * the new version information + */ + protected void notifyUpdates(VersionCheck updates) { + // Sent to syserr so not to cause problem if one tries to capture a + // story content in text mode + System.err.println( + "A new version of the program is available at https://github.com/nikiroo/fanfix/releases"); + System.err.println(""); + for (Version v : updates.getNewer()) { + System.err.println("\tVersion " + v); + System.err.println("\t-------------"); + System.err.println(""); + for (String it : updates.getChanges().get(v)) { + System.err.println("\t- " + it); + } + System.err.println(""); + } + } + /** * Import the given resource into the {@link LocalLibrary}. * - * @param urlString + * @param url * the resource to import * @param pg * the optional progress reporter * * @return the exit return code (0 = success) */ - public static int imprt(String urlString, Progress pg) { + protected static int imprt(URL url, Progress pg) { try { - MetaData meta = Instance.getInstance().getLibrary().imprt(BasicReader.getUrl(urlString), pg); + MetaData meta = Instance.getInstance().getLibrary().imprt(url, pg); System.out.println(meta.getLuid() + ": \"" + meta.getTitle() + "\" imported."); } catch (IOException e) { Instance.getInstance().getTraceHandler().error(e); @@ -641,7 +776,7 @@ public class Main { * * @param luid * the story LUID - * @param typeString + * @param type * the {@link OutputType} to use * @param target * the target @@ -650,14 +785,8 @@ public class Main { * * @return the exit return code (0 = success) */ - public static int export(String luid, String typeString, String target, + protected static int export(String luid, OutputType type, String target, Progress pg) { - OutputType type = OutputType.valueOfNullOkUC(typeString, null); - if (type == null) { - Instance.getInstance().getTraceHandler().error(new Exception(trans(StringId.OUTPUT_DESC, typeString))); - return 1; - } - try { Instance.getInstance().getLibrary().export(luid, type, target, pg); } catch (IOException e) { @@ -667,7 +796,7 @@ public class Main { return 0; } - + /** * List the stories of the given source from the {@link LocalLibrary} * (unless NULL is passed, in which case all stories will be listed). @@ -678,7 +807,7 @@ public class Main { * * @return the exit return code (0 = success) */ - private static int list(String source) { + protected int list(String source) { try { new CliReader().listBooks(source); } catch (IOException e) { @@ -694,24 +823,13 @@ public class Main { * * @param story * the story to read - * @param chapString + * @param chap * which {@link Chapter} to read (starting at 1), or NULL to get * the {@link Story} description * * @return the exit return code (0 = success) */ - private static int read(Story story, String chapString) { - Integer chap = null; - if (chapString != null) { - try { - chap = Integer.parseInt(chapString); - } catch (NumberFormatException e) { - Instance.getInstance().getTraceHandler().error(new IOException( - "Chapter number cannot be parsed: " + chapString, e)); - return 2; - } - } - + protected int read(Story story, Integer chap) { if (story != null) { try { if (chap == null) { @@ -726,7 +844,7 @@ public class Main { } } else { Instance.getInstance().getTraceHandler() - .error("Cannot find book: " + chapString); + .error("Cannot find book: " + story); return 2; } @@ -738,7 +856,7 @@ public class Main { * * @param urlString * the source {@link Story} to convert - * @param typeString + * @param type * the {@link OutputType} to convert to * @param target * the target file @@ -750,7 +868,7 @@ public class Main { * * @return the exit return code (0 = success) */ - public static int convert(String urlString, String typeString, + protected int convert(String urlString, OutputType type, String target, boolean infoCover, Progress pg) { int exitCode = 0; @@ -763,46 +881,42 @@ public class Main { sourceName = sourceName.substring("file://".length()); } - OutputType type = OutputType.valueOfAllOkUC(typeString, null); - if (type == null) { - Instance.getInstance().getTraceHandler() - .error(new IOException(trans(StringId.ERR_BAD_OUTPUT_TYPE, typeString))); + try { + BasicSupport support = BasicSupport.getSupport(source); - exitCode = 2; - } else { - try { - BasicSupport support = BasicSupport.getSupport(source); - - if (support != null) { - Instance.getInstance().getTraceHandler().trace("Support found: " + support.getClass()); - Progress pgIn = new Progress(); - Progress pgOut = new Progress(); - if (pg != null) { - pg.setMax(2); - pg.addProgress(pgIn, 1); - pg.addProgress(pgOut, 1); - } + if (support != null) { + Instance.getInstance().getTraceHandler() + .trace("Support found: " + support.getClass()); + Progress pgIn = new Progress(); + Progress pgOut = new Progress(); + if (pg != null) { + pg.setMax(2); + pg.addProgress(pgIn, 1); + pg.addProgress(pgOut, 1); + } - Story story = support.process(pgIn); - try { - target = new File(target).getAbsolutePath(); - BasicOutput.getOutput(type, infoCover, infoCover).process(story, target, pgOut); - } catch (IOException e) { - Instance.getInstance().getTraceHandler() - .error(new IOException(trans(StringId.ERR_SAVING, target), e)); - exitCode = 5; - } - } else { + Story story = support.process(pgIn); + try { + target = new File(target).getAbsolutePath(); + BasicOutput.getOutput(type, infoCover, infoCover) + .process(story, target, pgOut); + } catch (IOException e) { Instance.getInstance().getTraceHandler() - .error(new IOException(trans( StringId.ERR_NOT_SUPPORTED, source))); - - exitCode = 4; + .error(new IOException( + trans(StringId.ERR_SAVING, target), e)); + exitCode = 5; } - } catch (IOException e) { + } else { Instance.getInstance().getTraceHandler() - .error(new IOException(trans(StringId.ERR_LOADING, sourceName), e)); - exitCode = 3; + .error(new IOException( + trans(StringId.ERR_NOT_SUPPORTED, source))); + + exitCode = 4; } + } catch (IOException e) { + Instance.getInstance().getTraceHandler().error(new IOException( + trans(StringId.ERR_LOADING, sourceName), e)); + exitCode = 3; } } catch (MalformedURLException e) { Instance.getInstance().getTraceHandler().error(new IOException(trans(StringId.ERR_BAD_URL, sourceName), e)); @@ -812,18 +926,6 @@ public class Main { return exitCode; } - /** - * Simple shortcut method to call {link Instance#getTrans()#getString()}. - * - * @param id - * the ID to translate - * - * @return the translated result - */ - private static String trans(StringId id, Object... params) { - return Instance.getInstance().getTrans().getString(id, params); - } - /** * Display the correct syntax of the program to the user to stdout, or an * error message if the syntax used was wrong on stderr. @@ -831,7 +933,7 @@ public class Main { * @param showHelp * TRUE to show the syntax help, FALSE to show "syntax error" */ - private static void syntax(boolean showHelp) { + protected void syntax(boolean showHelp) { if (showHelp) { StringBuilder builder = new StringBuilder(); for (SupportType type : SupportType.values()) { @@ -856,4 +958,125 @@ public class Main { System.err.println(trans(StringId.ERR_SYNTAX)); } } + + /** + * Starts a search operation (i.e., list the available web sites we can + * search on). + * + * @throws IOException + * in case of I/O errors + */ + protected void search() throws IOException { + new CliReader().listSearchables(); + } + + /** + * Search for books by keywords on the given supported web site. + * + * @param searchOn + * the web site to search on + * @param search + * the keyword to look for + * @param page + * the page of results to get, or 0 to inquire about the number + * of pages + * @param item + * the index of the book we are interested by, or 0 to query + * about how many books are in that page of results + * + * @throws IOException + * in case of I/O error + */ + protected void searchKeywords(SupportType searchOn, String search, + int page, Integer item) throws IOException { + new CliReader().searchBooksByKeyword(searchOn, search, page, item); + } + + /** + * Search for books by tags on the given supported web site. + * + * @param searchOn + * the web site to search on + * @param page + * the page of results to get, or 0 to inquire about the number + * of pages + * @param item + * the index of the book we are interested by, or 0 to query + * about how many books are in that page of results + * @param tags + * the tags to look for + * + * @throws IOException + * in case of I/O error + */ + protected void searchTags(SupportType searchOn, Integer page, Integer item, + Integer[] tags) throws IOException { + new CliReader().searchBooksByTag(searchOn, page, item, tags); + } + + /** + * Start a Fanfix server. + * + * @param key + * the key taht will be needed to contact the Fanfix server + * @param port + * the port on which to run + * + * @throws IOException + * in case of I/O errors + * @throws SSLException + * when the key was not accepted + */ + private void startServer(String key, int port) throws IOException { + ServerObject server = new RemoteLibraryServer(key, port); + server.setTraceHandler(Instance.getInstance().getTraceHandler()); + server.run(); + } + + /** + * Stop a running Fanfix server. + * + * @param key + * the key to contact the Fanfix server + * @param host + * the host on which it runs (NULL means localhost) + * @param port + * the port on which it runs + * + * @throws IOException + * in case of I/O errors + * @throws SSLException + * when the key was not accepted + */ + private void stopServer( + String key, String host, Integer port) + throws IOException, SSLException { + new RemoteLibrary(key, host, port).exit(); + } + + /** + * We are done and ready to exit. + *

+ * By default, it will call {@link System#exit(int)} if the status is not 0. + * + * @param status + * the exit status + */ + protected void exit(int status) { + if (status != 0) { + System.exit(status); + } + } + + /** + * Simple shortcut method to call {link Instance#getTrans()#getString()}. + * + * @param id + * the ID to translate + * + * @return the translated result + */ + static private String trans(StringId id, Object... params) { + return Instance.getInstance().getTrans().getString(id, params); + } } diff --git a/VersionCheck.java b/VersionCheck.java index f64159a..fc631b0 100644 --- a/VersionCheck.java +++ b/VersionCheck.java @@ -20,7 +20,7 @@ import be.nikiroo.utils.Version; * @author niki */ public class VersionCheck { - private static final String base = "https://github.com/nikiroo/fanfix/raw/master/changelog${LANG}.md"; + private static final String base = "https://github.com/${PROJECT}/raw/master/changelog${LANG}.md"; private Version current; private List newer; @@ -100,16 +100,23 @@ public class VersionCheck { * Check if there are available {@link Version}s of this program more recent * than the current one. * + * @param githubProject + * the GitHub project to check on, for instance "nikiroo/fanfix" + * * @return a {@link VersionCheck} */ - public static VersionCheck check() { + public static VersionCheck check(String githubProject) { Version current = Version.getCurrentVersion(); List newer = new ArrayList(); Map> changes = new HashMap>(); if (Instance.getInstance().isVersionCheckNeeded()) { try { - // Prepare the URLs according to the user's language + // Use the right project: + String base = VersionCheck.base.replace("${PROJECT}", + githubProject); + + // Prepare the URLs according to the user's language: Locale lang = Instance.getInstance().getTrans().getLocale(); String fr = lang.getLanguage(); String BE = lang.getCountry().replace(".UTF8", ""); diff --git a/library/RemoteLibrary.java b/library/RemoteLibrary.java index bbe772a..a4f00ce 100644 --- a/library/RemoteLibrary.java +++ b/library/RemoteLibrary.java @@ -435,9 +435,11 @@ public class RemoteLibrary extends BasicLibrary { * Stop the server. * * @throws IOException - * in case of I/O error (including bad key) + * in case of I/O errors + * @throws SSLException + * when the key was not accepted */ - public void exit() throws IOException { + public void exit() throws IOException, SSLException { connectRemoteAction(new RemoteAction() { @Override public void action(ConnectActionClientObject action) diff --git a/test/BasicSupportDeprecatedTest.java b/test/BasicSupportDeprecatedTest.java new file mode 100644 index 0000000..c8c4b40 --- /dev/null +++ b/test/BasicSupportDeprecatedTest.java @@ -0,0 +1,451 @@ +package be.nikiroo.fanfix.test; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.util.ArrayList; +import java.util.List; +import java.util.Map.Entry; + +import be.nikiroo.fanfix.Instance; +import be.nikiroo.fanfix.bundles.StringId; +import be.nikiroo.fanfix.data.MetaData; +import be.nikiroo.fanfix.data.Paragraph; +import be.nikiroo.fanfix.data.Paragraph.ParagraphType; +import be.nikiroo.fanfix.data.Story; +import be.nikiroo.fanfix.supported.BasicSupport; +import be.nikiroo.fanfix.supported.BasicSupport_Deprecated; +import be.nikiroo.fanfix.supported.SupportType; +import be.nikiroo.utils.IOUtils; +import be.nikiroo.utils.Progress; +import be.nikiroo.utils.test.TestCase; +import be.nikiroo.utils.test.TestLauncher; + +class BasicSupportDeprecatedTest extends TestLauncher { + // quote chars + private char openQuote = Instance.getInstance().getTrans().getCharacter(StringId.OPEN_SINGLE_QUOTE); + private char closeQuote = Instance.getInstance().getTrans().getCharacter(StringId.CLOSE_SINGLE_QUOTE); + private char openDoubleQuote = Instance.getInstance().getTrans().getCharacter(StringId.OPEN_DOUBLE_QUOTE); + private char closeDoubleQuote = Instance.getInstance().getTrans().getCharacter(StringId.CLOSE_DOUBLE_QUOTE); + + public BasicSupportDeprecatedTest(String[] args) { + super("BasicSupportDeprecated", args); + + addSeries(new TestLauncher("General", args) { + { + addTest(new TestCase("BasicSupport.makeParagraphs()") { + @Override + public void test() throws Exception { + BasicSupportEmpty support = new BasicSupportEmpty() { + @Override + protected boolean isHtml() { + return true; + } + + @Override + public void fixBlanksBreaks(List paras) { + } + + @Override + public List requotify(Paragraph para) { + List paras = new ArrayList( + 1); + paras.add(para); + return paras; + } + }; + + List paras = null; + + paras = support.makeParagraphs(null, "", null); + assertEquals( + "An empty content should not generate paragraphs", + 0, paras.size()); + + paras = support.makeParagraphs(null, + "Line 1

Line 2

Line 3

", null); + assertEquals(5, paras.size()); + assertEquals("Line 1", paras.get(0).getContent()); + assertEquals(ParagraphType.BLANK, paras.get(1) + .getType()); + assertEquals("Line 2", paras.get(2).getContent()); + assertEquals(ParagraphType.BLANK, paras.get(3) + .getType()); + assertEquals("Line 3", paras.get(4).getContent()); + + paras = support.makeParagraphs(null, + "

Line1

Line2

Line3

", null); + assertEquals(6, paras.size()); + } + }); + + addTest(new TestCase("BasicSupport.removeDoubleBlanks()") { + @Override + public void test() throws Exception { + BasicSupportEmpty support = new BasicSupportEmpty() { + @Override + protected boolean isHtml() { + return true; + } + }; + + List paras = null; + + paras = support + .makeParagraphs( + null, + "

Line1

Line2

Line3

", + null); + assertEquals(5, paras.size()); + + paras = support + .makeParagraphs( + null, + "

Line1

Line2

Line3

* * *", + null); + assertEquals(5, paras.size()); + + paras = support.makeParagraphs(null, "1

* * *

2", + null); + assertEquals(3, paras.size()); + assertEquals(ParagraphType.BREAK, paras.get(1) + .getType()); + + paras = support.makeParagraphs(null, + "1


* * *

2", null); + assertEquals(3, paras.size()); + assertEquals(ParagraphType.BREAK, paras.get(1) + .getType()); + + paras = support.makeParagraphs(null, + "1

* * *


2", null); + assertEquals(3, paras.size()); + assertEquals(ParagraphType.BREAK, paras.get(1) + .getType()); + + paras = support.makeParagraphs(null, + "1



* * *


2", null); + assertEquals(3, paras.size()); + assertEquals(ParagraphType.BREAK, paras.get(1) + .getType()); + } + }); + + addTest(new TestCase("BasicSupport.processPara() quotes") { + @Override + public void test() throws Exception { + BasicSupportEmpty support = new BasicSupportEmpty() { + @Override + protected boolean isHtml() { + return true; + } + }; + + Paragraph para; + + // sanity check + para = support.processPara(""); + assertEquals(ParagraphType.BLANK, para.getType()); + // + + para = support.processPara("\"Yes, my Lord!\""); + assertEquals(ParagraphType.QUOTE, para.getType()); + assertEquals(openDoubleQuote + "Yes, my Lord!" + + closeDoubleQuote, para.getContent()); + + para = support.processPara("«Yes, my Lord!»"); + assertEquals(ParagraphType.QUOTE, para.getType()); + assertEquals(openDoubleQuote + "Yes, my Lord!" + + closeDoubleQuote, para.getContent()); + + para = support.processPara("'Yes, my Lord!'"); + assertEquals(ParagraphType.QUOTE, para.getType()); + assertEquals(openQuote + "Yes, my Lord!" + closeQuote, + para.getContent()); + + para = support.processPara("‹Yes, my Lord!›"); + assertEquals(ParagraphType.QUOTE, para.getType()); + assertEquals(openQuote + "Yes, my Lord!" + closeQuote, + para.getContent()); + } + }); + + addTest(new TestCase( + "BasicSupport.processPara() double-simple quotes") { + @Override + public void setUp() throws Exception { + super.setUp(); + + } + + @Override + public void tearDown() throws Exception { + + super.tearDown(); + } + + @Override + public void test() throws Exception { + BasicSupportEmpty support = new BasicSupportEmpty() { + @Override + protected boolean isHtml() { + return true; + } + }; + + Paragraph para; + + para = support.processPara("''Yes, my Lord!''"); + assertEquals(ParagraphType.QUOTE, para.getType()); + assertEquals(openDoubleQuote + "Yes, my Lord!" + + closeDoubleQuote, para.getContent()); + + para = support.processPara("‹‹Yes, my Lord!››"); + assertEquals(ParagraphType.QUOTE, para.getType()); + assertEquals(openDoubleQuote + "Yes, my Lord!" + + closeDoubleQuote, para.getContent()); + } + }); + + addTest(new TestCase("BasicSupport.processPara() apostrophe") { + @Override + public void test() throws Exception { + BasicSupportEmpty support = new BasicSupportEmpty() { + @Override + protected boolean isHtml() { + return true; + } + }; + + Paragraph para; + + String text = "Nous étions en été, mais cela aurait être l'hiver quand nous n'étions encore qu'à Aubeuge"; + para = support.processPara(text); + assertEquals(ParagraphType.NORMAL, para.getType()); + assertEquals(text, para.getContent()); + } + }); + + addTest(new TestCase("BasicSupport.processPara() words count") { + @Override + public void test() throws Exception { + BasicSupportEmpty support = new BasicSupportEmpty() { + @Override + protected boolean isHtml() { + return true; + } + }; + + Paragraph para; + + para = support.processPara("«Yes, my Lord!»"); + assertEquals(3, para.getWords()); + + para = support.processPara("One, twee, trois."); + assertEquals(3, para.getWords()); + } + }); + + addTest(new TestCase("BasicSupport.requotify() words count") { + @Override + public void test() throws Exception { + BasicSupportEmpty support = new BasicSupportEmpty(); + + char openDoubleQuote = Instance.getInstance().getTrans() + .getCharacter(StringId.OPEN_DOUBLE_QUOTE); + char closeDoubleQuote = Instance.getInstance().getTrans() + .getCharacter(StringId.CLOSE_DOUBLE_QUOTE); + + String content = null; + Paragraph para = null; + List paras = null; + long words = 0; + + content = "One, twee, trois."; + para = new Paragraph(ParagraphType.NORMAL, content, + content.split(" ").length); + paras = support.requotify(para); + words = 0; + for (Paragraph p : paras) { + words += p.getWords(); + } + assertEquals("Bad words count in a single paragraph", + para.getWords(), words); + + content = "Such WoW! So Web2.0! With Colours!"; + para = new Paragraph(ParagraphType.NORMAL, content, + content.split(" ").length); + paras = support.requotify(para); + words = 0; + for (Paragraph p : paras) { + words += p.getWords(); + } + assertEquals("Bad words count in a single paragraph", + para.getWords(), words); + + content = openDoubleQuote + "Such a good idea!" + + closeDoubleQuote + + ", she said. This ought to be a new para."; + para = new Paragraph(ParagraphType.QUOTE, content, + content.split(" ").length); + paras = support.requotify(para); + words = 0; + for (Paragraph p : paras) { + words += p.getWords(); + } + assertEquals( + "Bad words count in a requotified paragraph", + para.getWords(), words); + } + }); + } + }); + + addSeries(new TestLauncher("Text", args) { + { + addTest(new TestCase("Chapter detection simple") { + private File tmp; + + @Override + public void setUp() throws Exception { + tmp = File.createTempFile("fanfix-text-file_", ".test"); + IOUtils.writeSmallFile(tmp.getParentFile(), + tmp.getName(), "TITLE" + + "\n"// + + "By nona" + + "\n" // + + "\n" // + + "Chapter 0: Resumé" + "\n" + "\n" + + "'sume." + "\n" + "\n" + + "Chapter 1: chap1" + "\n" + "\n" + + "Fanfan." + "\n" + "\n" + + "Chapter 2: Chap2" + "\n" + "\n" // + + "Tulipe." + "\n"); + } + + @Override + public void tearDown() throws Exception { + tmp.delete(); + } + + @Override + public void test() throws Exception { + BasicSupport support = BasicSupport.getSupport( + SupportType.TEXT, tmp.toURI().toURL()); + + Story story = support.process(null); + + assertEquals(2, story.getChapters().size()); + assertEquals(1, story.getChapters().get(1) + .getParagraphs().size()); + assertEquals("Tulipe.", story.getChapters().get(1) + .getParagraphs().get(0).getContent()); + } + }); + + addTest(new TestCase("Chapter detection with String 'Chapter'") { + private File tmp; + + @Override + public void setUp() throws Exception { + tmp = File.createTempFile("fanfix-text-file_", ".test"); + IOUtils.writeSmallFile(tmp.getParentFile(), + tmp.getName(), "TITLE" + + "\n"// + + "By nona" + + "\n" // + + "\n" // + + "Chapter 0: Resumé" + "\n" + "\n" + + "'sume." + "\n" + "\n" + + "Chapter 1: chap1" + "\n" + "\n" + + "Chapter fout-la-merde" + "\n" + + "\n"// + + "Fanfan." + "\n" + "\n" + + "Chapter 2: Chap2" + "\n" + "\n" // + + "Tulipe." + "\n"); + } + + @Override + public void tearDown() throws Exception { + tmp.delete(); + } + + @Override + public void test() throws Exception { + BasicSupport support = BasicSupport.getSupport( + SupportType.TEXT, tmp.toURI().toURL()); + + Story story = support.process(null); + + assertEquals(2, story.getChapters().size()); + assertEquals(1, story.getChapters().get(1) + .getParagraphs().size()); + assertEquals("Tulipe.", story.getChapters().get(1) + .getParagraphs().get(0).getContent()); + } + }); + } + }); + } + + private class BasicSupportEmpty extends BasicSupport_Deprecated { + @Override + protected boolean supports(URL url) { + return false; + } + + @Override + protected boolean isHtml() { + return false; + } + + @Override + protected MetaData getMeta(URL source, InputStream in) + throws IOException { + return null; + } + + @Override + protected String getDesc(URL source, InputStream in) throws IOException { + return null; + } + + @Override + protected List> getChapters(URL source, + InputStream in, Progress pg) throws IOException { + return null; + } + + @Override + protected String getChapterContent(URL source, InputStream in, + int number, Progress pg) throws IOException { + return null; + } + + @Override + // and make it public! + public List makeParagraphs(URL source, String content, + Progress pg) throws IOException { + return super.makeParagraphs(source, content, pg); + } + + @Override + // and make it public! + public void fixBlanksBreaks(List paras) { + super.fixBlanksBreaks(paras); + } + + @Override + // and make it public! + public Paragraph processPara(String line) { + return super.processPara(line); + } + + @Override + // and make it public! + public List requotify(Paragraph para) { + return super.requotify(para); + } + } +} diff --git a/test/BasicSupportUtilitiesTest.java b/test/BasicSupportUtilitiesTest.java new file mode 100644 index 0000000..9cec220 --- /dev/null +++ b/test/BasicSupportUtilitiesTest.java @@ -0,0 +1,397 @@ +package be.nikiroo.fanfix.test; + +import java.io.File; +import java.io.IOException; +import java.net.URL; +import java.util.ArrayList; +import java.util.List; + +import be.nikiroo.fanfix.Instance; +import be.nikiroo.fanfix.bundles.StringId; +import be.nikiroo.fanfix.data.Paragraph; +import be.nikiroo.fanfix.data.Paragraph.ParagraphType; +import be.nikiroo.fanfix.data.Story; +import be.nikiroo.fanfix.supported.BasicSupport; +import be.nikiroo.fanfix.supported.BasicSupportHelper; +import be.nikiroo.fanfix.supported.BasicSupportImages; +import be.nikiroo.fanfix.supported.BasicSupportPara; +import be.nikiroo.fanfix.supported.SupportType; +import be.nikiroo.utils.IOUtils; +import be.nikiroo.utils.Progress; +import be.nikiroo.utils.test.TestCase; +import be.nikiroo.utils.test.TestLauncher; + +class BasicSupportUtilitiesTest extends TestLauncher { + // quote chars + private char openQuote = Instance.getInstance().getTrans().getCharacter(StringId.OPEN_SINGLE_QUOTE); + private char closeQuote = Instance.getInstance().getTrans().getCharacter(StringId.CLOSE_SINGLE_QUOTE); + private char openDoubleQuote = Instance.getInstance().getTrans().getCharacter(StringId.OPEN_DOUBLE_QUOTE); + private char closeDoubleQuote = Instance.getInstance().getTrans().getCharacter(StringId.CLOSE_DOUBLE_QUOTE); + + public BasicSupportUtilitiesTest(String[] args) { + super("BasicSupportUtilities", args); + + addSeries(new TestLauncher("General", args) { + { + addTest(new TestCase("BasicSupport.makeParagraphs()") { + @Override + public void test() throws Exception { + BasicSupportParaPublic bsPara = new BasicSupportParaPublic() { + @Override + public void fixBlanksBreaks(List paras) { + } + + @Override + public List requotify(Paragraph para, boolean html) { + List paras = new ArrayList( + 1); + paras.add(para); + return paras; + } + }; + + List paras = null; + + paras = bsPara.makeParagraphs(null, null, "", true, null); + assertEquals( + "An empty content should not generate paragraphs", + 0, paras.size()); + + paras = bsPara.makeParagraphs(null, null, + "Line 1

Line 2

Line 3

", true, null); + assertEquals(5, paras.size()); + assertEquals("Line 1", paras.get(0).getContent()); + assertEquals(ParagraphType.BLANK, paras.get(1) + .getType()); + assertEquals("Line 2", paras.get(2).getContent()); + assertEquals(ParagraphType.BLANK, paras.get(3) + .getType()); + assertEquals("Line 3", paras.get(4).getContent()); + + paras = bsPara.makeParagraphs(null, null, + "

Line1

Line2

Line3

", true, null); + assertEquals(6, paras.size()); + } + }); + + addTest(new TestCase("BasicSupport.removeDoubleBlanks()") { + @Override + public void test() throws Exception { + BasicSupportParaPublic support = new BasicSupportParaPublic(); + + List paras = null; + + paras = support + .makeParagraphs( + null, + null, + "

Line1

Line2

Line3

", + true, + null); + assertEquals(5, paras.size()); + + paras = support + .makeParagraphs( + null, + null, + "

Line1

Line2

Line3

* * *", + true, + null); + assertEquals(5, paras.size()); + + paras = support.makeParagraphs(null, null, "1

* * *

2", + true, null); + assertEquals(3, paras.size()); + assertEquals(ParagraphType.BREAK, paras.get(1) + .getType()); + + paras = support.makeParagraphs(null, null, + "1


* * *

2", true, null); + assertEquals(3, paras.size()); + assertEquals(ParagraphType.BREAK, paras.get(1) + .getType()); + + paras = support.makeParagraphs(null, null, + "1

* * *


2", true, null); + assertEquals(3, paras.size()); + assertEquals(ParagraphType.BREAK, paras.get(1) + .getType()); + + paras = support.makeParagraphs(null, null, + "1



* * *


2", true, null); + assertEquals(3, paras.size()); + assertEquals(ParagraphType.BREAK, paras.get(1) + .getType()); + } + }); + + addTest(new TestCase("BasicSupport.processPara() quotes") { + @Override + public void test() throws Exception { + BasicSupportParaPublic support = new BasicSupportParaPublic(); + + Paragraph para; + + // sanity check + para = support.processPara("", true); + assertEquals(ParagraphType.BLANK, para.getType()); + // + + para = support.processPara("\"Yes, my Lord!\"", true); + assertEquals(ParagraphType.QUOTE, para.getType()); + assertEquals(openDoubleQuote + "Yes, my Lord!" + + closeDoubleQuote, para.getContent()); + + para = support.processPara("«Yes, my Lord!»", true); + assertEquals(ParagraphType.QUOTE, para.getType()); + assertEquals(openDoubleQuote + "Yes, my Lord!" + + closeDoubleQuote, para.getContent()); + + para = support.processPara("'Yes, my Lord!'", true); + assertEquals(ParagraphType.QUOTE, para.getType()); + assertEquals(openQuote + "Yes, my Lord!" + closeQuote, + para.getContent()); + + para = support.processPara("‹Yes, my Lord!›", true); + assertEquals(ParagraphType.QUOTE, para.getType()); + assertEquals(openQuote + "Yes, my Lord!" + closeQuote, + para.getContent()); + } + }); + + addTest(new TestCase( + "BasicSupport.processPara() double-simple quotes") { + @Override + public void setUp() throws Exception { + super.setUp(); + + } + + @Override + public void tearDown() throws Exception { + + super.tearDown(); + } + + @Override + public void test() throws Exception { + BasicSupportParaPublic support = new BasicSupportParaPublic(); + + Paragraph para; + + para = support.processPara("''Yes, my Lord!''", true); + assertEquals(ParagraphType.QUOTE, para.getType()); + assertEquals(openDoubleQuote + "Yes, my Lord!" + + closeDoubleQuote, para.getContent()); + + para = support.processPara("‹‹Yes, my Lord!››", true); + assertEquals(ParagraphType.QUOTE, para.getType()); + assertEquals(openDoubleQuote + "Yes, my Lord!" + + closeDoubleQuote, para.getContent()); + } + }); + + addTest(new TestCase("BasicSupport.processPara() apostrophe") { + @Override + public void test() throws Exception { + BasicSupportParaPublic support = new BasicSupportParaPublic(); + + Paragraph para; + + String text = "Nous étions en été, mais cela aurait être l'hiver quand nous n'étions encore qu'à Aubeuge"; + para = support.processPara(text, true); + assertEquals(ParagraphType.NORMAL, para.getType()); + assertEquals(text, para.getContent()); + } + }); + + addTest(new TestCase("BasicSupport.processPara() words count") { + @Override + public void test() throws Exception { + BasicSupportParaPublic support = new BasicSupportParaPublic(); + + Paragraph para; + + para = support.processPara("«Yes, my Lord!»", true); + assertEquals(3, para.getWords()); + + para = support.processPara("One, twee, trois.", true); + assertEquals(3, para.getWords()); + } + }); + + addTest(new TestCase("BasicSupport.requotify() words count") { + @Override + public void test() throws Exception { + BasicSupportParaPublic support = new BasicSupportParaPublic(); + + char openDoubleQuote = Instance.getInstance().getTrans() + .getCharacter(StringId.OPEN_DOUBLE_QUOTE); + char closeDoubleQuote = Instance.getInstance().getTrans() + .getCharacter(StringId.CLOSE_DOUBLE_QUOTE); + + String content = null; + Paragraph para = null; + List paras = null; + long words = 0; + + content = "One, twee, trois."; + para = new Paragraph(ParagraphType.NORMAL, content, + content.split(" ").length); + paras = support.requotify(para, false); + words = 0; + for (Paragraph p : paras) { + words += p.getWords(); + } + assertEquals("Bad words count in a single paragraph", + para.getWords(), words); + + content = "Such WoW! So Web2.0! With Colours!"; + para = new Paragraph(ParagraphType.NORMAL, content, + content.split(" ").length); + paras = support.requotify(para, false); + words = 0; + for (Paragraph p : paras) { + words += p.getWords(); + } + assertEquals("Bad words count in a single paragraph", + para.getWords(), words); + + content = openDoubleQuote + "Such a good idea!" + + closeDoubleQuote + + ", she said. This ought to be a new para."; + para = new Paragraph(ParagraphType.QUOTE, content, + content.split(" ").length); + paras = support.requotify(para, false); + words = 0; + for (Paragraph p : paras) { + words += p.getWords(); + } + assertEquals( + "Bad words count in a requotified paragraph", + para.getWords(), words); + } + }); + } + }); + + addSeries(new TestLauncher("Text", args) { + { + addTest(new TestCase("Chapter detection simple") { + private File tmp; + + @Override + public void setUp() throws Exception { + tmp = File.createTempFile("fanfix-text-file_", ".test"); + IOUtils.writeSmallFile(tmp.getParentFile(), + tmp.getName(), "TITLE" + + "\n"// + + "By nona" + + "\n" // + + "\n" // + + "Chapter 0: Resumé" + "\n" + "\n" + + "'sume." + "\n" + "\n" + + "Chapter 1: chap1" + "\n" + "\n" + + "Fanfan." + "\n" + "\n" + + "Chapter 2: Chap2" + "\n" + "\n" // + + "Tulipe." + "\n"); + } + + @Override + public void tearDown() throws Exception { + tmp.delete(); + } + + @Override + public void test() throws Exception { + BasicSupport support = BasicSupport.getSupport( + SupportType.TEXT, tmp.toURI().toURL()); + + Story story = support.process(null); + + assertEquals(2, story.getChapters().size()); + assertEquals(1, story.getChapters().get(1) + .getParagraphs().size()); + assertEquals("Tulipe.", story.getChapters().get(1) + .getParagraphs().get(0).getContent()); + } + }); + + addTest(new TestCase("Chapter detection with String 'Chapter'") { + private File tmp; + + @Override + public void setUp() throws Exception { + tmp = File.createTempFile("fanfix-text-file_", ".test"); + IOUtils.writeSmallFile(tmp.getParentFile(), + tmp.getName(), "TITLE" + + "\n"// + + "By nona" + + "\n" // + + "\n" // + + "Chapter 0: Resumé" + "\n" + "\n" + + "'sume." + "\n" + "\n" + + "Chapter 1: chap1" + "\n" + "\n" + + "Chapter fout-la-merde" + "\n" + + "\n"// + + "Fanfan." + "\n" + "\n" + + "Chapter 2: Chap2" + "\n" + "\n" // + + "Tulipe." + "\n"); + } + + @Override + public void tearDown() throws Exception { + tmp.delete(); + } + + @Override + public void test() throws Exception { + BasicSupport support = BasicSupport.getSupport( + SupportType.TEXT, tmp.toURI().toURL()); + + Story story = support.process(null); + + assertEquals(2, story.getChapters().size()); + assertEquals(1, story.getChapters().get(1) + .getParagraphs().size()); + assertEquals("Tulipe.", story.getChapters().get(1) + .getParagraphs().get(0).getContent()); + } + }); + } + }); + } + + class BasicSupportParaPublic extends BasicSupportPara { + public BasicSupportParaPublic() { + super(new BasicSupportHelper(), new BasicSupportImages()); + } + + @Override + // and make it public! + public Paragraph makeParagraph(BasicSupport support, URL source, + String line, boolean html) { + return super.makeParagraph(support, source, line, html); + } + + @Override + // and make it public! + public List makeParagraphs(BasicSupport support, + URL source, String content, boolean html, Progress pg) + throws IOException { + return super.makeParagraphs(support, source, content, html, pg); + } + + @Override + // and make it public! + public Paragraph processPara(String line, boolean html) { + return super.processPara(line, html); + } + + @Override + // and make it public! + public List requotify(Paragraph para, boolean html) { + return super.requotify(para, html); + } + } +} diff --git a/test/ConversionTest.java b/test/ConversionTest.java new file mode 100644 index 0000000..2b22364 --- /dev/null +++ b/test/ConversionTest.java @@ -0,0 +1,294 @@ +package be.nikiroo.fanfix.test; + +import java.io.File; +import java.io.FilenameFilter; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +import be.nikiroo.fanfix.Instance; +import be.nikiroo.fanfix.Main; +import be.nikiroo.fanfix.output.BasicOutput; +import be.nikiroo.fanfix.output.BasicOutput.OutputType; +import be.nikiroo.utils.IOUtils; +import be.nikiroo.utils.Progress; +import be.nikiroo.utils.TraceHandler; +import be.nikiroo.utils.test.TestCase; +import be.nikiroo.utils.test.TestLauncher; + +class ConversionTest extends TestLauncher { + private class MainWithConvert extends Main { + @Override + public int convert(String urlString, OutputType type, String target, + boolean infoCover, Progress pg) { + return super.convert(urlString, type, target, infoCover, pg); + } + } + + private String testUri; + private String expectedDir; + private String resultDir; + private List realTypes; + private Map> skipCompare; + private Map> skipCompareCross; + + public ConversionTest(String testName, final String testUri, + final String expectedDir, final String resultDir, String[] args) { + super("Conversion - " + testName, args); + + this.testUri = testUri; + this.expectedDir = expectedDir; + this.resultDir = resultDir; + + // Special mode SYSOUT is not a file type (System.out) + realTypes = new ArrayList(); + for (BasicOutput.OutputType type : BasicOutput.OutputType.values()) { + if (!BasicOutput.OutputType.SYSOUT.equals(type)) { + realTypes.add(type); + } + } + + if (!testUri.startsWith("http://") && !testUri.startsWith("https://")) { + addTest(new TestCase("Read the test file") { + @Override + public void test() throws Exception { + assertEquals("The test file \"" + testUri + + "\" cannot be found", true, + new File(testUri).exists()); + } + }); + } + + addTest(new TestCase("Assure directories exist") { + @Override + public void test() throws Exception { + new File(expectedDir).mkdirs(); + new File(resultDir).mkdirs(); + assertEquals("The Expected directory \"" + expectedDir + + "\" cannot be created", true, + new File(expectedDir).exists()); + assertEquals("The Result directory \"" + resultDir + + "\" cannot be created", true, + new File(resultDir).exists()); + } + }); + + for (BasicOutput.OutputType type : realTypes) { + addTest(getTestFor(type)); + } + } + + @Override + protected void start() throws Exception { + skipCompare = new HashMap>(); + skipCompareCross = new HashMap>(); + + skipCompare.put("epb.ncx", Arrays.asList( + " ", + " ")); + skipCompare.put(".info", Arrays.asList("CREATION_DATE=", + "URL=\"file:/", "UUID=EPUBCREATOR=\"", "")); + skipCompare.put("URL", Arrays.asList("file:/")); + + for (String key : skipCompare.keySet()) { + skipCompareCross.put(key, skipCompare.get(key)); + } + + skipCompareCross.put(".info", Arrays.asList("")); + skipCompareCross.put("epb.opf", Arrays.asList(" ")); + skipCompareCross.put("index.html", + Arrays.asList("

")); + skipCompareCross.put("URL", Arrays.asList("")); + } + + @Override + protected void stop() throws Exception { + } + + private TestCase getTestFor(final BasicOutput.OutputType type) { + return new TestCase(type + " output mode") { + @Override + public void test() throws Exception { + File target = generate(this, testUri, new File(resultDir), type); + target = new File(target.getAbsolutePath() + + type.getDefaultExtension(false)); + + // Check conversion: + compareFiles(this, new File(expectedDir), new File(resultDir), + type, "Generate " + type); + + // LATEX not supported as input + if (BasicOutput.OutputType.LATEX.equals(type)) { + return; + } + + // Cross-checks: + for (BasicOutput.OutputType crossType : realTypes) { + File crossDir = Test.tempFiles + .createTempDir("cross-result"); + + generate(this, target.getAbsolutePath(), crossDir, + crossType); + compareFiles(this, new File(resultDir), crossDir, + crossType, "Cross compare " + crossType + + " generated from " + type); + } + } + }; + } + + private File generate(TestCase testCase, String testUri, File resultDir, + BasicOutput.OutputType type) throws Exception { + final List errors = new ArrayList(); + + TraceHandler previousTraceHandler = Instance.getInstance().getTraceHandler(); + Instance.getInstance().setTraceHandler(new TraceHandler(true, true, 0) { + @Override + public void error(String message) { + errors.add(message); + } + + @Override + public void error(Exception e) { + error(" "); + for (Throwable t = e; t != null; t = t.getCause()) { + error(((t == e) ? "(" : "..caused by: (") + + t.getClass().getSimpleName() + ") " + + t.getMessage()); + for (StackTraceElement s : t.getStackTrace()) { + error("\t" + s.toString()); + } + } + } + }); + + try { + File target = new File(resultDir, type.toString()); + int code = new MainWithConvert().convert(testUri, type, + target.getAbsolutePath(), false, null); + + String error = ""; + for (String err : errors) { + if (!error.isEmpty()) + error += "\n"; + error += err; + } + testCase.assertEquals("The conversion returned an error message: " + + error, 0, errors.size()); + if (code != 0) { + testCase.fail("The conversion failed with return code: " + code); + } + + return target; + } finally { + Instance.getInstance().setTraceHandler(previousTraceHandler); + } + } + + private void compareFiles(TestCase testCase, File expectedDir, + File resultDir, final BasicOutput.OutputType limitTiFiles, + final String errMess) throws Exception { + + Map> skipCompare = errMess.startsWith("Cross") ? this.skipCompareCross + : this.skipCompare; + + FilenameFilter filter = null; + if (limitTiFiles != null) { + filter = new FilenameFilter() { + @Override + public boolean accept(File dir, String name) { + return name.toLowerCase().startsWith( + limitTiFiles.toString().toLowerCase()); + } + }; + } + + List resultFiles; + List expectedFiles; + { + String[] resultArr = resultDir.list(filter); + Arrays.sort(resultArr); + resultFiles = Arrays.asList(resultArr); + String[] expectedArr = expectedDir.list(filter); + Arrays.sort(expectedArr); + expectedFiles = Arrays.asList(expectedArr); + } + + testCase.assertEquals(errMess, expectedFiles, resultFiles); + + for (int i = 0; i < resultFiles.size(); i++) { + File expected = new File(expectedDir, expectedFiles.get(i)); + File result = new File(resultDir, resultFiles.get(i)); + + testCase.assertEquals(errMess + ": type mismatch: expected a " + + (expected.isDirectory() ? "directory" : "file") + + ", received a " + + (result.isDirectory() ? "directory" : "file"), + expected.isDirectory(), result.isDirectory()); + + if (expected.isDirectory()) { + compareFiles(testCase, expected, result, null, errMess); + continue; + } + + if (expected.getName().endsWith(".cbz") + || expected.getName().endsWith(".epub")) { + File tmpExpected = Test.tempFiles.createTempDir(expected + .getName() + "[zip-content]"); + File tmpResult = Test.tempFiles.createTempDir(result.getName() + + "[zip-content]"); + IOUtils.unzip(expected, tmpExpected); + IOUtils.unzip(result, tmpResult); + compareFiles(testCase, tmpExpected, tmpResult, null, errMess); + } else { + List expectedLines = Arrays.asList(IOUtils + .readSmallFile(expected).split("\n")); + List resultLines = Arrays.asList(IOUtils.readSmallFile( + result).split("\n")); + + String name = expected.getAbsolutePath(); + if (name.startsWith(expectedDir.getAbsolutePath())) { + name = expectedDir.getName() + + name.substring(expectedDir.getAbsolutePath() + .length()); + } + + testCase.assertEquals(errMess + ": " + name + + ": the number of lines is not the same", + expectedLines.size(), resultLines.size()); + + for (int j = 0; j < expectedLines.size(); j++) { + String expectedLine = expectedLines.get(j); + String resultLine = resultLines.get(j); + + boolean skip = false; + for (Entry> skipThose : skipCompare + .entrySet()) { + for (String skipStart : skipThose.getValue()) { + if (name.endsWith(skipThose.getKey()) + && expectedLine.startsWith(skipStart) + && resultLine.startsWith(skipStart)) { + skip = true; + } + } + } + + if (skip) { + continue; + } + + testCase.assertEquals(errMess + ": line " + (j + 1) + + " is not the same in file " + name, expectedLine, + resultLine); + } + } + } + } +} diff --git a/test/LibraryTest.java b/test/LibraryTest.java new file mode 100644 index 0000000..da44438 --- /dev/null +++ b/test/LibraryTest.java @@ -0,0 +1,258 @@ +package be.nikiroo.fanfix.test; + +import java.io.File; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import be.nikiroo.fanfix.data.Chapter; +import be.nikiroo.fanfix.data.MetaData; +import be.nikiroo.fanfix.data.Paragraph; +import be.nikiroo.fanfix.data.Story; +import be.nikiroo.fanfix.library.BasicLibrary; +import be.nikiroo.fanfix.library.LocalLibrary; +import be.nikiroo.fanfix.output.BasicOutput.OutputType; +import be.nikiroo.utils.IOUtils; +import be.nikiroo.utils.test.TestCase; +import be.nikiroo.utils.test.TestLauncher; + +class LibraryTest extends TestLauncher { + private BasicLibrary lib; + private File tmp; + + public LibraryTest(String[] args) { + super("Library", args); + + final String luid1 = "001"; // A + final String luid2 = "002"; // B + final String luid3 = "003"; // B then A, then B + final String name1 = "My story 1"; + final String name2 = "My story 2"; + final String name3 = "My story 3"; + final String name3ex = "My story 3 [edited]"; + final String source1 = "Source A"; + final String source2 = "Source B"; + final String author1 = "Unknown author"; + final String author2 = "Another other otter author"; + + final String errMess = "The resulting stories in the list are not what we expected"; + + addSeries(new TestLauncher("Local", args) { + { + addTest(new TestCase("getList") { + @Override + public void test() throws Exception { + List metas = lib.getList().getMetas(); + assertEquals(errMess, Arrays.asList(), + titlesAsList(metas)); + } + }); + + addTest(new TestCase("save") { + @Override + public void test() throws Exception { + lib.save(story(luid1, name1, source1, author1), luid1, + null); + + List metas = lib.getList().getMetas(); + assertEquals(errMess, Arrays.asList(name1), + titlesAsList(metas)); + } + }); + + addTest(new TestCase("save more") { + @Override + public void test() throws Exception { + List metas = null; + + lib.save(story(luid2, name2, source2, author1), luid2, + null); + + metas = lib.getList().getMetas(); + assertEquals(errMess, Arrays.asList(name1, name2), + titlesAsList(metas)); + + lib.save(story(luid3, name3, source2, author1), luid3, + null); + + metas = lib.getList().getMetas(); + assertEquals(errMess, + Arrays.asList(name1, name2, name3), + titlesAsList(metas)); + } + }); + + addTest(new TestCase("save override luid (change author)") { + @Override + public void test() throws Exception { + // same luid as a previous one + lib.save(story(luid3, name3ex, source2, author2), + luid3, null); + + List metas = lib.getList().getMetas(); + assertEquals(errMess, + Arrays.asList(name1, name2, name3ex), + titlesAsList(metas)); + } + }); + + addTest(new TestCase("getList with results") { + @Override + public void test() throws Exception { + List metas = lib.getList().getMetas(); + assertEquals(3, metas.size()); + } + }); + + addTest(new TestCase("getList by source") { + @Override + public void test() throws Exception { + List metas = null; + + metas = lib.getList().filter(source1, null, null); + assertEquals(1, metas.size()); + + metas = lib.getList().filter(source2, null, null); + assertEquals(2, metas.size()); + + metas = lib.getList().filter((String)null, null, null); + assertEquals(3, metas.size()); + } + }); + + addTest(new TestCase("getList by author") { + @Override + public void test() throws Exception { + List metas = null; + + metas = lib.getList().filter(null, author1, null); + assertEquals(2, metas.size()); + + metas = lib.getList().filter(null, author2, null); + assertEquals(1, metas.size()); + + metas = lib.getList().filter((String)null, null, null); + assertEquals(3, metas.size()); + } + }); + + addTest(new TestCase("changeType") { + @Override + public void test() throws Exception { + List metas = null; + + lib.changeSource(luid3, source1, null); + + metas = lib.getList().filter(source1, null, null); + assertEquals(2, metas.size()); + + metas = lib.getList().filter(source2, null, null); + assertEquals(1, metas.size()); + + metas = lib.getList().filter((String)null, null, null); + assertEquals(3, metas.size()); + } + }); + + addTest(new TestCase("save override luid (change source)") { + @Override + public void test() throws Exception { + List metas = null; + + // same luid as a previous one + lib.save(story(luid3, "My story 3", source2, author2), + luid3, null); + + metas = lib.getList().filter(source1, null, null); + assertEquals(1, metas.size()); + + metas = lib.getList().filter(source2, null, null); + assertEquals(2, metas.size()); + + metas = lib.getList().filter((String)null, null, null); + assertEquals(3, metas.size()); + } + }); + } + }); + } + + @Override + protected void start() throws Exception { + tmp = File.createTempFile(".test-fanfix", ".library"); + tmp.delete(); + tmp.mkdir(); + + lib = new LocalLibrary(tmp, OutputType.INFO_TEXT, OutputType.CBZ); + } + + @Override + protected void stop() throws Exception { + IOUtils.deltree(tmp); + } + + /** + * Return the (sorted) list of titles present in this list of + * {@link MetaData}s. + * + * @param metas + * the meta + * + * @return the sorted list + */ + private List titlesAsList(List metas) { + List list = new ArrayList(); + for (MetaData meta : metas) { + list.add(meta.getTitle()); + } + + Collections.sort(list); + return list; + } + + private Story story(String luid, String title, String source, String author) { + Story story = new Story(); + + MetaData meta = new MetaData(); + meta.setLuid(luid); + meta.setTitle(title); + meta.setSource(source); + meta.setAuthor(author); + story.setMeta(meta); + + Chapter resume = chapter(0, "Resume"); + meta.setResume(resume); + + List chapters = new ArrayList(); + chapters.add(chapter(1, "Chap 1")); + chapters.add(chapter(2, "Chap 2")); + story.setChapters(chapters); + + long words = 0; + for (Chapter chap : story.getChapters()) { + words += chap.getWords(); + } + meta.setWords(words); + + return story; + } + + private Chapter chapter(int number, String name) { + Chapter chapter = new Chapter(number, name); + + List paragraphs = new ArrayList(); + paragraphs.add(new Paragraph(Paragraph.ParagraphType.NORMAL, + "some words in this paragraph please thank you", 8)); + + chapter.setParagraphs(paragraphs); + + long words = 0; + for (Paragraph para : chapter.getParagraphs()) { + words += para.getWords(); + } + chapter.setWords(words); + + return chapter; + } +} diff --git a/test/Test.java b/test/Test.java new file mode 100644 index 0000000..5ec24a4 --- /dev/null +++ b/test/Test.java @@ -0,0 +1,177 @@ +package be.nikiroo.fanfix.test; + +import java.io.File; +import java.io.IOException; + +import be.nikiroo.fanfix.Instance; +import be.nikiroo.fanfix.bundles.Config; +import be.nikiroo.fanfix.bundles.ConfigBundle; +import be.nikiroo.utils.IOUtils; +import be.nikiroo.utils.TempFiles; +import be.nikiroo.utils.resources.Bundles; +import be.nikiroo.utils.test.TestLauncher; + +/** + * Tests for Fanfix. + * + * @author niki + */ +public class Test extends TestLauncher { + // + // 4 files can control the test: + // - test/VERBOSE: enable verbose mode + // - test/OFFLINE: to forbid any downloading + // - test/URLS: to allow testing URLs + // - test/FORCE_REFRESH: to force a clear of the cache + // + // Note that test/CACHE can be kept, as it will contain all internet related + // files you need (if you allow URLs, run the test once which will populate + // the CACHE then go OFFLINE, it will still work). + // + // The test files will be: + // - test/*.url: URL to download in text format, content = URL + // - test/*.story: text mode story, content = story + // + + /** + * The temporary files handler. + */ + static TempFiles tempFiles; + + /** + * Create the Fanfix {@link TestLauncher}. + * + * @param args + * the arguments to configure the number of columns and the ok/ko + * {@link String}s + * @param urlsAllowed + * allow testing URLs (.url files) + * + * @throws IOException + */ + public Test(String[] args, boolean urlsAllowed) throws IOException { + super("Fanfix", args); + Instance.getInstance().setTraceHandler(null); + addSeries(new BasicSupportUtilitiesTest(args)); + addSeries(new BasicSupportDeprecatedTest(args)); + addSeries(new LibraryTest(args)); + + File sources = new File("test/"); + if (sources.isDirectory()) { + for (File file : sources.listFiles()) { + if (file.isDirectory()) { + continue; + } + + String expectedDir = new File(file.getParentFile(), "expected_" + + file.getName()).getAbsolutePath(); + String resultDir = new File(file.getParentFile(), "result_" + + file.getName()).getAbsolutePath(); + + String uri; + if (urlsAllowed && file.getName().endsWith(".url")) { + uri = IOUtils.readSmallFile(file).trim(); + } else if (file.getName().endsWith(".story")) { + uri = file.getAbsolutePath(); + } else { + continue; + } + + addSeries(new ConversionTest(file.getName(), uri, expectedDir, + resultDir, args)); + } + } + } + + /** + * Main entry point of the program. + * + * @param args + * the arguments passed to the {@link TestLauncher}s. + * @throws IOException + * in case of I/O error + */ + static public void main(String[] args) throws IOException { + Instance.init(); + + // Verbose mode: + boolean verbose = new File("test/VERBOSE").exists(); + + // Can force refresh + boolean forceRefresh = new File("test/FORCE_REFRESH").exists(); + + // Allow URLs: + boolean urlsAllowed = new File("test/URLS").exists(); + + + // Only download files if allowed: + boolean offline = new File("test/OFFLINE").exists(); + Instance.getInstance().getCache().setOffline(offline); + + + + int result = 0; + tempFiles = new TempFiles("fanfix-test"); + try { + File tmpConfig = tempFiles.createTempDir("fanfix-config"); + File localCache = new File("test/CACHE"); + prepareCache(localCache, forceRefresh); + + ConfigBundle config = new ConfigBundle(); + Bundles.setDirectory(tmpConfig.getAbsolutePath()); + config.setString(Config.CACHE_DIR, localCache.getAbsolutePath()); + config.setInteger(Config.CACHE_MAX_TIME_STABLE, -1); + config.setInteger(Config.CACHE_MAX_TIME_CHANGING, -1); + config.updateFile(tmpConfig.getPath()); + System.setProperty("CONFIG_DIR", tmpConfig.getAbsolutePath()); + + Instance.init(true); + Instance.getInstance().getCache().setOffline(offline); + + TestLauncher tests = new Test(args, urlsAllowed); + tests.setDetails(verbose); + + result = tests.launch(); + + IOUtils.deltree(tmpConfig); + prepareCache(localCache, forceRefresh); + } finally { + // Test temp files + tempFiles.close(); + + // This is usually done in Fanfix.Main: + Instance.getInstance().getTempFiles().close(); + } + + System.exit(result); + } + + /** + * Prepare the cache (or clean it up). + *

+ * The cache directory will always exist if this method succeed + * + * @param localCache + * the cache directory + * @param forceRefresh + * TRUE to force acache refresh (delete all files) + * + * @throw IOException if the cache cannot be created + */ + private static void prepareCache(File localCache, boolean forceRefresh) + throws IOException { + // if needed + localCache.mkdirs(); + + if (!localCache.isDirectory()) { + throw new IOException("Cannot get a cache"); + } + + // delete local cached files (_*) or all files if forceRefresh + for (File f : localCache.listFiles()) { + if (forceRefresh || f.getName().startsWith("_")) { + IOUtils.deltree(f); + } + } + } +} diff --git a/test/expected_test.story/cbz.cbz b/test/expected_test.story/cbz.cbz new file mode 100644 index 0000000..28cc25b Binary files /dev/null and b/test/expected_test.story/cbz.cbz differ diff --git a/test/expected_test.story/epub.epub b/test/expected_test.story/epub.epub new file mode 100644 index 0000000..776d05d Binary files /dev/null and b/test/expected_test.story/epub.epub differ diff --git a/test/expected_test.story/html/html.info b/test/expected_test.story/html/html.info new file mode 100644 index 0000000..506e477 --- /dev/null +++ b/test/expected_test.story/html/html.info @@ -0,0 +1,18 @@ +TITLE="The trials of Fanfan" +AUTHOR="UnknownArtist366" +DATE="2018" +SUBJECT="test" +SOURCE="text" +URL="file:/media/xubuntu/sd32/workspace/fanfix/test/test.story" +TAGS="" +UUID="/media/xubuntu/sd32/workspace/fanfix/test/test.story" +LUID="" +LANG="en" +IMAGES_DOCUMENT="false" +TYPE="html" +COVER="" +EPUBCREATOR="Fanfix (by Niki)" +PUBLISHER="" +WORDCOUNT="57" +CREATION_DATE="2018-03-28 08:40:18" +FAKE_COVER="false" diff --git a/test/expected_test.story/html/html.txt b/test/expected_test.story/html/html.txt new file mode 100644 index 0000000..a3a3af3 --- /dev/null +++ b/test/expected_test.story/html/html.txt @@ -0,0 +1,27 @@ +The trials of Fanfan +by UnknownArtist366 (2018) + +Chapter 0: Description +—————————————————————— + +This ‘story’ is nothing more than a test file to check +that the program can convert it into different +formats correctly. + +Chapter 1: Quotes +————————————————— + +“Yes, quotes!”, I said. +‘Those can start with a single- or double quote sign’, I continued. +“They also supports other characters, and an optionnal leading dash.” +“The optionnal leading dash is enough to signify “quote”.” + +Chapter 2: “Quote” test +——————————————————————— + +This test was just for the chapter title. +We can also check for breaks. + +* * * + +This was a break space. diff --git a/test/expected_test.story/html/index.html b/test/expected_test.story/html/index.html new file mode 100644 index 0000000..aade9c3 --- /dev/null +++ b/test/expected_test.story/html/index.html @@ -0,0 +1,55 @@ + + + + + + + The trials of Fanfan + + + +

+

The trials of Fanfan

+
+
+ +
+
UnknownArtist366
+
+

+ This ‘story’ is nothing more than a test file to check + that the program can convert it into different + formats correctly. +
+ +
+

+ Chapter 1: + Quotes +

+ +
+
+
— “Yes, quotes!”, I said.
+
— ‘Those can start with a single- or double quote sign’, I continued.
+
— “They also supports other characters, and an optionnal leading dash.”
+
— “The optionnal leading dash is enough to signify “quote”.”
+
+ +
+

+ Chapter 2: + “Quote” test +

+ +
+
+ This test was just for the chapter title. + We can also check for breaks. +
+
+
+ This was a break space. +
+ +
diff --git a/test/expected_test.story/html/style.css b/test/expected_test.story/html/style.css new file mode 100644 index 0000000..6b6d0d2 --- /dev/null +++ b/test/expected_test.story/html/style.css @@ -0,0 +1,112 @@ +html { + text-align: justify; + max-width: 800px; + margin: auto; +} + +.titlepage { + padding-left: 10%; + padding-right: 10%; + width: 80%; +} + +h1 { + padding-bottom: 0; + margin-bottom: 0; + text-align: left; +} + +.type { + position: relative; + font-size: large; + color: #666666; + font-weight: bold; + padding-bottom: 10px; + text-align: left; +} + +.cover, .page-image { + width: 100%; +} + +.cover img { + height: 45%; + max-width: 100%; + margin: auto; +} + +.author { + text-align: right; + font-size: large; + font-style: italic; +} + +.book, .chapter_content { + NO_text-indent: 40px; + padding-top: 40px; + padding-left: 5%; + padding-right: 5%; + width: 90%; +} + +h2 { + border: 1px solid black; + color: #222222; + padding-left: 10px; + padding-right: 10px; + display: block; + padding-bottom: 0; + margin-bottom: 0; +} + +h2 .chap { + color: #000000; + font-size: large; + font-variant: small-caps; + display: block; +} + +h2 .chap:first-letter { + font-weight: bold; +} + +h2 .chapnumber { + color: #000000; + font-size: xx-large; +} + +h2 .chaptitle { + color: #444444; + font-size: large; + font-style: italic; + padding-bottom: 5px; + text-align: right; + display: block; +} + +.normals { +} + +.normal { + /* Can be removed if you want a more "compact" view */ + display: block; +} + +.blank { + /* Can be removed if you want a more "compact" view */ + height: 24px; + width: 100%; +} + +hr.break { + /* Can be removed if you want a more "compact" view */ + margin-top: 48px; + margin-bottom: 48px; +} + +.dialogues { +} + +.dialogue { + font-style: italic; +} diff --git a/test/expected_test.story/info_text b/test/expected_test.story/info_text new file mode 100644 index 0000000..768b3cc --- /dev/null +++ b/test/expected_test.story/info_text @@ -0,0 +1,24 @@ +The trials of Fanfan +by UnknownArtist366 (2018) + +Chapter 0: Description + +This 'story' is nothing more than a test file to check +that the program can convert it into different +formats correctly. + +Chapter 1: Quotes + +"Yes, quotes!", I said. +'Those can start with a single- or double quote sign', I continued. +"They also supports other characters, and an optionnal leading dash." +"The optionnal leading dash is enough to signify "quote"." + +Chapter 2: “Quote” test + +This test was just for the chapter title. +We can also check for breaks. + +* * * + +This was a break space. diff --git a/test/expected_test.story/info_text.info b/test/expected_test.story/info_text.info new file mode 100644 index 0000000..cc522dd --- /dev/null +++ b/test/expected_test.story/info_text.info @@ -0,0 +1,18 @@ +TITLE="The trials of Fanfan" +AUTHOR="UnknownArtist366" +DATE="2018" +SUBJECT="test" +SOURCE="text" +URL="file:/media/xubuntu/sd32/workspace/fanfix/test/test.story" +TAGS="" +UUID="/media/xubuntu/sd32/workspace/fanfix/test/test.story" +LUID="" +LANG="en" +IMAGES_DOCUMENT="false" +TYPE="info_text" +COVER="" +EPUBCREATOR="Fanfix (by Niki)" +PUBLISHER="" +WORDCOUNT="57" +CREATION_DATE="2018-03-28 08:39:39" +FAKE_COVER="false" diff --git a/test/expected_test.story/latex.tex b/test/expected_test.story/latex.tex new file mode 100644 index 0000000..d4dd68f --- /dev/null +++ b/test/expected_test.story/latex.tex @@ -0,0 +1,44 @@ +% +% This LaTeX document was auto-generated by Fanfic Reader, created by Niki. +% + +\documentclass[a4paper]{book} +\usepackage[english]{babel} +\usepackage[utf8]{inputenc} +\usepackage[T1]{fontenc} +\usepackage{lmodern} +\newcommand{\br}{\vspace{10 mm}} +\newcommand{\say}{--- \noindent\emph} +\hyphenpenalty=1000 +\tolerance=5000 +\begin{document} +\date{2018} +\title{The trials of Fanfan} +\author{UnknownArtist366} +\maketitle + + + +\chapter{Quotes} + +\say{``Yes, quotes!'', I said.} + +\say{`Those can start with a single- or double quote sign', I continued.} + +\noindent{} +\say{``They also supports other characters, and an optionnal leading dash.''} + +\noindent{} +\say{``The optionnal leading dash is enough to signify ``quote''.''} + +\noindent{} + + +\chapter{``Quote'' test} +This test was just for the chapter title. +We can also check for breaks. + +\br +This was a break space. + +\end{document} diff --git a/test/expected_test.story/text.txt b/test/expected_test.story/text.txt new file mode 100644 index 0000000..a3a3af3 --- /dev/null +++ b/test/expected_test.story/text.txt @@ -0,0 +1,27 @@ +The trials of Fanfan +by UnknownArtist366 (2018) + +Chapter 0: Description +—————————————————————— + +This ‘story’ is nothing more than a test file to check +that the program can convert it into different +formats correctly. + +Chapter 1: Quotes +————————————————— + +“Yes, quotes!”, I said. +‘Those can start with a single- or double quote sign’, I continued. +“They also supports other characters, and an optionnal leading dash.” +“The optionnal leading dash is enough to signify “quote”.” + +Chapter 2: “Quote” test +——————————————————————— + +This test was just for the chapter title. +We can also check for breaks. + +* * * + +This was a break space. diff --git a/test/test.story b/test/test.story new file mode 100644 index 0000000..7365b60 --- /dev/null +++ b/test/test.story @@ -0,0 +1,23 @@ +The trials of Fanfan +by UnknownArtist366 (2018) + +Chapter 0: Description + +This 'story' is nothing more than a test file to check +that the program can convert it into different +formats correctly. + +Chapter 1: Quotes +- "Yes, quotes!", I said. +'Those can start with a single- or double quote sign', I continued. +- «They also supports other characters, and an optionnal leading dash.» +- The optionnal leading dash is enough to signify "quote". + +Chapter 2: "Quote" test +This test was just for the chapter title. +We can also check for breaks. + +* * * + +This was a break space. +