Changed command line parser to picocli

This commit is contained in:
Vhati 2017-12-27 09:42:47 -05:00
parent cc00cd9c95
commit 31143cfbfb
6 changed files with 159 additions and 181 deletions

View file

@ -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<AbstractPack> srcPacks = new ArrayList<AbstractPack>( 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<File> modFiles = new ArrayList<File>();
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/.",
"",
};
}
}

View file

@ -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<ModsInfo> 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<ModsInfo> 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<ModsInfo> 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)<div class=\"postbody\"[^>]*>.*?<div class=\"content\"[^>]*>(.*?)</div>\\s*<dl class=\"postprofile\"[^>]*>" );
Pattern firstPostPtn = Pattern.compile( "(?s)<div class=\"postbody\"[^>]*>.*?<div class=\"content\"[^>]*>(.*?)</div>" );
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;