Skip to content
This repository has been archived by the owner on Jan 25, 2018. It is now read-only.

Commit

Permalink
Release 0.8
Browse files Browse the repository at this point in the history
  • Loading branch information
kshakir committed Jan 5, 2017
2 parents eed8bd7 + 99c7d22 commit 5314510
Show file tree
Hide file tree
Showing 4 changed files with 211 additions and 5 deletions.
103 changes: 103 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,109 @@ $ java -jar wdltool.jar highlight test.wdl html
}
```

## graph

The syntax of the graph command is:
```
wdltool graph [--all] wdlFile.wdl
```

Given a WDL file input, command generates the data-flow graph through the system in `.dot` format.

For example the fork-join WDL:
```
task mkFile {
command {
for i in `seq 1 1000`
do
echo $i
done
}
output {
File numbers = stdout()
}
runtime {docker: "ubuntu:latest"}
}
task grep {
String pattern
File in_file
command {
grep '${pattern}' ${in_file} | wc -l
}
output {
Int count = read_int(stdout())
}
runtime {docker: "ubuntu:latest"}
}
task wc {
File in_file
command {
cat ${in_file} | wc -l
}
output {
Int count = read_int(stdout())
}
runtime {docker: "ubuntu:latest"}
}
task join {
Int grepCount
Int wcCount
command {
expr ${wcCount} / ${grepCount}
}
output {
Int proportion = read_int(stdout())
}
runtime {docker: "ubuntu:latest"}
}
workflow forkjoin {
call mkFile
call grep { input: in_file = mkFile.numbers }
call wc { input: in_file=mkFile.numbers }
call join { input: wcCount = wc.count, grepCount = grep.count }
output {
join.proportion
}
}
```

Produces the DAG:
```
digraph forkjoin {
"call forkjoin.mkFile" -> "call forkjoin.wc"
"call forkjoin.mkFile" -> "call forkjoin.grep"
"call forkjoin.wc" -> "call forkjoin.join"
"call forkjoin.grep" -> "call forkjoin.join"
}
```

### The --all flag

If this flag is set, all WDL graph nodes become nodes in the generated DAG, even if they are not "executed". Typically this will mean task declarations and call outputs.
For example in the above example, with `--all` you would get:

```
digraph forkjoin {
"call forkjoin.grep" -> "String forkjoin.grep.pattern"
"call forkjoin.grep" -> "output { forkjoin.grep.count = read_int(stdout()) }"
"call forkjoin.grep" -> "File forkjoin.grep.in_file"
"call forkjoin.wc" -> "output { forkjoin.wc.count = read_int(stdout()) }"
"call forkjoin.grep" -> "call forkjoin.join"
"call forkjoin.wc" -> "File forkjoin.wc.in_file"
"call forkjoin.mkFile" -> "call forkjoin.grep"
"call forkjoin.join" -> "output { forkjoin.join.proportion = read_int(stdout()) }"
"call forkjoin.join" -> "Int forkjoin.join.wcCount"
"call forkjoin.wc" -> "call forkjoin.join"
"call forkjoin.mkFile" -> "output { forkjoin.mkFile.numbers = stdout() }"
"call forkjoin.mkFile" -> "call forkjoin.wc"
"call forkjoin.join" -> "Int forkjoin.join.grepCount"
}
```

# Getting Started with WDL

For documentation and many examples on how to use WDL see [the WDL website](https://software.broadinstitute.org/wdl/).
4 changes: 2 additions & 2 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ scalaVersion := "2.11.8"

lazy val versionSettings = Seq(
// Upcoming release, or current if we're on the master branch
git.baseVersion := "0.7",
git.baseVersion := "0.8",

// Shorten the git commit hash
git.gitHeadCommit := git.gitHeadCommit.value map { _.take(7) },
Expand All @@ -34,7 +34,7 @@ resolvers ++= Seq(
)

libraryDependencies ++= Seq(
"org.broadinstitute" %% "wdl4s" % "0.7-799567f-SNAP",
"org.broadinstitute" %% "wdl4s" % "0.8",
//---------- Test libraries -------------------//
"org.scalatest" %% "scalatest" % "2.2.5" % Test
)
Expand Down
77 changes: 77 additions & 0 deletions src/main/scala/wdltool/GraphPrint.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package wdltool

import java.nio.file.{Files, Paths}

import wdl4s.{CallOutput, Declaration, If, Scatter, _}
import scala.collection.JavaConverters._

object GraphPrint {

case class WorkflowDigraph(workflowName: String, digraph: Set[String])

def generateWorkflowDigraph(file: String, allNodesMode: Boolean): WorkflowDigraph = {
val namespace = WdlNamespaceWithWorkflow.load(Files.readAllLines(Paths.get(file)).asScala.mkString(System.lineSeparator()), Seq(WdlNamespace.fileResolver _))

val digraph = if (allNodesMode) {
listAllGraphNodes(namespace)
} else {
val executables = GraphPrint.listExecutableGraphNodes(namespace.workflow)
listAllGraphNodes(namespace, graphNode => executables.contains(graphNode))
}

WorkflowDigraph(namespace.workflow.unqualifiedName, digraph)
}

private def defaultFilter: GraphNode => Boolean = _ => true

private def listAllGraphNodes(namespace: WdlNamespaceWithWorkflow, filter: GraphNode => Boolean = defaultFilter): Set[String] = {

val graphNodes = namespace.descendants collect {
case g: GraphNode if filter(g) => g
}

graphNodes flatMap { graphNode =>
val name = graphName(graphNode)
val initialSet: Set[String] = graphNode match {
case c: Call => Set(s""""${dotSafe(name)}"""")
case _ => Set.empty
}
val upstreamLinks = graphNode.upstream collect {
case upstream if filter(upstream) =>
val upstreamName = graphName(upstream)
s""""${dotSafe(upstreamName)}" -> "${dotSafe(name)}""""
}

initialSet ++ upstreamLinks
}
}

private def listExecutableGraphNodes(s: Scope): Set[GraphNode] = {
s.children.toSet flatMap { child: Scope => child match {
case call: Call => Set[GraphNode](call)
case scatter: Scatter => Set[GraphNode](scatter) ++ listExecutableGraphNodes(scatter)
case i: If => Set[GraphNode](i) ++ listExecutableGraphNodes(i)
case declaration: Declaration => Set[GraphNode](declaration)
case _ => Set.empty[GraphNode]
}}
}


private def dotSafe(s: String) = s.replaceAllLiterally("\"", "\\\"")

private def graphName(g: GraphNode): String = g match {
case d: Declaration =>
val exprString = d.expression.map(e => " = " + e.toWdlString).getOrElse("")
s"${d.wdlType.toWdlString} ${d.fullyQualifiedName}$exprString"
case c: Call =>
s"call ${c.fullyQualifiedName}"
case i: If =>
s"if (${i.condition.toWdlString})"
case s: Scatter =>
s"scatter (${s.item} in ${s.collection.toWdlString})"
case c: CallOutput =>
val exprString = c.expression.map(e => " = " + e.toWdlString).getOrElse("")
s"output { ${c.fullyQualifiedName}$exprString }"
case other => s"${other.getClass.getSimpleName}: ${other.fullyQualifiedName}"
}
}
32 changes: 29 additions & 3 deletions src/main/scala/wdltool/Main.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@ package wdltool
import java.nio.file.Paths

import wdl4s.formatter.{AnsiSyntaxHighlighter, HtmlSyntaxHighlighter, SyntaxFormatter}
import wdl4s.{AstTools, WdlNamespace, WdlNamespaceWithWorkflow}
import wdl4s._
import spray.json._


import scala.util.{Failure, Success, Try}

object Main extends App {
Expand Down Expand Up @@ -33,6 +34,7 @@ object Main extends App {
case Some(x) if x == Actions.Highlight => highlight(args.tail)
case Some(x) if x == Actions.Inputs => inputs(args.tail)
case Some(x) if x == Actions.Parse => parse(args.tail)
case Some(x) if x == Actions.Graph => graph(args.tail)
case _ => BadUsageTermination
}
}
Expand Down Expand Up @@ -72,6 +74,23 @@ object Main extends App {
}
}

def graph(args: Seq[String]): Termination = {
continueIf(args.length == 1 || (args.length == 2 && args.head.equals("--all"))) {

val (file, allNodesMode) =
if (args.size == 1) (args.head, false)
else (args(1), true)

val workflowDigraph = GraphPrint.generateWorkflowDigraph(file, allNodesMode)

val result = s"""|digraph ${workflowDigraph.workflowName} {
| ${workflowDigraph.digraph.mkString(System.lineSeparator + " ")}
|}
|"""
SuccessfulTermination(result.stripMargin)
}
}

private[this] def continueIf(valid: => Boolean)(block: => Termination): Termination = if (valid) block else BadUsageTermination

private[this] def loadWdl(path: String)(f: WdlNamespace => Termination): Termination = {
Expand All @@ -88,7 +107,7 @@ object Main extends App {
} yield action

object Actions extends Enumeration {
val Parse, Validate, Highlight, Inputs = Value
val Parse, Validate, Highlight, Inputs, Graph = Value
}

val UsageMessage = """
Expand Down Expand Up @@ -119,7 +138,14 @@ object Main extends App {
| abstract syntax tree if it is valid, and a syntax error
| otherwise. Note that higher-level AST checks are not done
| via this sub-command and the 'validate' subcommand should
| be used for full validation
| be used for full validation.
|graph [--all] <WDL file>
|
| Reads a WDL file against the grammar and prints out a
| .dot of the DAG if it is valid, and a syntax error
| otherwise.
| Use [--all] to show all graph nodes in the WDL spec,
| even the non-executable nodes.
""".stripMargin

val termination = dispatchCommand(args)
Expand Down

0 comments on commit 5314510

Please sign in to comment.