Skip to content

Commit 1a1f85b

Browse files
committed
Allow fgbio tools to access meta information about themselves.
This allows SAM program records to be created.
1 parent 06b498f commit 1a1f85b

File tree

3 files changed

+111
-17
lines changed

3 files changed

+111
-17
lines changed

src/main/scala/com/fulcrumgenomics/cmdline/FgBioMain.scala

+16-5
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,12 @@ import java.io.IOException
2828
import java.net.InetAddress
2929
import java.nio.file.Paths
3030
import java.text.DecimalFormat
31-
3231
import com.fulcrumgenomics.bam.api.{SamSource, SamWriter}
3332
import com.fulcrumgenomics.cmdline.FgBioMain.FailureException
3433
import com.fulcrumgenomics.commons.CommonsDef._
3534
import com.fulcrumgenomics.commons.util.SystemUtil.IntelCompressionLibrarySupported
3635
import com.fulcrumgenomics.commons.util.{LazyLogging, LogLevel, Logger}
37-
import com.fulcrumgenomics.sopt.cmdline.CommandLineProgramParserStrings
36+
import com.fulcrumgenomics.sopt.cmdline.{CommandLineParser, CommandLineProgramParserStrings}
3837
import com.fulcrumgenomics.sopt.{Sopt, arg}
3938
import com.fulcrumgenomics.util.Io
4039
import com.fulcrumgenomics.vcf.api.VcfWriter
@@ -110,16 +109,27 @@ class FgBioMain extends LazyLogging {
110109
}
111110

