Example #1
0
def orange_files(event):
    """beautify one or more files at c.p."""
    c = event.get('c')
    if not c or not c.p:
        return
    t1 = time.process_time()
    tag = 'beautify-files'
    g.es(f"{tag}...")
    settings = orange_settings(c)
    roots = g.findRootsWithPredicate(c, c.p)
    n_changed = 0
    for root in roots:
        filename = g.fullPath(c, root)
        if os.path.exists(filename):
            print('')
            print(f"{tag}: {g.shortFileName(filename)}")
            changed = leoAst.Orange(settings=settings).beautify_file(filename)
            if changed:
                n_changed += 1
            changed_s = 'changed' if changed else 'unchanged'
            g.es(f"{changed_s:>9}: {g.shortFileName(filename)}")
        else:
            print('')
            print(f"{tag}: file not found:{filename}")
            g.es(f"{tag}: file not found:\n{filename}")
    t2 = time.process_time()
    print('')
    g.es_print(f"total files: {len(roots)}, "
               f"changed files: {n_changed}, "
               f"in {t2 - t1:5.2f} sec.")
Example #2
0
def fstringify_files_silent(event):
    """Silently fstringifying the external files at c.p."""
    c = event.get('c')
    if not c or not c.p:
        return
    t1 = time.process_time()
    tag = 'silent-fstringify-files'
    g.es(f"{tag}...")
    n_changed = 0
    roots = g.findRootsWithPredicate(c, c.p)
    for root in roots:
        filename = g.fullPath(c, root)
        if os.path.exists(filename):
            changed = leoAst.Fstringify().fstringify_file_silent(filename)
            if changed:
                n_changed += 1
        else:
            print('')
            print(f"File not found:{filename}")
            g.es(f"File not found:\n{filename}")
    t2 = time.process_time()
    print('')
    n_tot = len(roots)
    g.es_print(f"{n_tot} total file{g.plural(len(roots))}, "
               f"{n_changed} changed file{g.plural(n_changed)} "
               f"in {t2 - t1:5.2f} sec.")
Example #3
0
def orange_diff_files(event):
    """
    Show the diffs that would result from beautifying the external files at
    c.p.
    """
    c = event.get('c')
    if not c or not c.p:
        return
    t1 = time.process_time()
    tag = 'beautify-files-diff'
    g.es(f"{tag}...")
    settings = orange_settings(c)
    roots = g.findRootsWithPredicate(c, c.p)
    for root in roots:
        filename = g.fullPath(c, root)
        if os.path.exists(filename):
            print('')
            print(f"{tag}: {g.shortFileName(filename)}")
            changed = leoAst.Orange(
                settings=settings).beautify_file_diff(filename)
            changed_s = 'changed' if changed else 'unchanged'
            g.es(f"{changed_s:>9}: {g.shortFileName(filename)}")
        else:
            print('')
            print(f"{tag}: file not found:{filename}")
            g.es(f"file not found:\n{filename}")
    t2 = time.process_time()
    print('')
    g.es_print(
        f"{tag}: {len(roots)} file{g.plural(len(roots))} in {t2 - t1:5.2f} sec."
    )
Example #4
0
    def run(self):
        '''Run Pylint on all Python @<file> nodes in c.p's tree.'''
        c, root = self.c, self.c.p
        if not self.import_lint():
            return
        self.rc_fn = self.get_rc_file()
        if not self.rc_fn:
            return
        # Make sure Leo is on sys.path.
        leo_path = g.os_path_finalize_join(g.app.loadDir, '..')
        if leo_path not in sys.path:
            sys.path.append(leo_path)

        # Ignore @nopylint trees.

        def predicate(p):
            for parent in p.self_and_parents():
                if g.match_word(parent.h, 0, '@nopylint'):
                    return False
            return p.isAnyAtFileNode() and p.h.strip().endswith('.py')

        roots = g.findRootsWithPredicate(c, root, predicate=predicate)
        data = [(self.get_fn(p), p.copy()) for p in roots]
        data = [z for z in data if z[0] is not None]
        if not data:
            g.es('pylint: no files found', color='red')
            return
        for fn, p in data:
            self.run_pylint(fn, p)
