#!/usr/bin/python import itertools import optparse import os import shlex import subprocess import sys import time def run(running_jobs, cmd): p = subprocess.Popen(cmd, stdin=subprocess.DEVNULL) running_jobs.append(p) def wait_for_completion(max_jobs, running_jobs): while len(running_jobs) >= max_jobs: time.sleep(1) for job in running_jobs: if job.poll() is not None: running_jobs.remove(job) job.wait() def parse_file(fd): def count_indent(s): level = 0 for ch in s: if ch == "\t": level += 1 else: break return level for line in fd: if not line.strip(): continue # Ignore blank lines level = count_indent(line) line = line[level:] # Slice off the indentation yield level, line yield 0, None def batch_process(opts, lines): running_jobs = [] cmd = [] for level, line in lines: old_level = len(cmd) - 1 if level <= old_level: run(running_jobs, itertools.chain(*cmd)) wait_for_completion(opts.jobs, running_jobs) # Delete all options that belong to groups that are indented more than # this one cmd = cmd[:level] assert len(cmd) == level if line: cmd.append(shlex.split(line)) # Wait for remaining jobs to finish wait_for_completion(1, running_jobs) def make_and_chdir(filename): dirname = os.path.splitext(filename)[0] + ".out" try: os.makedirs(dirname) except FileExistsError: pass os.chdir(dirname) def parse_args(): parser = optparse.OptionParser(usage="%prog batchfile1 [batchfile2] ...") parser.add_option("-j", "--jobs", action="store", dest="jobs", default=1, type="int", help="The number of concurrent jobs to run") opts, args = parser.parse_args(sys.argv[1:]) opts.running_jobs = [] args = map(os.path.abspath, args) return opts, args def main(): opts, args = parse_args() filenames = list(map(os.path.abspath, args)) for filename in filenames: print("Processing", filename) make_and_chdir(filename) with open(filename) as fd: batch_process(opts, parse_file(fd)) if __name__ == "__main__": main()