From 0f284896a362f0814a22b84117474be244b72882 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 25 Feb 2026 02:57:23 +0000 Subject: [PATCH 1/3] Initial plan From 5bbec67f376532694d4f78f0a94a40ca98ae8c5e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 25 Feb 2026 03:05:27 +0000 Subject: [PATCH 2/3] Add multi-mode support to qannotate CLI flow Co-authored-by: holmeso <7066552+holmeso@users.noreply.github.com> --- qannotate/src/au/edu/qimr/qannotate/Main.java | 131 ++++++++++++++---- .../src/au/edu/qimr/qannotate/Options.java | 49 ++++--- .../test/au/edu/qimr/qannotate/MainTest.java | 15 ++ .../au/edu/qimr/qannotate/OptionsTest.java | 11 ++ 4 files changed, 157 insertions(+), 49 deletions(-) create mode 100644 qannotate/test/au/edu/qimr/qannotate/MainTest.java diff --git a/qannotate/src/au/edu/qimr/qannotate/Main.java b/qannotate/src/au/edu/qimr/qannotate/Main.java index d27cc1659..dd1afce04 100644 --- a/qannotate/src/au/edu/qimr/qannotate/Main.java +++ b/qannotate/src/au/edu/qimr/qannotate/Main.java @@ -5,6 +5,10 @@ */ package au.edu.qimr.qannotate; +import java.io.File; +import java.util.ArrayList; +import java.util.List; + import org.qcmg.common.log.QLogger; import org.qcmg.common.log.QLoggerFactory; @@ -32,36 +36,19 @@ public static void main(final String[] args) { logger = QLoggerFactory.getLogger(Main.class, options.getLogFileName(), options.getLogLevel()); logger.logInitialExecutionStats(options.getPGName(), options.getVersion(), args); - checkOptions(options); - - if (options.getMode() == Options.MODE.dbsnp) { - new DbsnpMode(options); - } else if (options.getMode() == Options.MODE.germline) { - new GermlineMode(options); - } else if (options.getMode() == Options.MODE.snpeff) { - new SnpEffMode(options); - } else if (options.getMode() == Options.MODE.confidence) { - new ConfidenceMode(options); - } else if (options.getMode() == Options.MODE.ccm) { - new CCMMode(options); - } else if (options.getMode() == Options.MODE.vcf2maf) { - new Vcf2maf(options); - } else if (options.getMode() == Options.MODE.cadd) { - new CaddMode(options); - } else if (options.getMode() == Options.MODE.indelconfidence) { - new IndelConfidenceMode(options); - } else if (options.getMode() == Options.MODE.hom) { - new HomopolymersMode(options); - } else if (options.getMode() == Options.MODE.trf) { - new TandemRepeatMode(options); - } else if (options.getMode() == Options.MODE.make_valid) { - new MakeValidMode(options); - } else if (options.getMode() == Options.MODE.overlap) { - new OverlapMode(options); - } else if (options.getMode() == null) { + List modes = options.getModes(); + if (modes.isEmpty()) { throw new IllegalArgumentException("No mode was specified on the commandline - please add the \"-mode\" option"); + } + if (modes.size() > 1 && (modes.contains(Options.MODE.vcf2maf) || modes.contains(Options.MODE.vcf2maftmp))) { + throw new IllegalArgumentException("Multiple mode runs do not support vcf2maf/vcf2maftmp"); + } + + if (modes.size() == 1) { + checkOptions(options); + runMode(options); } else { - throw new IllegalArgumentException("No valid mode are specified on commandline - please run \"qannotate -help\" to see the list of available modes"); + runMultipleModes(args, modes, options.getInputFileName(), options.getOutputFileName()); } logger.logFinalExecutionStats(0); @@ -79,6 +66,94 @@ public static void main(final String[] args) { } } + static void runMultipleModes(String[] args, List modes, String inputFile, String outputFile) throws Exception { + String currentInput = inputFile; + List tempFiles = new ArrayList<>(); + for (int i = 0 ; i < modes.size() ; i++) { + boolean finalMode = i == modes.size() - 1; + String currentOutput; + if (finalMode) { + currentOutput = outputFile; + } else { + File tmp = File.createTempFile("qannotate-mode-" + i + "-", ".vcf"); + tmp.deleteOnExit(); + tempFiles.add(tmp); + currentOutput = tmp.getAbsolutePath(); + } + Options modeOptions = new Options(rewriteArgsForMode(args, modes.get(i), currentInput, currentOutput)); + checkOptions(modeOptions); + runMode(modeOptions); + currentInput = currentOutput; + } + for (File temp : tempFiles) { + if (temp.exists() && ! temp.delete()) { + temp.deleteOnExit(); + } + } + } + + static String[] rewriteArgsForMode(String[] args, Options.MODE mode, String inputFile, String outputFile) { + List newArgs = new ArrayList<>(); + for (int i = 0 ; i < args.length ; i++) { + String arg = args[i]; + if (isOptionWithValue(arg, "mode", "input", "i", "o", "output")) { + i++; + continue; + } + if (arg.startsWith("--mode=") || arg.startsWith("--input=") || arg.startsWith("-i=") + || arg.startsWith("--output=") || arg.startsWith("-o=") || arg.startsWith("-output=")) { + continue; + } + newArgs.add(arg); + } + newArgs.add("--mode"); + newArgs.add(mode.name()); + newArgs.add("-i"); + newArgs.add(inputFile); + newArgs.add("-o"); + newArgs.add(outputFile); + return newArgs.toArray(new String[0]); + } + + private static boolean isOptionWithValue(String arg, String... options) { + for (String option : options) { + if (arg.equals("-" + option) || arg.equals("--" + option)) { + return true; + } + } + return false; + } + + private static void runMode(Options options) throws Exception { + if (options.getMode() == Options.MODE.dbsnp) { + new DbsnpMode(options); + } else if (options.getMode() == Options.MODE.germline) { + new GermlineMode(options); + } else if (options.getMode() == Options.MODE.snpeff) { + new SnpEffMode(options); + } else if (options.getMode() == Options.MODE.confidence) { + new ConfidenceMode(options); + } else if (options.getMode() == Options.MODE.ccm) { + new CCMMode(options); + } else if (options.getMode() == Options.MODE.vcf2maf) { + new Vcf2maf(options); + } else if (options.getMode() == Options.MODE.cadd) { + new CaddMode(options); + } else if (options.getMode() == Options.MODE.indelconfidence) { + new IndelConfidenceMode(options); + } else if (options.getMode() == Options.MODE.hom) { + new HomopolymersMode(options); + } else if (options.getMode() == Options.MODE.trf) { + new TandemRepeatMode(options); + } else if (options.getMode() == Options.MODE.make_valid) { + new MakeValidMode(options); + } else if (options.getMode() == Options.MODE.overlap) { + new OverlapMode(options); + } else { + throw new IllegalArgumentException("No valid mode are specified on commandline - please run \"qannotate -help\" to see the list of available modes"); + } + } + /** * Checks the Options object to see if the minimal options (input, output, database) have been supplied. * Using the switch statements fall through process here. diff --git a/qannotate/src/au/edu/qimr/qannotate/Options.java b/qannotate/src/au/edu/qimr/qannotate/Options.java index 3cf00ec5e..e412ca1ac 100644 --- a/qannotate/src/au/edu/qimr/qannotate/Options.java +++ b/qannotate/src/au/edu/qimr/qannotate/Options.java @@ -11,6 +11,7 @@ import java.io.File; import java.io.IOException; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.Optional; @@ -35,6 +36,7 @@ public enum MODE {dbsnp, germline, snpeff, confidence, vcf2maf, cadd, indelconfi private final String commandLine; private final Options.MODE mode; + private final List modes; private final OptionParser parser; private final String outputFileName; private final String inputFileName; @@ -83,12 +85,13 @@ public Options(final String[] args) throws IOException { parser = new OptionParser(); OptionSet options = parseArgs(args); - if (options.has("mode")) { - String m = ((String) options.valueOf("mode")).toLowerCase(); - this.mode = MODE.valueOf(m); //already checked the validation of mode - } else { - this.mode = null; + List modeStrings = (List) options.valuesOf("mode"); + List modeList = new ArrayList<>(); + for (String m : modeStrings) { + modeList.add(MODE.valueOf(m.toLowerCase())); } + this.modes = Collections.unmodifiableList(modeList); + this.mode = modeList.isEmpty() ? null : modeList.get(0); if (options.has("h") || options.has("help")) { displayHelp(mode); @@ -113,7 +116,7 @@ public Options(final String[] args) throws IOException { List dbList = (List) options.valuesOf("d"); databaseFiles = dbList.toArray(new String[dbList.size()]); - if (MODE.dbsnp == mode && dbList.isEmpty()) { + if (modes.contains(MODE.dbsnp) && dbList.isEmpty()) { displayHelp(mode); System.exit(0); } @@ -193,43 +196,43 @@ public OptionSet parseArgs(final String[] args) { System.exit(0); } - Options.MODE mm = null; + List selectedModes = new ArrayList<>(); if (options.has("mode")) { - final String m = ((String) options.valueOf("mode")).toLowerCase(); - try { - mm = MODE.valueOf(m); - } catch (IllegalArgumentException | NullPointerException e) { - System.err.println("invalid mode specified: " + m); - System.exit(1); + for (String m : (List) options.valuesOf("mode")) { + try { + selectedModes.add(MODE.valueOf(m.toLowerCase())); + } catch (IllegalArgumentException | NullPointerException e) { + System.err.println("invalid mode specified: " + m); + System.exit(1); + } } } - if (mm == null) { + if (selectedModes.isEmpty()) { /* * used by nanno.Annotate */ parser.accepts("config", Messages.getMessage("NANNO_CONF_FILE_DESCRIPTION")).withRequiredArg().ofType(String.class).describedAs("config file"); } else { - if (mm.equals(MODE.confidence) || mm.equals(MODE.vcf2maf)) { + if (selectedModes.contains(MODE.confidence) || selectedModes.contains(MODE.vcf2maf)) { parser.accepts(test, Messages.getMessage("TUMOUR_SAMPLEID_DESCRIPTION")).withRequiredArg().ofType(String.class).describedAs("testSample"); parser.accepts(control, Messages.getMessage("NORMAL_SAMPLEID_DESCRIPTION")).withRequiredArg().ofType(String.class).describedAs("controlSample"); - } else { - parser.acceptsAll(asList("d", "database"), Messages.getMessage("DATABASE_DESCRIPTION")).withRequiredArg().ofType(String.class).describedAs("database file"); } + parser.acceptsAll(asList("d", "database"), Messages.getMessage("DATABASE_DESCRIPTION")).withRequiredArg().ofType(String.class).describedAs("database file"); - if (mm.equals(MODE.snpeff)) { + if (selectedModes.contains(MODE.snpeff)) { parser.accepts("config", Messages.getMessage("CONF_FILE_DESCRIPTION")).withRequiredArg().ofType(String.class).describedAs("config file"); parser.accepts("summaryFile", Messages.getMessage("SUMMARY_FILE_DESCRIPTION")).withRequiredArg().ofType(String.class).describedAs("stat output"); } - if (mm.equals(MODE.trf)) + if (selectedModes.contains(MODE.trf)) parser.accepts("buffer", "check TRF region on both sides of indel within this nominated size").withRequiredArg().ofType(Integer.class);//.describedAs("integer"); - if (mm.equals(MODE.cadd)) + if (selectedModes.contains(MODE.cadd)) parser.accepts("gap", "adjacent variants size").withRequiredArg().ofType(String.class).describedAs("gap size"); - if (mm.equals(MODE.vcf2maf)) { + if (selectedModes.contains(MODE.vcf2maf)) { parser.accepts("outdir", Messages.getMessage("MAF_OUTPUT_DIRECTORY_OPTION_DESCRIPTION")).withRequiredArg().ofType(String.class).describedAs("output file location"); parser.accepts("donor", Messages.getMessage("DONOR_ID_DESCRIPTION")).withRequiredArg().ofType(String.class).describedAs("donor id"); parser.accepts("center", "Genome sequencing center").withRequiredArg().ofType(String.class).describedAs("center"); @@ -338,6 +341,10 @@ public MODE getMode() { return mode; } + public List getModes() { + return modes; + } + private void displayHelp(MODE mode) throws IOException { String mess = Messages.getMessage("USAGE"); if (mode != null) { diff --git a/qannotate/test/au/edu/qimr/qannotate/MainTest.java b/qannotate/test/au/edu/qimr/qannotate/MainTest.java new file mode 100644 index 000000000..ecc78cfb3 --- /dev/null +++ b/qannotate/test/au/edu/qimr/qannotate/MainTest.java @@ -0,0 +1,15 @@ +package au.edu.qimr.qannotate; + +import static org.junit.Assert.assertArrayEquals; + +import org.junit.Test; + +public class MainTest { + + @Test + public void rewriteArgsForModeReplacesModeAndIo() { + String[] args = {"--mode", "dbsnp", "--mode", "germline", "-i", "in.vcf", "-o", "out.vcf", "-d", "db.vcf"}; + String[] rewritten = Main.rewriteArgsForMode(args, Options.MODE.germline, "tmpIn.vcf", "tmpOut.vcf"); + assertArrayEquals(new String[]{"-d", "db.vcf", "--mode", "germline", "-i", "tmpIn.vcf", "-o", "tmpOut.vcf"}, rewritten); + } +} diff --git a/qannotate/test/au/edu/qimr/qannotate/OptionsTest.java b/qannotate/test/au/edu/qimr/qannotate/OptionsTest.java index 1afc3dda2..8df6f1628 100644 --- a/qannotate/test/au/edu/qimr/qannotate/OptionsTest.java +++ b/qannotate/test/au/edu/qimr/qannotate/OptionsTest.java @@ -4,6 +4,7 @@ import java.io.File; import java.io.IOException; +import java.util.Arrays; import java.util.Optional; import org.junit.Rule; @@ -82,4 +83,14 @@ public void homReportWindow() throws IOException { assertEquals(Optional.of(42), o.getHomoplymersReportWindow()); } + @Test + public void multipleModes() throws IOException { + File i = testFolder.newFile(); + File o = testFolder.newFile(); + File d = testFolder.newFile(); + Options options = new Options(new String[]{"--mode", "dbsnp", "--mode", "germline", "-i", i.getAbsolutePath(), "-o", o.getAbsolutePath(), "-d", d.getAbsolutePath()}); + assertEquals(Arrays.asList(Options.MODE.dbsnp, Options.MODE.germline), options.getModes()); + assertEquals(Options.MODE.dbsnp, options.getMode()); + } + } From 52dc3ad4d3c7c05596475d9254c1c6a35044c41a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 25 Feb 2026 06:50:19 +0000 Subject: [PATCH 3/3] Handle per-mode database args in multi-mode qannotate runs Co-authored-by: holmeso <7066552+holmeso@users.noreply.github.com> --- qannotate/src/au/edu/qimr/qannotate/Main.java | 43 +++++++++++++++++-- .../test/au/edu/qimr/qannotate/MainTest.java | 15 ++++++- 2 files changed, 52 insertions(+), 6 deletions(-) diff --git a/qannotate/src/au/edu/qimr/qannotate/Main.java b/qannotate/src/au/edu/qimr/qannotate/Main.java index dd1afce04..a75e40476 100644 --- a/qannotate/src/au/edu/qimr/qannotate/Main.java +++ b/qannotate/src/au/edu/qimr/qannotate/Main.java @@ -69,6 +69,8 @@ public static void main(final String[] args) { static void runMultipleModes(String[] args, List modes, String inputFile, String outputFile) throws Exception { String currentInput = inputFile; List tempFiles = new ArrayList<>(); + List databaseArgs = extractDatabaseArgs(args); + int databaseIndex = 0; for (int i = 0 ; i < modes.size() ; i++) { boolean finalMode = i == modes.size() - 1; String currentOutput; @@ -80,7 +82,11 @@ static void runMultipleModes(String[] args, List modes, String inp tempFiles.add(tmp); currentOutput = tmp.getAbsolutePath(); } - Options modeOptions = new Options(rewriteArgsForMode(args, modes.get(i), currentInput, currentOutput)); + List modeDatabaseArgs = new ArrayList<>(); + if (modeUsesDatabase(modes.get(i)) && databaseIndex < databaseArgs.size()) { + modeDatabaseArgs.add(databaseArgs.get(databaseIndex++)); + } + Options modeOptions = new Options(rewriteArgsForMode(args, modes.get(i), currentInput, currentOutput, modeDatabaseArgs)); checkOptions(modeOptions); runMode(modeOptions); currentInput = currentOutput; @@ -92,16 +98,17 @@ static void runMultipleModes(String[] args, List modes, String inp } } - static String[] rewriteArgsForMode(String[] args, Options.MODE mode, String inputFile, String outputFile) { + static String[] rewriteArgsForMode(String[] args, Options.MODE mode, String inputFile, String outputFile, List databaseArgs) { List newArgs = new ArrayList<>(); for (int i = 0 ; i < args.length ; i++) { String arg = args[i]; - if (isOptionWithValue(arg, "mode", "input", "i", "o", "output")) { + if (isOptionWithValue(arg, "mode", "input", "i", "o", "output", "d", "database")) { i++; continue; } if (arg.startsWith("--mode=") || arg.startsWith("--input=") || arg.startsWith("-i=") - || arg.startsWith("--output=") || arg.startsWith("-o=") || arg.startsWith("-output=")) { + || arg.startsWith("--output=") || arg.startsWith("-o=") || arg.startsWith("-output=") + || arg.startsWith("--database=") || arg.startsWith("-d=")) { continue; } newArgs.add(arg); @@ -112,9 +119,37 @@ static String[] rewriteArgsForMode(String[] args, Options.MODE mode, String inpu newArgs.add(inputFile); newArgs.add("-o"); newArgs.add(outputFile); + for (String databaseArg : databaseArgs) { + newArgs.add("-d"); + newArgs.add(databaseArg); + } return newArgs.toArray(new String[0]); } + static List extractDatabaseArgs(String[] args) { + List databaseArgs = new ArrayList<>(); + for (int i = 0 ; i < args.length ; i++) { + String arg = args[i]; + if (arg.equals("-d") || arg.equals("--database")) { + if (i + 1 < args.length) { + databaseArgs.add(args[++i]); + } + } else if (arg.startsWith("-d=")) { + databaseArgs.add(arg.substring(3)); + } else if (arg.startsWith("--database=")) { + databaseArgs.add(arg.substring(11)); + } + } + return databaseArgs; + } + + private static boolean modeUsesDatabase(Options.MODE mode) { + return switch (mode) { + case dbsnp, germline, snpeff, cadd, trf, hom, overlap, make_valid, indelconfidence -> true; + default -> false; + }; + } + private static boolean isOptionWithValue(String arg, String... options) { for (String option : options) { if (arg.equals("-" + option) || arg.equals("--" + option)) { diff --git a/qannotate/test/au/edu/qimr/qannotate/MainTest.java b/qannotate/test/au/edu/qimr/qannotate/MainTest.java index ecc78cfb3..4d62d96ef 100644 --- a/qannotate/test/au/edu/qimr/qannotate/MainTest.java +++ b/qannotate/test/au/edu/qimr/qannotate/MainTest.java @@ -1,6 +1,10 @@ package au.edu.qimr.qannotate; import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; + +import java.util.Collections; +import java.util.List; import org.junit.Test; @@ -9,7 +13,14 @@ public class MainTest { @Test public void rewriteArgsForModeReplacesModeAndIo() { String[] args = {"--mode", "dbsnp", "--mode", "germline", "-i", "in.vcf", "-o", "out.vcf", "-d", "db.vcf"}; - String[] rewritten = Main.rewriteArgsForMode(args, Options.MODE.germline, "tmpIn.vcf", "tmpOut.vcf"); - assertArrayEquals(new String[]{"-d", "db.vcf", "--mode", "germline", "-i", "tmpIn.vcf", "-o", "tmpOut.vcf"}, rewritten); + String[] rewritten = Main.rewriteArgsForMode(args, Options.MODE.germline, "tmpIn.vcf", "tmpOut.vcf", Collections.singletonList("db2.vcf")); + assertArrayEquals(new String[]{"--mode", "germline", "-i", "tmpIn.vcf", "-o", "tmpOut.vcf", "-d", "db2.vcf"}, rewritten); + } + + @Test + public void extractDatabaseArgsInOrder() { + String[] args = {"--mode", "dbsnp", "-d", "db1.vcf", "--database=db2.vcf", "-d=db3.vcf"}; + List dbs = Main.extractDatabaseArgs(args); + assertEquals(List.of("db1.vcf", "db2.vcf", "db3.vcf"), dbs); } }