112111
val startTime = System.currentTimeMillis()
113-
val exit = Sopt.parseCommandAndSubCommand[FgBioCommonArgs,FgBioTool](name, args.toIndexedSeq, Sopt.find[FgBioTool](packageList)) match {
112+
val parser = new CommandLineParser[FgBioTool](name) // Keep a reference to the parser so we can get the command line
113+
val exit = parser.parseCommandAndSubCommand[FgBioCommonArgs](args.toIndexedSeq, Sopt.find[FgBioTool](packageList)) match {
114114
case Sopt.Failure(usage) =>
115115
System.err.print(usage())
116116
1
117-
case Sopt.CommandSuccess(cmd) =>
117+
case Sopt.CommandSuccess(_) =>
118118
unreachable("CommandSuccess should never be returned by parseCommandAndSubCommand.")
119119
case Sopt.SubcommandSuccess(commonArgs, subcommand) =>
120120
FgBioCommonArgs.args = commonArgs
121121
val name = subcommand.getClass.getSimpleName
122122
try {
123+
parser.commandLine.foreach { commandLine =>
124+
parser.genericClpNameOnCommandLine
125+
subcommand.toolInfo = FgBioToolInfo(
126+
name = name,
127+
args = this.name +: args.toIndexedSeq, // make sure to include the tool set name
128+
commandLineWithDefaults = commandLine,
129+
description = parser.formatShortDescription(Sopt.inspect(subcommand.getClass).description),
130+
version = subcommand.getClass.getPackage.getImplementationVersion
131+
)
132+
}
123133
printStartupLines(name, args, commonArgs)
124134
subcommand.execute()
125135
printEndingLines(startTime, name, true)
@@ -152,7 +162,6 @@ class FgBioMain extends LazyLogging {
152162

153163
/** Prints a line of useful information when a tool starts executing. */
154164
protected def printStartupLines(tool: String, args: Array[String], commonArgs: FgBioCommonArgs): Unit = {
155-
val version = CommandLineProgramParserStrings.version(getClass, color=false).replace("Version: ", "")
156165
val host = try { InetAddress.getLocalHost.getHostName } catch { case _: Exception => "unknown-host" }
157166
val user = System.getProperty("user.name")
158167
val jreVersion = System.getProperty("java.runtime.version")
@@ -172,4 +181,6 @@ class FgBioMain extends LazyLogging {
172181

173182
/** The packages we wish to include in our command line **/
174183
protected def packageList: List[String] = List[String]("com.fulcrumgenomics")
184+
185+
private def version: String = CommandLineProgramParserStrings.version(getClass, color=false).replace("Version: ", "")
175186
}

src/main/scala/com/fulcrumgenomics/cmdline/FgBioTool.scala

+48
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,51 @@ package com.fulcrumgenomics.cmdline
2626
import com.fulcrumgenomics.cmdline.FgBioMain.FailureException
2727
import com.fulcrumgenomics.commons.util.LazyLogging
2828
import com.fulcrumgenomics.sopt.cmdline.ValidationException
29+
import com.fulcrumgenomics.util.Metric
30+
import htsjdk.samtools.{SAMFileHeader, SAMProgramRecord}
31+
32+
import scala.annotation.tailrec
33+
34+
/** Stores meta information about the command line use to invoke a tool.
35+
*
36+
* @param name the name of the tool.
37+
* @param args the list of arguments as given on the command line; this will contain any arguments also given to
38+
* [[FgBioMain]] in [[FgBioCommonArgs]].
39+
* @param commandLineWithDefaults the command line as given to the tool, along with the defaults for all other
40+
* arguments. This will include arguments given to [[FgBioMain]] in [[FgBioCommonArgs]].
41+
* @param description the description of the tool
42+
* @param version the version of the tool.
43+
*/
44+
case class FgBioToolInfo(name: String, args: Seq[String], commandLineWithDefaults: String, description: String, version: String) extends Metric {
45+
46+
/** The command line as given to the tool, without any defaults added. This will include arguments given to
47+
* [[FgBioMain]] in [[FgBioCommonArgs]].*/
48+
def commandLineWithoutDefaults: String = args.mkString(" ")
49+
50+
/** Adds a program group to the SAMFileHeader returning the ID. */
51+
def addProgramGroupTo(header: SAMFileHeader, id: Option[String]=None): SAMProgramRecord = {
52+
// Get the id
53+
val pgId = id.getOrElse {
54+
@tailrec
55+
def getPgId(intId: Int): String = if (header.getProgramRecord(intId.toString) == null) intId.toString else getPgId(intId + 1)
56+
getPgId(1)
57+
}
58+
val pg = new SAMProgramRecord(pgId)
59+
pg.setProgramName(name)
60+
pg.setCommandLine(commandLineWithDefaults)
61+
pg.setProgramVersion(version)
62+
header.addProgramRecord(pg)
63+
pg
64+
}
65+
}
66+
2967

3068
/** All fgbio tools should extend this. */
3169
trait FgBioTool extends LazyLogging {
70+
/** Meta information about the command line use to invoke this tool, or [[None]] if unset. */
71+
private var _toolInfo: Option[FgBioToolInfo] = None
72+
73+
/** All tools should implement this method. */
3274
def execute(): Unit
3375

3476
/** Fail with just an exit code. */
@@ -45,4 +87,10 @@ trait FgBioTool extends LazyLogging {
4587

4688
/** Generates a validation exception if the test value is false. */
4789
def validate(test: Boolean, message: => String) = if (!test) throw new ValidationException(message)
90+
91+
/** Meta information about the command line use to invoke this tool, or [[None]] if unset. */
92+
def toolInfo: Option[FgBioToolInfo] = _toolInfo
93+
94+
/** Sets the command line used to invoke this tool. */
95+
private[cmdline] def toolInfo_=(commandLine: FgBioToolInfo): Unit = this._toolInfo = Some(commandLine)
4896
}

src/test/scala/com/fulcrumgenomics/cmdline/ClpTests.scala

+47-12
Original file line numberDiff line numberDiff line change
@@ -26,14 +26,18 @@ package com.fulcrumgenomics.cmdline
2626

2727
import com.fulcrumgenomics.sopt.{arg, clp}
2828
import com.fulcrumgenomics.testing.UnitSpec
29+
import com.fulcrumgenomics.util.Metric
30+
31+
import java.nio.file.Path
2932

3033
/* Tis a silly CLP. */
3134
@clp(group=ClpGroups.Utilities, description="A test class")
3235
class TestClp
3336
(
3437
@arg(flag='e', doc="If set, exit with this code.") val exitCode: Option[Int],
3538
@arg(flag='m', doc="If set, fail with this message.") val message: Option[String],
36-
@arg(flag='p', doc="Print this message.") val printMe: Option[String]
39+
@arg(flag='p', doc="Print this message.") val printMe: Option[String],
40+
@arg(flag='i', doc="Write the tool information.") val infoPath: Option[Path] = None
3741
) extends FgBioTool {
3842
override def execute(): Unit = {
3943
(exitCode, message) match {
@@ -42,23 +46,54 @@ class TestClp
4246
case (None, Some(msg)) => fail(msg)
4347
case (None, None ) => printMe.foreach(println)
4448
}
49+
this.infoPath.foreach { path =>
50+
this.toolInfo.foreach { info => Metric.write(path, info) }
51+
}
4552
}
4653
}
4754

4855
/** Some basic test for the CLP classes. */
4956
class ClpTests extends UnitSpec {
50-
"FgBioMain" should "find a CLP and successfully set it up and execute it" in {
51-
new FgBioMain().makeItSo("TestClp --print-me=hello".split(' ')) shouldBe 0
52-
}
57+
// "FgBioMain" should "find a CLP and successfully set it up and execute it" in {
58+
// new FgBioMain().makeItSo("TestClp --print-me=hello".split(' ')) shouldBe 0
59+
// }
60+
//
61+
// it should "fail with the provided exit code" in {
62+
// new FgBioMain().makeItSo("TestClp -e 7".split(' ')) shouldBe 7
63+
// new FgBioMain().makeItSo("TestClp --exit-code=5".split(' ')) shouldBe 5
64+
// new FgBioMain().makeItSo("TestClp --exit-code=9 --message=FailBabyFail".split(' ')) shouldBe 9
65+
// new FgBioMain().makeItSo("TestClp --message=FailBabyFail".split(' ')) should not be 0
66+
// }
67+
//
68+
// it should "fail and print usage" in {
69+
// new FgBioMain().makeItSo("SomeProgram --with-args=that-dont-exist".split(' ')) should not be 0
70+
// }
5371

54-
it should "fail with the provided exit code" in {
55-
new FgBioMain().makeItSo("TestClp -e 7".split(' ')) shouldBe 7
56-
new FgBioMain().makeItSo("TestClp --exit-code=5".split(' ')) shouldBe 5
57-
new FgBioMain().makeItSo("TestClp --exit-code=9 --message=FailBabyFail".split(' ')) shouldBe 9
58-
new FgBioMain().makeItSo("TestClp --message=FailBabyFail".split(' ')) should not be 0
59-
}
72+
it should "provide command line information to the tool" in {
73+
val tmpPath = makeTempFile("tool_info.", ".tab")
74+
new FgBioMain().makeItSo(args=s"--async-io true TestClp -i $tmpPath".split(' ')) shouldBe 0
75+
val metrics = Metric.read[FgBioToolInfo](tmpPath)
76+
metrics should have length 1
77+
val metric = metrics.head
78+
79+
// args/commandLineWithoutDefaults
80+
metric.commandLineWithDefaults should include ("fgbio --async-io true") // argument set _prior_ to the tool name
81+
metric.commandLineWithDefaults should not include ("fgbio --compression 5") // default argument _prior_ to the tool name
82+
metric.commandLineWithoutDefaults should include ("TestClp")
83+
metric.commandLineWithoutDefaults should include (s"-i $tmpPath")
84+
metric.commandLineWithoutDefaults should not include ("--print-me :none:") // a default argument
85+
86+
// commandLineWithDefaults
87+
metric.commandLineWithDefaults should include ("fgbio --async-io true") // argument set _prior_ to the tool name
88+
metric.commandLineWithDefaults should include (" --compression 5") // default argument _prior_ to the tool namee
89+
metric.commandLineWithDefaults should include ("TestClp")
90+
metric.commandLineWithDefaults should include (s"--info-path $tmpPath")
91+
metric.commandLineWithDefaults should include ("--print-me :none:") // a default argument
92+
93+
// description
94+
metric.description shouldBe "A test class"
6095

61-
it should "fail and print usage" in {
62-
new FgBioMain().makeItSo("SomeProgram --with-args=that-dont-exist".split(' ')) should not be 0
96+
// version
97+
//metric.version shouldBe "null" // not set in scalatest
6398
}
6499
}

0 commit comments

Comments
 (0)