Example #5
0
    def run(self, p=None, force=False, pyflakes_errors_only=False):
        '''Run Pyflakes on all Python @<file> nodes in c.p's tree.'''
        c = self.c
        root = p or c.p
        # Make sure Leo is on sys.path.
        leo_path = g.os_path_finalize_join(g.app.loadDir, '..')
        if leo_path not in sys.path:
            sys.path.append(leo_path)
        t1 = time.time()

        def predicate(p):
            return p.isAnyAtFileNode() and p.h.strip().endswith('.py')

        roots = g.findRootsWithPredicate(c, root, predicate)
        if root:
            paths = [self.finalize(z) for z in roots]
            # These messages are important for clarity.
            log_flag = not force
            total_errors = self.check_all(log_flag, paths,
                                          pyflakes_errors_only)
            if total_errors > 0:
                g.es('ERROR: pyflakes: %s error%s' %
                     (total_errors, g.plural(total_errors)))
            elif force:
                g.es('OK: pyflakes: %s file%s in %s' %
                     (len(paths), g.plural(paths), g.timeSince(t1)))
            elif not pyflakes_errors_only:
                g.es('OK: pyflakes')
            ok = total_errors == 0
        else:
            ok = True
        return ok
Example #6
0
 def run(self, p=None, force=False, pyflakes_errors_only=False):
     """Run Pyflakes on all Python @<file> nodes in c.p's tree."""
     if not pyflakes:
         return True  # Pretend all is fine.
     c = self.c
     root = p or c.p
     # Make sure Leo is on sys.path.
     leo_path = g.os_path_finalize_join(g.app.loadDir, '..')
     if leo_path not in sys.path:
         sys.path.append(leo_path)
     t1 = time.time()
     roots = g.findRootsWithPredicate(c, root, predicate=None)
     if roots:
         # These messages are important for clarity.
         log_flag = not force
         total_errors = self.check_all(log_flag, pyflakes_errors_only,
                                       roots)
         if total_errors > 0:
             g.es(
                 f"ERROR: pyflakes: {total_errors} error{g.plural(total_errors)}"
             )
         elif force:
             g.es(f"OK: pyflakes: "
                  f"{len(roots)} file{g.plural(roots)} "
                  f"in {g.timeSince(t1)}")
         elif not pyflakes_errors_only:
             g.es('OK: pyflakes')
         ok = total_errors == 0
     else:
         ok = True
     return ok
