Commits

Jan Borsodi committed dc1731b

Added new command: scope-doc
This command is used to generate all documentation for the scope protocol, uses sphinx to build HTML documentation from service descriptions and protocol documents.

  • Participants
  • Parent commits 02a1580

Comments (0)

Files changed (2)

+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)
+
+class DocGenerator(object):
+    def __init__(self):
+        self.sectionCount = 0
+
+    def init(self):
+        for path in (self.build, self.out_dir):
+            if not isdir(path):
+                makedirs(path)
+
+        self.service_source_dir = self.projects["services"]["build"] or self.projects["services"]["path"]
+        self.specs = self.projects["specs"]["context"]
+        self.specs_dir = self.projects["specs"]["build"] or self.projects["specs"]["path"]
+        if self.service_dir:
+            if isabs(self.service_dir):
+                self.service_path = self.service_dir
+            else:
+                self.service_path = abspath(join(self.specs_dir, self.service_dir))
+        else:
+            self.service_path = self.specs_dir
+
+        self.updateRepos()
+
+    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
+        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_dir), "hob.conf")]
+            rst_opts = ["--syntax", self.syntax]
+        else:
+            hob_opts = ["--config-file", join(abspath(self.service_dir), "hob.conf")]
+        hob_opts += ["--target", self.target]
+
+        if self.clean_service_dir:
+            self.newSection("CLEAN SERVICES %s" % self.service_path)
+            # Remove old service files
+            if self.service_path and exists(self.service_path):
+                for fname in listdir(self.service_path):
+                    fpath = join(self.service_path, fname)
+                    if isfile(fpath):
+                        unlink(fpath)
+        # Create rst-docs from .proto files using hob
+        self.newSection("SERVICES %s" % self.service_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_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.specs.call([self.sphinx, "-b", sphinx_builder, "-c", self.specs_dir, "-d", join(self.out_dir, "doctrees"), self.specs_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):
+            context.call(["hg", "up", "-C"])
+            context.call(["hg", "pull", "-u", root])
+        else:
+            call(["hg", "clone", root, context.cwd])
+
+    def commitDoc(self, context, message=None):
+        if not message:
+            message = "Updated scope documentation"
+        self.newSection("COMMIT %s" % (context.cwd or ''))
+        context.call(["hg", "commit", "-m", message, "--addremove"])
+
+    def pushDoc(self, context, dest=None):
+        args = []
+        if dest:
+            args.append(dest)
+        self.newSection("PUSH %s" % (context.cwd or ''))
+        context.call(["hg", "push"] + args)
+
+def repoProject(repo, build):
+    if isdir(repo):
+        repo = abspath(repo)
+    proj = dict(build=build,
+                repo=repo,
+                )
+    proj["context"] = Context(proj["build"])
+    return proj
+
+def pathProject(path):
+    proj = dict(build=None,
+                path=path,
+                )
+    proj["context"] = Context(proj["build"])
+    return proj
+
+def scope_doc(ui, temp_dir, doc_root, scope_doc, target, specs_dir, specs_repo, syntax, index_script,
+              sphinx_cmd, sphinx_builder,
+              service_dir, service_repo, service_out_dir, service_cleanup,
+              commit, commit_message, push, push_dest,
+              **kwargs):
+    """Generate documentation for the scope protocol and related services.
+    """
+    gen = DocGenerator()
+
+    if not doc_root:
+        raise ProgramError("No --doc-root specified, cannot create documentation without it.")
+    gen.doc_root = doc_root
+    gen.out_dir = abspath(gen.doc_root)
+    if scope_doc:
+        if isabs(scope_doc):
+            gen.out_dir = scope_doc
+        else:
+            gen.out_dir = join(gen.out_dir, scope_doc)
+
+    if not temp_dir:
+        temp_dir = tempdir(["scope-doc"])
+    gen.build = gen.build_dir = temp_dir
+    gen.target = target or "all"
+    gen.syntax = syntax
+    gen.sphinx = sphinx_cmd
+    if not sphinx_builder:
+        sphinx_builder = ["html"]
+    gen.sphinx_builders = sphinx_builder
+    
+    gen.service_dir = service_out_dir
+    gen.clean_service_dir = service_cleanup
+
+    gen.projects = {}
+
+    if specs_repo:
+        gen.projects["specs"] = repoProject(specs_repo, abspath(join(gen.build, "specs")))
+    else:
+        if not specs_dir:
+            specs_dir = ""
+        gen.projects["specs"] = pathProject(specs_dir)
+
+    if service_repo:
+        gen.projects["services"] = repoProject(service_repo, abspath(join(gen.build, "services")))
+    elif service_dir:
+        if not exists(service_dir):
+            raise ProgramError("--service-dir does not point to a valid folder")
+        gen.projects["services"] = pathProject(service_dir)
+    else:
+        raise ProgramError("No service files specified, either specify --service-dir or --service-repo")
+
+    gen.init()
+
+    gen.createServices()
+    gen.createDoc()
+
+    if index_script:
+        gen.createIndex(index_script)
+
+    if commit:
+        doc_context = Context(gen.doc_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)
     import hob._js
     hob._js.generate_services(ui, ui.config.currentTarget(), services, out_dir, test_framework or js_test_framework, console_logger_tutorial)
 
+@cmds.add(
+    [
+      ('', 'temp_dir', None,
+       _("Directory to use for temporary files (e.g. local clones of Mercurial repos). Default is to create one in the home folder under .hob (~/.hob)")),
+      ('', 'doc_root', None,
+        _("Root directory where final documentation is located. See also --scope-doc.")),
+      ('', 'scope_doc', 'scope',
+        _("Sub-directory where generated HTML documentation for scope is placed, this is either relative to --doc_root or absolute. Default is a sub-directory named 'scope'.")),
+      ('', 'index_script', None,
+        _("Path to script which is used to make the main documentation index. If a relative path (or just name) is used it will assume it is located in --doc-root")),
+      ('', 'specs_dir', None,
+        _("Path to specs and documentation files. Selected services will be generated in a sub-folder called 'services'.")),
+      ('', 'specs_repo', None,
+        _("""URL or path to Mercurial repository specs and documentation files. It will be cloned to a temporary folder.
+          Overrides --specs-dir""")),
+      ('', 'syntax', 'scope',
+       _("Syntax to use for exported proto files. Default is 'scope'.")),
+      ('', 'sphinx_cmd', 'sphinx-build',
+       _("The name of the sphinx tool to execute. Default is 'sphinx-build'")),
+      ('', 'sphinx_builder', [],
+       _("Which sphinx builder to use, default is 'html'. Specify multiple times for additional builders.")),
+      ('', 'service_dir', None,
+       _("Directory path where service files and project file (hob.conf) can be found.")),
+      ('', 'service_repo', None,
+        _("""URL or path to Mercurial repository containing service files. It will be cloned to a temporary folder.
+          Overrides --service-dir""")),
+      ('', 'service_out_dir', 'services',
+       _("Path to where the generated service documentation files should be placed, either relative from specs folder or absolute. Default is 'services'")),
+      ('', 'service_cleanup', False,
+       _("If specified then all files in --service-out-dir will be removed before generating new ones. Default is to do nothing.")),
+
+      ('', 'commit', False,
+       _("If specified then it will assume --out-dir is a Mercurial repo and commit any changes (added/removed files as well) in it. Commit message can be customized with --commit-message")),
+      ('', 'commit_message', 'Updated scope documentation',
+       _("Commit message to use when commiting changes. Default is 'Updated scope documentation'")),
+      ('', 'push', False,
+       _("If specified then it will also push any commited changes in --out-dir. Will only push if --commit is specified.")),
+      ('', 'push_dest', None,
+       _("Destination name/url to use when pushing changes. Default is to not specify the destination and let Mercurial pick a default.")),
+     ],
+    [
+     ],
+    mutex=[('specs_dir', 'specs_repo'),
+           ('service_dir', 'service_repo')],
+    )
+def scope_doc(ui, temp_dir, doc_root, scope_doc, target, specs_dir, specs_repo, syntax, index_script,
+              sphinx_cmd, sphinx_builder,
+              service_dir, service_repo, service_out_dir, service_cleanup,
+              commit, commit_message, push, push_dest,
+              **kwargs):
+    """Generate documentation for the scope protocol and related services.
+    """
+    import hob._scope
+    hob._scope.scope_doc(ui, temp_dir, doc_root, scope_doc, target, specs_dir, specs_repo, syntax, index_script,
+                         sphinx_cmd, sphinx_builder,
+                         service_dir, service_repo, service_out_dir, service_cleanup,
+                         commit, commit_message, push, push_dest,
+                         **kwargs)
+
 def main(args=None):
     try:
         from mako.template import Template