From bfcd970b2646e28fe3725076a3161e08eeaacfbf Mon Sep 17 00:00:00 2001 From: Chris Norman Date: Sat, 15 Apr 2017 11:39:53 -0400 Subject: [PATCH] Switch to Barclay command line parser. --- build.gradle | 246 ++-- .../analysis/CollectAlignmentSummaryMetrics.java | 42 +- .../analysis/CollectBaseDistributionByCycle.java | 14 +- .../java/picard/analysis/CollectGcBiasMetrics.java | 22 +- .../picard/analysis/CollectInsertSizeMetrics.java | 30 +- .../analysis/CollectJumpingLibraryMetrics.java | 18 +- .../picard/analysis/CollectMultipleMetrics.java | 50 +- .../java/picard/analysis/CollectOxoGMetrics.java | 43 +- .../analysis/CollectQualityYieldMetrics.java | 14 +- .../java/picard/analysis/CollectRawWgsMetrics.java | 26 +- .../java/picard/analysis/CollectRnaSeqMetrics.java | 24 +- .../java/picard/analysis/CollectRrbsMetrics.java | 55 +- .../java/picard/analysis/CollectWgsMetrics.java | 72 +- .../CollectWgsMetricsWithNonZeroCoverage.java | 10 +- src/main/java/picard/analysis/CompareMetrics.java | 8 +- .../java/picard/analysis/MeanQualityByCycle.java | 14 +- .../picard/analysis/QualityScoreDistribution.java | 16 +- .../java/picard/analysis/SinglePassSamProgram.java | 19 +- .../CollectSequencingArtifactMetrics.java | 45 +- .../artifacts/ConvertSequencingArtifactToOxoG.java | 12 +- .../picard/analysis/directed/CollectHsMetrics.java | 26 +- .../analysis/directed/CollectTargetedMetrics.java | 26 +- .../directed/CollectTargetedPcrMetrics.java | 12 +- .../CollectIndependentReplicateMetrics.java | 32 +- .../picard/cmdline/CommandLineParseException.java | 36 - .../java/picard/cmdline/CommandLineParser.java | 1331 -------------------- .../CommandLineParserDefinitionException.java | 36 - .../java/picard/cmdline/CommandLineProgram.java | 203 ++- .../cmdline/CommandLineProgramProperties.java | 46 - .../cmdline/CommandLineSyntaxTranslater.java | 33 + .../picard/cmdline/CreateHtmlDocForProgram.java | 39 - .../cmdline/CreateHtmlDocForStandardOptions.java | 45 - src/main/java/picard/cmdline/NestedOptions.java | 43 - src/main/java/picard/cmdline/Option.java | 100 -- .../java/picard/cmdline/PicardCommandLine.java | 6 +- .../java/picard/cmdline/PositionalArguments.java | 52 - .../IntervalArgumentCollection.java | 13 + .../OptionalReferenceArgumentCollection.java | 24 + .../ReferenceArgumentCollection.java | 13 + .../RequiredReferenceArgumentCollection.java | 22 + .../java/picard/cmdline/programgroups/Alpha.java | 2 +- .../java/picard/cmdline/programgroups/Fasta.java | 2 +- .../cmdline/programgroups/Fingerprinting.java | 2 +- .../picard/cmdline/programgroups/Illumina.java | 2 +- .../picard/cmdline/programgroups/Intervals.java | 2 +- .../java/picard/cmdline/programgroups/Metrics.java | 2 +- .../java/picard/cmdline/programgroups/None.java | 2 +- .../picard/cmdline/programgroups/SamOrBam.java | 2 +- .../java/picard/cmdline/programgroups/Testing.java | 2 +- .../picard/cmdline/programgroups/VcfOrBcf.java | 2 +- src/main/java/picard/fastq/BamToBfq.java | 34 +- .../java/picard/fingerprint/CheckFingerprint.java | 28 +- .../fingerprint/ClusterCrosscheckMetrics.java | 14 +- .../picard/fingerprint/CrosscheckFingerprints.java | 34 +- .../CrosscheckReadGroupFingerprints.java | 15 +- .../picard/illumina/CheckIlluminaDirectory.java | 23 +- .../CollectIlluminaBasecallingMetrics.java | 21 +- .../illumina/CollectIlluminaLaneMetrics.java | 20 +- .../picard/illumina/ExtractIlluminaBarcodes.java | 36 +- .../picard/illumina/IlluminaBasecallsToFastq.java | 51 +- .../picard/illumina/IlluminaBasecallsToSam.java | 67 +- .../java/picard/illumina/MarkIlluminaAdapters.java | 38 +- .../quality/CollectHiSeqXPfFailMetrics.java | 20 +- .../java/picard/reference/ExtractSequences.java | 22 +- src/main/java/picard/reference/NonNFastaSize.java | 14 +- src/main/java/picard/reference/NormalizeFasta.java | 16 +- src/main/java/picard/sam/AddCommentsToBam.java | 14 +- .../java/picard/sam/AddOrReplaceReadGroups.java | 36 +- src/main/java/picard/sam/BamIndexStats.java | 10 +- src/main/java/picard/sam/BuildBamIndex.java | 12 +- .../picard/sam/CalculateReadGroupChecksum.java | 12 +- src/main/java/picard/sam/CheckTerminatorBlock.java | 10 +- src/main/java/picard/sam/CleanSam.java | 12 +- src/main/java/picard/sam/CompareSAMs.java | 8 +- .../java/picard/sam/CreateSequenceDictionary.java | 52 +- src/main/java/picard/sam/DownsampleSam.java | 38 +- src/main/java/picard/sam/FastqToSam.java | 52 +- src/main/java/picard/sam/FilterSamReads.java | 24 +- src/main/java/picard/sam/FixMateInformation.java | 20 +- src/main/java/picard/sam/GatherBamFiles.java | 12 +- src/main/java/picard/sam/MergeBamAlignment.java | 85 +- src/main/java/picard/sam/MergeSamFiles.java | 24 +- .../picard/sam/PositionBasedDownsampleSam.java | 20 +- src/main/java/picard/sam/ReorderSam.java | 44 +- src/main/java/picard/sam/ReplaceSamHeader.java | 14 +- ...RevertOriginalBaseQualitiesAndAddMateCigar.java | 18 +- src/main/java/picard/sam/RevertSam.java | 36 +- src/main/java/picard/sam/SamFormatConverter.java | 12 +- src/main/java/picard/sam/SamToFastq.java | 46 +- src/main/java/picard/sam/SetNmAndUqTags.java | 6 +- src/main/java/picard/sam/SetNmMdAndUqTags.java | 22 +- src/main/java/picard/sam/SortSam.java | 14 +- src/main/java/picard/sam/SplitSamByLibrary.java | 12 +- src/main/java/picard/sam/ValidateSamFile.java | 30 +- src/main/java/picard/sam/ViewSam.java | 20 +- .../markduplicates/EstimateLibraryComplexity.java | 30 +- .../picard/sam/markduplicates/MarkDuplicates.java | 28 +- .../MarkDuplicatesWithMateCigar.java | 14 +- .../SimpleMarkDuplicatesWithMateCigar.java | 6 +- .../UmiAwareMarkDuplicatesWithMateCigar.java | 18 +- .../AbstractMarkDuplicatesCommandLineProgram.java | 26 +- ...ctOpticalDuplicateFinderCommandLineProgram.java | 5 +- src/main/java/picard/util/BaitDesigner.java | 48 +- src/main/java/picard/util/BedToIntervalList.java | 18 +- src/main/java/picard/util/FifoBuffer.java | 16 +- src/main/java/picard/util/IntervalListToBed.java | 16 +- src/main/java/picard/util/IntervalListTools.java | 38 +- .../java/picard/util/LiftOverIntervalList.java | 18 +- src/main/java/picard/util/MetricsDoclet.java | 167 --- src/main/java/picard/util/PropertyUtils.java | 36 + .../java/picard/util/ScatterIntervalsByNs.java | 45 +- src/main/java/picard/util/help/HelpConstants.java | 69 + .../util/help/PicardHelpDocWorkUnitHandler.java | 39 + .../java/picard/util/help/PicardHelpDoclet.java | 87 ++ .../vcf/AccumulateVariantCallingMetrics.java | 12 +- .../picard/vcf/CollectVariantCallingMetrics.java | 22 +- src/main/java/picard/vcf/FixVcfHeader.java | 18 +- src/main/java/picard/vcf/GatherVcfs.java | 12 +- src/main/java/picard/vcf/GenotypeConcordance.java | 34 +- src/main/java/picard/vcf/LiftoverVcf.java | 50 +- src/main/java/picard/vcf/MakeSitesOnlyVcf.java | 14 +- .../FindMendelianViolations.java | 34 +- src/main/java/picard/vcf/MergeVcfs.java | 14 +- src/main/java/picard/vcf/RenameSampleInVcf.java | 16 +- src/main/java/picard/vcf/SortVcf.java | 14 +- src/main/java/picard/vcf/SplitVcfs.java | 18 +- .../picard/vcf/UpdateVcfSequenceDictionary.java | 14 +- src/main/java/picard/vcf/VcfFormatConverter.java | 14 +- src/main/java/picard/vcf/VcfToIntervalList.java | 12 +- src/main/java/picard/vcf/filter/FilterVcf.java | 24 +- .../resources/picard/helpTemplates/common.html | 65 + .../helpTemplates/generic.index.template.html | 70 + .../picard/helpTemplates/generic.template.html | 188 +++ .../barclayParserProperties.properties | 4 + .../legacyParserProperties.properties | 4 + .../analysis/directed/CollectHsMetricsTest.java | 2 +- .../java/picard/cmdline/CommandLineParserTest.java | 1167 ----------------- .../CommandLineProgramStartupErrorLogTest.java | 11 +- .../java/picard/cmdline/PicardCommandLineTest.java | 45 + src/test/java/picard/sam/RevertSamTest.java | 34 + 140 files changed, 2333 insertions(+), 4409 deletions(-) delete mode 100644 src/main/java/picard/cmdline/CommandLineParseException.java delete mode 100644 src/main/java/picard/cmdline/CommandLineParser.java delete mode 100644 src/main/java/picard/cmdline/CommandLineParserDefinitionException.java delete mode 100644 src/main/java/picard/cmdline/CommandLineProgramProperties.java create mode 100644 src/main/java/picard/cmdline/CommandLineSyntaxTranslater.java delete mode 100644 src/main/java/picard/cmdline/CreateHtmlDocForProgram.java delete mode 100644 src/main/java/picard/cmdline/CreateHtmlDocForStandardOptions.java delete mode 100644 src/main/java/picard/cmdline/NestedOptions.java delete mode 100644 src/main/java/picard/cmdline/Option.java delete mode 100644 src/main/java/picard/cmdline/PositionalArguments.java create mode 100644 src/main/java/picard/cmdline/argumentcollections/IntervalArgumentCollection.java create mode 100644 src/main/java/picard/cmdline/argumentcollections/OptionalReferenceArgumentCollection.java create mode 100644 src/main/java/picard/cmdline/argumentcollections/ReferenceArgumentCollection.java create mode 100644 src/main/java/picard/cmdline/argumentcollections/RequiredReferenceArgumentCollection.java delete mode 100644 src/main/java/picard/util/MetricsDoclet.java create mode 100644 src/main/java/picard/util/PropertyUtils.java create mode 100644 src/main/java/picard/util/help/HelpConstants.java create mode 100644 src/main/java/picard/util/help/PicardHelpDocWorkUnitHandler.java create mode 100644 src/main/java/picard/util/help/PicardHelpDoclet.java create mode 100644 src/main/resources/picard/helpTemplates/common.html create mode 100644 src/main/resources/picard/helpTemplates/generic.index.template.html create mode 100644 src/main/resources/picard/helpTemplates/generic.template.html create mode 100644 src/main/resources/properties.templates/barclayParserProperties.properties create mode 100644 src/main/resources/properties.templates/legacyParserProperties.properties delete mode 100644 src/test/java/picard/cmdline/CommandLineParserTest.java diff --git a/build.gradle b/build.gradle index 0f17c7854..e042dd14e 100644 --- a/build.gradle +++ b/build.gradle @@ -49,14 +49,23 @@ jacoco { final htsjdkVersion = System.getProperty('htsjdk.version', '2.10.1') +// Get the jdk files we need to run javaDoc. We need to use these during compile, testCompile, +// test execution, and gatkDoc generation, but we don't want them as part of the runtime +// classpath and we don't want to redistribute them in the uber jar. +final javadocJDKFiles = files(((URLClassLoader) ToolProvider.getSystemToolClassLoader()).getURLs()) + dependencies { compile('com.intel.gkl:gkl:0.5.3') { exclude module: 'htsjdk' } compile 'com.google.guava:guava:15.0' compile 'com.github.samtools:htsjdk:' + htsjdkVersion - //tools dependency for doclet requires sdk devel - compile(files(((URLClassLoader) ToolProvider.getSystemToolClassLoader()).getURLs())) + compile 'org.broadinstitute:barclay:1.1.0' + + // javadoc utilities; compile/test only to prevent redistribution of sdk jars + compileOnly(javadocJDKFiles) + testCompile(javadocJDKFiles) + testCompile 'org.testng:testng:6.9.10' testCompile 'org.apache.commons:commons-lang3:3.6' } @@ -80,9 +89,29 @@ group = 'com.github.broadinstitute' defaultTasks 'all' -task all(dependsOn: ['jar', 'distZip', 'documentAll', 'shadowJar', 'currentJar']) +task all(dependsOn: ['jar', 'distZip', 'javadoc', 'shadowJar', 'barclayShadowJar', 'currentJar']) -jar { +// Source file names for the picard command line properties file. We select and include only one of +// these two files in each jar, renamed to "picardCmdLine.properties", depending on which parser we +// want enabled. +final String legacySourcePropertyFile = 'legacyParserProperties.properties' +final String barclaySourcePropertyFile = 'barclayParserProperties.properties' + +// Target name/location for the picard command line properties file; one of the above source +// files will be included at this path/location for runtime access +final String picardTargetPropertiesPath = 'picard' +final String picardTargetPropertyFile = 'picardCmdLine.properties' + +sourceSets { + // no need to include these in the jar files; the correct one is selected by the build to control Barclay + main { + resources { + exclude ('properties.templates/**') + } + } +} + +tasks.withType(Jar){ manifest { attributes 'Main-Class': 'picard.cmdline.PicardCommandLine', 'Implementation-Title': 'Picard', @@ -90,13 +119,56 @@ jar { 'Implementation-Version': version } } -// This is a hack to disable the java 8 default javadoc lint until we fix the html formatting -if (JavaVersion.current().isJava8Compatible()) { - tasks.withType(Javadoc) { - options.addStringOption('Xdoclint:none', '-quiet') + +jar { + from('src/main/resources/properties.templates') { + // for the default jar, we want the properties file that enables the picard parser + include legacySourcePropertyFile + into picardTargetPropertiesPath + rename { String fileName -> + fileName.replace(legacySourcePropertyFile, picardTargetPropertyFile) + } } } +tasks.withType(Javadoc) { + // do this for all javadoc tasks, including gatkDoc + options.addStringOption('Xdoclint:none') +} + +javadoc { + options.addStringOption('Xdoclint:none', '-quiet') +} + +// Generate Picard Online Doc +task picardDoc(type: Javadoc, dependsOn: ['cleanPicardDoc', classes]) { + final File picardDocDir = new File("build/docs/picarddoc") + doFirst { + // make sure the output folder exists or we can create it + if (!picardDocDir.exists() && !picardDocDir.mkdirs()) { + throw new GradleException(String.format("Failure creating folder (%s) for picardDocDir doc output in task (%s)", + picardDocDir.getAbsolutePath(), + it.name)); + } + } + source = sourceSets.main.allJava + + // The gatkDoc process instantiates any documented feature classes, so to run it we need the entire + // runtime classpath, as well as jdk javadoc files such as tools.jar, where com.sun.javadoc lives. + classpath = sourceSets.main.runtimeClasspath + javadocJDKFiles + options.docletpath = classpath.asType(List) + options.doclet = "picard.util.help.PicardHelpDoclet" + + outputs.dir(picardDocDir) + options.destinationDirectory(picardDocDir) + + options.addStringOption("settings-dir", "src/main/resources/picard/helpTemplates/"); + options.addStringOption("output-file-extension", "html") + options.addStringOption("absolute-version", getVersion()) + options.addStringOption("build-timestamp", new Date().format("dd-mm-yyyy hh:mm:ss")) + options.addStringOption("verbose") +} + task currentJar(type: Copy){ from shadowJar into new File(buildDir, "libs") @@ -105,6 +177,48 @@ task currentJar(type: Copy){ shadowJar { finalizedBy currentJar + from('src/main/resources/properties.templates') { + // for the default jar, we want the properties file that enables the picard parser + include legacySourcePropertyFile + into picardTargetPropertiesPath + rename { String fileName -> + fileName.replace(legacySourcePropertyFile, picardTargetPropertyFile) + } + } +} + +// Create picardBarclay.jar, which is a identical to picard.jar, but contains a .properties +// file that tells Picard to use the Barclay command line parser instead of the Picard +// command line parser. +// +task barclayShadowJar(type: com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar) { + configurations = [project.configurations.runtime] + from project.sourceSets.main.output + from('src/main/resources/properties.templates') { + // for the default jar, we want the properties file that enables the picard parser + include barclaySourcePropertyFile + into picardTargetPropertiesPath + rename { String fileName -> + fileName.replace(barclaySourcePropertyFile, picardTargetPropertyFile) + } + } + archiveName 'picardBarclay.jar' +} + +// Run the tests using the legacy parser only. Assumes that test code is written using +// legacy command line parser syntax. +task legacyTest(type: Test) + +// Run the tests using the Barclay command line parser (useLegacyParser=false), which requires +// conversion of test command lines from Picard-style command line syntax to Barclay-style syntax. +task barclayTest(type: Test) { + systemProperty 'picard.convertCommandLine', 'true' + systemProperty 'picard.useLegacyParser', 'false' +} + +// Run tests using both the legacy and barclay command line parsers. +test { + dependsOn barclayTest } tasks.withType(Test) { @@ -157,104 +271,12 @@ tasks.withType(Test) { } } -ext.htmlDir = new File("build/docs/html") -ext.htmlDirInc = new File(htmlDir, "_includes") -ext.commandClasses = ["picard.sam.AddCommentsToBam", "picard.sam.AddOrReplaceReadGroups", "picard.util.BaitDesigner", "picard.fastq.BamToBfq", - "picard.sam.BamIndexStats", "picard.util.BedToIntervalList", "picard.sam.BuildBamIndex", - "picard.sam.CalculateReadGroupChecksum", "picard.sam.CleanSam", "picard.analysis.CollectAlignmentSummaryMetrics", - "picard.analysis.CollectBaseDistributionByCycle", "picard.analysis.CollectGcBiasMetrics", "picard.illumina.quality.CollectHiSeqXPfFailMetrics", - "picard.analysis.directed.CollectHsMetrics", "picard.illumina.CollectIlluminaBasecallingMetrics", "picard.illumina.CollectIlluminaLaneMetrics", - "picard.analysis.CollectInsertSizeMetrics", "picard.analysis.CollectJumpingLibraryMetrics", "picard.analysis.CollectMultipleMetrics", - "picard.analysis.CollectOxoGMetrics", "picard.analysis.CollectQualityYieldMetrics", "picard.analysis.CollectRawWgsMetrics", - "picard.analysis.directed.CollectTargetedPcrMetrics", "picard.analysis.CollectRnaSeqMetrics", "picard.analysis.CollectRrbsMetrics", - "picard.analysis.artifacts.CollectSequencingArtifactMetrics", "picard.vcf.CollectVariantCallingMetrics", "picard.analysis.CollectWgsMetrics", - "picard.analysis.CollectWgsMetricsWithNonZeroCoverage", "picard.analysis.CompareMetrics", "picard.sam.CompareSAMs", - "picard.analysis.artifacts.ConvertSequencingArtifactToOxoG", "picard.sam.CreateSequenceDictionary", "picard.sam.DownsampleSam", - "picard.illumina.ExtractIlluminaBarcodes", "picard.sam.markduplicates.EstimateLibraryComplexity", "picard.sam.FastqToSam", "picard.util.FifoBuffer", - "picard.vcf.MendelianViolations.FindMendelianViolations","picard.fingerprint.CrosscheckFingerprints", "picard.fingerprint.ClusterCrosscheckMetrics", "picard.fingerprint.CheckFingerprint", - "picard.sam.FilterSamReads", "picard.vcf.filter.FilterVcf", "picard.sam.FixMateInformation", "picard.sam.GatherBamFiles", "picard.vcf.GatherVcfs", - "picard.vcf.GenotypeConcordance", "picard.illumina.IlluminaBasecallsToFastq", "picard.illumina.IlluminaBasecallsToSam", "picard.illumina.CheckIlluminaDirectory", - "picard.sam.CheckTerminatorBlock", "picard.util.IntervalListTools", "picard.util.LiftOverIntervalList", "picard.vcf.LiftoverVcf", "picard.vcf.MakeSitesOnlyVcf", - "picard.sam.markduplicates.MarkDuplicates", "picard.sam.markduplicates.MarkDuplicatesWithMateCigar", "picard.analysis.MeanQualityByCycle", - "picard.sam.MergeBamAlignment", "picard.sam.MergeSamFiles", "picard.vcf.MergeVcfs", "picard.reference.NormalizeFasta", "picard.sam.PositionBasedDownsampleSam", - "picard.reference.ExtractSequences", "picard.analysis.QualityScoreDistribution", "picard.vcf.RenameSampleInVcf", "picard.sam.ReorderSam", - "picard.sam.ReplaceSamHeader", "picard.sam.RevertSam", "picard.sam.RevertOriginalBaseQualitiesAndAddMateCigar", "picard.sam.SamFormatConverter", - "picard.sam.SamToFastq", "picard.util.ScatterIntervalsByNs", "picard.sam.SetNmMdAndUqTags", - "picard.sam.SortSam", "picard.vcf.SortVcf", "picard.sam.SplitSamByLibrary", "picard.sam.markduplicates.UmiAwareMarkDuplicatesWithMateCigar", - "picard.vcf.UpdateVcfSequenceDictionary", "picard.vcf.VcfFormatConverter", "picard.illumina.MarkIlluminaAdapters", "picard.vcf.SplitVcfs", - "picard.sam.ValidateSamFile", "picard.sam.ViewSam", "picard.vcf.VcfToIntervalList"] - -//generate documentation - -task documentAll(dependsOn: ['documentCommands', 'createMetricsDoc', 'documentStandardOptions']){ - doFirst{ - htmlDirInc.mkdirs() - } -} - -task documentCommands { - def previousDocTask = null - def usageFile = new File(htmlDirInc, "command-line-usage.html") - def sidebarFile = new File(htmlDirInc, "command-line-sidebar.html") - - commandClasses.each { mainClass -> - task "document_${mainClass}"(type: JavaExec) { - main ='picard.cmdline.CreateHtmlDocForProgram' - classpath = sourceSets.main.runtimeClasspath - args mainClass - def outputFile = new File(htmlDirInc, mainClass.substring(mainClass.lastIndexOf(".") + 1) + ".html") - doFirst { - htmlDirInc.mkdirs() - standardOutput = new FileOutputStream(outputFile) - } - outputs.file outputFile - - if (previousDocTask != null) delegate.dependsOn previousDocTask - previousDocTask = delegate - documentCommands.dependsOn(delegate) - doLast { - usageFile.append("{% include ${mainClass.substring(mainClass.lastIndexOf(".") + 1) + ".html"} %}") - usageFile.append(System.getProperty("line.separator")) - sidebarFile.append("
  • ${mainClass.substring(mainClass.lastIndexOf(".") + 1)}") - sidebarFile.append(System.getProperty("line.separator")) - } - } - } - outputs.dir htmlDirInc -} - -task documentStandardOptions(type: JavaExec) { - main = 'picard.cmdline.CreateHtmlDocForStandardOptions' - classpath = sourceSets.main.runtimeClasspath - def standardOptionsFile = new File(htmlDirInc, "standard-options.html") - doFirst{ - htmlDirInc.mkdirs() - standardOutput = new FileOutputStream(standardOptionsFile) - } - outputs.file standardOptionsFile - } - -task createMetricsDoc(dependsOn: classes, type: Javadoc) { - doFirst { - htmlDirInc.mkdirs() - } - - source = sourceSets.main.allJava - classpath = sourceSets.main.runtimeClasspath - outputs.dir(htmlDirInc) - - options.destinationDirectory = htmlDirInc - options.doclet = 'picard.util.MetricsDoclet' - options.docletpath = sourceSets.main.runtimeClasspath.asType(List) -} -//end generate documentation - task wrapper(type: Wrapper) { description = "Regenerate the gradle wrapper" gradleVersion = '3.1' } -task javadocJar(type: Jar, dependsOn: documentAll) { +task javadocJar(type: Jar) { classifier = 'javadoc' from 'build/docs/javadoc' } @@ -333,25 +355,3 @@ uploadArchives { System.out.println("Uploading version $version") } } - -//update static web docs -task copyJavadoc(dependsOn: 'javadoc', type: Copy) { - from 'build/docs/javadoc' - into "$htmlDir/javadoc" -} - -task updateGhPages(dependsOn: ['copyJavadoc', 'documentAll']){ - outputs.dir htmlDir -} - -updateGhPages.finalizedBy publishGhPages - -githubPages { - repoUri = 'git@github.com:broadinstitute/picard.git' - targetBranch = 'gh-pages' - deleteExistingFiles = false - pages { - from htmlDir - into '.' - } -} diff --git a/src/main/java/picard/analysis/CollectAlignmentSummaryMetrics.java b/src/main/java/picard/analysis/CollectAlignmentSummaryMetrics.java index fec087dd1..0d6184302 100644 --- a/src/main/java/picard/analysis/CollectAlignmentSummaryMetrics.java +++ b/src/main/java/picard/analysis/CollectAlignmentSummaryMetrics.java @@ -33,9 +33,10 @@ import htsjdk.samtools.util.CollectionUtil; import htsjdk.samtools.util.IOUtil; import htsjdk.samtools.util.Log; -import picard.cmdline.CommandLineProgramProperties; -import picard.cmdline.Option; +import org.broadinstitute.barclay.argparser.Argument; +import org.broadinstitute.barclay.argparser.CommandLineProgramProperties; import picard.cmdline.StandardOptionDefinitions; +import picard.cmdline.argumentcollections.ReferenceArgumentCollection; import picard.cmdline.programgroups.Metrics; import java.io.File; @@ -73,8 +74,8 @@ * @author Doug Voet (dvoet at broadinstitute dot org) */ @CommandLineProgramProperties( - usage = CollectAlignmentSummaryMetrics.USAGE_SUMMARY + CollectAlignmentSummaryMetrics.USAGE_DETAILS, - usageShort = CollectAlignmentSummaryMetrics.USAGE_SUMMARY, + summary = CollectAlignmentSummaryMetrics.USAGE_SUMMARY + CollectAlignmentSummaryMetrics.USAGE_DETAILS, + oneLineSummary = CollectAlignmentSummaryMetrics.USAGE_SUMMARY, programGroup = Metrics.class ) public class CollectAlignmentSummaryMetrics extends SinglePassSamProgram { @@ -101,25 +102,21 @@ private static final Log log = Log.getInstance(CollectAlignmentSummaryMetrics.class); - @Option(doc="Paired-end reads above this insert size will be considered chimeric along with inter-chromosomal pairs.") + @Argument(doc="Paired-end reads above this insert size will be considered chimeric along with inter-chromosomal pairs.") public int MAX_INSERT_SIZE = ChimeraUtil.DEFAULT_INSERT_SIZE_LIMIT; - @Option(doc="Paired-end reads that do not have this expected orientation will be considered chimeric.") + @Argument(doc="Paired-end reads that do not have this expected orientation will be considered chimeric.") public Set EXPECTED_PAIR_ORIENTATIONS = EnumSet.copyOf(ChimeraUtil.DEFAULT_EXPECTED_ORIENTATIONS); - @Option(doc="List of adapter sequences to use when processing the alignment metrics.") + @Argument(doc="List of adapter sequences to use when processing the alignment metrics.") public List ADAPTER_SEQUENCE = AdapterUtility.DEFAULT_ADAPTER_SEQUENCE; - @Option(shortName="LEVEL", doc="The level(s) at which to accumulate metrics.") + @Argument(shortName="LEVEL", doc="The level(s) at which to accumulate metrics.") public Set METRIC_ACCUMULATION_LEVEL = CollectionUtil.makeSet(MetricAccumulationLevel.ALL_READS); - @Option(shortName="BS", doc="Whether the SAM or BAM file consists of bisulfite sequenced reads.") + @Argument(shortName="BS", doc="Whether the SAM or BAM file consists of bisulfite sequenced reads.") public boolean IS_BISULFITE_SEQUENCED = false; - //overridden to make it visible on the commandline and to change the doc. - @Option(shortName = StandardOptionDefinitions.REFERENCE_SHORT_NAME, doc = "Reference sequence file. Note that while this argument isn't required, without it only a small subset of the metrics will be calculated. Note also that if a reference sequence is provided, it must be accompanied by a sequence dictionary.", optional = true, overridable = true) - public File REFERENCE_SEQUENCE = Defaults.REFERENCE_FASTA; - private AlignmentSummaryMetricsCollector collector; /** Required main method implementation. */ @@ -155,4 +152,23 @@ public static void main(final String[] argv) { file.write(OUTPUT); } + + //overridden to make it visible on the commandline and to change the doc. + @Override + protected ReferenceArgumentCollection makeReferenceArgumentCollection() { + return new CollectAlignmentRefArgCollection(); + } + + public static class CollectAlignmentRefArgCollection implements ReferenceArgumentCollection { + @Argument(shortName = StandardOptionDefinitions.REFERENCE_SHORT_NAME, + doc = "Reference sequence file. Note that while this argument isn't required, without it only a small subset of the metrics will be calculated. Note also that if a reference sequence is provided, it must be accompanied by a sequence dictionary.", + optional = true) + public File REFERENCE_SEQUENCE = Defaults.REFERENCE_FASTA; + + @Override + public File getReferenceFile() { + return REFERENCE_SEQUENCE; + }; + } + } diff --git a/src/main/java/picard/analysis/CollectBaseDistributionByCycle.java b/src/main/java/picard/analysis/CollectBaseDistributionByCycle.java index f60109b10..473c0e1df 100644 --- a/src/main/java/picard/analysis/CollectBaseDistributionByCycle.java +++ b/src/main/java/picard/analysis/CollectBaseDistributionByCycle.java @@ -37,17 +37,17 @@ import java.util.List; import htsjdk.samtools.util.StringUtil; +import org.broadinstitute.barclay.argparser.Argument; import picard.PicardException; -import picard.cmdline.CommandLineProgramProperties; -import picard.cmdline.Option; +import org.broadinstitute.barclay.argparser.CommandLineProgramProperties; import picard.cmdline.programgroups.Metrics; import picard.util.RExecutor; @CommandLineProgramProperties( - usage = CollectBaseDistributionByCycle.USAGE_SUMMARY + CollectBaseDistributionByCycle.USAGE_DETAILS, - usageShort = CollectBaseDistributionByCycle.USAGE_SUMMARY, + summary = CollectBaseDistributionByCycle.USAGE_SUMMARY + CollectBaseDistributionByCycle.USAGE_DETAILS, + oneLineSummary = CollectBaseDistributionByCycle.USAGE_SUMMARY, programGroup = Metrics.class ) public class CollectBaseDistributionByCycle extends SinglePassSamProgram { @@ -83,13 +83,13 @@ "" + "
    " ; - @Option(shortName = "CHART", doc = "A file (with .pdf extension) to write the chart to.") + @Argument(shortName = "CHART", doc = "A file (with .pdf extension) to write the chart to.") public File CHART_OUTPUT; - @Option(doc = "If set to true, calculate the base distribution over aligned reads only.") + @Argument(doc = "If set to true, calculate the base distribution over aligned reads only.") public boolean ALIGNED_READS_ONLY = false; - @Option(doc = "If set to true, calculate the base distribution over PF reads only (Illumina specific). PF reads are reads that passed the internal quality filters applied by Illumina sequencers.") + @Argument(doc = "If set to true, calculate the base distribution over PF reads only (Illumina specific). PF reads are reads that passed the internal quality filters applied by Illumina sequencers.") public boolean PF_READS_ONLY = false; private HistogramGenerator hist; diff --git a/src/main/java/picard/analysis/CollectGcBiasMetrics.java b/src/main/java/picard/analysis/CollectGcBiasMetrics.java index 4f75493d6..3cc96b98e 100644 --- a/src/main/java/picard/analysis/CollectGcBiasMetrics.java +++ b/src/main/java/picard/analysis/CollectGcBiasMetrics.java @@ -30,8 +30,8 @@ import htsjdk.samtools.reference.ReferenceSequence; import htsjdk.samtools.util.CollectionUtil; import htsjdk.samtools.util.IOUtil; -import picard.cmdline.CommandLineProgramProperties; -import picard.cmdline.Option; +import org.broadinstitute.barclay.argparser.Argument; +import org.broadinstitute.barclay.argparser.CommandLineProgramProperties; import picard.cmdline.programgroups.Metrics; import picard.metrics.GcBiasMetrics; import picard.util.RExecutor; @@ -52,8 +52,8 @@ * edited by Kylee Bergin */ @CommandLineProgramProperties( - usage = CollectGcBiasMetrics.USAGE_SUMMARY + CollectGcBiasMetrics.USAGE_DETAILS, - usageShort = CollectGcBiasMetrics.USAGE_SUMMARY, + summary = CollectGcBiasMetrics.USAGE_SUMMARY + CollectGcBiasMetrics.USAGE_DETAILS, + oneLineSummary = CollectGcBiasMetrics.USAGE_SUMMARY, programGroup = Metrics.class ) public class CollectGcBiasMetrics extends SinglePassSamProgram { @@ -115,25 +115,25 @@ // Usage and parameters - @Option(shortName = "CHART", doc = "The PDF file to render the chart to.") + @Argument(shortName = "CHART", doc = "The PDF file to render the chart to.") public File CHART_OUTPUT; - @Option(shortName = "S", doc = "The text file to write summary metrics to.") + @Argument(shortName = "S", doc = "The text file to write summary metrics to.") public File SUMMARY_OUTPUT; - @Option(shortName = "WINDOW_SIZE", doc = "The size of the scanning windows on the reference genome that are used to bin reads.") + @Argument(shortName = "WINDOW_SIZE", doc = "The size of the scanning windows on the reference genome that are used to bin reads.") public int SCAN_WINDOW_SIZE = 100; - @Option(shortName = "MGF", doc = "For summary metrics, exclude GC windows that include less than this fraction of the genome.") + @Argument(shortName = "MGF", doc = "For summary metrics, exclude GC windows that include less than this fraction of the genome.") public double MINIMUM_GENOME_FRACTION = 0.00001; - @Option(shortName = "BS", doc = "Whether the SAM or BAM file consists of bisulfite sequenced reads.") + @Argument(shortName = "BS", doc = "Whether the SAM or BAM file consists of bisulfite sequenced reads.") public boolean IS_BISULFITE_SEQUENCED = false; - @Option(shortName = "LEVEL", doc = "The level(s) at which to accumulate metrics.") + @Argument(shortName = "LEVEL", doc = "The level(s) at which to accumulate metrics.") public Set METRIC_ACCUMULATION_LEVEL = CollectionUtil.makeSet(MetricAccumulationLevel.ALL_READS); - @Option(shortName = "ALSO_IGNORE_DUPLICATES", doc = "Use to get additional results without duplicates. This option " + + @Argument(shortName = "ALSO_IGNORE_DUPLICATES", doc = "Use to get additional results without duplicates. This option " + "allows to gain two plots per level at the same time: one is the usual one and the other excludes duplicates.") public boolean ALSO_IGNORE_DUPLICATES = false; diff --git a/src/main/java/picard/analysis/CollectInsertSizeMetrics.java b/src/main/java/picard/analysis/CollectInsertSizeMetrics.java index dffea1ed3..430ccd8ba 100644 --- a/src/main/java/picard/analysis/CollectInsertSizeMetrics.java +++ b/src/main/java/picard/analysis/CollectInsertSizeMetrics.java @@ -31,10 +31,11 @@ import htsjdk.samtools.util.CollectionUtil; import htsjdk.samtools.util.IOUtil; import htsjdk.samtools.util.Log; +import org.broadinstitute.barclay.argparser.Argument; +import org.broadinstitute.barclay.argparser.CommandLineProgramProperties; +import org.broadinstitute.barclay.help.DocumentedFeature; import picard.PicardException; import picard.analysis.directed.InsertSizeMetricsCollector; -import picard.cmdline.CommandLineProgramProperties; -import picard.cmdline.Option; import picard.cmdline.programgroups.Metrics; import picard.util.RExecutor; @@ -48,10 +49,11 @@ * @author Doug Voet (dvoet at broadinstitute dot org) */ @CommandLineProgramProperties( - usage = CollectInsertSizeMetrics.USAGE_SUMMARY + CollectInsertSizeMetrics.USAGE_BRIEF, - usageShort = CollectInsertSizeMetrics.USAGE_BRIEF, + summary = CollectInsertSizeMetrics.USAGE_SUMMARY + CollectInsertSizeMetrics.USAGE_BRIEF, + oneLineSummary = CollectInsertSizeMetrics.USAGE_BRIEF, programGroup = Metrics.class ) +@DocumentedFeature public class CollectInsertSizeMetrics extends SinglePassSamProgram { static final String USAGE_BRIEF = "Collect metrics about the insert size distribution of a paired-end library."; static final String USAGE_SUMMARY = "

    This tool provides useful metrics for validating library construction including " + @@ -83,26 +85,26 @@ private static final Log log = Log.getInstance(CollectInsertSizeMetrics.class); protected static final String Histogram_R_SCRIPT = "picard/analysis/insertSizeHistogram.R"; - @Option(shortName="H", doc="File to write insert size Histogram chart to.") - public File Histogram_FILE; + @Argument(shortName="H", doc="File to write insert size Histogram chart to.") + public File HISTOGRAM_FILE; - @Option(doc="Generate mean, sd and plots by trimming the data down to MEDIAN + DEVIATIONS*MEDIAN_ABSOLUTE_DEVIATION. " + + @Argument(doc="Generate mean, sd and plots by trimming the data down to MEDIAN + DEVIATIONS*MEDIAN_ABSOLUTE_DEVIATION. " + "This is done because insert size data typically includes enough anomalous values from chimeras and other " + "artifacts to make the mean and sd grossly misleading regarding the real distribution.") public double DEVIATIONS = 10; - @Option(shortName="W", doc="Explicitly sets the Histogram width, overriding automatic truncation of Histogram tail. " + + @Argument(shortName="W", doc="Explicitly sets the Histogram width, overriding automatic truncation of Histogram tail. " + "Also, when calculating mean and standard deviation, only bins <= Histogram_WIDTH will be included.", optional=true) public Integer HISTOGRAM_WIDTH = null; - @Option(shortName="M", doc="When generating the Histogram, discard any data categories (out of FR, TANDEM, RF) that have fewer than this " + + @Argument(shortName="M", doc="When generating the Histogram, discard any data categories (out of FR, TANDEM, RF) that have fewer than this " + "percentage of overall reads. (Range: 0 to 1).") public float MINIMUM_PCT = 0.05f; - @Option(shortName="LEVEL", doc="The level(s) at which to accumulate metrics. ") + @Argument(shortName="LEVEL", doc="The level(s) at which to accumulate metrics. ") public Set METRIC_ACCUMULATION_LEVEL = CollectionUtil.makeSet(MetricAccumulationLevel.ALL_READS); - @Option(doc="If true, also include reads marked as duplicates in the insert size histogram.") + @Argument(doc="If true, also include reads marked as duplicates in the insert size histogram.") public boolean INCLUDE_DUPLICATES = false; // Calculates InsertSizeMetrics for all METRIC_ACCUMULATION_LEVELs provided @@ -134,7 +136,7 @@ public static void main(final String[] argv) { @Override protected void setup(final SAMFileHeader header, final File samFile) { IOUtil.assertFileIsWritable(OUTPUT); - IOUtil.assertFileIsWritable(Histogram_FILE); + IOUtil.assertFileIsWritable(HISTOGRAM_FILE); //Delegate actual collection to InsertSizeMetricCollector multiCollector = new InsertSizeMetricsCollector(METRIC_ACCUMULATION_LEVEL, header.getReadGroups(), @@ -166,13 +168,13 @@ public static void main(final String[] argv) { rResult = RExecutor.executeFromClasspath( Histogram_R_SCRIPT, OUTPUT.getAbsolutePath(), - Histogram_FILE.getAbsolutePath(), + HISTOGRAM_FILE.getAbsolutePath(), INPUT.getName()); } else { rResult = RExecutor.executeFromClasspath( Histogram_R_SCRIPT, OUTPUT.getAbsolutePath(), - Histogram_FILE.getAbsolutePath(), + HISTOGRAM_FILE.getAbsolutePath(), INPUT.getName(), String.valueOf(HISTOGRAM_WIDTH) ); //Histogram_WIDTH is passed because R automatically sets Histogram width to the last //bin that has data, which may be less than Histogram_WIDTH and confuse the user. diff --git a/src/main/java/picard/analysis/CollectJumpingLibraryMetrics.java b/src/main/java/picard/analysis/CollectJumpingLibraryMetrics.java index f7196322f..ca2093d81 100644 --- a/src/main/java/picard/analysis/CollectJumpingLibraryMetrics.java +++ b/src/main/java/picard/analysis/CollectJumpingLibraryMetrics.java @@ -35,10 +35,10 @@ import htsjdk.samtools.util.CloserUtil; import htsjdk.samtools.util.Histogram; import htsjdk.samtools.util.IOUtil; +import org.broadinstitute.barclay.argparser.Argument; +import org.broadinstitute.barclay.argparser.CommandLineProgramProperties; import picard.PicardException; import picard.cmdline.CommandLineProgram; -import picard.cmdline.CommandLineProgramProperties; -import picard.cmdline.Option; import picard.cmdline.StandardOptionDefinitions; import picard.cmdline.programgroups.Metrics; import picard.sam.DuplicationMetrics; @@ -55,8 +55,8 @@ * @author ktibbett@broadinstitute.org */ @CommandLineProgramProperties( - usage = CollectJumpingLibraryMetrics.USAGE_SUMMARY + CollectJumpingLibraryMetrics.USAGE_DETAILS, - usageShort = CollectJumpingLibraryMetrics.USAGE_SUMMARY, + summary = CollectJumpingLibraryMetrics.USAGE_SUMMARY + CollectJumpingLibraryMetrics.USAGE_DETAILS, + oneLineSummary = CollectJumpingLibraryMetrics.USAGE_SUMMARY, programGroup = Metrics.class ) public class CollectJumpingLibraryMetrics extends CommandLineProgram { @@ -86,16 +86,16 @@ // Usage and parameters - @Option(shortName = StandardOptionDefinitions.INPUT_SHORT_NAME, doc = "BAM file(s) of reads with duplicates marked") + @Argument(shortName = StandardOptionDefinitions.INPUT_SHORT_NAME, doc = "BAM file(s) of reads with duplicates marked") public List INPUT = new ArrayList(); - @Option(shortName = StandardOptionDefinitions.OUTPUT_SHORT_NAME, doc = "File to which metrics should be written") + @Argument(shortName = StandardOptionDefinitions.OUTPUT_SHORT_NAME, doc = "File to which metrics should be written") public File OUTPUT; - @Option(shortName = StandardOptionDefinitions.MINIMUM_MAPPING_QUALITY_SHORT_NAME, doc = "Mapping quality minimum cutoff") + @Argument(shortName = StandardOptionDefinitions.MINIMUM_MAPPING_QUALITY_SHORT_NAME, doc = "Mapping quality minimum cutoff") public Integer MINIMUM_MAPPING_QUALITY = 0; - @Option(shortName = "T", doc = "When calculating mean and stdev stop when the bins in the tail of the distribution " + + @Argument(shortName = "T", doc = "When calculating mean and stdev stop when the bins in the tail of the distribution " + "contain fewer than mode/TAIL_LIMIT items") public int TAIL_LIMIT = 10000; - @Option(doc = "Jumps greater than or equal to the greater of this value or 2 times the mode of the " + + @Argument(doc = "Jumps greater than or equal to the greater of this value or 2 times the mode of the " + "outward-facing pairs are considered chimeras") public int CHIMERA_KB_MIN = 100000; diff --git a/src/main/java/picard/analysis/CollectMultipleMetrics.java b/src/main/java/picard/analysis/CollectMultipleMetrics.java index 8757b6392..262a9e48b 100644 --- a/src/main/java/picard/analysis/CollectMultipleMetrics.java +++ b/src/main/java/picard/analysis/CollectMultipleMetrics.java @@ -26,11 +26,11 @@ import htsjdk.samtools.util.CollectionUtil; import htsjdk.samtools.util.Log; +import org.broadinstitute.barclay.argparser.Argument; +import org.broadinstitute.barclay.argparser.CommandLineProgramProperties; import picard.PicardException; import picard.analysis.artifacts.CollectSequencingArtifactMetrics; import picard.cmdline.CommandLineProgram; -import picard.cmdline.CommandLineProgramProperties; -import picard.cmdline.Option; import picard.cmdline.programgroups.Metrics; import picard.cmdline.StandardOptionDefinitions; @@ -46,8 +46,8 @@ */ @CommandLineProgramProperties( - usage = CollectMultipleMetrics.USAGE_SUMMARY + CollectMultipleMetrics.USAGE_DETAILS, - usageShort = CollectMultipleMetrics.USAGE_SUMMARY, + summary = CollectMultipleMetrics.USAGE_SUMMARY + CollectMultipleMetrics.USAGE_DETAILS, + oneLineSummary = CollectMultipleMetrics.USAGE_SUMMARY, programGroup = Metrics.class ) public class CollectMultipleMetrics extends CommandLineProgram { @@ -120,7 +120,7 @@ public SinglePassSamProgram makeInstance(final String outbase, final String oute // overrides program.METRIC_ACCUMULATION_LEVEL = metricAccumulationLevel; program.INPUT = input; - program.REFERENCE_SEQUENCE = reference; + program.setReferenceSequence(reference); return program; } @@ -138,13 +138,13 @@ public boolean supportsMetricAccumulationLevel() { public SinglePassSamProgram makeInstance(final String outbase, final String outext, final File input, final File reference, final Set metricAccumulationLevel, final File dbSnp, final File intervals) { final CollectInsertSizeMetrics program = new CollectInsertSizeMetrics(); program.OUTPUT = new File(outbase + ".insert_size_metrics" + outext); - program.Histogram_FILE = new File(outbase + ".insert_size_histogram.pdf"); + program.HISTOGRAM_FILE = new File(outbase + ".insert_size_histogram.pdf"); // Generally programs should not be accessing these directly but it might make things smoother // to just set them anyway. These are set here to make sure that in case of a the derived class // overrides program.METRIC_ACCUMULATION_LEVEL = metricAccumulationLevel; program.INPUT = input; - program.REFERENCE_SEQUENCE = reference; + program.setReferenceSequence(reference); return program; } @@ -167,7 +167,7 @@ public SinglePassSamProgram makeInstance(final String outbase, final String oute // to just set them anyway. These are set here to make sure that in case of a the derived class // overrides program.INPUT = input; - program.REFERENCE_SEQUENCE = reference; + program.setReferenceSequence(reference); return program; } @@ -190,7 +190,7 @@ public SinglePassSamProgram makeInstance(final String outbase, final String oute // to just set them anyway. These are set here to make sure that in case of a the derived class // overrides program.INPUT = input; - program.REFERENCE_SEQUENCE = reference; + program.setReferenceSequence(reference); return program; } @@ -213,7 +213,7 @@ public SinglePassSamProgram makeInstance(final String outbase, final String oute // to just set them anyway. These are set here to make sure that in case of a the derived class // overrides program.INPUT = input; - program.REFERENCE_SEQUENCE = reference; + program.setReferenceSequence(reference); return program; } @@ -244,7 +244,7 @@ public SinglePassSamProgram makeInstance(final String outbase, final String oute program.ALSO_IGNORE_DUPLICATES = false; //GC_Bias actually uses the class-level REFERENCE_SEQUENCE variable. - program.REFERENCE_SEQUENCE = reference; + program.setReferenceSequence(reference); return program; } @@ -268,7 +268,7 @@ public SinglePassSamProgram makeInstance(final String outbase, final String oute // overrides program.METRIC_ACCUMULATION_LEVEL = metricAccumulationLevel; program.INPUT = input; - program.REFERENCE_SEQUENCE = reference; + program.setReferenceSequence(reference); return program; } @@ -296,7 +296,7 @@ public SinglePassSamProgram makeInstance(final String outbase, final String oute // to just set them anyway. These are set here to make sure that in case of a the derived class // overrides program.INPUT = input; - program.REFERENCE_SEQUENCE = reference; + program.setReferenceSequence(reference); return program; } }, @@ -317,47 +317,47 @@ public SinglePassSamProgram makeInstance(final String outbase, final String oute // to just set them anyway. These are set here to make sure that in case of a the derived class // overrides program.INPUT = input; - program.REFERENCE_SEQUENCE = reference; + program.setReferenceSequence(reference); return program; } } } - @Option(shortName = StandardOptionDefinitions.INPUT_SHORT_NAME, doc = "Input SAM or BAM file.") + @Argument(shortName = StandardOptionDefinitions.INPUT_SHORT_NAME, doc = "Input SAM or BAM file.") public File INPUT; - @Option(doc = "If true (default), then the sort order in the header file will be ignored.", + @Argument(doc = "If true (default), then the sort order in the header file will be ignored.", shortName = StandardOptionDefinitions.ASSUME_SORTED_SHORT_NAME) public boolean ASSUME_SORTED = true; - @Option(doc = "Stop after processing N reads, mainly for debugging.") + @Argument(doc = "Stop after processing N reads, mainly for debugging.") public int STOP_AFTER = 0; - @Option(shortName = StandardOptionDefinitions.OUTPUT_SHORT_NAME, doc = "Base name of output files.") + @Argument(shortName = StandardOptionDefinitions.OUTPUT_SHORT_NAME, doc = "Base name of output files.") public String OUTPUT; // create the default accumulation level as a variable. We'll use this to init the command-line arg and for validation later. private final Set accumLevelDefault = CollectionUtil.makeSet(MetricAccumulationLevel.ALL_READS); - @Option(shortName="LEVEL", doc="The level(s) at which to accumulate metrics.") + @Argument(shortName="LEVEL", doc="The level(s) at which to accumulate metrics.") public Set METRIC_ACCUMULATION_LEVEL = new HashSet<>(accumLevelDefault); - @Option(shortName = "EXT", doc="Append the given file extension to all metric file names (ex. OUTPUT.insert_size_metrics.EXT). None if null", optional=true) + @Argument(shortName = "EXT", doc="Append the given file extension to all metric file names (ex. OUTPUT.insert_size_metrics.EXT). None if null", optional=true) public String FILE_EXTENSION = null; - @Option(doc = "Set of metrics programs to apply during the pass through the SAM file.") + @Argument(doc = "Set of metrics programs to apply during the pass through the SAM file.") public Set PROGRAM = new LinkedHashSet<>(Arrays.asList(Program.CollectAlignmentSummaryMetrics, Program.CollectBaseDistributionByCycle, Program.CollectInsertSizeMetrics, Program.MeanQualityByCycle, Program.QualityScoreDistribution)); - @Option(doc = "An optional list of intervals to restrict analysis to. Only pertains to some of the PROGRAMs. Programs whose stand-alone CLP does not " + + @Argument(doc = "An optional list of intervals to restrict analysis to. Only pertains to some of the PROGRAMs. Programs whose stand-alone CLP does not " + "have an INTERVALS argument will silently ignore this argument.", optional = true) public File INTERVALS; - @Option(doc = "VCF format dbSNP file, used to exclude regions around known polymorphisms from analysis " + + @Argument(doc = "VCF format dbSNP file, used to exclude regions around known polymorphisms from analysis " + "by some PROGRAMs; PROGRAMs whose CLP doesn't allow for this argument will quietly ignore it.", optional = true) public File DB_SNP; - @Option(shortName = "UNPAIRED", doc = "Include unpaired reads in CollectSequencingArtifactMetrics. If set to true then all paired reads will be included as well - " + + @Argument(shortName = "UNPAIRED", doc = "Include unpaired reads in CollectSequencingArtifactMetrics. If set to true then all paired reads will be included as well - " + "MINIMUM_INSERT_SIZE and MAXIMUM_INSERT_SIZE will be ignored in CollectSequencingArtifactMetrics.") public boolean INCLUDE_UNPAIRED = false; /** @@ -415,7 +415,7 @@ public int doWork() { // Generally programs should not be accessing these directly but it might make things smoother // to just set them anyway instance.INPUT = INPUT; - instance.REFERENCE_SEQUENCE = REFERENCE_SEQUENCE; + instance.setReferenceSequence(REFERENCE_SEQUENCE); instance.setDefaultHeaders(getDefaultHeaders()); diff --git a/src/main/java/picard/analysis/CollectOxoGMetrics.java b/src/main/java/picard/analysis/CollectOxoGMetrics.java index 468bb35bf..295856586 100644 --- a/src/main/java/picard/analysis/CollectOxoGMetrics.java +++ b/src/main/java/picard/analysis/CollectOxoGMetrics.java @@ -43,10 +43,10 @@ import htsjdk.samtools.util.SamLocusIterator; import htsjdk.samtools.util.SequenceUtil; import htsjdk.samtools.util.StringUtil; +import org.broadinstitute.barclay.argparser.Argument; import picard.PicardException; import picard.cmdline.CommandLineProgram; -import picard.cmdline.CommandLineProgramProperties; -import picard.cmdline.Option; +import org.broadinstitute.barclay.argparser.CommandLineProgramProperties; import picard.cmdline.StandardOptionDefinitions; import picard.cmdline.programgroups.Metrics; import picard.util.DbSnpBitSetUtil; @@ -66,8 +66,8 @@ * Class for trying to quantify the CpCG->CpCA error rate. */ @CommandLineProgramProperties( - usage = CollectOxoGMetrics.USAGE_SUMMARY + CollectOxoGMetrics.USAGE_DETAILS, - usageShort = CollectOxoGMetrics.USAGE_SUMMARY, + summary = CollectOxoGMetrics.USAGE_SUMMARY + CollectOxoGMetrics.USAGE_DETAILS, + oneLineSummary = CollectOxoGMetrics.USAGE_SUMMARY, programGroup = Metrics.class ) public class CollectOxoGMetrics extends CommandLineProgram { @@ -94,55 +94,51 @@ "" + "" + "


    "; - @Option(shortName = StandardOptionDefinitions.INPUT_SHORT_NAME, + @Argument(shortName = StandardOptionDefinitions.INPUT_SHORT_NAME, doc = "Input BAM file for analysis.") public File INPUT; - @Option(shortName = StandardOptionDefinitions.OUTPUT_SHORT_NAME, + @Argument(shortName = StandardOptionDefinitions.OUTPUT_SHORT_NAME, doc = "Location of output metrics file to write.") public File OUTPUT; - @Option(shortName = StandardOptionDefinitions.REFERENCE_SHORT_NAME, - doc = "Reference sequence to which BAM is aligned.") - public File REFERENCE_SEQUENCE; - - @Option(doc = "An optional list of intervals to restrict analysis to.", + @Argument(doc = "An optional list of intervals to restrict analysis to.", optional = true) public File INTERVALS; - @Option(doc = "VCF format dbSNP file, used to exclude regions around known polymorphisms from analysis.", + @Argument(doc = "VCF format dbSNP file, used to exclude regions around known polymorphisms from analysis.", optional = true) public File DB_SNP; - @Option(shortName = "Q", + @Argument(shortName = "Q", doc = "The minimum base quality score for a base to be included in analysis.") public int MINIMUM_QUALITY_SCORE = 20; - @Option(shortName = MINIMUM_MAPPING_QUALITY_SHORT_NAME, + @Argument(shortName = MINIMUM_MAPPING_QUALITY_SHORT_NAME, doc = "The minimum mapping quality score for a base to be included in analysis.") public int MINIMUM_MAPPING_QUALITY = 30; - @Option(shortName = "MIN_INS", + @Argument(shortName = "MIN_INS", doc = "The minimum insert size for a read to be included in analysis. Set of 0 to allow unpaired reads.") public int MINIMUM_INSERT_SIZE = 60; - @Option(shortName = "MAX_INS", + @Argument(shortName = "MAX_INS", doc = "The maximum insert size for a read to be included in analysis. Set of 0 to allow unpaired reads.") public int MAXIMUM_INSERT_SIZE = 600; - @Option(shortName = "NON_PF", doc = "Whether or not to include non-PF reads.") + @Argument(shortName = "NON_PF", doc = "Whether or not to include non-PF reads.") public boolean INCLUDE_NON_PF_READS = true; - @Option(doc = "When available, use original quality scores for filtering.") + @Argument(doc = "When available, use original quality scores for filtering.") public boolean USE_OQ = true; - @Option(doc = "The number of context bases to include on each side of the assayed G/C base.") + @Argument(doc = "The number of context bases to include on each side of the assayed G/C base.") public int CONTEXT_SIZE = 1; - @Option(doc = "The optional set of sequence contexts to restrict analysis to. If not supplied all contexts are analyzed.") + @Argument(doc = "The optional set of sequence contexts to restrict analysis to. If not supplied all contexts are analyzed.", optional = true) public Set CONTEXTS = new HashSet(); - @Option(doc = "For debugging purposes: stop after visiting this many sites with at least 1X coverage.") + @Argument(doc = "For debugging purposes: stop after visiting this many sites with at least 1X coverage.") public int STOP_AFTER = Integer.MAX_VALUE; private final Log log = Log.getInstance(CollectOxoGMetrics.class); @@ -219,6 +215,11 @@ public static void main(final String[] args) { } @Override + protected boolean requiresReference() { + return true; + } + + @Override protected String[] customCommandLineValidation() { final int size = 1 + 2 * CONTEXT_SIZE; final List messages = new ArrayList(); diff --git a/src/main/java/picard/analysis/CollectQualityYieldMetrics.java b/src/main/java/picard/analysis/CollectQualityYieldMetrics.java index e150da179..b71aef9c7 100644 --- a/src/main/java/picard/analysis/CollectQualityYieldMetrics.java +++ b/src/main/java/picard/analysis/CollectQualityYieldMetrics.java @@ -30,8 +30,8 @@ import htsjdk.samtools.metrics.MetricsFile; import htsjdk.samtools.reference.ReferenceSequence; import htsjdk.samtools.util.IOUtil; -import picard.cmdline.CommandLineProgramProperties; -import picard.cmdline.Option; +import org.broadinstitute.barclay.argparser.Argument; +import org.broadinstitute.barclay.argparser.CommandLineProgramProperties; import picard.cmdline.StandardOptionDefinitions; import picard.cmdline.programgroups.Metrics; @@ -45,8 +45,8 @@ @CommandLineProgramProperties( - usage = CollectQualityYieldMetrics.USAGE_SUMMARY + CollectQualityYieldMetrics.USAGE_DETAILS, - usageShort = CollectQualityYieldMetrics.USAGE_SUMMARY, + summary = CollectQualityYieldMetrics.USAGE_SUMMARY + CollectQualityYieldMetrics.USAGE_DETAILS, + oneLineSummary = CollectQualityYieldMetrics.USAGE_SUMMARY, programGroup = Metrics.class ) public class CollectQualityYieldMetrics extends SinglePassSamProgram { @@ -76,16 +76,16 @@ "the QualityYieldMetrics documentation for details and explanations of the output metrics." + "
    "; - @Option(shortName = StandardOptionDefinitions.USE_ORIGINAL_QUALITIES_SHORT_NAME, + @Argument(shortName = StandardOptionDefinitions.USE_ORIGINAL_QUALITIES_SHORT_NAME, doc = "If available in the OQ tag, use the original quality scores " + "as inputs instead of the quality scores in the QUAL field.") public boolean USE_ORIGINAL_QUALITIES = true; - @Option(doc="If true, include bases from secondary alignments in metrics. Setting to true may cause double-counting " + + @Argument(doc="If true, include bases from secondary alignments in metrics. Setting to true may cause double-counting " + "of bases if there are secondary alignments in the input file.") public boolean INCLUDE_SECONDARY_ALIGNMENTS = false; - @Option(doc="If true, include bases from supplemental alignments in metrics. Setting to true may cause double-counting " + + @Argument(doc="If true, include bases from supplemental alignments in metrics. Setting to true may cause double-counting " + "of bases if there are supplemental alignments in the input file.") public boolean INCLUDE_SUPPLEMENTAL_ALIGNMENTS = false; diff --git a/src/main/java/picard/analysis/CollectRawWgsMetrics.java b/src/main/java/picard/analysis/CollectRawWgsMetrics.java index c647e112f..760393ffd 100644 --- a/src/main/java/picard/analysis/CollectRawWgsMetrics.java +++ b/src/main/java/picard/analysis/CollectRawWgsMetrics.java @@ -26,12 +26,9 @@ import htsjdk.samtools.util.Histogram; import htsjdk.samtools.util.IntervalList; -import picard.cmdline.CommandLineProgramProperties; -import picard.cmdline.Option; +import org.broadinstitute.barclay.argparser.CommandLineProgramProperties; import picard.cmdline.programgroups.Metrics; -import static picard.cmdline.StandardOptionDefinitions.MINIMUM_MAPPING_QUALITY_SHORT_NAME; - /** * Computes a number of metrics that are useful for evaluating coverage and performance of whole genome sequencing * experiments, same implementation as CollectWgsMetrics, with different defaults: lacks baseQ and mappingQ filters @@ -40,8 +37,8 @@ * @author farjoun */ @CommandLineProgramProperties( - usage = CollectRawWgsMetrics.USAGE_SUMMARY + CollectRawWgsMetrics.USAGE_DETAILS, - usageShort = CollectRawWgsMetrics.USAGE_SUMMARY, + summary = CollectRawWgsMetrics.USAGE_SUMMARY + CollectRawWgsMetrics.USAGE_DETAILS, + oneLineSummary = CollectRawWgsMetrics.USAGE_SUMMARY, programGroup = Metrics.class ) public class CollectRawWgsMetrics extends CollectWgsMetrics{ @@ -75,17 +72,14 @@ "" + "the WgsMetrics documentation for detailed explanations of the output metrics." + "
    "; - @Option(shortName=MINIMUM_MAPPING_QUALITY_SHORT_NAME, doc="Minimum mapping quality for a read to contribute coverage.") - public int MINIMUM_MAPPING_QUALITY = 0; - - @Option(shortName="Q", doc="Minimum base quality for a base to contribute coverage.") - public int MINIMUM_BASE_QUALITY = 3; - @Option(shortName="CAP", doc="Treat bases with coverage exceeding this value as if they had coverage at this value.") - public int COVERAGE_CAP = 100000; - - @Option(doc="At positions with coverage exceeding this value, completely ignore reads that accumulate beyond this value (so that they will not be considered for PCT_EXC_CAPPED). Used to keep memory consumption in check, but could create bias if set too low") - public int LOCUS_ACCUMULATION_CAP = 200000; + public CollectRawWgsMetrics() { + //Override default values inherited from CollectWgsMetrics + MINIMUM_MAPPING_QUALITY = 0; + MINIMUM_BASE_QUALITY = 3; + COVERAGE_CAP = 100000; + LOCUS_ACCUMULATION_CAP = 200000; + } // rename the class so that in the metric file it is annotated differently. public static class RawWgsMetrics extends WgsMetrics { diff --git a/src/main/java/picard/analysis/CollectRnaSeqMetrics.java b/src/main/java/picard/analysis/CollectRnaSeqMetrics.java index a46e1cd6a..8e50340c1 100644 --- a/src/main/java/picard/analysis/CollectRnaSeqMetrics.java +++ b/src/main/java/picard/analysis/CollectRnaSeqMetrics.java @@ -34,12 +34,12 @@ import htsjdk.samtools.util.Interval; import htsjdk.samtools.util.Log; import htsjdk.samtools.util.OverlapDetector; +import org.broadinstitute.barclay.argparser.Argument; +import org.broadinstitute.barclay.argparser.CommandLineProgramProperties; import picard.PicardException; import picard.analysis.directed.RnaSeqMetricsCollector; import picard.annotation.Gene; import picard.annotation.GeneAnnotationReader; -import picard.cmdline.CommandLineProgramProperties; -import picard.cmdline.Option; import picard.cmdline.programgroups.Metrics; import picard.util.RExecutor; @@ -49,8 +49,8 @@ import java.util.Set; @CommandLineProgramProperties( - usage = CollectRnaSeqMetrics.USAGE_SUMMARY + CollectRnaSeqMetrics.USAGE_DETAILS, - usageShort = CollectRnaSeqMetrics.USAGE_SUMMARY, + summary = CollectRnaSeqMetrics.USAGE_SUMMARY + CollectRnaSeqMetrics.USAGE_DETAILS, + oneLineSummary = CollectRnaSeqMetrics.USAGE_SUMMARY, programGroup = Metrics.class ) public class CollectRnaSeqMetrics extends SinglePassSamProgram { @@ -102,32 +102,32 @@ private static final Log LOG = Log.getInstance(CollectRnaSeqMetrics.class); - @Option(doc="Gene annotations in refFlat form. Format described here: http://genome.ucsc.edu/goldenPath/gbdDescriptionsOld.html#RefFlat") + @Argument(doc="Gene annotations in refFlat form. Format described here: http://genome.ucsc.edu/goldenPath/gbdDescriptionsOld.html#RefFlat") public File REF_FLAT; - @Option(doc="Location of rRNA sequences in genome, in interval_list format. " + + @Argument(doc="Location of rRNA sequences in genome, in interval_list format. " + "If not specified no bases will be identified as being ribosomal. " + "Format described here:", optional = true) public File RIBOSOMAL_INTERVALS; - @Option(shortName = "STRAND", doc="For strand-specific library prep. " + + @Argument(shortName = "STRAND", doc="For strand-specific library prep. " + "For unpaired reads, use FIRST_READ_TRANSCRIPTION_STRAND if the reads are expected to be on the transcription strand.") public RnaSeqMetricsCollector.StrandSpecificity STRAND_SPECIFICITY; - @Option(doc="When calculating coverage based values (e.g. CV of coverage) only use transcripts of this length or greater.") + @Argument(doc="When calculating coverage based values (e.g. CV of coverage) only use transcripts of this length or greater.") public int MINIMUM_LENGTH = 500; - @Option(doc="The PDF file to write out a plot of normalized position vs. coverage.", shortName="CHART", optional = true) + @Argument(doc="The PDF file to write out a plot of normalized position vs. coverage.", shortName="CHART", optional = true) public File CHART_OUTPUT; - @Option(doc="If a read maps to a sequence specified with this option, all the bases in the read are counted as ignored bases. " + + @Argument(doc="If a read maps to a sequence specified with this option, all the bases in the read are counted as ignored bases. " + "These reads are not counted as ") public Set IGNORE_SEQUENCE = new HashSet(); - @Option(doc="This percentage of the length of a fragment must overlap one of the ribosomal intervals for a read or read pair to be considered rRNA.") + @Argument(doc="This percentage of the length of a fragment must overlap one of the ribosomal intervals for a read or read pair to be considered rRNA.") public double RRNA_FRAGMENT_PERCENTAGE = 0.8; - @Option(shortName="LEVEL", doc="The level(s) at which to accumulate metrics. ") + @Argument(shortName="LEVEL", doc="The level(s) at which to accumulate metrics. ") public Set METRIC_ACCUMULATION_LEVEL = CollectionUtil.makeSet(MetricAccumulationLevel.ALL_READS); private RnaSeqMetricsCollector collector; diff --git a/src/main/java/picard/analysis/CollectRrbsMetrics.java b/src/main/java/picard/analysis/CollectRrbsMetrics.java index d698b91f9..d1b728f5b 100644 --- a/src/main/java/picard/analysis/CollectRrbsMetrics.java +++ b/src/main/java/picard/analysis/CollectRrbsMetrics.java @@ -24,10 +24,7 @@ package picard.analysis; -import htsjdk.samtools.SAMFileHeader; -import htsjdk.samtools.SAMRecord; -import htsjdk.samtools.SamReader; -import htsjdk.samtools.SamReaderFactory; +import htsjdk.samtools.*; import htsjdk.samtools.metrics.MetricsFile; import htsjdk.samtools.reference.ReferenceSequence; import htsjdk.samtools.reference.ReferenceSequenceFileWalker; @@ -36,11 +33,12 @@ import htsjdk.samtools.util.IOUtil; import htsjdk.samtools.util.Log; import htsjdk.samtools.util.ProgressLogger; +import org.broadinstitute.barclay.argparser.Argument; +import org.broadinstitute.barclay.argparser.CommandLineProgramProperties; import picard.PicardException; import picard.cmdline.CommandLineProgram; -import picard.cmdline.CommandLineProgramProperties; -import picard.cmdline.Option; import picard.cmdline.StandardOptionDefinitions; +import picard.cmdline.argumentcollections.ReferenceArgumentCollection; import picard.cmdline.programgroups.Metrics; import picard.util.RExecutor; @@ -58,8 +56,8 @@ */ @CommandLineProgramProperties( - usage = CollectRrbsMetrics.USAGE_SUMMARY + CollectRrbsMetrics.USAGE_DETAILS, - usageShort = CollectRrbsMetrics.USAGE_SUMMARY, + summary = CollectRrbsMetrics.USAGE_SUMMARY + CollectRrbsMetrics.USAGE_DETAILS, + oneLineSummary = CollectRrbsMetrics.USAGE_SUMMARY, programGroup = Metrics.class ) public class CollectRrbsMetrics extends CommandLineProgram { @@ -99,26 +97,24 @@ private static final String R_SCRIPT = "picard/analysis/rrbsQc.R"; - @Option(doc = "The BAM or SAM file containing aligned reads. Must be coordinate sorted", shortName = StandardOptionDefinitions.INPUT_SHORT_NAME) + @Argument(doc = "The BAM or SAM file containing aligned reads. Must be coordinate sorted", shortName = StandardOptionDefinitions.INPUT_SHORT_NAME) public File INPUT; - @Option(doc = "Base name for output files", shortName = StandardOptionDefinitions.METRICS_FILE_SHORT_NAME) + @Argument(doc = "Base name for output files", shortName = StandardOptionDefinitions.METRICS_FILE_SHORT_NAME) public String METRICS_FILE_PREFIX; - @Option(doc = "The reference sequence fasta file", shortName = StandardOptionDefinitions.REFERENCE_SHORT_NAME) - public File REFERENCE; - @Option(doc = "Minimum read length") + @Argument(doc = "Minimum read length") public int MINIMUM_READ_LENGTH = 5; - @Option(doc = "Threshold for base quality of a C base before it is considered") + @Argument(doc = "Threshold for base quality of a C base before it is considered") public int C_QUALITY_THRESHOLD = 20; - @Option(doc = "Threshold for quality of a base next to a C before the C base is considered") + @Argument(doc = "Threshold for quality of a base next to a C before the C base is considered") public int NEXT_BASE_QUALITY_THRESHOLD = 10; - @Option(doc = "Maximum percentage of mismatches in a read for it to be considered, with a range of 0-1") + @Argument(doc = "Maximum percentage of mismatches in a read for it to be considered, with a range of 0-1") public double MAX_MISMATCH_RATE = 0.1; - @Option(doc = "Set of sequence names to consider, if not specified all sequences will be used", optional = true) + @Argument(doc = "Set of sequence names to consider, if not specified all sequences will be used", optional = true) public Set SEQUENCE_NAMES = new HashSet(); - @Option(shortName = StandardOptionDefinitions.ASSUME_SORTED_SHORT_NAME, + @Argument(shortName = StandardOptionDefinitions.ASSUME_SORTED_SHORT_NAME, doc = "If true, assume that the input file is coordinate sorted even if the header says otherwise.") public boolean ASSUME_SORTED = false; - @Option(shortName = "LEVEL", doc = "The level(s) at which to accumulate metrics. ") + @Argument(shortName = "LEVEL", doc = "The level(s) at which to accumulate metrics. ") public Set METRIC_ACCUMULATION_LEVEL = CollectionUtil.makeSet(MetricAccumulationLevel.ALL_READS); public static final String DETAIL_FILE_EXTENSION = "rrbs_detail_metrics"; @@ -127,6 +123,23 @@ private static final Log log = Log.getInstance(CollectRrbsMetrics.class); + // return a custom argument collection since this tool uses a (required) argument name + // of "REFERENCE", not "REFERENCE_SEQUENCE" + @Override + protected ReferenceArgumentCollection makeReferenceArgumentCollection() { + return new CollectRrbsMetricsReferenceArgumentCollection(); + } + + public static class CollectRrbsMetricsReferenceArgumentCollection implements ReferenceArgumentCollection { + @Argument(doc = "The reference sequence fasta file", shortName = StandardOptionDefinitions.REFERENCE_SHORT_NAME) + public File REFERENCE; + + @Override + public File getReferenceFile() { + return REFERENCE; + }; + } + public static void main(final String[] args) { new CollectRrbsMetrics().instanceMainWithExit(args); } @@ -146,7 +159,7 @@ protected int doWork() { throw new PicardException("The input file " + INPUT.getAbsolutePath() + " does not appear to be coordinate sorted"); } - final ReferenceSequenceFileWalker refWalker = new ReferenceSequenceFileWalker(REFERENCE); + final ReferenceSequenceFileWalker refWalker = new ReferenceSequenceFileWalker(REFERENCE_SEQUENCE); final ProgressLogger progressLogger = new ProgressLogger(log); final RrbsMetricsCollector metricsCollector = new RrbsMetricsCollector(METRIC_ACCUMULATION_LEVEL, samReader.getFileHeader().getReadGroups(), @@ -186,7 +199,7 @@ private boolean isSequenceFiltered(final String sequenceName) { private void assertIoFiles(final File summaryFile, final File detailsFile, final File plotsFile) { IOUtil.assertFileIsReadable(INPUT); - IOUtil.assertFileIsReadable(REFERENCE); + IOUtil.assertFileIsReadable(REFERENCE_SEQUENCE); IOUtil.assertFileIsWritable(summaryFile); IOUtil.assertFileIsWritable(detailsFile); IOUtil.assertFileIsWritable(plotsFile); diff --git a/src/main/java/picard/analysis/CollectWgsMetrics.java b/src/main/java/picard/analysis/CollectWgsMetrics.java index 2c7912a16..87d8b2c72 100644 --- a/src/main/java/picard/analysis/CollectWgsMetrics.java +++ b/src/main/java/picard/analysis/CollectWgsMetrics.java @@ -33,11 +33,13 @@ import htsjdk.samtools.reference.ReferenceSequence; import htsjdk.samtools.reference.ReferenceSequenceFileWalker; import htsjdk.samtools.util.*; +import org.broadinstitute.barclay.argparser.Argument; +import org.broadinstitute.barclay.argparser.ArgumentCollection; import picard.PicardException; import picard.cmdline.CommandLineProgram; -import picard.cmdline.CommandLineProgramProperties; -import picard.cmdline.Option; +import org.broadinstitute.barclay.argparser.CommandLineProgramProperties; import picard.cmdline.StandardOptionDefinitions; +import picard.cmdline.argumentcollections.IntervalArgumentCollection; import picard.cmdline.programgroups.Metrics; import picard.filter.CountingDuplicateFilter; import picard.filter.CountingFilter; @@ -60,8 +62,8 @@ * @author tfennell */ @CommandLineProgramProperties( - usage = CollectWgsMetrics.USAGE_SUMMARY + CollectWgsMetrics.USAGE_DETAILS, - usageShort = CollectWgsMetrics.USAGE_SUMMARY, + summary = CollectWgsMetrics.USAGE_SUMMARY + CollectWgsMetrics.USAGE_DETAILS, + oneLineSummary = CollectWgsMetrics.USAGE_SUMMARY, programGroup = Metrics.class ) public class CollectWgsMetrics extends CommandLineProgram { @@ -84,57 +86,76 @@ "
    " ; - @Option(shortName = StandardOptionDefinitions.INPUT_SHORT_NAME, doc = "Input SAM or BAM file.") + @Argument(shortName = StandardOptionDefinitions.INPUT_SHORT_NAME, doc = "Input SAM or BAM file.") public File INPUT; - @Option(shortName = StandardOptionDefinitions.OUTPUT_SHORT_NAME, doc = "Output metrics file.") + @Argument(shortName = StandardOptionDefinitions.OUTPUT_SHORT_NAME, doc = "Output metrics file.") public File OUTPUT; - @Option(shortName = StandardOptionDefinitions.REFERENCE_SHORT_NAME, doc = "The reference sequence fasta aligned to.") - public File REFERENCE_SEQUENCE; - - @Option(shortName = MINIMUM_MAPPING_QUALITY_SHORT_NAME, doc = "Minimum mapping quality for a read to contribute coverage.", overridable = true) + @Argument(shortName = MINIMUM_MAPPING_QUALITY_SHORT_NAME, doc = "Minimum mapping quality for a read to contribute coverage.") public int MINIMUM_MAPPING_QUALITY = 20; - @Option(shortName = "Q", doc = "Minimum base quality for a base to contribute coverage. N bases will be treated as having a base quality " + - "of negative infinity and will therefore be excluded from coverage regardless of the value of this parameter.", overridable = true) + @Argument(shortName = "Q", doc = "Minimum base quality for a base to contribute coverage. N bases will be treated as having a base quality " + + "of negative infinity and will therefore be excluded from coverage regardless of the value of this parameter.") public int MINIMUM_BASE_QUALITY = 20; - @Option(shortName = "CAP", doc = "Treat positions with coverage exceeding this value as if they had coverage at this value (but calculate the difference for PCT_EXC_CAPPED).", overridable = true) + @Argument(shortName = "CAP", doc = "Treat positions with coverage exceeding this value as if they had coverage at this value (but calculate the difference for PCT_EXC_CAPPED).") public int COVERAGE_CAP = 250; - @Option(doc="At positions with coverage exceeding this value, completely ignore reads that accumulate beyond this value (so that they will not be considered for PCT_EXC_CAPPED). Used to keep memory consumption in check, but could create bias if set too low", overridable = true) + @Argument(doc="At positions with coverage exceeding this value, completely ignore reads that accumulate beyond this value (so that they will not be considered for PCT_EXC_CAPPED). Used to keep memory consumption in check, but could create bias if set too low") public int LOCUS_ACCUMULATION_CAP = 100000; - @Option(doc = "For debugging purposes, stop after processing this many genomic bases.") + @Argument(doc = "For debugging purposes, stop after processing this many genomic bases.") public long STOP_AFTER = -1; - @Option(doc = "Determines whether to include the base quality histogram in the metrics file.") + @Argument(doc = "Determines whether to include the base quality histogram in the metrics file.") public boolean INCLUDE_BQ_HISTOGRAM = false; - @Option(doc="If true, count unpaired reads, and paired reads with one end unmapped") + @Argument(doc="If true, count unpaired reads, and paired reads with one end unmapped") public boolean COUNT_UNPAIRED = false; - @Option(doc="Sample Size used for Theoretical Het Sensitivity sampling. Default is 10000.", optional = true) + @Argument(doc="Sample Size used for Theoretical Het Sensitivity sampling. Default is 10000.", optional = true) public int SAMPLE_SIZE=10000; - @Option(doc = "If true, fast algorithm is used.") + @ArgumentCollection + protected IntervalArgumentCollection intervalArugmentCollection = makeIntervalArgumentCollection(); + + @Argument(doc = "If true, fast algorithm is used.") public boolean USE_FAST_ALGORITHM = false; - @Option(doc = "Average read length in the file. Default is 150.", optional = true) + @Argument(doc = "Average read length in the file. Default is 150.", optional = true) public int READ_LENGTH = 150; - @Option(doc = "An interval list file that contains the positions to restrict the assessment. Please note that " + - "all bases of reads that overlap these intervals will be considered, even if some of those bases extend beyond the boundaries of " + - "the interval. The ideal use case for this argument is to use it to restrict the calculation to a subset of (whole) contigs.", - optional = true, overridable = true) - public File INTERVALS = null; + protected File INTERVALS = null; private SAMFileHeader header = null; private final Log log = Log.getInstance(CollectWgsMetrics.class); private static final double LOG_ODDS_THRESHOLD = 3.0; + @Override + protected boolean requiresReference() { + return true; + } + + /** + * @return An interval argument collection to be used for this tool. Subclasses can override this + * to provide an argument collection with alternative arguments or argument annotations. + */ + protected IntervalArgumentCollection makeIntervalArgumentCollection() { + return new CollectWgsMetricsIntervalArgumentCollection(); + } + + public static class CollectWgsMetricsIntervalArgumentCollection implements IntervalArgumentCollection { + @Argument(doc = "An interval list file that contains the positions to restrict the assessment. Please note that " + + "all bases of reads that overlap these intervals will be considered, even if some of those bases extend beyond the boundaries of " + + "the interval. The ideal use case for this argument is to use it to restrict the calculation to a subset of (whole) contigs.", + optional = true) + public File INTERVALS; + + public File getIntervalFile() { return INTERVALS; }; + }; + /** Metrics for evaluating the performance of whole genome sequencing experiments. */ public static class WgsMetrics extends MergeableMetricBase { @@ -428,6 +449,7 @@ protected int doWork() { IOUtil.assertFileIsReadable(INPUT); IOUtil.assertFileIsWritable(OUTPUT); IOUtil.assertFileIsReadable(REFERENCE_SEQUENCE); + INTERVALS = intervalArugmentCollection.getIntervalFile(); if (INTERVALS != null) { IOUtil.assertFileIsReadable(INTERVALS); } diff --git a/src/main/java/picard/analysis/CollectWgsMetricsWithNonZeroCoverage.java b/src/main/java/picard/analysis/CollectWgsMetricsWithNonZeroCoverage.java index 17e013afd..06254bff4 100644 --- a/src/main/java/picard/analysis/CollectWgsMetricsWithNonZeroCoverage.java +++ b/src/main/java/picard/analysis/CollectWgsMetricsWithNonZeroCoverage.java @@ -33,8 +33,8 @@ import htsjdk.samtools.util.Log; import htsjdk.samtools.util.StringUtil; import picard.PicardException; -import picard.cmdline.CommandLineProgramProperties; -import picard.cmdline.Option; +import org.broadinstitute.barclay.argparser.Argument; +import org.broadinstitute.barclay.argparser.CommandLineProgramProperties; import picard.cmdline.programgroups.Alpha; import picard.filter.CountingFilter; import picard.filter.CountingPairedFilter; @@ -44,8 +44,8 @@ import java.util.List; @CommandLineProgramProperties( - usage = CollectWgsMetricsWithNonZeroCoverage.USAGE_SUMMARY + CollectWgsMetricsWithNonZeroCoverage.USAGE_DETAILS, - usageShort = CollectWgsMetricsWithNonZeroCoverage.USAGE_SUMMARY, + summary = CollectWgsMetricsWithNonZeroCoverage.USAGE_SUMMARY + CollectWgsMetricsWithNonZeroCoverage.USAGE_DETAILS, + oneLineSummary = CollectWgsMetricsWithNonZeroCoverage.USAGE_SUMMARY, programGroup = Alpha.class ) public class CollectWgsMetricsWithNonZeroCoverage extends CollectWgsMetrics { @@ -69,7 +69,7 @@ "WgsMetricsWithNonZeroCoverage documentation for detailed explanations of the output metrics." + "
    "; - @Option(shortName = "CHART", doc = "A file (with .pdf extension) to write the chart to.") + @Argument(shortName = "CHART", doc = "A file (with .pdf extension) to write the chart to.") public File CHART_OUTPUT; private final Log log = Log.getInstance(CollectWgsMetricsWithNonZeroCoverage.class); diff --git a/src/main/java/picard/analysis/CompareMetrics.java b/src/main/java/picard/analysis/CompareMetrics.java index 0ea545786..ba95f7de8 100644 --- a/src/main/java/picard/analysis/CompareMetrics.java +++ b/src/main/java/picard/analysis/CompareMetrics.java @@ -29,8 +29,8 @@ import htsjdk.samtools.util.Log; import picard.PicardException; import picard.cmdline.CommandLineProgram; -import picard.cmdline.CommandLineProgramProperties; -import picard.cmdline.PositionalArguments; +import org.broadinstitute.barclay.argparser.CommandLineProgramProperties; +import org.broadinstitute.barclay.argparser.PositionalArguments; import picard.cmdline.programgroups.Metrics; import java.io.File; @@ -41,8 +41,8 @@ * Compare two metrics files. */ @CommandLineProgramProperties( - usage = CompareMetrics.USAGE_SUMMARY + CompareMetrics.USAGE_DETAIL, - usageShort = CompareMetrics.USAGE_SUMMARY, + summary = CompareMetrics.USAGE_SUMMARY + CompareMetrics.USAGE_DETAIL, + oneLineSummary = CompareMetrics.USAGE_SUMMARY, programGroup = Metrics.class ) public class CompareMetrics extends CommandLineProgram { diff --git a/src/main/java/picard/analysis/MeanQualityByCycle.java b/src/main/java/picard/analysis/MeanQualityByCycle.java index db46ded00..ffa35a46e 100644 --- a/src/main/java/picard/analysis/MeanQualityByCycle.java +++ b/src/main/java/picard/analysis/MeanQualityByCycle.java @@ -33,9 +33,9 @@ import htsjdk.samtools.util.IOUtil; import htsjdk.samtools.util.Log; import htsjdk.samtools.util.StringUtil; +import org.broadinstitute.barclay.argparser.Argument; import picard.PicardException; -import picard.cmdline.CommandLineProgramProperties; -import picard.cmdline.Option; +import org.broadinstitute.barclay.argparser.CommandLineProgramProperties; import picard.cmdline.programgroups.Metrics; import picard.util.RExecutor; @@ -52,8 +52,8 @@ * @author Tim Fennell */ @CommandLineProgramProperties( - usage = MeanQualityByCycle.USAGE_SUMMARY + MeanQualityByCycle.USAGE_DETAILS, - usageShort = MeanQualityByCycle.USAGE_SUMMARY, + summary = MeanQualityByCycle.USAGE_SUMMARY + MeanQualityByCycle.USAGE_DETAILS, + oneLineSummary = MeanQualityByCycle.USAGE_SUMMARY, programGroup = Metrics.class ) public class MeanQualityByCycle extends SinglePassSamProgram { @@ -74,13 +74,13 @@ " CHART=mean_qual_by_cycle.pdf" + "" + "
    "; - @Option(shortName="CHART", doc="A file (with .pdf extension) to write the chart to.") + @Argument(shortName="CHART", doc="A file (with .pdf extension) to write the chart to.") public File CHART_OUTPUT; - @Option(doc="If set to true, calculate mean quality over aligned reads only.") + @Argument(doc="If set to true, calculate mean quality over aligned reads only.") public boolean ALIGNED_READS_ONLY = false; - @Option(doc="If set to true calculate mean quality over PF reads only.") + @Argument(doc="If set to true calculate mean quality over PF reads only.") public boolean PF_READS_ONLY = false; private final HistogramGenerator q = new HistogramGenerator(false); diff --git a/src/main/java/picard/analysis/QualityScoreDistribution.java b/src/main/java/picard/analysis/QualityScoreDistribution.java index 360f3726e..925684111 100644 --- a/src/main/java/picard/analysis/QualityScoreDistribution.java +++ b/src/main/java/picard/analysis/QualityScoreDistribution.java @@ -33,9 +33,9 @@ import htsjdk.samtools.util.IOUtil; import htsjdk.samtools.util.Log; import htsjdk.samtools.util.SequenceUtil; +import org.broadinstitute.barclay.argparser.Argument; +import org.broadinstitute.barclay.argparser.CommandLineProgramProperties; import picard.PicardException; -import picard.cmdline.CommandLineProgramProperties; -import picard.cmdline.Option; import picard.cmdline.programgroups.Metrics; import picard.util.RExecutor; @@ -48,8 +48,8 @@ * @author Tim Fennell */ @CommandLineProgramProperties( - usage = QualityScoreDistribution.USAGE_SUMMARY + QualityScoreDistribution.USAGE_DETAILS, - usageShort = QualityScoreDistribution.USAGE_SUMMARY, + summary = QualityScoreDistribution.USAGE_SUMMARY + QualityScoreDistribution.USAGE_DETAILS, + oneLineSummary = QualityScoreDistribution.USAGE_SUMMARY, programGroup = Metrics.class ) public class QualityScoreDistribution extends SinglePassSamProgram { @@ -74,16 +74,16 @@ " CHART=qual_score_dist.pdf" + "" + "
    "; - @Option(shortName="CHART", doc="A file (with .pdf extension) to write the chart to.") + @Argument(shortName="CHART", doc="A file (with .pdf extension) to write the chart to.") public File CHART_OUTPUT; - @Option(doc="If set to true calculate mean quality over aligned reads only.") + @Argument(doc="If set to true calculate mean quality over aligned reads only.") public boolean ALIGNED_READS_ONLY = false; - @Option(shortName="PF", doc="If set to true calculate mean quality over PF reads only.") + @Argument(shortName="PF", doc="If set to true calculate mean quality over PF reads only.") public boolean PF_READS_ONLY = false; - @Option(doc="If set to true, include quality for no-call bases in the distribution.") + @Argument(doc="If set to true, include quality for no-call bases in the distribution.") public boolean INCLUDE_NO_CALLS = false; private final long[] qCounts = new long[128]; diff --git a/src/main/java/picard/analysis/SinglePassSamProgram.java b/src/main/java/picard/analysis/SinglePassSamProgram.java index 70211881a..bb93ed994 100644 --- a/src/main/java/picard/analysis/SinglePassSamProgram.java +++ b/src/main/java/picard/analysis/SinglePassSamProgram.java @@ -36,9 +36,9 @@ import htsjdk.samtools.util.Log; import htsjdk.samtools.util.ProgressLogger; import htsjdk.samtools.util.SequenceUtil; +import org.broadinstitute.barclay.argparser.Argument; import picard.PicardException; import picard.cmdline.CommandLineProgram; -import picard.cmdline.Option; import picard.cmdline.StandardOptionDefinitions; import java.io.File; @@ -53,22 +53,29 @@ * @author Tim Fennell */ public abstract class SinglePassSamProgram extends CommandLineProgram { - @Option(shortName = StandardOptionDefinitions.INPUT_SHORT_NAME, doc = "Input SAM or BAM file.") + @Argument(shortName = StandardOptionDefinitions.INPUT_SHORT_NAME, doc = "Input SAM or BAM file.") public File INPUT; - @Option(shortName = "O", doc = "File to write the output to.") + @Argument(shortName = "O", doc = "File to write the output to.") public File OUTPUT; - @Option(doc = "If true (default), then the sort order in the header file will be ignored.", + @Argument(doc = "If true (default), then the sort order in the header file will be ignored.", shortName = StandardOptionDefinitions.ASSUME_SORTED_SHORT_NAME) public boolean ASSUME_SORTED = true; - @Option(doc = "Stop after processing N reads, mainly for debugging.") + @Argument(doc = "Stop after processing N reads, mainly for debugging.") public long STOP_AFTER = 0; private static final Log log = Log.getInstance(SinglePassSamProgram.class); /** + * Set the reference File. + */ + public void setReferenceSequence(final File referenceFile) { + REFERENCE_SEQUENCE = referenceFile; + }; + + /** * Final implementation of doWork() that checks and loads the input and optionally reference * sequence files and the runs the sublcass through the setup() acceptRead() and finish() steps. */ @@ -158,7 +165,7 @@ public static void makeItSo(final File input, } } - /** Can be overriden and set to false if the section of unmapped reads at the end of the file isn't needed. */ + /** Can be overridden and set to false if the section of unmapped reads at the end of the file isn't needed. */ protected boolean usesNoRefReads() { return true; } /** Should be implemented by subclasses to do one-time initialization work. */ diff --git a/src/main/java/picard/analysis/artifacts/CollectSequencingArtifactMetrics.java b/src/main/java/picard/analysis/artifacts/CollectSequencingArtifactMetrics.java index 60513f4c2..1ad0f9549 100644 --- a/src/main/java/picard/analysis/artifacts/CollectSequencingArtifactMetrics.java +++ b/src/main/java/picard/analysis/artifacts/CollectSequencingArtifactMetrics.java @@ -8,14 +8,14 @@ import htsjdk.samtools.metrics.MetricsFile; import htsjdk.samtools.reference.ReferenceSequence; import htsjdk.samtools.util.*; +import org.broadinstitute.barclay.argparser.Argument; import picard.PicardException; import picard.analysis.SinglePassSamProgram; import picard.analysis.artifacts.SequencingArtifactMetrics.BaitBiasDetailMetrics; import picard.analysis.artifacts.SequencingArtifactMetrics.BaitBiasSummaryMetrics; import picard.analysis.artifacts.SequencingArtifactMetrics.PreAdapterDetailMetrics; import picard.analysis.artifacts.SequencingArtifactMetrics.PreAdapterSummaryMetrics; -import picard.cmdline.CommandLineProgramProperties; -import picard.cmdline.Option; +import org.broadinstitute.barclay.argparser.CommandLineProgramProperties; import picard.cmdline.programgroups.Metrics; import picard.util.DbSnpBitSetUtil; import picard.util.VariantType; @@ -44,8 +44,8 @@ * */ @CommandLineProgramProperties( - usage = CollectSequencingArtifactMetrics.USAGE_SUMMARY + CollectSequencingArtifactMetrics.USAGE_DETAILS, - usageShort = CollectSequencingArtifactMetrics.USAGE_SUMMARY, + summary = CollectSequencingArtifactMetrics.USAGE_SUMMARY + CollectSequencingArtifactMetrics.USAGE_DETAILS, + oneLineSummary = CollectSequencingArtifactMetrics.USAGE_SUMMARY, programGroup = Metrics.class ) public class CollectSequencingArtifactMetrics extends SinglePassSamProgram { @@ -87,49 +87,49 @@ "for complete descriptions of the output metrics produced by this tool. "+ "
    " ; - @Option(doc = "An optional list of intervals to restrict analysis to.", optional = true) + @Argument(doc = "An optional list of intervals to restrict analysis to.", optional = true) public File INTERVALS; - @Option(doc = "VCF format dbSNP file, used to exclude regions around known polymorphisms from analysis.", optional = true) + @Argument(doc = "VCF format dbSNP file, used to exclude regions around known polymorphisms from analysis.", optional = true) public File DB_SNP; - @Option(shortName = "Q", doc = "The minimum base quality score for a base to be included in analysis.") + @Argument(shortName = "Q", doc = "The minimum base quality score for a base to be included in analysis.") public int MINIMUM_QUALITY_SCORE = 20; - @Option(shortName = MINIMUM_MAPPING_QUALITY_SHORT_NAME, doc = "The minimum mapping quality score for a base to be included in analysis.") + @Argument(shortName = MINIMUM_MAPPING_QUALITY_SHORT_NAME, doc = "The minimum mapping quality score for a base to be included in analysis.") public int MINIMUM_MAPPING_QUALITY = 30; - @Option(shortName = "MIN_INS", doc = "The minimum insert size for a read to be included in analysis.") + @Argument(shortName = "MIN_INS", doc = "The minimum insert size for a read to be included in analysis.") public int MINIMUM_INSERT_SIZE = 60; - @Option(shortName = "MAX_INS", doc = "The maximum insert size for a read to be included in analysis. Set to 0 to have no maximum.") + @Argument(shortName = "MAX_INS", doc = "The maximum insert size for a read to be included in analysis. Set to 0 to have no maximum.") public int MAXIMUM_INSERT_SIZE = 600; - @Option(shortName = "UNPAIRED", doc = "Include unpaired reads. If set to true then all paired reads will be included as well - " + + @Argument(shortName = "UNPAIRED", doc = "Include unpaired reads. If set to true then all paired reads will be included as well - " + "MINIMUM_INSERT_SIZE and MAXIMUM_INSERT_SIZE will be ignored.") public boolean INCLUDE_UNPAIRED = false; - @Option(shortName = "DUPES", doc = "Include duplicate reads. If set to true then all reads flagged as duplicates will be included as well.") + @Argument(shortName = "DUPES", doc = "Include duplicate reads. If set to true then all reads flagged as duplicates will be included as well.") public boolean INCLUDE_DUPLICATES = false; - @Option(shortName = "NON_PF", doc = "Whether or not to include non-PF reads.") + @Argument(shortName = "NON_PF", doc = "Whether or not to include non-PF reads.") public boolean INCLUDE_NON_PF_READS = false; - @Option(shortName = "TANDEM", doc = "Set to true if mate pairs are being sequenced from the same strand, " + + @Argument(shortName = "TANDEM", doc = "Set to true if mate pairs are being sequenced from the same strand, " + "i.e. they're expected to face the same direction.") public boolean TANDEM_READS = false; - @Option(doc = "When available, use original quality scores for filtering.") + @Argument(doc = "When available, use original quality scores for filtering.") public boolean USE_OQ = true; - @Option(doc = "The number of context bases to include on each side of the assayed base.") + @Argument(doc = "The number of context bases to include on each side of the assayed base.") public int CONTEXT_SIZE = 1; - @Option(doc = "If specified, only print results for these contexts in the detail metrics output. " + - "However, the summary metrics output will still take all contexts into consideration.") + @Argument(doc = "If specified, only print results for these contexts in the detail metrics output. " + + "However, the summary metrics output will still take all contexts into consideration.", optional = true) public Set CONTEXTS_TO_PRINT = new HashSet(); - @Option(shortName = "EXT", doc="Append the given file extension to all metric file names (ex. OUTPUT.pre_adapter_summary_metrics.EXT). None if null", optional=true) + @Argument(shortName = "EXT", doc="Append the given file extension to all metric file names (ex. OUTPUT.pre_adapter_summary_metrics.EXT). None if null", optional=true) public String FILE_EXTENSION = null; private static final String UNKNOWN_LIBRARY = "UnknownLibrary"; @@ -155,6 +155,11 @@ private static final Log log = Log.getInstance(CollectSequencingArtifactMetrics.class); @Override + protected boolean requiresReference() { + return true; + } + + @Override protected String[] customCommandLineValidation() { final List messages = new ArrayList(); @@ -172,8 +177,6 @@ messages.add("MAXIMUM_INSERT_SIZE cannot be less than MINIMUM_INSERT_SIZE unless set to 0"); } - if (REFERENCE_SEQUENCE == null) messages.add("REFERENCE_SEQUENCE must be provided."); - return messages.isEmpty() ? null : messages.toArray(new String[messages.size()]); } diff --git a/src/main/java/picard/analysis/artifacts/ConvertSequencingArtifactToOxoG.java b/src/main/java/picard/analysis/artifacts/ConvertSequencingArtifactToOxoG.java index e65c04a53..60f829a0a 100644 --- a/src/main/java/picard/analysis/artifacts/ConvertSequencingArtifactToOxoG.java +++ b/src/main/java/picard/analysis/artifacts/ConvertSequencingArtifactToOxoG.java @@ -4,8 +4,8 @@ import htsjdk.samtools.util.IOUtil; import htsjdk.samtools.util.SequenceUtil; import picard.cmdline.CommandLineProgram; -import picard.cmdline.CommandLineProgramProperties; -import picard.cmdline.Option; +import org.broadinstitute.barclay.argparser.CommandLineProgramProperties; +import org.broadinstitute.barclay.argparser.Argument; import picard.cmdline.StandardOptionDefinitions; import picard.cmdline.programgroups.Metrics; import picard.analysis.CollectOxoGMetrics.*; @@ -20,8 +20,8 @@ import java.util.Set; @CommandLineProgramProperties( - usage = ConvertSequencingArtifactToOxoG.USAGE_SUMMARY + ConvertSequencingArtifactToOxoG.USAGE_DETAILS, - usageShort = ConvertSequencingArtifactToOxoG.USAGE_SUMMARY, + summary = ConvertSequencingArtifactToOxoG.USAGE_SUMMARY + ConvertSequencingArtifactToOxoG.USAGE_DETAILS, + oneLineSummary = ConvertSequencingArtifactToOxoG.USAGE_SUMMARY, programGroup = Metrics.class ) public class ConvertSequencingArtifactToOxoG extends CommandLineProgram { @@ -50,11 +50,11 @@ "for detailed descriptions of the output metrics produced by this tool."+ "
    " ; - @Option(shortName = StandardOptionDefinitions.INPUT_SHORT_NAME, + @Argument(shortName = StandardOptionDefinitions.INPUT_SHORT_NAME, doc = "Basename of the input artifact metrics file (output by CollectSequencingArtifactMetrics)") public File INPUT_BASE; - @Option(shortName = StandardOptionDefinitions.OUTPUT_SHORT_NAME, + @Argument(shortName = StandardOptionDefinitions.OUTPUT_SHORT_NAME, doc = "Basename for output OxoG metrics. Defaults to same basename as input metrics", optional = true) public File OUTPUT_BASE; diff --git a/src/main/java/picard/analysis/directed/CollectHsMetrics.java b/src/main/java/picard/analysis/directed/CollectHsMetrics.java index 716de1d6c..12c2bb04e 100644 --- a/src/main/java/picard/analysis/directed/CollectHsMetrics.java +++ b/src/main/java/picard/analysis/directed/CollectHsMetrics.java @@ -28,9 +28,9 @@ import htsjdk.samtools.util.IOUtil; import htsjdk.samtools.util.IntervalList; import htsjdk.samtools.util.StringUtil; +import org.broadinstitute.barclay.argparser.Argument; +import org.broadinstitute.barclay.argparser.CommandLineProgramProperties; import picard.analysis.MetricAccumulationLevel; -import picard.cmdline.CommandLineProgramProperties; -import picard.cmdline.Option; import picard.cmdline.programgroups.Metrics; import java.io.File; @@ -47,8 +47,8 @@ * @author Tim Fennell */ @CommandLineProgramProperties( - usage = CollectHsMetrics.USAGE_SUMMARY + CollectHsMetrics.USAGE_DETAILS, - usageShort = CollectHsMetrics.USAGE_SUMMARY, + summary = CollectHsMetrics.USAGE_SUMMARY + CollectHsMetrics.USAGE_DETAILS, + oneLineSummary = CollectHsMetrics.USAGE_SUMMARY, programGroup = Metrics.class ) public class CollectHsMetrics extends CollectTargetedMetrics { @@ -90,20 +90,18 @@ "
    " ; - @Option(shortName = "BI", doc = "An interval list file that contains the locations of the baits used.", minElements=1) + @Argument(shortName = "BI", doc = "An interval list file that contains the locations of the baits used.", minElements=1) public List BAIT_INTERVALS; - @Option(shortName = "N", doc = "Bait set name. If not provided it is inferred from the filename of the bait intervals.", optional = true) + @Argument(shortName = "N", doc = "Bait set name. If not provided it is inferred from the filename of the bait intervals.", optional = true) public String BAIT_SET_NAME; - @Option(shortName = MINIMUM_MAPPING_QUALITY_SHORT_NAME, doc = "Minimum mapping quality for a read to contribute coverage.", overridable = true) - public int MINIMUM_MAPPING_QUALITY = 20; - - @Option(shortName = "Q", doc = "Minimum base quality for a base to contribute coverage.", overridable = true) - public int MINIMUM_BASE_QUALITY = 20; - - @Option(doc = "True if we are to clip overlapping reads, false otherwise.", optional=true, overridable = true) - public boolean CLIP_OVERLAPPING_READS = true; + public CollectHsMetrics() { + // Override inherited default values + MINIMUM_MAPPING_QUALITY = 20; + MINIMUM_BASE_QUALITY = 20; + CLIP_OVERLAPPING_READS = true; + } @Override protected IntervalList getProbeIntervals() { diff --git a/src/main/java/picard/analysis/directed/CollectTargetedMetrics.java b/src/main/java/picard/analysis/directed/CollectTargetedMetrics.java index 2ec89d370..eb17442e5 100644 --- a/src/main/java/picard/analysis/directed/CollectTargetedMetrics.java +++ b/src/main/java/picard/analysis/directed/CollectTargetedMetrics.java @@ -14,9 +14,9 @@ import htsjdk.samtools.util.Log; import htsjdk.samtools.util.ProgressLogger; import htsjdk.samtools.util.SequenceUtil; +import org.broadinstitute.barclay.argparser.Argument; import picard.analysis.MetricAccumulationLevel; import picard.cmdline.CommandLineProgram; -import picard.cmdline.Option; import picard.cmdline.StandardOptionDefinitions; import picard.metrics.MultilevelMetrics; @@ -60,42 +60,42 @@ protected abstract COLLECTOR makeCollector(final Set ac final int nearProbeDistance); - @Option(shortName = "TI", doc = "An interval list file that contains the locations of the targets.", minElements=1) + @Argument(shortName = "TI", doc = "An interval list file that contains the locations of the targets.", minElements=1) public List TARGET_INTERVALS; - @Option(shortName = StandardOptionDefinitions.INPUT_SHORT_NAME, doc = "An aligned SAM or BAM file.") + @Argument(shortName = StandardOptionDefinitions.INPUT_SHORT_NAME, doc = "An aligned SAM or BAM file.") public File INPUT; - @Option(shortName = StandardOptionDefinitions.OUTPUT_SHORT_NAME, doc = "The output file to write the metrics to.") + @Argument(shortName = StandardOptionDefinitions.OUTPUT_SHORT_NAME, doc = "The output file to write the metrics to.") public File OUTPUT; - @Option(shortName = "LEVEL", doc = "The level(s) at which to accumulate metrics.") + @Argument(shortName = "LEVEL", doc = "The level(s) at which to accumulate metrics.") public Set METRIC_ACCUMULATION_LEVEL = CollectionUtil.makeSet(MetricAccumulationLevel.ALL_READS); - @Option(optional = true, doc = "An optional file to output per target coverage information to.") + @Argument(optional = true, doc = "An optional file to output per target coverage information to.") public File PER_TARGET_COVERAGE; - @Option(optional = true, doc = "An optional file to output per base coverage information to. The per-base file contains " + + @Argument(optional = true, doc = "An optional file to output per base coverage information to. The per-base file contains " + "one line per target base and can grow very large. It is not recommended for use with large target sets.") public File PER_BASE_COVERAGE; - @Option(optional = true, doc= "The maximum distance between a read and the nearest probe/bait/amplicon for the read to be " + + @Argument(optional = true, doc= "The maximum distance between a read and the nearest probe/bait/amplicon for the read to be " + "considered 'near probe' and included in percent selected.") public int NEAR_DISTANCE = TargetedPcrMetricsCollector.NEAR_PROBE_DISTANCE_DEFAULT; - @Option(shortName = MINIMUM_MAPPING_QUALITY_SHORT_NAME, doc = "Minimum mapping quality for a read to contribute coverage.", overridable = true) + @Argument(shortName = MINIMUM_MAPPING_QUALITY_SHORT_NAME, doc = "Minimum mapping quality for a read to contribute coverage.") public int MINIMUM_MAPPING_QUALITY = 1; - @Option(shortName = "Q", doc = "Minimum base quality for a base to contribute coverage.", overridable = true) + @Argument(shortName = "Q", doc = "Minimum base quality for a base to contribute coverage.") public int MINIMUM_BASE_QUALITY = 0; - @Option(doc = "True if we are to clip overlapping reads, false otherwise.", optional=true, overridable = true) + @Argument(doc = "True if we are to clip overlapping reads, false otherwise.", optional=true) public boolean CLIP_OVERLAPPING_READS = false; - @Option(shortName = "covMax", doc = "Parameter to set a max coverage limit for Theoretical Sensitivity calculations. Default is 200.", optional = true) + @Argument(shortName = "covMax", doc = "Parameter to set a max coverage limit for Theoretical Sensitivity calculations. Default is 200.", optional = true) public int COVERAGE_CAP = 200; - @Option(doc="Sample Size used for Theoretical Het Sensitivity sampling. Default is 10000.", optional = true) + @Argument(doc="Sample Size used for Theoretical Het Sensitivity sampling. Default is 10000.", optional = true) public int SAMPLE_SIZE=10000; /** diff --git a/src/main/java/picard/analysis/directed/CollectTargetedPcrMetrics.java b/src/main/java/picard/analysis/directed/CollectTargetedPcrMetrics.java index 8c5c7c03d..0a83aad26 100644 --- a/src/main/java/picard/analysis/directed/CollectTargetedPcrMetrics.java +++ b/src/main/java/picard/analysis/directed/CollectTargetedPcrMetrics.java @@ -3,9 +3,9 @@ import htsjdk.samtools.SAMReadGroupRecord; import htsjdk.samtools.reference.ReferenceSequenceFile; import htsjdk.samtools.util.IntervalList; +import org.broadinstitute.barclay.argparser.Argument; +import org.broadinstitute.barclay.argparser.CommandLineProgramProperties; import picard.analysis.MetricAccumulationLevel; -import picard.cmdline.CommandLineProgramProperties; -import picard.cmdline.Option; import picard.cmdline.programgroups.Metrics; import java.io.File; @@ -17,8 +17,8 @@ * more information */ @CommandLineProgramProperties( - usage = CollectTargetedPcrMetrics.USAGE_SUMMARY + CollectTargetedPcrMetrics.USAGE_DETAILS, - usageShort = CollectTargetedPcrMetrics.USAGE_SUMMARY, + summary = CollectTargetedPcrMetrics.USAGE_SUMMARY + CollectTargetedPcrMetrics.USAGE_DETAILS, + oneLineSummary = CollectTargetedPcrMetrics.USAGE_SUMMARY, programGroup = Metrics.class ) public class CollectTargetedPcrMetrics extends CollectTargetedMetrics { @@ -48,10 +48,10 @@ "TargetedPcrMetrics " + "for detailed explanations of the output metrics produced by this tool." + "
    "; - @Option(shortName = "AI", doc = "An interval list file that contains the locations of the baits used.") + @Argument(shortName = "AI", doc = "An interval list file that contains the locations of the baits used.") public File AMPLICON_INTERVALS; - @Option(shortName = "N", doc = "Custom amplicon set name. If not provided it is inferred from the filename of the AMPLICON_INTERVALS intervals.", optional = true) + @Argument(shortName = "N", doc = "Custom amplicon set name. If not provided it is inferred from the filename of the AMPLICON_INTERVALS intervals.", optional = true) public String CUSTOM_AMPLICON_SET_NAME; /** diff --git a/src/main/java/picard/analysis/replicates/CollectIndependentReplicateMetrics.java b/src/main/java/picard/analysis/replicates/CollectIndependentReplicateMetrics.java index 59322a3ce..08317c2e5 100644 --- a/src/main/java/picard/analysis/replicates/CollectIndependentReplicateMetrics.java +++ b/src/main/java/picard/analysis/replicates/CollectIndependentReplicateMetrics.java @@ -57,9 +57,9 @@ import htsjdk.variant.vcf.VCFContigHeaderLine; import htsjdk.variant.vcf.VCFFileReader; import htsjdk.variant.vcf.VCFHeader; +import org.broadinstitute.barclay.argparser.Argument; import picard.cmdline.CommandLineProgram; -import picard.cmdline.CommandLineProgramProperties; -import picard.cmdline.Option; +import org.broadinstitute.barclay.argparser.CommandLineProgramProperties; import picard.cmdline.StandardOptionDefinitions; import picard.cmdline.programgroups.Alpha; import picard.filter.CountingPairedFilter; @@ -102,50 +102,50 @@ */ @CommandLineProgramProperties( - usage = "Estimates the rate of independent replication rate of reads within a bam. \n" + + summary = "Estimates the rate of independent replication rate of reads within a bam. \n" + "That is, it estimates the fraction of the reads which would be marked as duplicates but " + "are actually biological replicates, independent observations of the data. ", - usageShort = "Estimates the rate of independent replication of reads within a bam.", + oneLineSummary = "Estimates the rate of independent replication of reads within a bam.", programGroup = Alpha.class ) public class CollectIndependentReplicateMetrics extends CommandLineProgram { private static final int DOUBLETON_SIZE = 2, TRIPLETON_SIZE = 3; - @Option(shortName = StandardOptionDefinitions.INPUT_SHORT_NAME, doc = "Input (indexed) BAM file.") + @Argument(shortName = StandardOptionDefinitions.INPUT_SHORT_NAME, doc = "Input (indexed) BAM file.") public File INPUT; - @Option(shortName = StandardOptionDefinitions.OUTPUT_SHORT_NAME, doc = "Write metrics to this file") + @Argument(shortName = StandardOptionDefinitions.OUTPUT_SHORT_NAME, doc = "Write metrics to this file") public File OUTPUT; - @Option(shortName = "MO", doc = "Write the confusion matrix (of UMIs) to this file", optional = true) + @Argument(shortName = "MO", doc = "Write the confusion matrix (of UMIs) to this file", optional = true) public File MATRIX_OUTPUT; - @Option(shortName = "V", doc = "Input VCF file") + @Argument(shortName = "V", doc = "Input VCF file") public File VCF; - @Option(shortName = "GQ", doc = "minimal value for the GQ field in the VCF to use variant site.", optional = true) + @Argument(shortName = "GQ", doc = "minimal value for the GQ field in the VCF to use variant site.", optional = true) public Integer MINIMUM_GQ = 90; - @Option(shortName = MINIMUM_MAPPING_QUALITY_SHORT_NAME, doc = "minimal value for the mapping quality of the reads to be used in the estimation.", optional = true) + @Argument(shortName = MINIMUM_MAPPING_QUALITY_SHORT_NAME, doc = "minimal value for the mapping quality of the reads to be used in the estimation.", optional = true) public Integer MINIMUM_MQ = 40; - @Option(shortName = "BQ", doc = "minimal value for the base quality of a base to be used in the estimation.", optional = true) + @Argument(shortName = "BQ", doc = "minimal value for the base quality of a base to be used in the estimation.", optional = true) public Integer MINIMUM_BQ = 17; - @Option(shortName = StandardOptionDefinitions.SAMPLE_ALIAS_SHORT_NAME, + @Argument(shortName = StandardOptionDefinitions.SAMPLE_ALIAS_SHORT_NAME, doc = "Name of sample to look at in VCF. Can be omitted if VCF contains only one sample.", optional = true) public String SAMPLE = null; - @Option(doc = "Number of sets to examine before stopping.", optional = true) + @Argument(doc = "Number of sets to examine before stopping.", optional = true) public Integer STOP_AFTER = 0; - @Option(doc = "Barcode SAM tag.", optional = true) + @Argument(doc = "Barcode SAM tag.", optional = true) public String BARCODE_TAG = "RX"; - @Option(doc = "Barcode Quality SAM tag.", optional = true) + @Argument(doc = "Barcode Quality SAM tag.", optional = true) public String BARCODE_BQ = "QX"; - @Option(shortName = "MBQ", doc = "minimal value for the base quality of all the bases in a molecular barcode, for it to be used.", optional = true) + @Argument(shortName = "MBQ", doc = "minimal value for the base quality of all the bases in a molecular barcode, for it to be used.", optional = true) public Integer MINIMUM_BARCODE_BQ = 30; private static final Log log = Log.getInstance(CollectIndependentReplicateMetrics.class); diff --git a/src/main/java/picard/cmdline/CommandLineParseException.java b/src/main/java/picard/cmdline/CommandLineParseException.java deleted file mode 100644 index c9e9758f2..000000000 --- a/src/main/java/picard/cmdline/CommandLineParseException.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * The MIT License - * - * Copyright (c) 2009 The Broad Institute - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package picard.cmdline; - -import picard.PicardException; - -public class CommandLineParseException extends PicardException { - public CommandLineParseException(final String s) { - super(s); - } - - public CommandLineParseException(final String s, final Throwable throwable) { - super(s, throwable); - } -} diff --git a/src/main/java/picard/cmdline/CommandLineParser.java b/src/main/java/picard/cmdline/CommandLineParser.java deleted file mode 100644 index 9a72f1586..000000000 --- a/src/main/java/picard/cmdline/CommandLineParser.java +++ /dev/null @@ -1,1331 +0,0 @@ -/* - * The MIT License - * - * Copyright (c) 2009 The Broad Institute - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package picard.cmdline; - -import com.google.common.base.CharMatcher; -import htsjdk.samtools.util.CloserUtil; -import htsjdk.samtools.util.CollectionUtil.MultiMap; -import htsjdk.samtools.util.StringUtil; -import picard.PicardException; - -import java.io.BufferedReader; -import java.io.File; -import java.io.FileReader; -import java.io.IOException; -import java.io.PrintStream; -import java.lang.reflect.Constructor; -import java.lang.reflect.Field; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.ParameterizedType; -import java.lang.reflect.Type; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.Comparator; -import java.util.HashMap; -import java.util.HashSet; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.regex.Pattern; - -/** - * Annotation-driven utility for parsing command-line arguments, checking for errors, and producing usage message. - *

    - * This class supports options of the form KEY=VALUE, plus positional arguments. Positional arguments must not contain - * an equal sign lest they be mistaken for a KEY=VALUE pair. - *

    - * The caller must supply an object that both defines the command line and has the parsed options set into it. - * For each possible KEY=VALUE option, there must be a public data member annotated with @Option. The KEY name is - * the name of the data member. An abbreviated name may also be specified with the shortName attribute of @Option. - * If the data member is a List, then the option may be specified multiple times. The type of the data member, - * or the type of the List element must either have a ctor T(String), or must be an Enum. List options must - * be initialized by the caller with some kind of list. Any other option that is non-null is assumed to have the given - * value as a default. If an option has no default value, and does not have the optional attribute of @Option set, - * is required. For List options, minimum and maximum number of elements may be specified in the @Option annotation. - *

    - * A single List data member may be annotated with the @PositionalArguments. This behaves similarly to a Option - * with List data member: the caller must initialize the data member, the type must be constructable from String, and - * min and max number of elements may be specified. If no @PositionalArguments annotation appears in the object, - * then it is an error for the command line to contain positional arguments. - *

    - * A single String public data member may be annotated with @Usage. This string, if present, is used to - * construct the usage message. Details about the possible options are automatically appended to this string. - * If @Usage does not appear, a boilerplate usage message is used. - */ -public class CommandLineParser { - // For formatting option section of usage message. - private static final int OPTION_COLUMN_WIDTH = 30; - private static final int DESCRIPTION_COLUMN_WIDTH = 90; - - private static final Boolean[] TRUE_FALSE_VALUES = {Boolean.TRUE, Boolean.FALSE}; - - private static final String[] PACKAGES_WITH_WEB_DOCUMENTATION = {"picard"}; - - // Use these if no @Usage annotation - private static final String defaultUsagePreamble = "Usage: program [options...]\n"; - private static final String defaultUsagePreambleWithPositionalArguments = - "Usage: program [options...] [positional-arguments...]\n"; - private static final String OPTIONS_FILE = "OPTIONS_FILE"; - - private static final String PRECEDENCE_SYMBOL = "++"; - - /** name, shortName, description for options built in to framework */ - private static final String[][] FRAMEWORK_OPTION_DOC = { - {"--help", "-h", "Displays options specific to this tool."}, - {"--stdhelp", "-H", "Displays options specific to this tool AND " + - "options common to all Picard command line tools."}, - {"--version", null, "Displays program version."} - }; - - private final Set optionsThatCannotBeOverridden = new HashSet(); - - /** - * A typical command line program will call this to get the beginning of the usage message, - * and then append a description of the program, like this: - *

    - * \@Usage - * public String USAGE = CommandLineParser.getStandardUsagePreamble(getClass()) + "Frobnicates the freebozzle." - */ - public static String getStandardUsagePreamble(final Class mainClass) { - return "USAGE: " + mainClass.getSimpleName() + " [options]\n\n" + - (hasWebDocumentation(mainClass) ? - "Documentation: http://broadinstitute.github.io/picard/command-line-overview.html#" + - mainClass.getSimpleName() + "\n\n" - : ""); - } - - /** - * Determines if a class has web documentation based on its package name - * - * @param clazz - * @return true if the class has web documentation, false otherwise - */ - public static boolean hasWebDocumentation(final Class clazz) { - for (final String pkg : PACKAGES_WITH_WEB_DOCUMENTATION) { - if (clazz.getPackage().getName().startsWith(pkg)) { - return true; - } - } - return false; - } - - /** - * @return the link to a FAQ - */ - public static String getFaqLink() { - return "To get help, see http://broadinstitute.github.io/picard/index.html#GettingHelp"; - } - - /** - * Find all of the members annotated with @NestedOptions. - * This is package scope and static so that CommandLineProgram can use it to provide default implementation - * of its own getNestedOptions() method. - */ - static Map getNestedOptions(final Object callerOptions) { - // LinkedHashMap so usage message is generated in order of declaration - final Map ret = new LinkedHashMap(); - final Class clazz = callerOptions.getClass(); - for (final Field field : getAllFields(clazz)) { - if (field.getAnnotation(NestedOptions.class) != null) { - field.setAccessible(true); - try { - ret.put(field.getName(), field.get(callerOptions)); - } catch (final IllegalAccessException e) { - throw new RuntimeException("Should never happen.", e); - } - } - } - return ret; - } - - // This is the object that the caller has provided that contains annotations, - // and into which the values will be assigned. - private final Object callerOptions; - - // For child CommandLineParser, this contains the prefix for the option names, which is needed for generating - // the command line. For non-nested, this is the empty string. - private final String prefix; - // For non-nested, empty string. For nested, prefix + "." - private final String prefixDot; - - // null if no @PositionalArguments annotation - private Field positionalArguments; - private int minPositionalArguments; - private int maxPositionalArguments; - - // List of all the data members with @Option annotation - private final List optionDefinitions = new ArrayList<>(); - - // Maps long name, and short name, if present, to an option definition that is - // also in the optionDefinitions list. - private final Map optionMap = new HashMap<>(); - - // Maps child options prefix to CommandLineParser for the child object. - // Key: option prefix. - private final Map childOptionsMap = new LinkedHashMap<>(); - - // Holds the command-line arguments for a child option parser. - // Key: option prefix. Value: List of arguments for child corresponding to that prefix (with prefix stripped). - private final MultiMap childOptionArguments = new MultiMap<>(); - - // For printing error messages when parsing command line. - private PrintStream messageStream; - - // In case implementation wants to get at arg for some reason. - private String[] argv; - - private String programVersion = null; - - // The command line used to launch this program, including non-null default options that - // weren't explicitly specified. This is used for logging and debugging. - private String commandLine = ""; - - /** - * This attribute is here just to facilitate printing usage for OPTIONS_FILE - */ - public File IGNORE_THIS_PROPERTY; - - // The associated program properties using the CommandLineProgramProperties annotation - private final CommandLineProgramProperties programProperties; - - /** - * Prepare for parsing command line arguments, by validating annotations. - * - * @param callerOptions This object contains annotations that define the acceptable command-line options, - * and ultimately will receive the settings when a command line is parsed. - */ - public CommandLineParser(final Object callerOptions) { - this(callerOptions, ""); - } - - private String getUsagePreamble() { - String usagePreamble = ""; - if (null != programProperties) { - usagePreamble += programProperties.usage(); - } else if (positionalArguments == null) { - usagePreamble += defaultUsagePreamble; - } else { - usagePreamble += defaultUsagePreambleWithPositionalArguments; - } - - if (null != this.programVersion && 0 < this.programVersion.length()) { - usagePreamble += "Version: " + getVersion() + "\n"; - } - //checkForNonASCII(usagePreamble, "preamble"); - - return usagePreamble; - } - - /** - * @param prefix Non-empty for child options object. - */ - private CommandLineParser(final Object callerOptions, final String prefix) { - this.callerOptions = callerOptions; - - this.prefix = prefix; - if (prefix.isEmpty()) { - prefixDot = ""; - } else { - prefixDot = prefix + "."; - } - - int fieldCounter = 1; - for (final Field field : getAllFields(this.callerOptions.getClass())) { - if (field.getAnnotation(PositionalArguments.class) != null) { - handlePositionalArgumentAnnotation(field); - } - if (field.getAnnotation(Option.class) != null) { - handleOptionAnnotation(field, fieldCounter); - // only increase counter if the field had default printOrder - if (field.getAnnotation(Option.class).printOrder() == Integer.MAX_VALUE) - fieldCounter++; - } else if (!isCommandLineProgram() && field.getAnnotation(NestedOptions.class) != null) { - // If callerOptions is an instance of CommandLineProgram, defer creation of child - // CommandLineParsers until after parsing options for this parser, in case CommandLineProgram - // wants to do something dynamic based on values for this parser. - handleNestedOptionsAnnotation(field); - } - - } - - // make sure to sort options according to printOrder - if (optionDefinitions != null && !optionDefinitions.isEmpty()){ - Collections.sort(optionDefinitions, new OptionDefinitionByPrintOrderComparator()); - } - - this.programProperties = this.callerOptions.getClass().getAnnotation(CommandLineProgramProperties.class); - } - - private boolean isCommandLineProgram() { - return callerOptions instanceof CommandLineProgram; - } - - private static List getAllFields(Class clazz) { - final List ret = new ArrayList<>(); - do { - ret.addAll(Arrays.asList(clazz.getDeclaredFields())); - clazz = clazz.getSuperclass(); - } while (clazz != null); - return ret; - } - - public String getVersion() { - return this.callerOptions.getClass().getPackage().getImplementationVersion(); - } - - /** - * Print a usage message based on the options object passed to the ctor. - * - * @param stream Where to write the usage message. - */ - public void usage(final PrintStream stream, final boolean printCommon) { - - if (prefix.isEmpty()) { - final String preamble = htmlUnescape(convertFromHtml(getStandardUsagePreamble(callerOptions.getClass()) + getUsagePreamble())); - checkForNonASCII(preamble, "Tool description"); - stream.print(preamble); - stream.println("\nVersion: " + getVersion()); - stream.println("\n\nOptions:\n"); - - for (final String[] optionDoc : FRAMEWORK_OPTION_DOC) { - printOptionParamUsage(stream, optionDoc[0], optionDoc[1], null, optionDoc[2]); - } - } - - if (!optionDefinitions.isEmpty()) { - optionDefinitions.stream().filter(optionDefinition -> printCommon || !optionDefinition.isCommon).forEach(optionDefinition -> printOptionUsage(stream, optionDefinition)); - } - - if (printCommon) { - final Field fileField; - try { - fileField = getClass().getField("IGNORE_THIS_PROPERTY"); - } catch (final NoSuchFieldException e) { - throw new PicardException("Should never happen", e); - } - final OptionDefinition optionsFileOptionDefinition = - new OptionDefinition(fileField, OPTIONS_FILE, "", - "File of OPTION_NAME=value pairs. No positional parameters allowed. Unlike command-line options, " + - "unrecognized options are ignored. " + "A single-valued option set in an options file may be overridden " + - "by a subsequent command-line option. " + - "A line starting with '#' is considered a comment.", - false, true, false, 0, Integer.MAX_VALUE, null, true, new String[0], Integer.MAX_VALUE); - printOptionUsage(stream, optionsFileOptionDefinition); - } - - // Generate usage for child parsers. - getChildParsersForHelp() - .stream() - .forEach(childClp -> childClp.usage(stream, printCommon)); - } - - static void checkForNonASCII(String documentationText, String location) { - if (!CharMatcher.ASCII.matchesAllOf(documentationText)) { - throw new AssertionError("Non-ASCII character used in documentation ("+location+"). Only ASCII characters are allowed."); - } - //make sure that html-encoded non-ascii characters are found as well - if ( Pattern.compile(".*&[a-zA-Z]*?;.*",Pattern.MULTILINE).matcher(documentationText).find()) { - throw new AssertionError("Non-ASCII character used in documentation ("+location+"). Only ASCII characters are allowed."); - } - } - // package local for testing - static String convertFromHtml(final String textToConvert) { - - //LinkedHashmap since the order matters - final Map regexps = new LinkedHashMap<>(); - - regexps.put("< *a *href=[\'\"](.*?)[\'\"] *>(.*?)","$2 ($1)"); - regexps.put("< *a *href=[\'\"](.*?)[\'\"] *>(.*?)< *a */>","$2 ($1)"); - regexps.put("","\n"); - regexps.put("< *(br|p|table|h[1-4]|pre|hr|li|ul) */>","\n"); - regexps.put("< *(p|table|h[1-4]|ul|pre) *>","\n"); - regexps.put("

  • ", " - "); - regexps.put("", "\t"); - regexps.put("<\\w*?>", ""); - - return regexps.entrySet().stream().sequential() - .reduce(textToConvert, (string, entrySet) -> string.replaceAll(entrySet.getKey(), entrySet.getValue()), (a, b) -> b); - } - - private Collection getChildParsersForHelp() { - final Collection childClps; - if (isCommandLineProgram()) { - childClps = new ArrayList<>(); - for (final Map.Entry entry : - ((CommandLineProgram) callerOptions).getNestedOptionsForHelp().entrySet()) { - if (entry.getKey().contains(".")) { - throw new IllegalArgumentException("Prefix for nested options should not contain period: " + entry.getKey()); - } - childClps.add(new CommandLineParser(entry.getValue(), prefixDot + entry.getKey())); - } - } else { - childClps = childOptionsMap.values(); - } - return childClps; - } - - - public void htmlUsage(final PrintStream stream, final String programName, final boolean printCommon) { - // TODO: Should HTML escape usage preamble and option usage, including line breaks - stream.println("

    " + programName + "

    "); - stream.println("
    "); - stream.println("

    " + getUsagePreamble() + "

    "); - boolean hasOptions = false; - for (final OptionDefinition optionDefinition : optionDefinitions) { - if (!optionDefinition.isCommon || printCommon) { - hasOptions = true; - break; - } - } - if (hasOptions) { - htmlPrintOptions(stream, printCommon); - } - stream.println("
    "); - } - - public void htmlPrintOptions(final PrintStream stream, final boolean printCommon) { - stream.println(""); - stream.println(""); - if (printCommon) { - for (final String[] optionDoc : FRAMEWORK_OPTION_DOC) { - stream.println(""); - } - } - htmlPrintOptionTableRows(stream, printCommon); - stream.println("
    OptionDescription
    " + optionDoc[0] + "" + - optionDoc[2] + "
    "); - } - - /** - * Prints options as rows in an HTML table. - * - * @param stream stream into which to write the output - * @param printCommon whether or not to print the common information - */ - private void htmlPrintOptionTableRows(final PrintStream stream, final boolean printCommon) { - for (final OptionDefinition optionDefinition : optionDefinitions) { - - checkForNonASCII(optionDefinition.doc, optionDefinition.name); - - if (!optionDefinition.isCommon || printCommon) { - printHtmlOptionUsage(stream, optionDefinition); - } - } - for (final CommandLineParser childParser : getChildParsersForHelp()) { - childParser.htmlPrintOptionTableRows(stream, false); - } - } - - private static final Map htmlToText = new LinkedHashMap(){{ - put("<","<"); - put(">",">"); - put("≥",">="); - put("≤","<="); - - put("

    ","\n"); - }}; - - static String htmlUnescape(String str) { - // May need more here - return htmlToText.entrySet().stream().sequential() - .reduce(str, (string, entrySet) -> string.replace(entrySet.getKey(), entrySet.getValue()), (a, b) -> b); - } - - /** - * Parse command-line options, and store values in callerOptions object passed to ctor. - * - * @param messageStream Where to write error messages. - * @param args Command line tokens. - * @return true if command line is valid. - */ - public boolean parseOptions(final PrintStream messageStream, final String[] args) { - this.argv = args; - this.messageStream = messageStream; - if (prefix.isEmpty()) { - commandLine = callerOptions.getClass().getName(); - } - for (int i = 0; i < args.length; ++i) { - final String arg = args[i]; - if (arg.equals("-h") || arg.equals("--help")) { - usage(messageStream, false); - return false; - } - if (arg.equals("-H") || arg.equals("--stdhelp")) { - usage(messageStream, true); - return false; - } - - if (arg.equals("--version")) { - messageStream.println(getVersion()); - return false; - } - - final String[] pair = arg.split("=", 2); - if (pair.length == 2) { - if (pair[1].isEmpty() && i < args.length - 1) { - pair[1] = args[++i]; - } - if (!parseOption(pair[0], pair[1], false)) { - messageStream.println(); - usage(messageStream, true); - return false; - } - } else if (!parsePositionalArgument(arg)) { - messageStream.println(); - usage(messageStream, false); - return false; - } - } - if (!checkNumArguments()) { - messageStream.println(); - usage(messageStream, false); - return false; - } - - if (!parseChildOptions()) { - messageStream.println(); - usage(messageStream, false); - return false; - } - - return true; - } - - /** - * After command line has been parsed, make sure that all required options have values, and that - * lists with minimum # of elements have sufficient. - * - * @return true if valid - */ - private boolean checkNumArguments() { - //Also, since we're iterating over all options and args, use this opportunity to recreate the commandLineString - final StringBuilder commandLineString = new StringBuilder(); - try { - for (final OptionDefinition optionDefinition : optionDefinitions) { - final String fullName = prefixDot + optionDefinition.name; - final StringBuilder mutextOptionNames = new StringBuilder(); - for (final String mutexOption : optionDefinition.mutuallyExclusive) { - final OptionDefinition mutextOptionDef = optionMap.get(mutexOption); - if (mutextOptionDef != null && mutextOptionDef.hasBeenSet) { - mutextOptionNames.append(' ').append(prefixDot).append(mutextOptionDef.name); - } - } - if (optionDefinition.hasBeenSet && mutextOptionNames.length() > 0) { - messageStream.println("ERROR: Option '" + fullName + - "' cannot be used in conjunction with option(s)" + - mutextOptionNames.toString()); - return false; - } - if (optionDefinition.isCollection) { - final Collection c = (Collection) optionDefinition.field.get(callerOptions); - if (c.size() < optionDefinition.minElements) { - messageStream.println("ERROR: Option '" + fullName + "' must be specified at least " + - optionDefinition.minElements + " times."); - return false; - } - } else if (!optionDefinition.optional && !optionDefinition.hasBeenSet && - !optionDefinition.hasBeenSetFromParent && mutextOptionNames.length() == 0) { - messageStream.print("ERROR: Option '" + fullName + "' is required"); - if (optionDefinition.mutuallyExclusive.isEmpty()) { - messageStream.println("."); - } else { - messageStream.println(" unless any of " + optionDefinition.mutuallyExclusive + - " are specified."); - } - return false; - } - } - - if (positionalArguments != null) { - final Collection c = (Collection) positionalArguments.get(callerOptions); - if (c.size() < minPositionalArguments) { - messageStream.println("ERROR: At least " + minPositionalArguments + - " positional arguments must be specified."); - return false; - } - for (final Object posArg : c) { - commandLineString.append(' ').append(posArg.toString()); - } - } - //first, append args that were explicitly set - for (final OptionDefinition optionDefinition : optionDefinitions) { - if (optionDefinition.hasBeenSet) { - commandLineString.append(' ').append(prefixDot).append(optionDefinition.name).append('=').append( - optionDefinition.field.get(callerOptions)); - } - } - commandLineString.append(" "); //separator to tell the 2 apart - //next, append args that weren't explicitly set, but have a default value - for (final OptionDefinition optionDefinition : optionDefinitions) { - if (!optionDefinition.hasBeenSet && !optionDefinition.defaultValue.equals("null")) { - commandLineString.append(' ').append(prefixDot).append(optionDefinition.name).append('=').append( - optionDefinition.defaultValue); - } - } - this.commandLine += commandLineString.toString(); - return true; - } catch (final IllegalAccessException e) { - // Should never happen because lack of publicness has already been checked. - throw new RuntimeException(e); - } - } - - private boolean parsePositionalArgument(final String stringValue) { - if (positionalArguments == null) { - messageStream.println("ERROR: Invalid argument '" + stringValue + "'."); - return false; - } - final Object value; - try { - value = constructFromString(getUnderlyingType(positionalArguments), stringValue); - } catch (final CommandLineParseException e) { - messageStream.println("ERROR: " + e.getMessage()); - return false; - } - final Collection c; - try { - c = (Collection) positionalArguments.get(callerOptions); - } catch (final IllegalAccessException e) { - throw new RuntimeException(e); - } - if (c.size() >= maxPositionalArguments) { - messageStream.println("ERROR: No more than " + maxPositionalArguments + - " positional arguments may be specified on the command line."); - return false; - } - c.add(value); - return true; - } - - private boolean parseOption(final String key, final String stringValue, final boolean optionsFile) { - return parseOption(key, stringValue, optionsFile, false); - } - - private boolean parseOption(String key, final String stringValue, final boolean optionsFile, - boolean precedenceSet) { - key = key.toUpperCase(); - if (key.equals(OPTIONS_FILE)) { - commandLine += " " + prefix + OPTIONS_FILE + "=" + stringValue; - return parseOptionsFile(stringValue); - } - - // Check to see if the precedence symbol was used - if (key.startsWith(PRECEDENCE_SYMBOL)) { - key = key.substring(PRECEDENCE_SYMBOL.length()); - precedenceSet = true; - } - - // Save child options for later processing. - final Integer prefixIndex = key.indexOf('.'); - if (prefixIndex != -1) { - final String prefix = key.substring(0, prefixIndex); - final String subKey = key.substring(prefixIndex + 1); - if (!subKey.isEmpty()) { - childOptionArguments.append(prefix, new ChildOptionArg(subKey, stringValue, optionsFile, - precedenceSet)); - return true; - } else { - messageStream.println("ERROR: Unrecognized option: " + key); - return false; - } - } - - final OptionDefinition optionDefinition = optionMap.get(key); - if (optionDefinition == null) { - if (optionsFile) { - // Silently ignore unrecognized option from options file - return true; - } - messageStream.println("ERROR: Unrecognized option: " + key); - return false; - } - - // Check to see if the option has been "fixed" already - if (this.optionsThatCannotBeOverridden.contains(optionDefinition.name)) { - return true; - } else if (precedenceSet) { - this.optionsThatCannotBeOverridden.add(optionDefinition.name); - } - - if (!optionDefinition.isCollection && optionDefinition.hasBeenSet && !optionDefinition.hasBeenSetFromOptionsFile) { - messageStream.println("ERROR: Option '" + key + "' cannot be specified more than once."); - return false; - } - final Object value; - try { - if (stringValue.equals("null")) { - //"null" is a special value that allows the user to override any default - //value set for this arg. It can only be used for optional args. When - //used for a list arg, it will clear the list. - if (optionDefinition.optional) { - value = null; - } else { - messageStream.println("ERROR: non-null value must be provided for '" + key + "'."); - return false; - } - } else { - value = constructFromString(getUnderlyingType(optionDefinition.field), stringValue); - } - - } catch (final CommandLineParseException e) { - messageStream.println("ERROR: " + e.getMessage()); - return false; - } - try { - if (optionDefinition.isCollection) { - final Collection c = (Collection) optionDefinition.field.get(callerOptions); - if (value == null) { - //user specified this arg=null which is interpreted as empty list - c.clear(); - } else if (c.size() >= optionDefinition.maxElements) { - messageStream.println("ERROR: Option '" + key + "' cannot be used more than " + - optionDefinition.maxElements + " times."); - return false; - } else { - c.add(value); - } - optionDefinition.hasBeenSet = true; - optionDefinition.hasBeenSetFromOptionsFile = optionsFile; - } else { - //get all fields with this name and set them to the argument. - final String fieldName = optionDefinition.field.getName(); - final Field[] fields = callerOptions.getClass().getFields(); - for (final Field field : fields) { - if (field.getName().equals(fieldName)) { - field.set(callerOptions, value); - optionDefinition.hasBeenSet = true; - } - } - if (!optionDefinition.hasBeenSet) { - optionDefinition.field.set(callerOptions, value); - optionDefinition.hasBeenSet = true; - } - optionDefinition.hasBeenSetFromOptionsFile = optionsFile; - } - } catch (final IllegalAccessException e) { - // Should never happen because we only iterate through public fields. - throw new RuntimeException(e); - } - return true; - } - - /** - * Parsing of options from file is looser than normal. Any unrecognized options are - * ignored, and a single-valued option that is set in a file may be overridden by a - * subsequent appearance of that option. - * A line that starts with '#' is ignored. - * - * @param optionsFile - * @return false if a fatal error occurred - */ - private boolean parseOptionsFile(final String optionsFile) { - return parseOptionsFile(optionsFile, true); - } - - /** - * @param optionFileStyleValidation true: unrecognized options are silently ignored; and a single-valued option may be overridden. - * false: standard rules as if the options in the file were on the command line directly. - * @return - */ - public boolean parseOptionsFile(final String optionsFile, final boolean optionFileStyleValidation) { - BufferedReader reader = null; - try { - reader = new BufferedReader(new FileReader(optionsFile)); - String line; - while ((line = reader.readLine()) != null) { - if (line.startsWith("#") || line.trim().isEmpty()) { - continue; - } - final String[] pair = line.split("=", 2); - if (pair.length == 2) { - if (!parseOption(pair[0], pair[1], optionFileStyleValidation)) { - messageStream.println(); - usage(messageStream, true); - return false; - } - } else { - messageStream.println("Strange line in OPTIONS_FILE " + optionsFile + ": " + line); - usage(messageStream, true); - return false; - } - } - reader.close(); - return true; - - } catch (final IOException e) { - throw new PicardException("I/O error loading OPTIONS_FILE=" + optionsFile, e); - } finally { - CloserUtil.close(reader); - } - } - - private void printHtmlOptionUsage(final PrintStream stream, final OptionDefinition optionDefinition) { - final String type = getUnderlyingType(optionDefinition.field).getSimpleName(); - final String optionLabel = prefixDot + optionDefinition.name + " (" + type + ")"; - stream.println("" + optionLabel + "" + makeOptionDescription(optionDefinition) + ""); - } - - private void printOptionUsage(final PrintStream stream, final OptionDefinition optionDefinition) { - printOptionParamUsage(stream, optionDefinition.name, optionDefinition.shortName, - getUnderlyingType(optionDefinition.field).getSimpleName(), - makeOptionDescription(optionDefinition)); - } - - - private void printOptionParamUsage(final PrintStream stream, final String name, final String shortName, - final String type, final String optionDescription) { - String optionLabel = prefixDot + name; - if (type != null) optionLabel += "=" + type; - - stream.print(optionLabel); - if (shortName != null && !shortName.isEmpty()) { - stream.println(); - optionLabel = prefixDot + shortName; - if (type != null) optionLabel += "=" + type; - stream.print(optionLabel); - } - - int numSpaces = OPTION_COLUMN_WIDTH - optionLabel.length(); - if (optionLabel.length() > OPTION_COLUMN_WIDTH) { - stream.println(); - numSpaces = OPTION_COLUMN_WIDTH; - } - printSpaces(stream, numSpaces); - checkForNonASCII(optionDescription, name); - - final String wrappedDescription = StringUtil.wordWrap(convertFromHtml(optionDescription), DESCRIPTION_COLUMN_WIDTH); - final String[] descriptionLines = wrappedDescription.split("\n"); - for (int i = 0; i < descriptionLines.length; ++i) { - if (i > 0) { - printSpaces(stream, OPTION_COLUMN_WIDTH); - } - stream.println(descriptionLines[i]); - } - stream.println(); - } - - private String makeOptionDescription(final OptionDefinition optionDefinition) { - final StringBuilder sb = new StringBuilder(); - if (!optionDefinition.doc.isEmpty()) { - sb.append(optionDefinition.doc); - sb.append(" "); - } - if (optionDefinition.optional) { - sb.append("Default value: "); - sb.append(optionDefinition.defaultValue); - sb.append(". "); - if (!optionDefinition.defaultValue.equals("null")) { - sb.append("This option can be set to 'null' to clear the default value. "); - } - } else if (!optionDefinition.isCollection) { - sb.append("Required. "); - } - Object[] enumConstants = getUnderlyingType(optionDefinition.field).getEnumConstants(); - if (enumConstants == null && getUnderlyingType(optionDefinition.field) == Boolean.class) { - enumConstants = TRUE_FALSE_VALUES; - } - - if (enumConstants != null) { - final Boolean isClpEnum = enumConstants.length > 0 && (enumConstants[0] instanceof ClpEnum); - - sb.append("Possible values: {"); - if (isClpEnum) sb.append('\n'); - - for (int i = 0; i < enumConstants.length; ++i) { - if (i > 0 && !isClpEnum) { - sb.append(", "); - } - sb.append(enumConstants[i].toString()); - - if (isClpEnum) { - sb.append(" (").append(((ClpEnum) enumConstants[i]).getHelpDoc()).append(")\n"); - } - } - sb.append("} "); - } - if (optionDefinition.isCollection) { - if (optionDefinition.minElements == 0) { - if (optionDefinition.maxElements == Integer.MAX_VALUE) { - sb.append("This option may be specified 0 or more times. "); - } else { - sb.append("This option must be specified no more than ").append(optionDefinition.maxElements).append( - " times. "); - } - } else if (optionDefinition.maxElements == Integer.MAX_VALUE) { - sb.append("This option must be specified at least ").append(optionDefinition.minElements).append(" times. "); - } else { - sb.append("This option may be specified between ").append(optionDefinition.minElements).append( - " and ").append(optionDefinition.maxElements).append(" times. "); - } - - if (!optionDefinition.defaultValue.equals("null")) { - sb.append("This option can be set to 'null' to clear the default list. "); - } - - } - if (!optionDefinition.mutuallyExclusive.isEmpty()) { - sb.append(" Cannot be used in conjuction with option(s)"); - for (final String option : optionDefinition.mutuallyExclusive) { - final OptionDefinition mutextOptionDefinition = optionMap.get(option); - - if (mutextOptionDefinition == null) { - throw new PicardException("Invalid option definition in source code. " + option + - " doesn't match any known option."); - } - - sb.append(' ').append(mutextOptionDefinition.name); - if (!mutextOptionDefinition.shortName.isEmpty()) { - sb.append(" (").append(mutextOptionDefinition.shortName).append(')'); - } - } - } - return sb.toString(); - } - - private void printSpaces(final PrintStream stream, final int numSpaces) { - final StringBuilder sb = new StringBuilder(); - for (int i = 0; i < numSpaces; ++i) { - sb.append(' '); - } - stream.print(sb); - } - - /** - * @param field the command line parameter as a {@link Field} - * @param fieldPosition the field number as returned by getAllFields() that returns all fields including those of superclasses - */ - private void handleOptionAnnotation(final Field field, final int fieldPosition) { - try { - field.setAccessible(true); - final Option optionAnnotation = field.getAnnotation(Option.class); - final boolean isCollection = isCollectionField(field); - if (isCollection) { - if (optionAnnotation.maxElements() == 0) { - throw new CommandLineParserDefinitionException("@Option member " + field.getName() + - "has maxElements = 0"); - } - if (optionAnnotation.minElements() > optionAnnotation.maxElements()) { - throw new CommandLineParserDefinitionException("In @Option member " + field.getName() + - ", minElements cannot be > maxElements"); - } - if (field.get(callerOptions) == null) { - createCollection(field, callerOptions, "@Option"); - } - } - if (!canBeMadeFromString(getUnderlyingType(field))) { - throw new CommandLineParserDefinitionException("@Option member " + field.getName() + - " must have a String ctor or be an enum"); - } - - int printOrder = optionAnnotation.printOrder(); - /* - * check if we got the default printOrder (ie the print order was not specified in - * field annotation). - * If so we use the field position to set its default print order - * *but* we multiply the field position by 1000 to - * (1) make sure that custom ordering is preserved as long as it is below 1000 - * (2) get rooms in between each options to be able to insert your own options - */ - if (printOrder == Integer.MAX_VALUE) { - printOrder = fieldPosition * 1000; - } - - - final OptionDefinition optionDefinition = new OptionDefinition(field, - field.getName(), - optionAnnotation.shortName(), - optionAnnotation.doc(), optionAnnotation.optional() || (field.get(callerOptions) != null), - optionAnnotation.overridable(), isCollection, optionAnnotation.minElements(), - optionAnnotation.maxElements(), field.get(callerOptions), optionAnnotation.common(), - optionAnnotation.mutex(), - printOrder); - - for (final String option : optionAnnotation.mutex()) { - final OptionDefinition mutextOptionDef = optionMap.get(option); - if (mutextOptionDef != null) { - mutextOptionDef.mutuallyExclusive.add(field.getName()); - } - } - if (!optionDefinition.overridable && optionMap.containsKey(optionDefinition.name)) { - throw new CommandLineParserDefinitionException(optionDefinition.name + " has already been used."); - } - if (!optionDefinition.shortName.isEmpty() && !optionDefinition.shortName.equals(optionDefinition.name)) { - if (optionMap.containsKey(optionDefinition.shortName)) { - if (!optionDefinition.overridable) { - throw new CommandLineParserDefinitionException(optionDefinition.shortName + - " has already been used"); - } - } else { - optionMap.put(optionDefinition.shortName, optionDefinition); - } - } - //if we are overridable and we already exist don't add again to the option defs - if (!(optionDefinition.overridable && optionMap.containsKey(optionDefinition.name))) { - optionDefinitions.add(optionDefinition); - optionMap.put(optionDefinition.name, optionDefinition); - } - //we are overridable but we already exist in the map so we need to update the hidden field value - else if (optionMap.containsKey(optionDefinition.name)) { - field.set(this.callerOptions, optionMap.get(optionDefinition.name).field.get(callerOptions)); - } - } catch (final IllegalAccessException e) { - throw new CommandLineParserDefinitionException(field.getName() + - " must have public visibility to have @Option annotation"); - } - } - - private void handlePositionalArgumentAnnotation(final Field field) { - if (positionalArguments != null) { - throw new CommandLineParserDefinitionException - ("@PositionalArguments cannot be used more than once in an option class."); - } - field.setAccessible(true); - positionalArguments = field; - if (!isCollectionField(field)) { - throw new CommandLineParserDefinitionException("@PositionalArguments must be applied to a Collection"); - } - - if (!canBeMadeFromString(getUnderlyingType(field))) { - throw new CommandLineParserDefinitionException("@PositionalParameters member " + field.getName() + - "does not have a String ctor"); - } - - final PositionalArguments positionalArgumentsAnnotation = field.getAnnotation(PositionalArguments.class); - minPositionalArguments = positionalArgumentsAnnotation.minElements(); - maxPositionalArguments = positionalArgumentsAnnotation.maxElements(); - if (minPositionalArguments > maxPositionalArguments) { - throw new CommandLineParserDefinitionException("In @PositionalArguments, minElements cannot be > maxElements"); - } - try { - if (field.get(callerOptions) == null) { - createCollection(field, callerOptions, "@PositionalParameters"); - } - } catch (final IllegalAccessException e) { - throw new CommandLineParserDefinitionException(field.getName() + - " must have public visibility to have @PositionalParameters annotation"); - - } - } - - private void handleNestedOptionsAnnotation(final Field field) { - field.setAccessible(true); - try { - childOptionsMap.put(field.getName(), - new CommandLineParser(field.get(this.callerOptions), prefixDot + field.getName())); - } catch (final IllegalAccessException e) { - throw new CommandLineParserDefinitionException("Should never happen.", e); - } - } - - private boolean isCollectionField(final Field field) { - try { - field.getType().asSubclass(Collection.class); - return true; - } catch (final ClassCastException e) { - return false; - } - } - - private void createCollection(final Field field, final Object callerOptions, final String annotationType) - throws IllegalAccessException { - try { - field.set(callerOptions, field.getType().newInstance()); - } catch (final Exception ex) { - try { - field.set(callerOptions, new ArrayList()); - } catch (final IllegalArgumentException e) { - throw new CommandLineParserDefinitionException("In collection " + annotationType + - " member " + field.getName() + - " cannot be constructed or auto-initialized with ArrayList, so collection must be initialized explicitly."); - } - - } - - } - - /** - * Returns the type that each instance of the argument needs to be converted to. In - * the case of primitive fields it will return the wrapper type so that String - * constructors can be found. - */ - private Class getUnderlyingType(final Field field) { - if (isCollectionField(field)) { - final ParameterizedType clazz = (ParameterizedType) (field.getGenericType()); - final Type[] genericTypes = clazz.getActualTypeArguments(); - if (genericTypes.length != 1) { - throw new CommandLineParserDefinitionException("Strange collection type for field " + - field.getName()); - } - return (Class) genericTypes[0]; - - } else { - final Class type = field.getType(); - if (type == Byte.TYPE) return Byte.class; - if (type == Short.TYPE) return Short.class; - if (type == Integer.TYPE) return Integer.class; - if (type == Long.TYPE) return Long.class; - if (type == Float.TYPE) return Float.class; - if (type == Double.TYPE) return Double.class; - if (type == Boolean.TYPE) return Boolean.class; - - return type; - } - } - - // True if clazz is an enum, or if it has a ctor that takes a single String argument. - private boolean canBeMadeFromString(final Class clazz) { - if (clazz.isEnum()) { - return true; - } - try { - clazz.getConstructor(String.class); - return true; - } catch (final NoSuchMethodException e) { - return false; - } - } - - private Object constructFromString(final Class clazz, final String s) { - try { - if (clazz.isEnum()) { - try { - return Enum.valueOf(clazz, s); - } catch (final IllegalArgumentException e) { - throw new CommandLineParseException("'" + s + "' is not a valid value for " + - clazz.getSimpleName() + ".", e); - } - } - final Constructor ctor = clazz.getConstructor(String.class); - return ctor.newInstance(s); - } catch (final NoSuchMethodException e) { - // Shouldn't happen because we've checked for presence of ctor - throw new CommandLineParseException("Cannot find string ctor for " + clazz.getName(), e); - } catch (final InstantiationException e) { - throw new CommandLineParseException("Abstract class '" + clazz.getSimpleName() + - "'cannot be used for an option value type.", e); - } catch (final IllegalAccessException e) { - throw new CommandLineParseException("String constructor for option value type '" + clazz.getSimpleName() + - "' must be public.", e); - } catch (final InvocationTargetException e) { - throw new CommandLineParseException("Problem constructing " + clazz.getSimpleName() + - " from the string '" + s + "'.", e.getCause()); - } - } - - public String[] getArgv() { - return argv; - } - - public interface ClpEnum { - String getHelpDoc(); - } - - protected static class OptionDefinitionByPrintOrderComparator implements Comparator { - - @Override - public int compare(OptionDefinition o1, OptionDefinition o2) { - return o1.printOrder - o2.printOrder; - } - } - - protected static final class OptionDefinition { - final Field field; - final String name; - final String shortName; - final String doc; - final boolean optional; - final boolean overridable; - final boolean isCollection; - final int minElements; - final int maxElements; - final int printOrder; - final String defaultValue; - final boolean isCommon; - boolean hasBeenSet = false; - boolean hasBeenSetFromOptionsFile = false; - boolean hasBeenSetFromParent = false; - final Set mutuallyExclusive; - - private OptionDefinition(final Field field, final String name, final String shortName, final String doc, - final boolean optional, final boolean overridable, boolean collection, final int minElements, - final int maxElements, final Object defaultValue, final boolean isCommon, - final String[] mutuallyExclusive, final int printOrder) { - this.field = field; - this.name = name.toUpperCase(); - this.shortName = shortName.toUpperCase(); - this.doc = doc; - this.optional = optional; - this.overridable = overridable; - isCollection = collection; - this.minElements = minElements; - this.maxElements = maxElements; - if (defaultValue != null) { - if (isCollection && ((Collection) defaultValue).isEmpty()) { - //treat empty collections the same as uninitialized primitive types - this.defaultValue = "null"; - } else { - //this is an intialized primitive type or a non-empty collection - this.defaultValue = defaultValue.toString(); - } - } else { - this.defaultValue = "null"; - } - this.isCommon = isCommon; - this.mutuallyExclusive = new HashSet(Arrays.asList(mutuallyExclusive)); - this.printOrder = printOrder; - } - } - - /** - * Holds a command-line argument that is destined for a child parser. Prefix has been stripped from name. - */ - private static final class ChildOptionArg { - final String name; - final String value; - final boolean fromFile; - final boolean precedenceSet; - - private ChildOptionArg(final String name, final String value, final boolean fromFile, final boolean precedenceSet) { - this.name = name; - this.value = value; - this.fromFile = fromFile; - this.precedenceSet = precedenceSet; - } - } - - /** - * Propagate options from parent to children as appropriate, parse command line options for - * children, and then validate that children have been properly initialized. This is done recursively - * for any child that itself has a child. - * - * @return true if parsing is successful. Writes any errors to the message stream. - */ - private boolean parseChildOptions() { - - // If callerOptions is not an instance of CommandLineProgram, then the child options are populated - // when annotations are processed. - if (isCommandLineProgram()) { - final CommandLineProgram commandLineProgram = (CommandLineProgram) callerOptions; - for (final Map.Entry entry : commandLineProgram.getNestedOptions().entrySet()) { - if (entry.getKey().contains(".")) { - throw new IllegalArgumentException("Prefix for nested options should not contain period: " + entry.getKey()); - } - childOptionsMap.put(entry.getKey(), - new CommandLineParser(entry.getValue(), prefixDot + entry.getKey())); - } - } - boolean retval = true; - - // Check for child options for which there is no parser - for (final String prefix : childOptionArguments.keySet()) { - if (!childOptionsMap.containsKey(prefix)) { - messageStream.println("ERROR: Option prefix '" + prefix + "' is not valid."); - retval = false; - } - } - - try { - // Propagate options from this parser to child parsers - for (final OptionDefinition optionDefinition : optionDefinitions) { - // Handling collection value propagation is confusing, so just don't do it. - if (optionDefinition.isCollection) continue; - final Object value = optionDefinition.field.get(callerOptions); - if (value == null) continue; - for (final CommandLineParser childParser : childOptionsMap.values()) { - maybePropagateValueToChild(childParser, optionDefinition, value); - } - } - } catch (final IllegalAccessException e) { - throw new RuntimeException("Should never happen", e); - } - - for (final Map.Entry entry : childOptionsMap.entrySet()) { - final String prefix = entry.getKey(); - final CommandLineParser childParser = entry.getValue(); - childParser.messageStream = this.messageStream; - final Collection childOptionArgs = this.childOptionArguments.get(prefix); - if (childOptionArgs != null) { - for (final ChildOptionArg arg : childOptionArgs) { - childParser.parseOption(arg.name, arg.value, arg.fromFile, arg.precedenceSet); - } - } - if (!childParser.checkNumArguments()) { - retval = false; - } - if (!childParser.parseChildOptions()) { - retval = false; - } - this.commandLine += " " + childParser.getCommandLine(); - } - - return retval; - } - - /** - * Propagate value from parent to child if appropriate to do so. - */ - private void maybePropagateValueToChild(final CommandLineParser childParser, - final OptionDefinition optionDefinition, - final Object value) { - try { - final OptionDefinition childOptionDefinition = childParser.optionMap.get(optionDefinition.name); - if (childOptionDefinition != null) { - final Object childValue = childOptionDefinition.field.get(childParser.callerOptions); - if (childValue == null || optionDefinition.hasBeenSet) { - childOptionDefinition.field.set(childParser.callerOptions, value); - childOptionDefinition.hasBeenSetFromParent = true; - childOptionDefinition.hasBeenSetFromOptionsFile = optionDefinition.hasBeenSetFromOptionsFile; - } - } - } catch (final IllegalAccessException e) { - throw new RuntimeException("Should never happen", e); - } - } - - /** - * The commandline used to run this program, including any default args that - * weren't necessarily specified. This is used for logging and debugging. - *

    - * NOTE: {@link #parseOptions(PrintStream, String[])} must be called before - * calling this method. - * - * @return The commandline, or null if {@link #parseOptions(PrintStream, String[])} - * hasn't yet been called, or didn't complete successfully. - */ - public String getCommandLine() { return commandLine; } - - /** - * This method is only needed when calling one of the public methods that doesn't take a messageStream argument. - */ - public void setMessageStream(final PrintStream messageStream) { - this.messageStream = messageStream; - } - - public Object getCallerOptions() { - return callerOptions; - } -} diff --git a/src/main/java/picard/cmdline/CommandLineParserDefinitionException.java b/src/main/java/picard/cmdline/CommandLineParserDefinitionException.java deleted file mode 100644 index e9b857b36..000000000 --- a/src/main/java/picard/cmdline/CommandLineParserDefinitionException.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * The MIT License - * - * Copyright (c) 2009 The Broad Institute - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package picard.cmdline; - -import picard.PicardException; - -public class CommandLineParserDefinitionException extends PicardException { - public CommandLineParserDefinitionException(final String s) { - super(s); - } - - public CommandLineParserDefinitionException(final String s, final Throwable throwable) { - super(s, throwable); - } -} diff --git a/src/main/java/picard/cmdline/CommandLineProgram.java b/src/main/java/picard/cmdline/CommandLineProgram.java index 144f1e361..8c2439dc7 100644 --- a/src/main/java/picard/cmdline/CommandLineProgram.java +++ b/src/main/java/picard/cmdline/CommandLineProgram.java @@ -40,22 +40,37 @@ import htsjdk.samtools.util.Log; import htsjdk.variant.variantcontext.writer.Options; import htsjdk.variant.variantcontext.writer.VariantContextWriterBuilder; +import org.broadinstitute.barclay.argparser.Argument; +import org.broadinstitute.barclay.argparser.ArgumentCollection; +import org.broadinstitute.barclay.argparser.CommandLineArgumentParser; +import org.broadinstitute.barclay.argparser.CommandLineException; +import org.broadinstitute.barclay.argparser.CommandLineParser; +import org.broadinstitute.barclay.argparser.CommandLineParserOptions; +import org.broadinstitute.barclay.argparser.LegacyCommandLineArgumentParser; +import org.broadinstitute.barclay.argparser.SpecialArgumentsCollection; +import picard.cmdline.argumentcollections.OptionalReferenceArgumentCollection; +import picard.cmdline.argumentcollections.ReferenceArgumentCollection; +import picard.cmdline.argumentcollections.RequiredReferenceArgumentCollection; +import picard.util.PropertyUtils; import java.io.File; import java.net.InetAddress; import java.text.DecimalFormat; +import java.util.Arrays; import java.util.ArrayList; +import java.util.Collections; import java.util.Date; +import java.util.HashSet; import java.util.List; -import java.util.Map; +import java.util.Properties; /** * Abstract class to facilitate writing command-line programs. * * To use: * - * 1. Extend this class with a concrete class that has data members annotated with @Option, @PositionalArguments - * and/or @Usage annotations. + * 1. Extend this class with a concrete class that is annotated with @COmmandLineProgramProperties, and has data members + * annotated with @Argument, @PositionalArguments, and/or @ArgumentCollection annotations. * * 2. If there is any custom command-line validation, override customCommandLineValidation(). When this method is * called, the command line has been parsed and set into the data members of the concrete class. @@ -64,54 +79,64 @@ * the exit status of the program. It is assumed that the concrete class emits any appropriate error message before * returning non-zero. doWork() may throw unchecked exceptions, which are caught and reported appropriately. * - * 4. Implement the following static method in the concrete class: - * - * public static void main(String[] argv) { - new MyConcreteClass().instanceMain(argv); - } - - */ public abstract class CommandLineProgram { - @Option(common=true, optional=true) + // Picard CmdLine properties file resource path, placed at this path by the gradle build script + private static String PICARD_CMDLINE_PROPERTIES_FILE = "picard/picardCmdLine.properties"; + private static String PROPERTY_USE_LEGACY_PARSER = "picard.useLegacyParser"; + private static String PROPERTY_CONVERT_LEGACY_COMMAND_LINE = "picard.convertCommandLine"; + private static Boolean useLegacyParser; + + @Argument(doc="One or more directories with space available to be used by this program for temporary storage of working files", + common=true, optional=true) public List TMP_DIR = new ArrayList(); - @Option(doc = "Control verbosity of logging.", common=true) + @Argument(doc = "Control verbosity of logging.", common=true) public Log.LogLevel VERBOSITY = Log.LogLevel.INFO; - @Option(doc = "Whether to suppress job-summary info on System.err.", common=true) + @Argument(doc = "Whether to suppress job-summary info on System.err.", common=true) public Boolean QUIET = false; - @Option(doc = "Validation stringency for all SAM files read by this program. Setting stringency to SILENT " + + @Argument(doc = "Validation stringency for all SAM files read by this program. Setting stringency to SILENT " + "can improve performance when processing a BAM file in which variable-length data (read, qualities, tags) " + "do not otherwise need to be decoded.", common=true) public ValidationStringency VALIDATION_STRINGENCY = ValidationStringency.DEFAULT_STRINGENCY; - @Option(doc = "Compression level for all compressed files created (e.g. BAM and GELI).", common=true) + @Argument(doc = "Compression level for all compressed files created (e.g. BAM and GELI).", common=true) public int COMPRESSION_LEVEL = Defaults.COMPRESSION_LEVEL; - @Option(doc = "When writing SAM files that need to be sorted, this will specify the number of records stored in RAM before spilling to disk. Increasing this number reduces the number of file handles needed to sort a SAM file, and increases the amount of RAM needed.", optional=true, common=true) + @Argument(doc = "When writing SAM files that need to be sorted, this will specify the number of records stored in RAM before spilling to disk. Increasing this number reduces the number of file handles needed to sort a SAM file, and increases the amount of RAM needed.", optional=true, common=true) public Integer MAX_RECORDS_IN_RAM = SAMFileWriterImpl.getDefaultMaxRecordsInRam(); - @Option(doc = "Whether to create a BAM index when writing a coordinate-sorted BAM file.", common=true) + @Argument(doc = "Whether to create a BAM index when writing a coordinate-sorted BAM file.", common=true) public Boolean CREATE_INDEX = Defaults.CREATE_INDEX; - @Option(doc="Whether to create an MD5 digest for any BAM or FASTQ files created. ", common=true) + @Argument(doc="Whether to create an MD5 digest for any BAM or FASTQ files created. ", common=true) public boolean CREATE_MD5_FILE = Defaults.CREATE_MD5; - @Option(shortName = StandardOptionDefinitions.REFERENCE_SHORT_NAME, doc = "Reference sequence file.", common = true, optional = true, overridable = true) - public File REFERENCE_SEQUENCE = Defaults.REFERENCE_FASTA; + @ArgumentCollection + public ReferenceArgumentCollection referenceSequence = makeReferenceArgumentCollection(); + + // This is retained for compatibility with existing code that depends on accessing it, and is populated + // after argument parsing using the value established by the user in the referenceSequence argument collection. + protected File REFERENCE_SEQUENCE = Defaults.REFERENCE_FASTA; - @Option(doc="Google Genomics API client_secrets.json file path.", common = true) + @Argument(doc="Google Genomics API client_secrets.json file path.", common = true) public String GA4GH_CLIENT_SECRETS="client_secrets.json"; - @Option(shortName = "use_jdk_deflater", doc = "Use the JDK Deflater instead of the Intel Deflater for writing compressed output", common = true) + @ArgumentCollection(doc="Special Arguments that have meaning to the argument parsing system. " + + "It is unlikely these will ever need to be accessed by the command line program") + public Object specialArgumentsCollection = useLegacyParser(getClass()) ? + new Object() : // legacy parser does not require these + new SpecialArgumentsCollection(); + + @Argument(shortName = "use_jdk_deflater", doc = "Use the JDK Deflater instead of the Intel Deflater for writing compressed output", common = true) public Boolean USE_JDK_DEFLATER = false; - @Option(shortName = "use_jdk_inflater", doc = "Use the JDK Inflater instead of the Intel Inflater for reading compressed input", common = true) + @Argument(shortName = "use_jdk_inflater", doc = "Use the JDK Inflater instead of the Intel Inflater for reading compressed input", common = true) public Boolean USE_JDK_INFLATER = false; - - private final String standardUsagePreamble = CommandLineParser.getStandardUsagePreamble(getClass()); + + private static final String[] PACKAGES_WITH_WEB_DOCUMENTATION = {"picard"}; static { // Register custom reader factory for reading data from Google Genomics @@ -148,12 +173,27 @@ */ protected abstract int doWork(); + protected boolean requiresReference() { + return false; + } + + protected ReferenceArgumentCollection makeReferenceArgumentCollection() { + return requiresReference() ? + new RequiredReferenceArgumentCollection() : + new OptionalReferenceArgumentCollection(); + } + public void instanceMainWithExit(final String[] argv) { System.exit(instanceMain(argv)); } public int instanceMain(final String[] argv) { - if (!parseArgs(argv)) { + String actualArgs[] = argv; + + if (System.getProperty(PROPERTY_CONVERT_LEGACY_COMMAND_LINE, "false").equals("true")) { + actualArgs = CommandLineSyntaxTranslater.translatePicardStyleToPosixStyle(argv); + } + if (!parseArgs(actualArgs)) { return 1; } @@ -217,7 +257,7 @@ public int instanceMain(final String[] argv) { System.getProperty("os.name"), System.getProperty("os.version"), System.getProperty("os.arch"), System.getProperty("java.vm.name"), System.getProperty("java.runtime.version"), usingIntelDeflater ? "Intel" : "Jdk", usingIntelInflater ? "Intel" : "Jdk", - commandLineParser.getVersion()); + getCommandLineParser().getVersion()); System.err.println(msg); } catch (Exception e) { /* Unpossible! */ } @@ -235,7 +275,7 @@ public int instanceMain(final String[] argv) { final String elapsedString = new DecimalFormat("#,##0.00").format(elapsedMinutes); System.err.println("[" + endDate + "] " + getClass().getName() + " done. Elapsed time: " + elapsedString + " minutes."); System.err.println("Runtime.totalMemory()=" + Runtime.getRuntime().totalMemory()); - if (ret != 0 && CommandLineParser.hasWebDocumentation(this.getClass())) System.err.println(CommandLineParser.getFaqLink()); + if (ret != 0 && hasWebDocumentation(this.getClass())) System.err.println(getFaqLink()); } } catch (Throwable e) { @@ -253,21 +293,6 @@ public int instanceMain(final String[] argv) { * to be written to the appropriate place. */ protected String[] customCommandLineValidation() { - final List ret = new ArrayList(); - for (final Object childOptionsObject : getNestedOptions().values()) { - if (childOptionsObject instanceof CommandLineProgram) { - final CommandLineProgram childClp = (CommandLineProgram)childOptionsObject; - final String[] childErrors = childClp.customCommandLineValidation(); - if (childErrors != null) { - for (final String error: childErrors) { - ret.add(error); - } - } - } - } - if (!ret.isEmpty()) { - ret.toArray(new String[ret.size()]); - } return null; } @@ -277,18 +302,30 @@ public int instanceMain(final String[] argv) { */ protected boolean parseArgs(final String[] argv) { - commandLineParser = new CommandLineParser(this); - final boolean ret = commandLineParser.parseOptions(System.err, argv); + commandLineParser = getCommandLineParser(); + + boolean ret; + try { + ret = commandLineParser.parseArguments(System.err, argv); + } catch (CommandLineException e) { + // Barclay command line parser throws on parsing/argument errors + System.err.println(commandLineParser.usage(false,false)); + System.err.println(e.getMessage()); + ret = false; + } + commandLine = commandLineParser.getCommandLine(); if (!ret) { return false; } + REFERENCE_SEQUENCE = referenceSequence.getReferenceFile(); + final String[] customErrorMessages = customCommandLineValidation(); if (customErrorMessages != null) { + System.err.print(commandLineParser.usage(false, false)); for (final String msg : customErrorMessages) { System.err.println(msg); } - commandLineParser.usage(System.err, false); return false; } return true; @@ -305,13 +342,47 @@ protected boolean parseArgs(final String[] argv) { } public String getStandardUsagePreamble() { - return standardUsagePreamble; + return getCommandLineParser().getStandardUsagePreamble(getClass()); } + /** + * @return Return the command line parser to be used. + */ public CommandLineParser getCommandLineParser() { + if (commandLineParser == null) { + commandLineParser = useLegacyParser(getClass()) ? + new LegacyCommandLineArgumentParser(this) : + new CommandLineArgumentParser(this, + Collections.EMPTY_LIST, + new HashSet<>(Collections.singleton(CommandLineParserOptions.APPEND_TO_COLLECTIONS))); + } return commandLineParser; } + /** + * Return true if the Picard command line parser should be used in place of the Barclay command line parser, + * otherwise, false. + * + * The Barclay parser is enabled by opt-in only, via the presence of a (true-valued) boolean property + * "picard.useLegacyParser", either as a System property, or property in a file called "picard.properties". + * System property value takes precedence. + * + * @return true if the legacy parser should be used + */ + public static boolean useLegacyParser(final Class clazz) { + if (useLegacyParser == null) { + String legacyPropertyValue = legacyPropertyValue = System.getProperty(PROPERTY_USE_LEGACY_PARSER); + if (null == legacyPropertyValue){ + final Properties props = PropertyUtils.loadPropertiesFile(PICARD_CMDLINE_PROPERTIES_FILE, clazz); + if (props != null) { + legacyPropertyValue = props.getProperty(PROPERTY_USE_LEGACY_PARSER); + } + } + // remember the value so we only have to load the properties file once + useLegacyParser = legacyPropertyValue == null ? true : Boolean.parseBoolean(legacyPropertyValue); + } + return useLegacyParser; + } /** * @return Version stored in the manifest of the jarfile. @@ -334,20 +405,38 @@ public void setDefaultHeaders(final List

    headers) { } /** - * @return Map of nested options, where the key is the prefix to be used when specifying Options inside of a nested - * options object, and the value is the nested options object itself. Default implementation is to return a - * map of all the fields annotated with @NestedOptions, with key being the field name. + * A typical command line program will call this to get the beginning of the usage message, + * and then typically append a description of the program, like this: + * + * public String USAGE = getStandardUsagePreamble(getClass()) + "Frobnicate the freebozle." */ - public Map getNestedOptions() { - return CommandLineParser.getNestedOptions(this); + public static String getStandardUsagePreamble(final Class mainClass) { + return "USAGE: " + mainClass.getSimpleName() +" [options]\n\n" + + (hasWebDocumentation(mainClass) ? + "Documentation: http://broadinstitute.github.io/picard/command-line-overview.html" + + mainClass.getSimpleName() + "\n\n" : + ""); + } + + /** + * Determine if a class has web documentation based on its package name + * + * @param clazz + * @return true if the class has web documentation + */ + public static boolean hasWebDocumentation(final Class clazz){ + for (final String pkg: PACKAGES_WITH_WEB_DOCUMENTATION) { + if (clazz.getPackage().getName().startsWith(pkg)) { + return true; + } + } + return false; } /** - * @return Map of nested options, where the key is the prefix to be used when specifying Options inside of a nested - * options object, and the value is the nested options object itself, for the purpose of generating help. - * Default implementation is to return the same map as getNestedOptions(). + * @return the link to a FAQ */ - public Map getNestedOptionsForHelp() { - return getNestedOptions(); + public static String getFaqLink() { + return "To get help, see http://broadinstitute.github.io/picard/index.html#GettingHelp"; } } diff --git a/src/main/java/picard/cmdline/CommandLineProgramProperties.java b/src/main/java/picard/cmdline/CommandLineProgramProperties.java deleted file mode 100644 index 1bc5f7e68..000000000 --- a/src/main/java/picard/cmdline/CommandLineProgramProperties.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * The MIT License - * - * Copyright (c) 2009 The Broad Institute - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package picard.cmdline; - -import picard.cmdline.programgroups.None; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; -import java.lang.annotation.ElementType; - -/** - * Annotates a command line program with various properties, such as usage (short and long), - * as well as to which program group it belongs. - * - * TODO: enforced that any CommandLineProgram has this property defined (use an annotation processor?). - */ -@Retention(RetentionPolicy.RUNTIME) -@Target(ElementType.TYPE) -public @interface CommandLineProgramProperties { - String usage(); - String usageShort(); - Class programGroup() default None.class; - boolean omitFromCommandLine() default false; -} diff --git a/src/main/java/picard/cmdline/CommandLineSyntaxTranslater.java b/src/main/java/picard/cmdline/CommandLineSyntaxTranslater.java new file mode 100644 index 000000000..5e5b99fa9 --- /dev/null +++ b/src/main/java/picard/cmdline/CommandLineSyntaxTranslater.java @@ -0,0 +1,33 @@ +package picard.cmdline; + +import java.util.*; +import java.util.stream.Collectors; + +/** + * Class for handling translation of Picard-style command line argument syntax to POSIX-style argument syntax; + * used for running tests written with Picard style syntax against the Barclay command line parser. + */ +public class CommandLineSyntaxTranslater { + + public static String[] translatePicardStyleToPosixStyle(final String argv[]) { + final List convertedArgs = Arrays.stream(argv).flatMap( + originalArgPair -> { + final String[] splitArgPair = originalArgPair.split("=", -1); + if (splitArgPair.length == 1) { // assume positional arg + return Arrays.stream(new String[]{ originalArgPair }); + } else if (splitArgPair.length == 2) { + // it doesn't matter whether we use the short short name token ("-") or the long name token + // ("--"), so just treat everything as if it were a short name, since the CLP will accept either + return Arrays.stream(new String[]{"-" + splitArgPair[0], splitArgPair[1]}); + } + else { + throw new RuntimeException( + "Argument syntax conversion failed. Too many \"=\" separated tokens to translate: " + originalArgPair); + } + } + ).collect(Collectors.toList()); + return convertedArgs.toArray(new String[convertedArgs.size()]); + + } + +} diff --git a/src/main/java/picard/cmdline/CreateHtmlDocForProgram.java b/src/main/java/picard/cmdline/CreateHtmlDocForProgram.java deleted file mode 100644 index c22f5b5fb..000000000 --- a/src/main/java/picard/cmdline/CreateHtmlDocForProgram.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * The MIT License - * - * Copyright (c) 2009 The Broad Institute - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package picard.cmdline; - -/** - * Print out the usage for one or more CommandLinePrograms in a form close to what is used in the Sourceforge Picard website. - * - * @author alecw@broadinstitute.org - */ -public class CreateHtmlDocForProgram { - public static void main(final String[] args) throws Exception { - for (final String clazz : args) { - CommandLineProgram mainClass = (CommandLineProgram)Class.forName(clazz).newInstance(); - CommandLineParser clp = new CommandLineParser(mainClass); - clp.htmlUsage(System.out, mainClass.getClass().getSimpleName(), false); - } - } -} diff --git a/src/main/java/picard/cmdline/CreateHtmlDocForStandardOptions.java b/src/main/java/picard/cmdline/CreateHtmlDocForStandardOptions.java deleted file mode 100644 index 553cf4fe7..000000000 --- a/src/main/java/picard/cmdline/CreateHtmlDocForStandardOptions.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * The MIT License - * - * Copyright (c) 2010 The Broad Institute - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package picard.cmdline; - -public class CreateHtmlDocForStandardOptions { - public static void main(final String[] args) throws Exception { - System.setProperty("java.io.tmpdir", ""); - System.setProperty("user.name", ""); - CommandLineParser clp = new CommandLineParser(new DummyProgram()); - clp.htmlPrintOptions(System.out, true); - } - - @CommandLineProgramProperties( - usage = "", - usageShort = "", - omitFromCommandLine = true - ) - static class DummyProgram extends CommandLineProgram { - @Override - protected int doWork() { - return 0; - } - } -} diff --git a/src/main/java/picard/cmdline/NestedOptions.java b/src/main/java/picard/cmdline/NestedOptions.java deleted file mode 100644 index 00661977d..000000000 --- a/src/main/java/picard/cmdline/NestedOptions.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * The MIT License - * - * Copyright (c) 2012 The Broad Institute - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package picard.cmdline; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/** - * Used to annotate a field in a CommandLineProgram that holds a instance containing @Option-annotated - * fields. To set a value for a nested option on the command line, use .