1. Sean Davis
  2. Snakemake

Commits

Johannes Köster  committed 0915766

Finished subworkflow support.

  • Participants
  • Parent commits 6317a8d
  • Branches master

Comments (0)

Files changed (6)

File snakemake/__init__.py

View file
  • Ignore whitespace
                         logger.warning("Executing subworkflow {}.".format(subworkflow.name))
                         if not subsnakemake(subworkflow.snakefile, workdir=subworkflow.workdir, targets=subworkflow.targets):
                             success = False
+                    if workflow.subworkflows:
+                        logger.warning("Executing main workflow.")
                 if success:
                     success = workflow.execute(
                         targets=targets, dryrun=dryrun, touch=touch,

File snakemake/parser.py

View file
  • Ignore whitespace
 
 class KeywordState(TokenAutomaton):
 
+    prefix = ""
+
     def __init__(self, snakefile, base_indent=0, dedent=0, root=True):
         super().__init__(snakefile, base_indent=base_indent, dedent=dedent, root=root)
         self.line = 0
 
     @property
     def keyword(self):
-        return self.__class__.__name__.lower()
+        return self.__class__.__name__.lower()[len(self.prefix):]
 
     def end(self):
         yield ")"
 
 
 class SubworkflowKeywordState(KeywordState):
+    prefix = "Subworkflow"
 
     def start(self):
-        yield "{keyword}=".format(keyword=self.keyword)
+        yield ", {keyword}=".format(keyword=self.keyword)
+
+    def end(self):
+        # no end needed
+        return list()
 
 
 # Global keyword states
     def __init__(self, snakefile, base_indent=0, dedent=0, root=True):
         super().__init__(snakefile, base_indent=base_indent, dedent=dedent, root=root)
         self.state = self.name
-        self.snakefile = False
-        self.workdir = False
+        self.has_snakefile = False
+        self.has_workdir = False
+        self.has_name = False
+        self.primary_token = None
 
     def end(self):
-        if not (self.snakefile or self.workdir):
-            self.error("A subworkflow needs either a path to a Snakefile or to a workdir.")
+        if not (self.has_snakefile or self.has_workdir):
+            self.error("A subworkflow needs either a path to a Snakefile or to a workdir.", self.primary_token)
         yield ")"
 
     def name(self, token):
         if is_name(token):
-            yield "workflow.subworkflow('{name}'".format(name=token.string)
-            self.state = self.colon
-        else:
-            self.error("Expected name after subworkflow keyword.", token)
-
-    def colon(self, token):
-        if is_colon(token):
+            yield "workflow.subworkflow('{name}'".format(name=token.string), token
+            self.has_name = True
+        elif is_colon(token) and self.has_name:
+            self.primary_token = token
             self.state = self.block
         else:
-            self.error("Expected colon after subworkflow name.", token)
+            self.error("Expected name after subworkflow keyword.", token)
 
     def block_content(self, token):
         if is_name(token):
             try:
                 if token.string == "snakefile":
-                    self.snakefile = True
+                    self.has_snakefile = True
                 if token.string == "workdir":
-                    self.workdir == True
+                    self.has_workdir = True
                 for t in self.subautomaton(
                     token.string).consume():
                     yield t
             yield "\n", token
             yield token.string, token
         elif is_string(token):
-            yield "\n", token
-            yield "@workflow.docstring({})".format(token.string), token
+            # ignore docstring
+            pass
         else:
             self.error("Expecting subworkflow keyword, comment or docstrings "
                 "inside a subworkflow definition.", token)
         include=Include,
         workdir=Workdir,
         ruleorder=Ruleorder,
-        rule=Rule)
+        rule=Rule,
+        subworkflow=Subworkflow)
 
     def __init__(self, snakefile, base_indent=0, dedent=0, root=True):
         super().__init__(snakefile, base_indent=base_indent, dedent=dedent, root=root)

File snakemake/workflow.py

View file
  • Ignore whitespace
         self.linemaps = dict()
         self.rule_count = 0
         self.snakefile = snakefile
+        self.basedir = os.path.dirname(snakefile)
         self.snakemakepath = os.path.abspath(snakemakepath)
         self.jobscript = jobscript
         self.persistence = None
         os.chdir(workdir)
 
         if not targets:
-            targets = [self.first_rule]
+            targets = [self.first_rule] if self.first_rule is not None else list()
         if prioritytargets is None:
             prioritytargets = list()
         if forcerun is None:
         self._ruleorder.add(*rulenames)
 
     def subworkflow(self, name, snakefile=None, workdir=None):
-        sw = Subworkflow(name, snakefile, workdir)
+        sw = Subworkflow(self, name, snakefile, workdir)
         self._subworkflows[name] = sw
         self.globals[name] = sw.target
 
 
 class Subworkflow:
 
-    def __init__(self, name, snakefile, workdir):
+    def __init__(self, workflow, name, snakefile, workdir):
+        self.workflow = workflow
         self.name = name
-        self.snakefile = snakefile
-        self.workdir = workdir
+        self._snakefile = snakefile
+        self._workdir = workdir
         self.targets = set()
 
+    @property
+    def snakefile(self):
+        if self._snakefile is None:
+            return os.path.join(self.workdir, "Snakefile")
+        return os.path.join(self.workflow.basedir, self._snakefile)
+
+    @property
+    def workdir(self):
+        workdir = "." if self._workdir is None else self._workdir
+        return os.path.join(self.workflow.basedir, workdir)
+
     def target(self, path):
         self.targets.add(path)
         return os.path.join(self.workdir, path)

File tests/test_subworkflows/Snakefile

View file
  • Ignore whitespace
+
+subworkflow test02:
+    workdir: "../test02"
+
+
+rule:
+    input: test02("test.out")
+    output: "test.out"
+    shell: "cp {input} {output}"

File tests/test_subworkflows/expected-results/test.out

View file
  • Ignore whitespace
+testz0r

File tests/tests.py

View file
  • Ignore whitespace
 
 def test_keyword_list():
 	run(dpath("test_keyword_list"))
+
+def test_subworkflows():
+    run(dpath("test_subworkflows"))