Example #7
0
def find_missing_docstrings(event):
    """Report missing docstrings in the log, with clickable links."""
    c = event.get('c')
    if not c:
        return
    #@+others # Define functions
    #@+node:ekr.20190615181104.1: *4* function: has_docstring
    def has_docstring(lines, n):
        """
        Returns True if function/method/class whose definition
        starts on n-th line in lines has a docstring
        """
        # By Виталије Милошевић.
        for line in lines[n:]:
            s = line.strip()
            if not s or s.startswith('#'):
                continue
            if s.startswith(('"""', "'''")):
                return True
        return False

    #@+node:ekr.20190615181104.2: *4* function: is_a_definition
    def is_a_definition(line):
        """Return True if line is a definition line."""
        # By Виталије Милошевић.
        # It may be useful to skip __init__ methods because their docstring
        # is usually docstring of the class
        return (line.startswith(('def ', 'class '))
                and not line.partition(' ')[2].startswith('__init__'))

    #@+node:ekr.20190615182754.1: *4* function: is_root
    def is_root(p):
        """
        A predicate returning True if p is an @<file> node that is not under @nopylint.
        """
        for parent in p.self_and_parents():
            if g.match_word(parent.h, 0, '@nopylint'):
                return False
        return p.isAnyAtFileNode() and p.h.strip().endswith('.py')

    #@-others
    count, files, found, t1 = 0, 0, [], time.process_time()
    for root in g.findRootsWithPredicate(c, c.p, predicate=is_root):
        files += 1
        for p in root.self_and_subtree():
            lines = p.b.split('\n')
            for i, line in enumerate(lines):
                if is_a_definition(line) and not has_docstring(lines, i):
                    count += 1
                    if root.v not in found:
                        found.append(root.v)
                        g.es_print('')
                        g.es_print(root.h)
                    print(line)
                    g.es_clickable_link(c, p, i + 1, line)  # *Local* index.
                    break
    g.es_print('')
    g.es_print(f"found {count} missing docstring{g.plural(count)} "
               f"in {files} file{g.plural(files)} "
               f"in {time.process_time() - t1:5.2f} sec.")
 def run(self, p=None, force=False, pyflakes_errors_only=False):
     '''Run Pyflakes on all Python @<file> nodes in c.p's tree.'''
     c = self.c
     root = p or c.p
     # Make sure Leo is on sys.path.
     leo_path = g.os_path_finalize_join(g.app.loadDir, '..')
     if leo_path not in sys.path:
         sys.path.append(leo_path)
     t1 = time.time()
     roots = g.findRootsWithPredicate(c, root, predicate=None)
     if root:
         paths = [self.finalize(z) for z in roots]
         # These messages are important for clarity.
         log_flag = not force
         total_errors = self.check_all(log_flag, paths, pyflakes_errors_only, roots=roots)
         if total_errors > 0:
             g.es('ERROR: pyflakes: %s error%s' % (
                 total_errors, g.plural(total_errors)))
         elif force:
             g.es('OK: pyflakes: %s file%s in %s' % (
                 len(paths), g.plural(paths), g.timeSince(t1)))
         elif not pyflakes_errors_only:
             g.es('OK: pyflakes')
         ok = total_errors == 0
     else:
         ok = True
     return ok
Example #9
0
    def command_helper(self, event, kind, preview, verbose):
        def predicate(p):
            return self.filename(p)

        # Find all roots.

        t1 = time.time()
        c = self.c
        self.kind = kind
        p = event.p if event and hasattr(event, 'p') else c.p
        roots = g.findRootsWithPredicate(c, p, predicate=predicate)
        if not roots:
            g.warning('No @adoc nodes in', p.h)
            return []
        # Write each root to a file.
        i_paths = []
        for p in roots:
            try:
                i_path = self.filename(p)
                # #1398.
                i_path = c.expand_path_expression(i_path)
                i_path = g.os_path_finalize(i_path)
                with open(i_path, 'w', encoding='utf-8',
                          errors='replace') as self.output_file:
                    self.write_root(p)
                    i_paths.append(i_path)
            except IOError:
                g.es_print(f"Can not open {i_path!r}")
            except Exception:
                g.es_print(f"Unexpected exception opening {i_path!r}")
                g.es_exception()
        # Convert each file to html.
        o_paths = []
        for i_path in i_paths:
            o_path = self.compute_opath(i_path)
            o_paths.append(o_path)
            if kind == 'adoc':
                self.run_asciidoctor(i_path, o_path)
            elif kind == 'pandoc':
                self.run_pandoc(i_path, o_path)
            elif kind == 'sphinx':
                self.run_sphinx(i_path, o_path)
            else:
                g.trace('BAD KIND')
                return None
            if kind != 'sphinx':
                print(f"{kind}: wrote {o_path}")
        if preview:
            if kind == 'sphinx':
                g.es_print('preview not available for sphinx')
            else:
                # open .html files in the default browser.
                g.execute_shell_commands(o_paths)
        t2 = time.time()
        if verbose:
            n = len(i_paths)
            g.es_print(f"{kind}: wrote {n} file{g.plural(n)} "
                       f"in {(t2-t1):4.2f} sec.")
        return i_paths
Example #10
0
 def run(self, p):
     """Run mypy on all Python @<file> nodes in c.p's tree."""
     c = self.c
     root = p.copy()
     # Make sure Leo is on sys.path.
     leo_path = g.os_path_finalize_join(g.app.loadDir, '..')
     if leo_path not in sys.path:
         sys.path.append(leo_path)
     roots = g.findRootsWithPredicate(c, root, predicate=None)
     self.check_all(roots)
