From 31143cfbfbe4570ab788e77f65869fd18b3544fb Mon Sep 17 00:00:00 2001 From: Vhati Date: Wed, 27 Dec 2017 09:42:47 -0500 Subject: [PATCH] Changed command line parser to picocli --- pom.xml | 6 +- readme_developers.txt | 6 +- skel_common/backup/auto_update.json | 3 +- skel_common/readme_changelog.txt | 1 + .../vhati/modmanager/cli/SlipstreamCLI.java | 174 +++++++++--------- .../modmanager/scraper/ForumScraper.java | 150 +++++++-------- 6 files changed, 159 insertions(+), 181 deletions(-) diff --git a/pom.xml b/pom.xml index e29cddc..a64f5e4 100644 --- a/pom.xml +++ b/pom.xml @@ -57,9 +57,9 @@ 2.0.6 - commons-cli - commons-cli - 1.4 + info.picocli + picocli + 2.2.0 diff --git a/readme_developers.txt b/readme_developers.txt index 679cc79..e615cc9 100644 --- a/readme_developers.txt +++ b/readme_developers.txt @@ -47,9 +47,9 @@ This project depends on the following libraries. - Logback https://logback.qos.ch/ (For JavaDocs, look left.) -- Apache Commons CLI 1.x - http://commons.apache.org/proper/commons-cli/ - (For JavaDocs, scroll down.) +- picocli 2.x + http://picocli.info/ + (For JavaDocs, look left and scroll down to "API Javadoc".) diff --git a/skel_common/backup/auto_update.json b/skel_common/backup/auto_update.json index 84f714f..7b1b4e0 100644 --- a/skel_common/backup/auto_update.json +++ b/skel_common/backup/auto_update.json @@ -19,7 +19,8 @@ "Made the comments in boilerplace mod metadata optional", "Fixed omitted Validate warnings for PNG files", "Added Validate warnings about FTL 1.6.1+ for TTF, MP3, and PNG files", - "Changed logging framework to SLF4J/Logback" + "Changed logging framework to SLF4J/Logback", + "Changed command line parser to picocli" ] }, { diff --git a/skel_common/readme_changelog.txt b/skel_common/readme_changelog.txt index 37edc70..678d30d 100644 --- a/skel_common/readme_changelog.txt +++ b/skel_common/readme_changelog.txt @@ -6,6 +6,7 @@ Changelog - Fixed omitted Validate warnings for PNG files - Added Validate warnings about FTL 1.6.1+ for TTF, MP3, and PNG files - Changed logging framework to SLF4J/Logback +- Changed command line parser to picocli 1.9: - Fixed corrupted civilian sector music in FTL 1.6.1+ diff --git a/src/main/java/net/vhati/modmanager/cli/SlipstreamCLI.java b/src/main/java/net/vhati/modmanager/cli/SlipstreamCLI.java index 11f5fca..4a87b4c 100644 --- a/src/main/java/net/vhati/modmanager/cli/SlipstreamCLI.java +++ b/src/main/java/net/vhati/modmanager/cli/SlipstreamCLI.java @@ -16,12 +16,12 @@ import java.util.Properties; import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; -import org.apache.commons.cli.BasicParser; -import org.apache.commons.cli.Option; -import org.apache.commons.cli.Options; -import org.apache.commons.cli.CommandLine; -import org.apache.commons.cli.HelpFormatter; -import org.apache.commons.cli.ParseException; +import picocli.CommandLine; +import picocli.CommandLine.Command; +import picocli.CommandLine.IVersionProvider; +import picocli.CommandLine.Option; +import picocli.CommandLine.ParameterException; +import picocli.CommandLine.Parameters; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -62,77 +62,38 @@ public class SlipstreamCLI { } }; - BasicParser parser = new BasicParser(); - - Options options = new Options(); - options.addOption( Option.builder() - .longOpt( "extract-dats" ) - .desc( "extract FTL resources into a dir" ) - .hasArg() - .argName( "DIR" ) - .build() ); - options.addOption( Option.builder() - .longOpt( "global-panic" ) - .desc( "patch as if advanced find tags had panic='true'" ) - .build() ); - options.addOption( Option.builder() - .longOpt( "list-mods" ) - .desc( "list available mod names" ) - .build() ); - options.addOption( Option.builder() - .longOpt( "runftl" ) - .desc( "run the game (standalone or with 'patch')" ) - .build() ); - options.addOption( Option.builder() - .longOpt( "patch" ) - .desc( "revert to vanilla and add named mods (if any)" ) - .build() ); - options.addOption( Option.builder() - .longOpt( "validate" ) - .desc( "check named mods for problems" ) - .build() ); - options.addOption( "h", "help", false, "display this help and exit" ); - options.addOption( Option.builder() - .longOpt( "version" ) - .desc( "output version information and exit" ) - .build() ); - CommandLine cmdline = null; + SlipstreamCommand slipstreamCmd = new SlipstreamCommand(); + CommandLine commandLine = new CommandLine( slipstreamCmd ); try { - cmdline = parser.parse( options, args, true ); + commandLine.parse( args ); } - catch( ParseException e ) { + catch ( ParameterException e ) { + //For multiple subcommands, commandLine.getCommandLine() returns the one that failed. + System.err.println( "Error parsing commandline: "+ e.getMessage() ); System.exit( 1 ); } - if ( cmdline.hasOption( "h" ) ) { // Exits. - HelpFormatter formatter = new HelpFormatter(); - - String helpHeader = "Perform actions against an FTL installation and/or a list of named mods."+ formatter.getNewLine(); - - String helpFooter = formatter.getNewLine(); - helpFooter += "Each MODFILE is a filename in the mods/ dir."+ formatter.getNewLine(); - helpFooter += "If a named mod is a directory, a temporary zip will be created."; - - formatter.printHelp( "modman [OPTION] [MODFILE]...", helpHeader, options, helpFooter ); + if ( commandLine.isUsageHelpRequested() ) { + commandLine.usage( System.out ); System.exit( 0 ); } - if ( cmdline.hasOption( "version" ) ) { // Exits. - System.out.println( getVersionMessage() ); + if ( commandLine.isVersionHelpRequested() ) { + commandLine.printVersionHelp( System.out ); System.exit( 0 ); } DelayedDeleteHook deleteHook = new DelayedDeleteHook(); Runtime.getRuntime().addShutdownHook( deleteHook ); - if ( cmdline.hasOption( "validate" ) ) { // Exits (0/1). + if ( slipstreamCmd.validate ) { // Exits (0/1). log.info( "Validating..." ); StringBuilder resultBuf = new StringBuilder(); ReportFormatter formatter = new ReportFormatter(); boolean anyInvalid = false; - for ( String modFileName : cmdline.getArgs() ) { + for ( String modFileName : slipstreamCmd.modFileNames ) { File modFile = new File( modsDir, modFileName ); if ( modFile.isDirectory() ) { @@ -175,7 +136,7 @@ public class SlipstreamCLI { File configFile = new File( "modman.cfg" ); SlipstreamConfig appConfig = getConfig( configFile ); - if ( cmdline.hasOption( "list-mods" ) ) { // Exits. + if ( slipstreamCmd.listMods ) { // Exits. log.info( "Listing mods..." ); boolean allowZip = appConfig.getProperty( SlipstreamConfig.ALLOW_ZIP, "false" ).equals( "true" ); @@ -197,17 +158,16 @@ public class SlipstreamCLI { } File datsDir = null; - if ( cmdline.hasOption( "extract-dats" ) || - cmdline.hasOption( "patch" ) || - cmdline.hasOption( "runftl" ) ) { + if ( slipstreamCmd.extractDatsDir != null || + slipstreamCmd.patch || + slipstreamCmd.runftl ) { datsDir = getDatsDir( appConfig ); } - if ( cmdline.hasOption( "extract-dats" ) ) { // Exits (0/1). + if ( slipstreamCmd.extractDatsDir != null ) { // Exits (0/1). log.info( "Extracting dats..." ); - String extractPath = cmdline.getOptionValue( "extract-dats" ); - File extractDir = new File( extractPath ); + File extractDir = slipstreamCmd.extractDatsDir; FolderPack dstPack = null; List srcPacks = new ArrayList( 2 ); @@ -269,11 +229,11 @@ public class SlipstreamCLI { System.exit( 0 ); } - if ( cmdline.hasOption( "patch" ) ) { // Exits sometimes (1 on failure). + if ( slipstreamCmd.patch ) { // Exits sometimes (1 on failure). log.info( "Patching..." ); List modFiles = new ArrayList(); - for ( String modFileName : cmdline.getArgs() ) { + for ( String modFileName : slipstreamCmd.modFileNames ) { File modFile = new File( modsDir, modFileName ); if ( modFile.isDirectory() ) { @@ -291,7 +251,7 @@ public class SlipstreamCLI { modFiles.add( modFile ); } - boolean globalPanic = cmdline.hasOption( "global-panic" ); + boolean globalPanic = slipstreamCmd.globalPanic; SilentPatchObserver patchObserver = new SilentPatchObserver(); ModPatchThread patchThread = new ModPatchThread( modFiles, datsDir, backupDir, globalPanic, patchObserver ); @@ -307,7 +267,7 @@ public class SlipstreamCLI { if ( !patchObserver.hasSucceeded() ) System.exit( 1 ); } - if ( cmdline.hasOption( "runftl" ) ) { // Exits (0/1). + if ( slipstreamCmd.runftl ) { // Exits (0/1). log.info( "Running FTL..." ); File exeFile = null; @@ -489,24 +449,66 @@ public class SlipstreamCLI { } } - private static String getVersionMessage() { - StringBuilder buf = new StringBuilder(); - buf.append( String.format( "%s %s\n", FTLModManager.APP_NAME, FTLModManager.APP_VERSION ) ); - buf.append( "Copyright (C) 2014 David Millis\n" ); - buf.append( "\n" ); - buf.append( "This program is free software; you can redistribute it and/or modify\n" ); - buf.append( "it under the terms of the GNU General Public License as published by\n" ); - buf.append( "the Free Software Foundation; version 2.\n" ); - buf.append( "\n" ); - buf.append( "This program is distributed in the hope that it will be useful,\n" ); - buf.append( "but WITHOUT ANY WARRANTY; without even the implied warranty of\n" ); - buf.append( "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n" ); - buf.append( "GNU General Public License for more details.\n" ); - buf.append( "\n" ); - buf.append( "You should have received a copy of the GNU General Public License\n" ); - buf.append( "along with this program. If not, see http://www.gnu.org/licenses/.\n" ); - buf.append( "\n" ); - return buf.toString(); + + + @Command( + name = "modman", + abbreviateSynopsis = true, + sortOptions = false, + description = "Perform actions against an FTL installation and/or a list of named mods.", + footer = "%nIf a named mod is a directory, a temporary zip will be created.", + versionProvider = SlipstreamVersionProvider.class + ) + public static class SlipstreamCommand { + @Option(names = "--extract-dats", paramLabel = "DIR", description = "extract FTL resources into a dir") + File extractDatsDir; + + @Option(names = "--global-panic", description = "patch as if advanced find tags had panic='true'") + boolean globalPanic; + + @Option(names = "--list-mods", description = "list available mod names") + boolean listMods; + + @Option(names = "--runftl", description = "run the game (standalone or with 'patch')") + boolean runftl; + + @Option(names = "--patch", description = "revert to vanilla and add named mods (if any)") + boolean patch; + + @Option(names = "--validate", description = "check named mods for problems") + boolean validate; + + @Option(names = {"-h", "--help"}, usageHelp = true, description = "display this help and exit") + boolean helpRequested; + + @Option(names = "--version", versionHelp = true, description = "output version information and exit") + boolean versionRequested; + + @Parameters(paramLabel = "MODFILE", description = "names of files or directories in the mods/ dir") + String[] modFileNames; + } + + public static class SlipstreamVersionProvider implements IVersionProvider { + @Override + public String[] getVersion() { + return new String[] { + String.format( "%s %s", FTLModManager.APP_NAME, FTLModManager.APP_VERSION ), + "Copyright (C) 2013,2014,2017 David Millis", + "", + "This program is free software; you can redistribute it and/or modify", + "it under the terms of the GNU General Public License as published by", + "the Free Software Foundation; version 2.", + "", + "This program is distributed in the hope that it will be useful,", + "but WITHOUT ANY WARRANTY; without even the implied warranty of", + "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the", + "GNU General Public License for more details.", + "", + "You should have received a copy of the GNU General Public License", + "along with this program. If not, see http://www.gnu.org/licenses/.", + "", + }; + } } diff --git a/src/main/java/net/vhati/modmanager/scraper/ForumScraper.java b/src/main/java/net/vhati/modmanager/scraper/ForumScraper.java index e264bf1..f202c25 100644 --- a/src/main/java/net/vhati/modmanager/scraper/ForumScraper.java +++ b/src/main/java/net/vhati/modmanager/scraper/ForumScraper.java @@ -46,12 +46,11 @@ import org.jdom2.input.SAXBuilder; import org.jdom2.output.Format; import org.jdom2.output.XMLOutputter; -import org.apache.commons.cli.BasicParser; -import org.apache.commons.cli.OptionBuilder; -import org.apache.commons.cli.Options; -import org.apache.commons.cli.CommandLine; -import org.apache.commons.cli.HelpFormatter; -import org.apache.commons.cli.ParseException; +import picocli.CommandLine; +import picocli.CommandLine.Command; +import picocli.CommandLine.IVersionProvider; +import picocli.CommandLine.Option; +import picocli.CommandLine.ParameterException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -90,129 +89,75 @@ public class ForumScraper { // Engi Scrap Advantage is bundled in SMM. ignoredURLs.add( "https://subsetgames.com/forum/viewtopic.php?f=12&t=17102" ); - - BasicParser parser = new BasicParser(); - - Options options = new Options(); - options.addOption( OptionBuilder.withLongOpt( "load-json" ) - .withDescription( "load moddb from a json catalog" ) - .hasArg() - .withArgName("FILE") - .create() ); - options.addOption( OptionBuilder.withLongOpt( "load-xml" ) - .withDescription( "load moddb from an xml file" ) - .hasArg() - .withArgName("FILE") - .create() ); - options.addOption( OptionBuilder.withLongOpt( "scrape" ) - .withDescription( "write changed forum posts to an xml file" ) - .hasArg() - .withArgName("FILE") - .create() ); - options.addOption( OptionBuilder.withLongOpt( "dump-json" ) - .withDescription( "write the moddb to a json file" ) - .hasArg() - .withArgName("FILE") - .create() ); - options.addOption( OptionBuilder.withLongOpt( "dump-xml" ) - .withDescription( "write the moddb to an xml file" ) - .hasArg() - .withArgName("FILE") - .create() ); - options.addOption( OptionBuilder.withLongOpt( "hash-thread" ) - .withDescription( "print the hash of a specific thread" ) - .hasArg() - .withArgName("URL") - .create() ); - options.addOption( OptionBuilder.withLongOpt( "first-post" ) - .withDescription( "print the first post of a thread (debugging)" ) - .hasArg() - .withArgName("URL") - .create() ); - options.addOption( "h", "help", false, "display this help and exit" ); - - CommandLine cmdline = null; + ScraperCommand scraperCmd = new ScraperCommand(); + CommandLine commandLine = new CommandLine( scraperCmd ); try { - cmdline = parser.parse( options, args, true ); + commandLine.parse( args ); } - catch( ParseException e ) { + catch ( ParameterException e ) { System.err.println( "Error parsing commandline: "+ e.getMessage() ); System.exit( 1 ); } - if ( cmdline.hasOption( "h" ) ) { - HelpFormatter formatter = new HelpFormatter(); - - String syntax = ForumScraper.class.getCanonicalName() +" [OPTIONS]"; - - String helpHeader = "Load an existing catalog as the moddb, and scrape."+ formatter.getNewLine(); - helpHeader += "Edit the catalog by copy/pasting scrape snippets."+ formatter.getNewLine(); - helpHeader += "Load the edited catalog and dump json."+ formatter.getNewLine(); - - PrintWriter pw = new PrintWriter( System.out ); - formatter.printUsage( pw, formatter.getWidth(), syntax ); - pw.write( helpHeader ); - pw.write( formatter.getNewLine() ); - formatter.printOptions( pw, formatter.getWidth(), options, formatter.getLeftPadding(), formatter.getDescPadding() ); - pw.flush(); - + if ( commandLine.isUsageHelpRequested() ) { + commandLine.usage( System.out ); System.exit( 0 ); } ModDB modDB = new ModDB(); try { - if ( cmdline.hasOption( "load-json" ) ) { + if ( scraperCmd.jsonSrcFile != null ) { log.info( "Loading json catalog..." ); - File srcFile = new File( cmdline.getOptionValue( "load-json" ) ); + File srcFile = scraperCmd.jsonSrcFile; ModDB newDB = JacksonCatalogReader.parse( srcFile ); if ( newDB != null ) modDB = newDB; } - if ( cmdline.hasOption( "load-xml" ) ) { + if ( scraperCmd.xmlSrcFile != null ) { log.info( "Loading xml catalog..." ); - File srcFile = new File( cmdline.getOptionValue( "load-xml" ) ); + File srcFile = scraperCmd.xmlSrcFile; ModDB newDB = parseCatalogXML( srcFile ); if ( newDB != null ) modDB = newDB; } - if ( cmdline.hasOption( "scrape" ) ) { + if ( scraperCmd.scrapeDstFile != null ) { log.info( "Scraping..." ); - File dstFile = new File( cmdline.getOptionValue( "scrape" ) ); + File dstFile = scraperCmd.scrapeDstFile; List data = scrape( modDB, MASTER_LIST_URL, ignoredURLs ); if ( data.size() > 0 ) writeXML( data, dstFile ); } - if ( cmdline.hasOption( "dump-json" ) ) { + if ( scraperCmd.jsonDstFile != null ) { log.info( "Dumping json..." ); - File dstFile = new File( cmdline.getOptionValue( "dump-json" ) ); + File dstFile = scraperCmd.jsonDstFile; List data = modDB.getCollatedModInfo(); if ( data.size() > 0 ) JacksonCatalogWriter.write( data, dstFile ); } - if ( cmdline.hasOption( "dump-xml" ) ) { + if ( scraperCmd.xmlDstFile != null ) { log.info( "Dumping xml..." ); - File dstFile = new File( cmdline.getOptionValue( "dump-xml" ) ); + File dstFile = scraperCmd.xmlDstFile; List data = modDB.getCollatedModInfo(); if ( data.size() > 0 ) writeXML( data, dstFile ); } - if ( cmdline.hasOption( "hash-thread" ) ) { + if ( scraperCmd.hashThreadURL != null ) { log.info( "Hashing thread..." ); - String threadURL = cmdline.getOptionValue( "hash-thread" ); + String threadURL = scraperCmd.hashThreadURL; System.out.println( hashThread( threadURL ) ); } - if ( cmdline.hasOption( "first-post" ) ) { + if ( scraperCmd.firstPostURL != null ) { log.info( "Getting thread's first post..." ); - String threadURL = cmdline.getOptionValue( "first-post" ); + String threadURL = scraperCmd.firstPostURL; System.out.println( getFirstPost( threadURL ) ); } } @@ -221,7 +166,6 @@ public class ForumScraper { } } - /** * Scrapes the forum for changed posts and returns info from updated mods. */ @@ -244,7 +188,6 @@ public class ForumScraper { return results; } - /** * Scrape the Master Mod List on the FTL forum. * @@ -400,14 +343,13 @@ public class ForumScraper { return results; } - /** * Extracts the html content of the first post in a forum thread. */ private static String getFirstPost( String url ) throws IOException { String htmlSrc = fetchWebPage( url ); - Pattern firstPostPtn = Pattern.compile( "(?s)
]*>.*?
]*>(.*?)
\\s*
]*>" ); + Pattern firstPostPtn = Pattern.compile( "(?s)
]*>.*?
]*>(.*?)
" ); Matcher m = null; String postContent = ""; @@ -434,7 +376,6 @@ public class ForumScraper { return postContent; } - /** * Calculates an MD5 hash of the first post in a thread. */ @@ -443,7 +384,6 @@ public class ForumScraper { return PackUtilities.calcStreamMD5( new ByteArrayInputStream( rawDesc.getBytes( Charset.forName("UTF-8") ) ) ); } - /** * Downloads a URL and returns the string content, decoded as UTF-8. */ @@ -492,7 +432,6 @@ public class ForumScraper { return result; } - /** * Writes collated catalog entries to a file, as human-editable xml. */ @@ -563,7 +502,6 @@ public class ForumScraper { return dst; } - /** * Parses dumped xml and returns a new catalog. */ @@ -611,6 +549,42 @@ public class ForumScraper { } + + @Command( + name = "ForumScraper", + abbreviateSynopsis = true, + sortOptions = false, + description = "Harvest new forum content to edit into the moddb catalog.", + footer = "%nLoad an existing catalog as the moddb, and scrape.%nManually edit the catalog by copy/pasting scraped snippets.%nLoad the edited catalog and dump back to json." + ) + public static class ScraperCommand { + @Option(names = "--load-json", paramLabel = "FILE", description = "load moddb from a json catalog") + File jsonSrcFile; + + @Option(names = "--load-xml", paramLabel = "FILE", description = "load moddb from an xml file") + File xmlSrcFile; + + @Option(names = "--scrape", paramLabel = "FILE", description = "write changed forum posts to an xml file") + File scrapeDstFile; + + @Option(names = "--dump-json", paramLabel = "FILE", description = "write the moddb to a json file") + File jsonDstFile; + + @Option(names = "--dump-xml", paramLabel = "FILE", description = "write the moddb to an xml file") + File xmlDstFile; + + @Option(names = "--hash-thread", paramLabel = "URL", description = "print the hash of a specific thread") + String hashThreadURL; + + @Option(names = "--first-post", paramLabel = "URL", description = "print the first post of a thread (debugging)") + String firstPostURL; + + @Option(names = {"-h", "--help"}, usageHelp = true, description = "display this help and exit") + boolean helpRequested; + } + + + /** Information gleaned from scraping the forum. */ private static class ScrapeResult { public String threadURL = null;