Switch branches/tags
Nothing to show
Find file
Fetching contributors…
Cannot retrieve contributors at this time
executable file 226 lines (189 sloc) 8.32 KB
#! /usr/bin/env python
Copyright 2009 Bernhard Leiner <>
A front end to the Docutils Publisher, producing a ctags tags file.
This software may be used and distributed according to the terms of the
MIT license, incorporated herein by reference.
import sys
import os
import subprocess
from docutils import nodes
from docutils.core import Publisher
from import FileInput, NullOutput
from docutils.frontend import OptionParser
from docutils.readers import standalone
from docutils.parsers import rst
from docutils.writers import UnfilteredWriter
except ImportError:
raise SystemExit()
class CTagsWriter(UnfilteredWriter):
"""rst2ctags specific docutils writer class.
URL = ""
VERSION = "rst2ctags v1.0"
metadata = \
["!_TAG_FILE_FORMAT\t2\t/extended format/",
"!_TAG_FILE_SORTED\t{sorted}\t/0=unsorted, 1=sorted, 2=foldcase/",
"!_TAG_PROGRAM_AUTHOR\tBernhard Leiner\t/",
"!_TAG_PROGRAM_URL\t%s\t//" % URL,
settings_spec = \
("CTAGS-Specific Options",
(('Write tags to specified file (default: "tags"). Value of "-" '
'writes tags to stdout.',
["-f"], {"metavar": "<FILE>", "dest": "ctags_file",
"default": "tags"}),
("Recurse into directories supplied on command line",
["-R"], {"action": "store_true", "dest": "ctags_recurse",
"default": False}),
('Should tags be sorted by name? Default is "no", which will sort '
'by line number.',
["--sort"], {"metavar": "[yes|no]", "dest": "ctags_sort",
"default": "no"}),
("Compatibility mode in order to be used as main ctags executable "
"for the taglist Vim plugin.",
["--taglist"], {"action": "store_true", "dest": "ctags_taglist",
"default": False})))
def __init__(self, *args, **kwargs):
UnfilteredWriter.__init__(self, *args, **kwargs)
self.tags = []
def recursive_section_node_walker(self, node, level):
"""Translates the doctree into a list of tags and appends it to
tagtype = None
if isinstance(node, nodes.section):
tagtype = "s"
tagfile = os.path.relpath(node.source)
tagid = \
tagaddress = '/^%s$/;"' % tagid
tagline = node.line
# increase level for next found section
level += 1
elif (isinstance(node, nodes.image) or
isinstance(node, nodes.figure)):
tagtype = "i"
tagfile = os.path.relpath(node.source)
tagid = node.attributes['uri']
tagaddress = '/^%s$/;"' % node.rawsource.split('\n')[0]
# nodes with options have no line!
tagline = node.line if node.line else node.parent.line
# if *something* goes wrong, just ignore the node
if tagtype and tagline:
tagfield = '%s\tline:%d' % (tagtype, tagline)
# tag data structure: ([field, ...], (metainfo, ...))
self.tags.append(([tagid, tagfile, tagaddress, tagfield],
(tagline -1, level, tagtype)))
if len(node.children):
for child in node:
self.recursive_section_node_walker(child, level)
def translate(self):
"""Translate the doctree to the output format. In out case, this
output format is a list of tags, stored in :attr:`self.tags`.
doctree = self.document
level = -1
self.recursive_section_node_walker(doctree, level)
def tags_string(self, sort, taglist):
"""Convert :attr:`tags` into a string.
if sort == "yes":
self.tags.sort(key = lambda t: t[0][0])
# sort by line file and line number
self.tags.sort(key = lambda t: (t[0][1], t[1][0]))
def indent_tag(tag):
# if we are using the taglist plugin we fake indention of sections
# by placing spaces in front of the tag names to indicate
# subsections
if taglist and tag[1][1] != 0 and tag[1][2] == "s":
tag[0][0] = "".join([" " * tag[1][1], tag[0][0]])
return tag
return "\n".join("\t".join(t for t in indent_tag(tag)[0])
for tag in self.tags) + "\n"
def get_source_files(source_args, recurse):
"""Return an iterable containing all source files.
rst_ext = ".rst"
source_files = []
def is_rst_file(f):
# EDIT: always assume reST for use with Vim
# return os.path.splitext(f)[1] == rst_ext
return True
if not recurse:
# treat all source args as files
source_files = [f for f in source_args if is_rst_file(f)]
# source_args may be a mixture of files and directories
cwd = os.getcwd()
if cwd not in source_args:
source_files = \
[f for f in source_args if os.path.isfile(f) and is_rst_file(f)]
for source_dir in (d for d in source_args if os.path.isdir(d)):
for dirpath, _, filenames in os.walk(source_dir):
source_files.extend(os.path.join(dirpath, f)
for f in filenames if is_rst_file(f))
# return unique sources
return set(os.path.abspath(f) for f in source_files)
def vim_taglist_plugin_handling():
"""This script can also be used as main ctags executable by the taglist
Vim plugin. In order to do this, the taglist `Tlist_Ctags_Cmd` variable
shall be set to "rst2ctags --taglist". If this script is used with this
command line option we rely on the following command line::
rst2ctags --taglist -f - --format=2 --excmd=pattern --fields=nks file
1) If `file` is not a rst file, strip away the "--taglist" option an
pass the call on to the real ctags program.
2) If `file` is a rst file, strip away unknown options and continue.
if len(sys.argv) > 1 and sys.argv[1] == "--taglist":
# EDIT: If called from Vim, just assume input is a reST file
# if os.path.splitext(sys.argv[-1])[1] == ".rst":
# sys.argv = [sys.argv[0], "--taglist", "-f", "-", "--traceback",
# sys.argv[-1]]
# else:
# retcode =["ctags"] + sys.argv[2:])
# exit(retcode)
sys.argv = [sys.argv[0], "--taglist", "-f", "-", "--traceback",
def main():
"""Write a `tags` file containing the tags of a single rst input file.
usage = "%prog [options] [file(s)]"
pub = Publisher(reader = standalone.Reader(),
parser = rst.Parser(),
writer = CTagsWriter(),
source_class = FileInput,
destination_class = NullOutput)
# monkey patch OptionParser.check_args method and process command line
OptionParser.check_args = lambda self, args: (args, None)
pub.process_command_line(usage = usage)
# open the destination tag file
ctags_file = pub.settings.ctags_file
if ctags_file == "-":
tagfile = sys.stdout
tagfile = open(ctags_file, "w")
sorted = "%d" % 1 if pub.settings.ctags_sort == "yes" else 0
tagfile.write(("\n".join(CTagsWriter.metadata) + "\n")
# parse all source files and collect tags
source_files = get_source_files(pub.settings._source,
for src_path in source_files:
pub.set_source(None, src_path)
# write the tags file
if __name__ == "__main__":