hob / hob /

from os import makedirs
from os.path import exists, isdir, join, abspath, isfile, isabs, dirname, basename
from os import listdir, unlink
from subprocess import check_call, Popen, PIPE, CalledProcessError
import sys, optparse
from distutils.version import LooseVersion as Version

from hob.cmd import CommandTable
from hob.utils import _, tempdir
from hob.extension import ExtensionError
from hob.cmd import ProgramError

cmds = CommandTable()

def cmdLines(text):
    lines = text.splitlines()
    if lines:
        yield "> ", "", lines[0]
        for line in lines[1:]:
            yield "  ", " \\", line

def printCmd(text):
    for pre, post, line in cmdLines(text):
        print pre + line + post

def call(args, **kwargs):
    if "cwd" in kwargs and kwargs["cwd"]:
        printCmd("cd %s" % kwargs["cwd"])
    printCmd(" ".join(args))
    check_call(args, **kwargs)

def capture(args, **kwargs):
    if "stdout" not in kwargs and "stderr" not in kwargs:
        kwargs["stdout"] = PIPE
        kwargs["stderr"] = PIPE
    if "cwd" in kwargs and kwargs["cwd"]:
        printCmd("cd %s;" % kwargs["cwd"])
    #printCmd(" ".join(args + ["1>", "2>"]))
    p = Popen(args, **kwargs)
    out, err = p.communicate()
    if p.returncode != 0:
        CalledProcessError(p.returncode, args)
    return out, err

class Context(object):
    def __init__(self, cwd):
        self.cwd = cwd

    def call(self, args, **kwargs):
        return call(args, cwd=self.cwd, **kwargs)

    def capture(self, args, **kwargs):
        return capture(args, cwd=self.cwd, **kwargs)

def walkParents(path):
    if not isdir(path):
        path = dirname(path)
    while path:
        yield path
        parent = dirname(path)
        if parent == path or not parent:
        path = parent

def isHgDir(path):
    for root in walkParents(path):
        if exists(join(root, ".hg")):
            return True
    return False

