#!/usr/bin/env python3
"""
Run a command on many subdirectories, collecting results and output (in
parallel), and potentially filtering on some condition.

e.g. ::

  many dir1 dir2 dir3 -- grep etc/env LIB
  many -e .hg -- hg update

"""
__author__ = 'Martin Blais <blais@furius.ca>'
__license__ = 'GNU GPL'

import sys
import os
import time
import optparse
import logging
from os.path import *
from subprocess import *
from concurrent import futures


def runcmd(dn, cmd):
    p = Popen(cmd, shell=True, cwd=dn, stdout=PIPE, stderr=PIPE)
    return p.communicate()


def main():
    logging.basicConfig(level=logging.INFO,
                        format='%(levelname)-8s: %(message)s')

    # Split args and command.
    try:
        i = sys.argv.index('--')
        j = i+1
    except ValueError:
        i = j = 1
    args, command = sys.argv[:i], sys.argv[j:]

    parser = optparse.OptionParser(__doc__.strip())

    parser.add_option('-e', '--exists', action='store',
                      help="Only consider the directory if the given relative filename exists.")

    parser.add_option('-d', '--dir', action='store', default=os.getcwd(),
                      help="Run on subdirectories of the given subdirectory.")

    parser.add_option('-j', '--jobs', action='store', type=int,
                      help="Number of concurrent jobs to run.")

    opts, dirlist = parser.parse_args(args[1:])


    if not command or command[0].startswith('-'):
        parser.error("Invalid command: '%s'" % command)
    logging.info("Command: '%s'" % ' '.join(command))
    command = ' '.join(command)

    os.chdir(opts.dir)
    if not dirlist:
        dirlist = os.listdir(os.getcwd())

    executor = futures.ThreadPoolExecutor(max_workers=opts.jobs)
    futlist = []
    for dn in filter(isdir, map(abspath, dirlist)):
        if opts.exists and not exists(join(dn, opts.exists)):
            continue
        fut = executor.submit(runcmd, dn, command)
        fut.dn = dn
        futlist.append(fut)

    for fut in futures.as_completed(futlist):
        logging.info("-"*32 + fut.dn)
        out, err = fut.result()
        sys.stdout.write(out.decode('utf8'))
        sys.stdout.flush()
        sys.stderr.write(err.decode('utf8'))
        sys.stderr.flush()


if __name__ == '__main__':
    main()