Example #11
0
    def processTopTree(self, p):
        """Call processTree for @rst and @slides node p's subtree or p's ancestors."""

        def predicate(p):
            return self.is_rst_node(p) or g.match_word(p.h, 0, '@slides')

        roots = g.findRootsWithPredicate(self.c, p, predicate=predicate)
        if roots:
            for p in roots:
                self.processTree(p)
        else:
            g.warning('No @rst or @slides nodes in', p.h)
Example #12
0
 def run(self):
     '''Run Pylint on all Python @<file> nodes in c.p's tree.'''
     c, root = self.c, self.c.p
     rc_fn = self.get_rc_file()
     if not rc_fn:
         return
     # Make sure Leo is on sys.path.
     leo_path = g.os_path_finalize_join(g.app.loadDir, '..')
     if leo_path not in sys.path:
         sys.path.append(leo_path)
     roots = g.findRootsWithPredicate(c, root, predicate=None)
     for p in roots:
         self.check(p, rc_fn)
Example #13
0
 def run(self, p):
     """Run mypy on all Python @<file> nodes in c.p's tree."""
     c = self.c
     if not mypy:
         print('install mypy with `pip install mypy`')
         return
     root = p.copy()
     # Make sure Leo is on sys.path.
     leo_path = g.os_path_finalize_join(g.app.loadDir, '..')
     if leo_path not in sys.path:
         sys.path.append(leo_path)
     roots = g.findRootsWithPredicate(c, root, predicate=None)
     g.printObj([z.h for z in roots], tag='mypy.run')
     self.check_all(roots)
Example #14
0
 def run_pylint(self, fn, rc_fn):
     '''Run pylint on fn with the given pylint configuration file.'''
     c = self.c
     try:
         from pylint import lint
         assert lint  # to molify pyflakes
     except ImportError:
         if not self.pylint_install_message:
             self.pylint_install_message = True
             g.es_print('pylint is not installed')
         return
     if not os.path.exists(fn):
         g.es_print('pylint: file not found:', fn)
         return
     if 1:  # Invoke pylint directly.
         # Escaping args is harder here because we are creating an args array.
         is_win = sys.platform.startswith('win')
         args = ','.join([
             "'--rcfile=%s'" % (rc_fn),
             "'%s'" % (fn),
         ])
         if is_win:
             args = args.replace('\\', '\\\\')
         command = '%s -c "from pylint import lint; args=[%s]; lint.Run(args)"' % (
             sys.executable, args)
         if not is_win:
             command = shlex.split(command)
     else:
         # Invoke g.run_pylint.
         args = [
             "fn=r'%s'" % (fn),
             "rc=r'%s'" % (rc_fn),
         ]
         # When shell is True, it's recommended to pass a string, not a sequence.
         command = '%s -c "import leo.core.leoGlobals as g; g.run_pylint(%s)"' % (
             sys.executable, ','.join(args))
     #
     # Run the command using the BPM.
     bpm = g.app.backgroundProcessManager
     roots = g.findRootsWithPredicate(c, c.p, predicate=None)
     bpm.start_process(
         c,
         command,
         fn=fn,
         kind='pylint',
         link_pattern=r'^\w+:(.*),.*:(.*)$',
         link_root=roots and roots[0],
     )
 def run(self):
     '''Run Pylint on all Python @<file> nodes in c.p's tree.'''
     c, root = self.c, self.c.p
     rc_fn = self.get_rc_file()
     if not rc_fn:
         return
     # Make sure Leo is on sys.path.
     leo_path = g.os_path_finalize_join(g.app.loadDir, '..')
     if leo_path not in sys.path:
         sys.path.append(leo_path)
     t1 = time.time()
     roots = g.findRootsWithPredicate(c, root, predicate=None)
     for p in roots:
         self.check(p, rc_fn)
     if self.wait:
         g.es_print('pylint done %s' % g.timeSince(t1))
