Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,16 @@ jdk:
scala:
- 2.11.8

script: sbt ++$TRAVIS_SCALA_VERSION clean compile
script: sbt ++$TRAVIS_SCALA_VERSION clean coverageTest

cache:
directories:
- $HOME/.ivy2/cache
- $HOME/.sbt/boot/
- $HOME/.zinc

after_success: sbt ++$TRAVIS_SCALA_VERSION travis-report

before_cache:
# Tricks to avoid unnecessary cache updates
- find $HOME/.ivy2 -name "ivydata-*.properties" -delete
Expand Down
16 changes: 14 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,17 @@
# spark-commands

[![Maven Central](https://img.shields.io/maven-central/v/org.hammerlab/spark-commands_2.11.svg?maxAge=600)](http://search.maven.org/#search%7Cga%7C1%7Cspark-commands)
[![Build Status](https://travis-ci.org/hammerlab/spark-commands.svg?branch=master)](https://travis-ci.org/hammerlab/spark-commands)
[![Coverage Status](https://coveralls.io/repos/github/hammerlab/spark-commands/badge.svg)](https://coveralls.io/github/hammerlab/spark-commands)

Interfaces for creating CLI-runnable and testable commands/apps, with [Spark](http://spark.apache.org/)-based and non-Spark flavors.

## args4j
[![Maven Central](https://img.shields.io/maven-central/v/org.hammerlab.cli/args4j_2.11.svg?maxAge=600)](http://search.maven.org/#search%7Cga%7C1%7Chammerlab%20args4j)


## case-app
[![Maven Central](https://img.shields.io/maven-central/v/org.hammerlab.cli/case-app_2.11.svg?maxAge=600)](http://search.maven.org/#search%7Cga%7C1%7Chammerlab%20case-app)




Interfaces for creating CLI-runnable and testable Spark commands/apps.
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
package org.hammerlab.commands
package org.hammerlab.cli.args4j

import org.apache.spark.SparkContext
import org.bdgenomics.utils.cli.Args4jBase

trait Args extends Args4jBase {
trait Args
extends Args4jBase {
def validate(sc: SparkContext): Unit = {}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package org.hammerlab.commands
package org.hammerlab.cli.args4j

import grizzled.slf4j.Logging
import org.bdgenomics.utils.cli.Args4j
Expand All @@ -21,7 +21,12 @@ abstract class Command[T <: Args: Manifest]
*
* @param args the command line arguments.
*/
def run(args: Array[String]): Unit = run(Args4j[T](args))
def run(args: Array[String]): Unit =
Args4j[T](args) match {
case Left(args) ⇒ run(args)
case _ ⇒
}

def run(args: String*): Unit = run(args.toArray)

def run(args: T): Unit
Expand Down
39 changes: 39 additions & 0 deletions args4j/src/main/scala/org/hammerlab/cli/args4j/SparkCommand.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package org.hammerlab.cli.args4j

import org.apache.spark.SparkContext
import org.hammerlab.spark.{ SparkConfBase, confs }

abstract class SparkCommand[T <: Args: Manifest]
extends Command[T]
with SparkConfBase
with confs.Kryo {

override def run(args: T): Unit = {
val sc = createSparkContext()
try {
args.validate(sc)
run(args, sc)
} finally {
sc.stop()
}
}

def run(args: T, sc: SparkContext): Unit

/**
* Return a spark context.
*
* Typically, most properties are set through config file / cmd-line.
* @return
*/
private def createSparkContext(): SparkContext = {
val conf = makeSparkConf

conf.getOption("spark.app.name") match {
case Some(cmdLineName) => conf.setAppName(s"$cmdLineName: $name")
case _ => conf.setAppName(name)
}

new SparkContext(conf)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package org.hammerlab.cli.args4j

import com.esotericsoftware.kryo.Kryo
import org.apache.spark.SparkContext
import org.apache.spark.serializer.KryoRegistrator
import org.hammerlab.paths.Path
import org.hammerlab.test.Suite
import org.kohsuke.args4j.Argument

class SparkCommandTest
extends Suite {
test("command") {
val outFile = tmpPath()

TestCommand.main(
Array(
outFile.toString
)
)

outFile
.lines
.toList should be(
List(
"8mb",
"org.hammerlab.cli.args4j.TestRegistrar"
)
)
}
}

class TestArgs
extends Args {
@Argument(required = true)
var outFile: String = _
}

class TestRegistrar
extends KryoRegistrator {
override def registerClasses(kryo: Kryo): Unit = {}
}

import org.hammerlab.spark.test.suite.TestConfs

object TestCommand
extends SparkCommand[TestArgs]
with TestConfs {

override def name: String = "test"
override def description: String = "test command"

sparkConf(
"spark.kryoserializer.buffer" → "8mb"
)

override def registrar = classOf[TestRegistrar]

override def run(args: TestArgs, sc: SparkContext): Unit = {
val conf = sc.getConf
Path(args.outFile).writeLines(
List(
conf.get("spark.kryoserializer.buffer"),
conf.get("spark.kryo.registrator")
)
)
}
}
36 changes: 30 additions & 6 deletions build.sbt
Original file line number Diff line number Diff line change
@@ -1,9 +1,33 @@
name := "spark-commands"
version := "1.0.4"

providedDeps += spark.value
val defaults = Seq(
organization := "org.hammerlab.cli",
deps ++= Seq(
slf4j,
spark_util % "1.3.0"
)
) ++ addSparkDeps

deps ++= Seq(
libs.value('bdg_utils_cli),
libs.value('slf4j)
lazy val args4j = project.settings(
defaults,
version := "1.1.0-SNAPSHOT",
deps += bdg_utils_cli % "0.3.0",
testDeps += Parent.autoImport.args4j
)

lazy val case_app = project.settings(
defaults,
name := "case-app",
version := "1.0.0-SNAPSHOT",
deps ++= Seq(
Parent.autoImport.case_app,
io % "1.2.0",
paths % "1.2.0"
)
)

lazy val cli_root =
rootProject(
"cli-root",
args4j,
case_app
)
34 changes: 34 additions & 0 deletions case_app/src/main/scala/org/hammerlab/cli/app/App.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package org.hammerlab.cli.app

import caseapp.core.Messages
import caseapp.{ CaseApp, Parser, RemainingArgs }
import grizzled.slf4j.Logging

abstract class App[Args : Parser : Messages]
extends CaseApp[Args]
with Logging {

final override def run(options: Args, remainingArgs: RemainingArgs): Unit =
remainingArgs match {
case RemainingArgs(args, Nil) ⇒
run(
options,
args
)
case RemainingArgs(args, unparsed) ⇒
throw new IllegalArgumentException(
s"Unparsed arguments: ${unparsed.mkString(" ")}"
)
}

def done(): Unit = {}

final def run(options: Args, remainingArgs: Seq[String]): Unit =
try {
_run(options, remainingArgs)
} finally {
done()
}

protected def _run(options: Args, remainingArgs: Seq[String]): Unit
}
36 changes: 36 additions & 0 deletions case_app/src/main/scala/org/hammerlab/cli/app/IndexingApp.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package org.hammerlab.cli.app

import caseapp.Parser
import caseapp.core.Messages
import org.hammerlab.io.Printer
import org.hammerlab.paths.Path

trait OutPathArgs {
def out: Option[Path]
}

/**
* Interface for apps that take a [[Path]] and "index" it in some way, generating an output file that is by default
* named by appending an extension to the input path.
*
* @param defaultSuffix if [[OutPathArgs.out]] is empty, construct an output path by appending this string to the argument
* value [[PathApp.path]].
*/
abstract class IndexingApp[Args <: OutPathArgs : Parser : Messages](defaultSuffix: String)
extends PathApp[Args] {
implicit var printer: Printer = _

override def init(options: Args): Unit = {
printer =
Printer(
options
.out
.getOrElse(
path + defaultSuffix
)
)
}

override def close(): Unit =
printer.close()
}
33 changes: 33 additions & 0 deletions case_app/src/main/scala/org/hammerlab/cli/app/PathApp.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package org.hammerlab.cli.app

import java.io.Closeable

import caseapp.Parser
import caseapp.core.Messages
import org.hammerlab.paths.Path

abstract class PathApp[Args : Parser : Messages]
extends App[Args]
with Closeable {

@transient implicit var path: Path = _

def init(options: Args): Unit = {}
def close(): Unit = {}

final protected override def _run(options: Args, args: Seq[String]): Unit = {
if (args.size != 1) {
throw new IllegalArgumentException(
s"Exactly one argument (a BAM file path) is required"
)
}

path = Path(args.head)

init(options)
run(options)
close()
}

protected def run(options: Args): Unit
}
Loading