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

@ -57,9 +57,9 @@
<version>2.0.6</version> <version>2.0.6</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>commons-cli</groupId> <groupId>info.picocli</groupId>
<artifactId>commons-cli</artifactId> <artifactId>picocli</artifactId>
<version>1.4</version> <version>2.2.0</version>
</dependency> </dependency>
</dependencies> </dependencies>

View file

@ -47,9 +47,9 @@ This project depends on the following libraries.
- Logback - Logback
https://logback.qos.ch/ https://logback.qos.ch/
(For JavaDocs, look left.) (For JavaDocs, look left.)
- Apache Commons CLI 1.x - picocli 2.x
http://commons.apache.org/proper/commons-cli/ http://picocli.info/
(For JavaDocs, scroll down.) (For JavaDocs, look left and scroll down to "API Javadoc".)

View file

@ -19,7 +19,8 @@
"Made the comments in boilerplace mod metadata optional", "Made the comments in boilerplace mod metadata optional",
"Fixed omitted Validate warnings for PNG files", "Fixed omitted Validate warnings for PNG files",
"Added Validate warnings about FTL 1.6.1+ for TTF, MP3, and 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"
] ]
}, },
{ {

View file

@ -6,6 +6,7 @@ Changelog
- Fixed omitted Validate warnings for PNG files - Fixed omitted Validate warnings for PNG files
- Added Validate warnings about FTL 1.6.1+ for TTF, MP3, and 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
1.9: 1.9:
- Fixed corrupted civilian sector music in FTL 1.6.1+ - Fixed corrupted civilian sector music in FTL 1.6.1+

View file

@ -16,12 +16,12 @@ import java.util.Properties;
import java.util.zip.ZipEntry; import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream; import java.util.zip.ZipOutputStream;
import org.apache.commons.cli.BasicParser; import picocli.CommandLine;
import org.apache.commons.cli.Option; import picocli.CommandLine.Command;
import org.apache.commons.cli.Options; import picocli.CommandLine.IVersionProvider;
import org.apache.commons.cli.CommandLine; import picocli.CommandLine.Option;
import org.apache.commons.cli.HelpFormatter; import picocli.CommandLine.ParameterException;
import org.apache.commons.cli.ParseException; import picocli.CommandLine.Parameters;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -62,77 +62,38 @@ public class SlipstreamCLI {
} }
}; };
BasicParser parser = new BasicParser(); SlipstreamCommand slipstreamCmd = new SlipstreamCommand();
CommandLine commandLine = new CommandLine( slipstreamCmd );
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;
try { 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.err.println( "Error parsing commandline: "+ e.getMessage() );
System.exit( 1 ); System.exit( 1 );
} }
if ( cmdline.hasOption( "h" ) ) { // Exits. if ( commandLine.isUsageHelpRequested() ) {
HelpFormatter formatter = new HelpFormatter(); commandLine.usage( System.out );
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 );
System.exit( 0 ); System.exit( 0 );
} }
if ( cmdline.hasOption( "version" ) ) { // Exits. if ( commandLine.isVersionHelpRequested() ) {
System.out.println( getVersionMessage() ); commandLine.printVersionHelp( System.out );
System.exit( 0 ); System.exit( 0 );
} }
DelayedDeleteHook deleteHook = new DelayedDeleteHook(); DelayedDeleteHook deleteHook = new DelayedDeleteHook();
Runtime.getRuntime().addShutdownHook( deleteHook ); Runtime.getRuntime().addShutdownHook( deleteHook );
if ( cmdline.hasOption( "validate" ) ) { // Exits (0/1). if ( slipstreamCmd.validate ) { // Exits (0/1).
log.info( "Validating..." ); log.info( "Validating..." );
StringBuilder resultBuf = new StringBuilder(); StringBuilder resultBuf = new StringBuilder();
ReportFormatter formatter = new ReportFormatter(); ReportFormatter formatter = new ReportFormatter();
boolean anyInvalid = false; boolean anyInvalid = false;
for ( String modFileName : cmdline.getArgs() ) { for ( String modFileName : slipstreamCmd.modFileNames ) {
File modFile = new File( modsDir, modFileName ); File modFile = new File( modsDir, modFileName );
if ( modFile.isDirectory() ) { if ( modFile.isDirectory() ) {
@ -175,7 +136,7 @@ public class SlipstreamCLI {
File configFile = new File( "modman.cfg" ); File configFile = new File( "modman.cfg" );
SlipstreamConfig appConfig = getConfig( configFile ); SlipstreamConfig appConfig = getConfig( configFile );
if ( cmdline.hasOption( "list-mods" ) ) { // Exits. if ( slipstreamCmd.listMods ) { // Exits.
log.info( "Listing mods..." ); log.info( "Listing mods..." );
boolean allowZip = appConfig.getProperty( SlipstreamConfig.ALLOW_ZIP, "false" ).equals( "true" ); boolean allowZip = appConfig.getProperty( SlipstreamConfig.ALLOW_ZIP, "false" ).equals( "true" );
@ -197,17 +158,16 @@ public class SlipstreamCLI {
} }
File datsDir = null; File datsDir = null;
if ( cmdline.hasOption( "extract-dats" ) || if ( slipstreamCmd.extractDatsDir != null ||
cmdline.hasOption( "patch" ) || slipstreamCmd.patch ||
cmdline.hasOption( "runftl" ) ) { slipstreamCmd.runftl ) {
datsDir = getDatsDir( appConfig ); datsDir = getDatsDir( appConfig );
} }
if ( cmdline.hasOption( "extract-dats" ) ) { // Exits (0/1). if ( slipstreamCmd.extractDatsDir != null ) { // Exits (0/1).
log.info( "Extracting dats..." ); log.info( "Extracting dats..." );
String extractPath = cmdline.getOptionValue( "extract-dats" ); File extractDir = slipstreamCmd.extractDatsDir;
File extractDir = new File( extractPath );
FolderPack dstPack = null; FolderPack dstPack = null;
List<AbstractPack> srcPacks = new ArrayList<AbstractPack>( 2 ); List<AbstractPack> srcPacks = new ArrayList<AbstractPack>( 2 );
@ -269,11 +229,11 @@ public class SlipstreamCLI {
System.exit( 0 ); System.exit( 0 );
} }
if ( cmdline.hasOption( "patch" ) ) { // Exits sometimes (1 on failure). if ( slipstreamCmd.patch ) { // Exits sometimes (1 on failure).
log.info( "Patching..." ); log.info( "Patching..." );
List<File> modFiles = new ArrayList<File>(); List<File> modFiles = new ArrayList<File>();
for ( String modFileName : cmdline.getArgs() ) { for ( String modFileName : slipstreamCmd.modFileNames ) {
File modFile = new File( modsDir, modFileName ); File modFile = new File( modsDir, modFileName );
if ( modFile.isDirectory() ) { if ( modFile.isDirectory() ) {
@ -291,7 +251,7 @@ public class SlipstreamCLI {
modFiles.add( modFile ); modFiles.add( modFile );
} }
boolean globalPanic = cmdline.hasOption( "global-panic" ); boolean globalPanic = slipstreamCmd.globalPanic;
SilentPatchObserver patchObserver = new SilentPatchObserver(); SilentPatchObserver patchObserver = new SilentPatchObserver();
ModPatchThread patchThread = new ModPatchThread( modFiles, datsDir, backupDir, globalPanic, patchObserver ); ModPatchThread patchThread = new ModPatchThread( modFiles, datsDir, backupDir, globalPanic, patchObserver );
@ -307,7 +267,7 @@ public class SlipstreamCLI {
if ( !patchObserver.hasSucceeded() ) System.exit( 1 ); if ( !patchObserver.hasSucceeded() ) System.exit( 1 );
} }
if ( cmdline.hasOption( "runftl" ) ) { // Exits (0/1). if ( slipstreamCmd.runftl ) { // Exits (0/1).
log.info( "Running FTL..." ); log.info( "Running FTL..." );
File exeFile = null; 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 ) ); @Command(
buf.append( "Copyright (C) 2014 David Millis\n" ); name = "modman",
buf.append( "\n" ); abbreviateSynopsis = true,
buf.append( "This program is free software; you can redistribute it and/or modify\n" ); sortOptions = false,
buf.append( "it under the terms of the GNU General Public License as published by\n" ); description = "Perform actions against an FTL installation and/or a list of named mods.",
buf.append( "the Free Software Foundation; version 2.\n" ); footer = "%nIf a named mod is a directory, a temporary zip will be created.",
buf.append( "\n" ); versionProvider = SlipstreamVersionProvider.class
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" ); public static class SlipstreamCommand {
buf.append( "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n" ); @Option(names = "--extract-dats", paramLabel = "DIR", description = "extract FTL resources into a dir")
buf.append( "GNU General Public License for more details.\n" ); File extractDatsDir;
buf.append( "\n" );
buf.append( "You should have received a copy of the GNU General Public License\n" ); @Option(names = "--global-panic", description = "patch as if advanced find tags had panic='true'")
buf.append( "along with this program. If not, see http://www.gnu.org/licenses/.\n" ); boolean globalPanic;
buf.append( "\n" );
return buf.toString(); @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.Format;
import org.jdom2.output.XMLOutputter; import org.jdom2.output.XMLOutputter;
import org.apache.commons.cli.BasicParser; import picocli.CommandLine;
import org.apache.commons.cli.OptionBuilder; import picocli.CommandLine.Command;
import org.apache.commons.cli.Options; import picocli.CommandLine.IVersionProvider;
import org.apache.commons.cli.CommandLine; import picocli.CommandLine.Option;
import org.apache.commons.cli.HelpFormatter; import picocli.CommandLine.ParameterException;
import org.apache.commons.cli.ParseException;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -90,129 +89,75 @@ public class ForumScraper {
// Engi Scrap Advantage is bundled in SMM. // Engi Scrap Advantage is bundled in SMM.
ignoredURLs.add( "https://subsetgames.com/forum/viewtopic.php?f=12&t=17102" ); ignoredURLs.add( "https://subsetgames.com/forum/viewtopic.php?f=12&t=17102" );
ScraperCommand scraperCmd = new ScraperCommand();
BasicParser parser = new BasicParser(); CommandLine commandLine = new CommandLine( scraperCmd );
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;
try { 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.err.println( "Error parsing commandline: "+ e.getMessage() );
System.exit( 1 ); System.exit( 1 );
} }
if ( cmdline.hasOption( "h" ) ) { if ( commandLine.isUsageHelpRequested() ) {
HelpFormatter formatter = new HelpFormatter(); commandLine.usage( System.out );
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();
System.exit( 0 ); System.exit( 0 );
} }
ModDB modDB = new ModDB(); ModDB modDB = new ModDB();
try { try {
if ( cmdline.hasOption( "load-json" ) ) { if ( scraperCmd.jsonSrcFile != null ) {
log.info( "Loading json catalog..." ); log.info( "Loading json catalog..." );
File srcFile = new File( cmdline.getOptionValue( "load-json" ) ); File srcFile = scraperCmd.jsonSrcFile;
ModDB newDB = JacksonCatalogReader.parse( srcFile ); ModDB newDB = JacksonCatalogReader.parse( srcFile );
if ( newDB != null ) modDB = newDB; if ( newDB != null ) modDB = newDB;
} }
if ( cmdline.hasOption( "load-xml" ) ) { if ( scraperCmd.xmlSrcFile != null ) {
log.info( "Loading xml catalog..." ); log.info( "Loading xml catalog..." );
File srcFile = new File( cmdline.getOptionValue( "load-xml" ) ); File srcFile = scraperCmd.xmlSrcFile;
ModDB newDB = parseCatalogXML( srcFile ); ModDB newDB = parseCatalogXML( srcFile );
if ( newDB != null ) modDB = newDB; if ( newDB != null ) modDB = newDB;
} }
if ( cmdline.hasOption( "scrape" ) ) { if ( scraperCmd.scrapeDstFile != null ) {
log.info( "Scraping..." ); log.info( "Scraping..." );
File dstFile = new File( cmdline.getOptionValue( "scrape" ) ); File dstFile = scraperCmd.scrapeDstFile;
List<ModsInfo> data = scrape( modDB, MASTER_LIST_URL, ignoredURLs ); List<ModsInfo> data = scrape( modDB, MASTER_LIST_URL, ignoredURLs );
if ( data.size() > 0 ) writeXML( data, dstFile ); if ( data.size() > 0 ) writeXML( data, dstFile );
} }
if ( cmdline.hasOption( "dump-json" ) ) { if ( scraperCmd.jsonDstFile != null ) {
log.info( "Dumping json..." ); log.info( "Dumping json..." );
File dstFile = new File( cmdline.getOptionValue( "dump-json" ) ); File dstFile = scraperCmd.jsonDstFile;
List<ModsInfo> data = modDB.getCollatedModInfo(); List<ModsInfo> data = modDB.getCollatedModInfo();
if ( data.size() > 0 ) JacksonCatalogWriter.write( data, dstFile ); if ( data.size() > 0 ) JacksonCatalogWriter.write( data, dstFile );
} }
if ( cmdline.hasOption( "dump-xml" ) ) { if ( scraperCmd.xmlDstFile != null ) {
log.info( "Dumping xml..." ); log.info( "Dumping xml..." );
File dstFile = new File( cmdline.getOptionValue( "dump-xml" ) ); File dstFile = scraperCmd.xmlDstFile;
List<ModsInfo> data = modDB.getCollatedModInfo(); List<ModsInfo> data = modDB.getCollatedModInfo();
if ( data.size() > 0 ) writeXML( data, dstFile ); if ( data.size() > 0 ) writeXML( data, dstFile );
} }
if ( cmdline.hasOption( "hash-thread" ) ) { if ( scraperCmd.hashThreadURL != null ) {
log.info( "Hashing thread..." ); log.info( "Hashing thread..." );
String threadURL = cmdline.getOptionValue( "hash-thread" ); String threadURL = scraperCmd.hashThreadURL;
System.out.println( hashThread( threadURL ) ); System.out.println( hashThread( threadURL ) );
} }
if ( cmdline.hasOption( "first-post" ) ) { if ( scraperCmd.firstPostURL != null ) {
log.info( "Getting thread's first post..." ); log.info( "Getting thread's first post..." );
String threadURL = cmdline.getOptionValue( "first-post" ); String threadURL = scraperCmd.firstPostURL;
System.out.println( getFirstPost( threadURL ) ); System.out.println( getFirstPost( threadURL ) );
} }
} }
@ -221,7 +166,6 @@ public class ForumScraper {
} }
} }
/** /**
* Scrapes the forum for changed posts and returns info from updated mods. * Scrapes the forum for changed posts and returns info from updated mods.
*/ */
@ -244,7 +188,6 @@ public class ForumScraper {
return results; return results;
} }
/** /**
* Scrape the Master Mod List on the FTL forum. * Scrape the Master Mod List on the FTL forum.
* *
@ -400,14 +343,13 @@ public class ForumScraper {
return results; return results;
} }
/** /**
* Extracts the html content of the first post in a forum thread. * Extracts the html content of the first post in a forum thread.
*/ */
private static String getFirstPost( String url ) throws IOException { private static String getFirstPost( String url ) throws IOException {
String htmlSrc = fetchWebPage( url ); 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; Matcher m = null;
String postContent = ""; String postContent = "";
@ -434,7 +376,6 @@ public class ForumScraper {
return postContent; return postContent;
} }
/** /**
* Calculates an MD5 hash of the first post in a thread. * 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") ) ) ); return PackUtilities.calcStreamMD5( new ByteArrayInputStream( rawDesc.getBytes( Charset.forName("UTF-8") ) ) );
} }
/** /**
* Downloads a URL and returns the string content, decoded as UTF-8. * Downloads a URL and returns the string content, decoded as UTF-8.
*/ */
@ -492,7 +432,6 @@ public class ForumScraper {
return result; return result;
} }
/** /**
* Writes collated catalog entries to a file, as human-editable xml. * Writes collated catalog entries to a file, as human-editable xml.
*/ */
@ -563,7 +502,6 @@ public class ForumScraper {
return dst; return dst;
} }
/** /**
* Parses dumped xml and returns a new catalog. * 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. */ /** Information gleaned from scraping the forum. */
private static class ScrapeResult { private static class ScrapeResult {
public String threadURL = null; public String threadURL = null;