Example #16
0
 def run(self):
     '''Run Pylint on all Python @<file> nodes in c.p's tree.'''
     c, root = self.c, self.c.p
     rc_fn = self.get_rc_file()
     if not rc_fn:
         return
     # Make sure Leo is on sys.path.
     leo_path = g.os_path_finalize_join(g.app.loadDir, '..')
     if leo_path not in sys.path:
         sys.path.append(leo_path)
     roots = g.findRootsWithPredicate(c, root, predicate=None)
     if roots:
         for p in roots:
             self.check(p, rc_fn)
     else:
         g.es('pylint: no files found', color='red')
Example #17
0
def blacken_files(event):
    """Run black on one or more files at c.p."""
    c = event.get('c')
    if not c or not c.p:
        return
    tag = 'blacken-files'
    if not black:
        g.es_print(f"{tag} can not import black")
        return
    for root in g.findRootsWithPredicate(c, c.p):
        path = g.fullPath(c, root)
        if path and os.path.exists(path):
            g.es_print(f"{tag}: {path}")
            g.execute_shell_commands(f"&black --skip-string-normalization {path}")
        else:
            print(f"{tag}: file not found:{path}")
            g.es(f"{tag}: file not found:\n{path}")
Example #18
0
    def run(self):
        '''Run Pylint on all Python @<file> nodes in c.p's tree.'''
        c, root = self.c, self.c.p
        rc_fn = self.get_rc_file()
        if not rc_fn:
            return
        # Make sure Leo is on sys.path.
        leo_path = g.os_path_finalize_join(g.app.loadDir, '..')
        if leo_path not in sys.path:
            sys.path.append(leo_path)
        t1 = time.time()

        def predicate(p):
            return p.isAnyAtFileNode() and p.h.strip().endswith('.py')

        roots = g.findRootsWithPredicate(c, root, predicate)
        for p in roots:
            self.check(p, rc_fn)
        if self.wait:
            g.es_print('pylint done %s' % g.timeSince(t1))
Example #19
0
def flake8_command(event):
    """
    Run flake8 on all nodes of the selected tree,
    or the first @<file> node in an ancestor.
    """
    tag = 'flake8-files'
    if not flake8:
        g.es_print(f"{tag} can not import flake8")
        return
    c = event and event.get('c')
    if not c or not c.p:
        return
    python = sys.executable
    for root in g.findRootsWithPredicate(c, c.p):
        path = g.fullPath(c, root)
        if path and os.path.exists(path):
            g.es_print(f"{tag}: {path}")
            g.execute_shell_commands(f'&"{python}" -m flake8 "{path}"')
        else:
            g.es_print(f"{tag}: file not found:{path}")
Example #20
0
def blacken_files_diff(event):
    """
    Show the diffs that would result from blacking the external files at
    c.p.
    """
    c = event.get('c')
    if not c or not c.p:
        return
    tag = 'blacken-files-diff'
    if not black:
        g.es_print(f"{tag} can not import black")
        return
    for root in g.findRootsWithPredicate(c, c.p):
        path = g.fullPath(c, root)
        if path and os.path.exists(path):
            g.es_print(f"{tag}: {path}")
            g.execute_shell_commands(f"&black --skip-string-normalization --diff {path}")
        else:
            print(f"{tag}: file not found:{path}")
            g.es(f"{tag}: file not found:\n{path}")