class DocGenerator(object):
    def __init__(self):
        self.sectionCount = 0
        self.projects = {}
        self.root = None
        self.out_dir = None = None
        self.build_dir = None = None
        self.specs = None
        self.specs_dir = None
        self.doc_dir = None
        self.service_source_dir = "services"
        self.service_source_path = None

    def init(self):
        for path in (, self.out_dir):
            if not isdir(path):

        self.specs = self.projects["specs"]["context"]
        self.specs_dir = self.projects["specs"]["build"] or self.projects["specs"]["path"]
        self.doc_dir = join(self.specs_dir, "doc")
        if isabs(self.service_source_dir):
            self.service_source_path = self.service_source_dir
            self.service_source_path = join(self.specs_dir, self.service_source_dir)
        self.service_out_path = join(self.doc_dir, "services")


        if isHgDir(self.out_dir):

    def updateRepos(self):
        # Sync working directories from hg repos
        for project in self.projects.itervalues():
            if "repo" in project:
                self.newSection("SYNC %s" % project["repo"])
                self.sync(project["context"], project["repo"])

    def newSection(self, title):
        if self.sectionCount > 0:
        print "===== %s =====" % title
        self.sectionCount += 1

    def createServices(self):
        # Check version of hob to determine options to use
        hob_opts = []
        rst_opts = []
        hob_version = capture(["hob", "--version"])[1].splitlines()[0]
        if Version(hob_version) > "0.1":
            hob_opts = ["--project", join(abspath(self.service_source_path), "hob.conf")]
            rst_opts = ["--syntax", self.syntax]
            hob_opts = ["--config-file", join(abspath(self.service_source_path), "hob.conf")]
        hob_opts += ["--target",]

        if self.clean_service_dir:
            self.newSection("CLEAN SERVICES %s" % self.service_out_path)
            # Remove old service files
            if self.service_out_path and exists(self.service_out_path):
                for fname in listdir(self.service_out_path):
                    fpath = join(self.service_out_path, fname)
                    if isfile(fpath):
        # Create rst-docs from .proto files using hob
        self.newSection("SERVICES %s" % self.service_out_path)
        # TODO: It would been preferred if we could execute rst-doc directly, but it requires the ability
        #       to clone the current hob instance and setting basic settings such as --project
        call(["hob"] + hob_opts + ["rst-doc", "--out-dir", self.service_out_path] + rst_opts)

    def createDoc(self):
        # Create documentation with sphinx
        for sphinx_builder in self.sphinx_builders:
            self.newSection("SPHINX %s" % self.out_dir)
  [self.sphinx, "-b", sphinx_builder, "-c", self.doc_dir, "-d", join(self.out_dir, "doctrees"), self.doc_dir, self.out_dir])

    def createIndex(self, index_script):
        if exists(index_script):
            script_dir = dirname(index_script)
            script_name = basename(index_script)
            self.newSection("INDEX %s" % script_dir)
            call([sys.executable, script_name], cwd=script_dir)

    def sync(self, context, root):
        if exists(context.cwd):
  ["hg", "up", "-C"])
  ["hg", "pull", "-u", root])
            call(["hg", "clone", root, context.cwd])

    def updateDoc(self, path):
        self.newSection("PULL UPSTREAM %s" % path)
        context = Context(path)["hg", "pull", "-u"])

    def commitDoc(self, context, message=None):
        if not message:
            message = "Updated scope documentation"
        self.newSection("COMMIT %s" % (context.cwd or ''))["hg", "commit", "-m", message, "--addremove"])

    def pushDoc(self, context, dest=None):
        args = []
        if dest:
        self.newSection("PUSH %s" % (context.cwd or ''))["hg", "push"] + args)

def repoProject(repo, build):
    if isdir(repo):
        repo = abspath(repo)
    proj = dict(build=build,
    proj["context"] = Context(proj["build"])
    return proj

def pathProject(path):
    proj = dict(build=None,
    proj["context"] = Context(proj["build"])
    return proj

def scope_doc(ui, temp_dir, root, scope_doc, target,
              specs_dir, specs_repo, index_script,
              service_path, service_cleanup,
              commit, commit_message, push, push_dest,
    """Generate documentation for the scope protocol and related services.
    gen = DocGenerator()

    if not root:
        raise ProgramError("No root specified, cannot create documentation without it.")
    gen.root = root
    gen.out_dir = abspath(gen.root)
    if scope_doc:
        if isabs(scope_doc):
            gen.out_dir = scope_doc
            gen.out_dir = join(gen.out_dir, scope_doc)

    if not temp_dir:
        temp_dir = tempdir(["scope-doc"]) = gen.build_dir = temp_dir = target or "all"
    gen.syntax = 'scope'
    gen.sphinx = sphinx_cmd
    gen.sphinx_builders = ["html"]
    gen.clean_service_dir = service_cleanup

    gen.projects = {}

    if service_path:
        gen.service_source_dir = service_path

    if not specs_repo and not specs_dir:
        if exists("doc/") and exists("services/hob.conf"):
            specs_dir = ""
            gen.service_source_dir = "services"
            specs_repo = ""

    if specs_repo:
        gen.projects["specs"] = repoProject(specs_repo, abspath(join(, "specs")))
        gen.projects["specs"] = pathProject(specs_dir)



    if not index_script:
        index = abspath(join(root, ""))
        if exists(index):
            index_script = index
    if index_script:

    if commit:
        doc_context = Context(gen.root)
        # TODO: Should figure out if there are any modified files present and only commit when modified
        gen.commitDoc(doc_context, commit_message)
        if push:
            gen.pushDoc(doc_context, push_dest)

    print "Documentation has been generated in %s" % gen.out_dir