Example #21
0
 def run_pylint(self, fn, rc_fn):
     '''Run pylint on fn with the given pylint configuration file.'''
     c = self.c
     try:
         from pylint import lint
         assert lint # to molify pyflakes
     except ImportError:
         if not self.pylint_install_message:
             self.pylint_install_message = True
             g.es_print('pylint is not installed')
         return
     if not os.path.exists(fn):
         g.es_print('pylint: file not found:', fn)
         return
     if 1: # Invoke pylint directly.
         # Escaping args is harder here because we are creating an args array.
         is_win = sys.platform.startswith('win')
         args =  ','.join(["'--rcfile=%s'" % (rc_fn), "'%s'" % (fn),])
         if is_win:
             args = args.replace('\\','\\\\')
         command = '%s -c "from pylint import lint; args=[%s]; lint.Run(args)"' % (
             sys.executable, args)
         if not is_win:
             command = shlex.split(command)
     else:
         # Invoke g.run_pylint.
         args = ["fn=r'%s'" % (fn), "rc=r'%s'" % (rc_fn),]
         # When shell is True, it's recommended to pass a string, not a sequence.
         command = '%s -c "import leo.core.leoGlobals as g; g.run_pylint(%s)"' % (
             sys.executable, ','.join(args))
     #
     # Run the command using the BPM.
     bpm = g.app.backgroundProcessManager
     roots = g.findRootsWithPredicate(c, c.p, predicate=None)
     bpm.start_process(c, command,
         fn=fn,
         kind='pylint',
         link_pattern = r'^\w+:(.*),.*:(.*)$',
         link_root = roots and roots[0],
     )
Example #22
0
    def run(self, last_path=None):
        """Run Pylint on all Python @<file> nodes in c.p's tree."""
        c, root = self.c, self.c.p
        if not lint:
            g.es_print('pylint is not installed')
            return False
        self.rc_fn = self.get_rc_file()
        if not self.rc_fn:
            return False
        # Make sure Leo is on sys.path.
        leo_path = g.os_path_finalize_join(g.app.loadDir, '..')
        if leo_path not in sys.path:
            sys.path.append(leo_path)

        # Ignore @nopylint trees.

        def predicate(p):
            for parent in p.self_and_parents():
                if g.match_word(parent.h, 0, '@nopylint'):
                    return False
            return p.isAnyAtFileNode() and p.h.strip().endswith(
                ('.py', '.pyw'))  # #2354.

        roots = g.findRootsWithPredicate(c, root, predicate=predicate)
        data = [(self.get_fn(p), p.copy()) for p in roots]
        data = [z for z in data if z[0] is not None]
        if not data and last_path:
            # Default to the last path.
            fn = last_path
            for p in c.all_positions():
                if p.isAnyAtFileNode() and g.fullPath(c, p) == fn:
                    data = [(fn, p.copy())]
                    break
        if not data:
            g.es('pylint: no files found', color='red')
            return None
        for fn, p in data:
            self.run_pylint(fn, p)
        # #1808: return the last data file.
        return data[-1] if data else False
Example #23
0
 def run(self, p):
     """Run Pyflakes on all Python @<file> nodes in p's tree."""
     ok = True
     if not pyflakes:
         return ok
     c = self.c
     root = p
     # Make sure Leo is on sys.path.
     leo_path = g.os_path_finalize_join(g.app.loadDir, '..')
     if leo_path not in sys.path:
         sys.path.append(leo_path)
     roots = g.findRootsWithPredicate(c, root, predicate=None)
     if roots:
         # These messages are important for clarity.
         total_errors = self.check_all(roots)
         if total_errors > 0:
             g.es(
                 f"ERROR: pyflakes: {total_errors} error{g.plural(total_errors)}"
             )
         ok = total_errors == 0
     else:
         ok = True
     return ok
Example #24
0
def find_missing_docstrings(event):
    '''Report missing docstrings in the log, with clickable links.'''
    c = event.get('c')
    if not c:
        return

    #@+others # Define functions
    #@+node:ekr.20190615181104.1: *4* function: has_docstring
    def has_docstring(lines, n):
        '''
        Returns True if function/method/class whose definition
        starts on n-th line in lines has a docstring
        '''
        # By Виталије Милошевић.
        for line in lines[n:]:
            s = line.strip()
            if not s or s.startswith('#'):
                continue
            if s.startswith(('"""', "'''")):
                return True
        return False

    #@+node:ekr.20190615181104.2: *4* function: is_a_definition
    def is_a_definition(line):
        '''Return True if line is a definition line.'''
        # By Виталије Милошевић.
        # It may be useful to skip __init__ methods because their docstring
        # is usually docstring of the class
        return (line.startswith(('def ', 'class '))
                and not line.partition(' ')[2].startswith('__init__'))

    #@+node:ekr.20190615182754.1: *4* function: is_root
    def is_root(p):
        '''
        A predicate returning True if p is an @<file> node that is not under @nopylint.
        '''
        for parent in p.self_and_parents():
            if g.match_word(parent.h, 0, '@nopylint'):
                return False
        return p.isAnyAtFileNode() and p.h.strip().endswith('.py')

    #@+node:ekr.20190615180900.1: *4* function: clickable_link
    def clickable_link(p, i):
        '''Return a clickable link to line i of p.b.'''
        link = p.get_UNL(with_proto=True, with_count=True, with_index=True)
        return "%s,%d" % (link, i)

    #@-others

    count, found, t1 = 0, [], time.clock()
    for root in g.findRootsWithPredicate(c, c.p, predicate=is_root):
        for p in root.self_and_subtree():
            lines = p.b.split('\n')
            for i, line in enumerate(lines):
                if is_a_definition(line) and not has_docstring(lines, i):
                    count += 1
                    if root.v not in found:
                        found.append(root.v)
                        g.es_print('')
                        g.es_print(root.h)
                    print(line)
                    g.es(line, nodeLink=clickable_link(p, i + 1))
                    break
    g.es_print('')
    g.es_print('found %s missing docstring%s in %s file%s in %5.2f sec.' %
               (count, g.plural(count), len(found), g.plural(len(found)),
                (time.clock() - t1)))
Example #25
0
    def ad_command(self, event=None):
        #@+<< adoc command docstring >>
        #@+node:ekr.20190515115100.1: *3* << adoc command docstring >>
        '''
        The adoc command writes all @adoc nodes in the selected tree to the
        files given in each @doc node. If no @adoc nodes are found, the
        command looks up the tree.

        Each @adoc node should have the form: `@adoc x.adoc`. Relative file names
        are relative to the base directory.  See below.

        By default, the adoc command creates AsciiDoctor headings from Leo
        headlines. However, the following kinds of nodes are treated differently:
            
        - @ignore-tree: Ignore the node and its descendants.
        - @ignore-node: Ignore the node.
        - @no-head:     Ignore the headline. Do not generate a heading.

        After running the adoc command, use the asciidoctor command to convert the
        x.adoc files to x.html.
            
        Settings
        --------

        AsciiDoctor itself provides many settings, including::
            
            = Title
            :stylesdir: mystylesheets/
            :stylesheet: mystyles.css
            
        These can also be specified on the command line::

            asciidoctor -a stylesdir=mystylesheets/ -a stylesheet=mystyles.css 

        @string adoc-base-directory specifies the base for relative file names.
        The default is c.frame.openDirectory

        Scripting interface
        -------------------

        Scripts may invoke the adoc command as follows::
            
            event = g.Bunch(base_dicrectory=my_directory, p=some_node)
            c.asciiDoctorCommands.ad_command(event=event)
            
        This @button node runs the adoc command and coverts all results to .html::
            
            import os
            paths = c.asciiDoctorCommands.ad_command(event=g.Bunch(p=p))
            paths = [z.replace('/', os.path.sep) for z in paths]
            input_paths = ' '.join(paths)
            g.execute_shell_commands(['asciidoctor %s' % input_paths])

        '''
        #@-<< adoc command docstring >>

        def predicate(p):
            return self.ad_filename(p)

        # Find all roots.
        t1 = time.time()
        c = self.c
        p = event.p if event and hasattr(event, 'p') else c.p
        if event and hasattr(event, 'base_directory'):
            self.base_directory = event.base_directory
        else:
            directory = c.config.getString('adoc-base-directory')
            self.base_directory = directory or c.frame.openDirectory
        roots = g.findRootsWithPredicate(c, p, predicate=predicate)
        if not roots:
            g.warning('No @ascii-doctor nodes in', p.h)
            return []
        # Write each root.
        paths = []
        for p in roots:
            path = self.write_root(p)
            if path:
                paths.append(path)
        t2 = time.time()
        g.es_print('adoc: wrote %s file%s in %4.2f sec.' % (
            len(paths), g.plural(len(paths)), t2 - t1))
        return paths