diff --git a/doc/markup/desc.rst b/doc/markup/desc.rst index f121d23d7..892cdc4a9 100644 --- a/doc/markup/desc.rst +++ b/doc/markup/desc.rst @@ -17,7 +17,7 @@ typical module section might start like this:: .. moduleauthor:: John Idle -The directives you can use for module are: +The directives you can use for module declarations are: .. directive:: .. module:: name @@ -67,8 +67,8 @@ The directives you can use for module are: .. _desc-units: -Description units ------------------ +Object description units +------------------------ There are a number of directives used to describe specific features provided by modules. Each directive requires one or more signatures to provide basic @@ -204,39 +204,6 @@ The directives are: Like :dir:`method`, but indicates that the method is a static method. .. versionadded:: 0.4 - -.. directive:: .. cmdoption:: name args, name args, ... - - Describes a command line option or switch. Option argument names should be - enclosed in angle brackets. Example:: - - .. cmdoption:: -m , --module - - Run a module as a script. - - The directive will create a cross-reference target named after the *first* - option, referencable by :role:`option` (in the example case, you'd use - something like ``:option:`-m```). - -.. directive:: .. envvar:: name - - Describes an environment variable that the documented code uses or defines. - - -There is also a generic version of these directives: - -.. directive:: .. describe:: text - - This directive produces the same formatting as the specific ones explained - above but does not create index entries or cross-referencing targets. It is - used, for example, to describe the directives in this document. Example:: - - .. describe:: opcode - - Describes a Python bytecode instruction. - -Extensions may add more directives like that, using the -:func:`~sphinx.application.Sphinx.add_description_unit` method. .. _signatures: @@ -308,3 +275,77 @@ This will render like this: :param limit: maximum number of stack frames to show :type limit: integer or None :rtype: list of strings + + +Command-line program markup +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +There is a set of directives allowing documenting command-line programs: + +.. directive:: .. cmdoption:: name args, name args, ... + + Describes a command line option or switch. Option argument names should be + enclosed in angle brackets. Example:: + + .. cmdoption:: -m , --module + + Run a module as a script. + + The directive will create a cross-reference target named after the *first* + option, referencable by :role:`option` (in the example case, you'd use + something like ``:option:`-m```). + +.. directive:: .. envvar:: name + + Describes an environment variable that the documented code or program uses or + defines. + + +.. directive:: .. program:: name + + Like :dir:`currentmodule`, this directive produces no output. Instead, it + serves to notify Sphinx that all following :dir:`cmdoption` directives + document options for the program called *name*. + + If you use :dir:`program`, you have to qualify the references in your + :role:`option` roles by the program name, so if you have the following + situation :: + + .. program:: rm + + .. cmdoption:: -r + + Work recursively. + + .. program:: svn + + .. cmdoption:: -r revision + + Specify the revision to work upon. + + then ``:option:`rm -r``` would refer to the first option, while + ``:option:`svn -r``` would refer to the second one. + + The program name may contain spaces (in case you want to document subcommands + like ``svn add`` and ``svn commit`` separately). + + .. versionadded:: 0.5 + + +Custom description units +~~~~~~~~~~~~~~~~~~~~~~~~ + +There is also a generic version of these directives: + +.. directive:: .. describe:: text + + This directive produces the same formatting as the specific ones explained + above but does not create index entries or cross-referencing targets. It is + used, for example, to describe the directives in this document. Example:: + + .. describe:: opcode + + Describes a Python bytecode instruction. + +Extensions may add more directives like that, using the +:func:`~sphinx.application.Sphinx.add_description_unit` method. diff --git a/sphinx/directives/desc.py b/sphinx/directives/desc.py index a73748920..3d0fcb1fb 100644 --- a/sphinx/directives/desc.py +++ b/sphinx/directives/desc.py @@ -14,10 +14,9 @@ from docutils import nodes from docutils.parsers.rst import directives from sphinx import addnodes +from sphinx.util import ws_re -ws_re = re.compile(r'\s+') - # ------ information units --------------------------------------------------------- def desc_index_text(desctype, module, name, add_modules): @@ -352,17 +351,17 @@ def parse_c_signature(signode, sig, desctype): option_desc_re = re.compile( - r'(/|-|--)([-_a-zA-Z0-9]+)(\s*.*?)(?=,\s+(?:/|-|--)|$)') + r'((?:/|-|--)[-_a-zA-Z0-9]+)(\s*.*?)(?=,\s+(?:/|-|--)|$)') def parse_option_desc(signode, sig): """Transform an option description into RST nodes.""" count = 0 firstname = '' for m in option_desc_re.finditer(sig): - prefix, optname, args = m.groups() + optname, args = m.groups() if count: signode += addnodes.desc_addname(', ', ', ') - signode += addnodes.desc_name(prefix+optname, prefix+optname) + signode += addnodes.desc_name(optname, optname) signode += addnodes.desc_addname(args, args) if not count: firstname = optname @@ -402,12 +401,17 @@ def desc_directive(desctype, arguments, options, content, lineno, elif desctype == 'cmdoption': optname = parse_option_desc(signode, sig) if not noindex: - targetname = 'cmdoption-' + optname + targetname = optname.replace('/', '-') + if env.currprogram: + targetname = '-' + env.currprogram + targetname + targetname = 'cmdoption' + targetname signode['ids'].append(targetname) state.document.note_explicit_target(signode) - inode['entries'].append(('pair', _('command line option; %s') % sig, - targetname, targetname)) - env.note_reftarget('option', optname, targetname) + inode['entries'].append( + ('pair', _('%scommand line option; %s') % + ((env.currprogram and env.currprogram + ' ' or ''), sig), + targetname, targetname)) + env.note_progoption(optname, targetname) continue elif desctype == 'describe': signode.clear() diff --git a/sphinx/directives/other.py b/sphinx/directives/other.py index 01b9b8da4..03df20770 100644 --- a/sphinx/directives/other.py +++ b/sphinx/directives/other.py @@ -14,9 +14,8 @@ from docutils import nodes from docutils.parsers.rst import directives from sphinx import addnodes -from sphinx.util import patfilter -from sphinx.roles import caption_ref_re from sphinx.locale import pairindextypes +from sphinx.util import patfilter, ws_re, caption_ref_re from sphinx.util.compat import make_admonition @@ -159,6 +158,20 @@ directives.register_directive('sectionauthor', author_directive) directives.register_directive('moduleauthor', author_directive) +def program_directive(name, arguments, options, content, lineno, + content_offset, block_text, state, state_machine): + env = state.document.settings.env + program = ws_re.sub('-', arguments[0].strip()) + if program == 'None': + env.currprogram = None + else: + env.currprogram = program + return [] + +program_directive.arguments = (1, 0, 1) +directives.register_directive('program', program_directive) + + # ------ index markup -------------------------------------------------------------- indextypes = [ diff --git a/sphinx/environment.py b/sphinx/environment.py index fc6f7a23c..1ebff8427 100644 --- a/sphinx/environment.py +++ b/sphinx/environment.py @@ -57,7 +57,7 @@ default_settings = { # This is increased every time an environment attribute is added # or changed to properly invalidate pickle files. -ENV_VERSION = 25 +ENV_VERSION = 26 default_substitutions = set([ @@ -266,8 +266,9 @@ class BuildEnvironment: self.modules = {} # modname -> docname, synopsis, platform, deprecated self.labels = {} # labelname -> docname, labelid, sectionname self.anonlabels = {} # labelname -> docname, labelid + self.progoptions = {} # (program, name) -> docname, labelid self.reftargets = {} # (type, name) -> docname, labelid - # where type is term, token, option, envvar, citation + # where type is term, token, envvar, citation # Other inventories self.indexentries = {} # docname -> list of @@ -281,6 +282,7 @@ class BuildEnvironment: self.currmodule = None # current module name self.currclass = None # current class name self.currdesc = None # current descref name + self.currprogram = None # current program name self.index_num = 0 # autonumber for index targets self.gloss_entries = set() # existing definition labels @@ -331,6 +333,9 @@ class BuildEnvironment: for key, (fn, _) in self.reftargets.items(): if fn == docname: del self.reftargets[key] + for key, (fn, _) in self.progoptions.items(): + if fn == docname: + del self.progoptions[key] for version, changes in self.versionchanges.items(): new = [change for change in changes if change[1] != docname] changes[:] = new @@ -826,6 +831,9 @@ class BuildEnvironment: self.modules[modname] = (self.docname, synopsis, platform, deprecated) self.filemodules.setdefault(self.docname, []).append(modname) + def note_progoption(self, optname, labelid): + self.progoptions[self.currprogram, optname] = (self.docname, labelid) + def note_reftarget(self, type, name, labelid): self.reftargets[type, name] = (self.docname, labelid) @@ -968,7 +976,7 @@ class BuildEnvironment: 'meth', 'cfunc', 'cmember', 'cdata', 'ctype', 'cmacro')) def resolve_references(self, doctree, fromdocname, builder): - reftarget_roles = set(('token', 'term', 'option', 'citation')) + reftarget_roles = set(('token', 'term', 'citation')) # add all custom xref types too reftarget_roles.update(i[0] for i in additional_xref_types.values()) @@ -1028,6 +1036,19 @@ class BuildEnvironment: newnode['refuri'] = builder.get_relative_uri( fromdocname, docname) + '#' + labelid newnode.append(contnode) + elif typ == 'option': + progname = node['refprogram'] + docname, labelid = self.progoptions.get((progname, target), ('', '')) + if not docname: + newnode = contnode + else: + newnode = nodes.reference('', '') + if docname == fromdocname: + newnode['refid'] = labelid + else: + newnode['refuri'] = builder.get_relative_uri( + fromdocname, docname) + '#' + labelid + newnode.append(contnode) elif typ in reftarget_roles: docname, labelid = self.reftargets.get((typ, target), ('', '')) if not docname: diff --git a/sphinx/ext/autodoc.py b/sphinx/ext/autodoc.py index db9b71653..b6113c1f1 100644 --- a/sphinx/ext/autodoc.py +++ b/sphinx/ext/autodoc.py @@ -643,6 +643,8 @@ def setup(app): noindex=directives.flag) app.add_directive('autoattribute', auto_directive, 1, (1, 0, 1), noindex=directives.flag) + app.add_directive('autoprogram', auto_directive, 1, (1, 0, 1), + noindex=directives.flag) # deprecated: remove in some future version. app.add_config_value('automodule_skip_lines', 0, True) app.add_config_value('autoclass_content', 'class', True) diff --git a/sphinx/roles.py b/sphinx/roles.py index e4a35c634..d28112384 100644 --- a/sphinx/roles.py +++ b/sphinx/roles.py @@ -15,9 +15,8 @@ from docutils import nodes, utils from docutils.parsers.rst import roles from sphinx import addnodes +from sphinx.util import ws_re, caption_ref_re -ws_re = re.compile(r'\s+') -caption_ref_re = re.compile(r'^([^<]+?)\s*<(.+)>$') generic_docroles = { 'command' : nodes.strong, @@ -143,7 +142,7 @@ def xfileref_role(typ, rawtext, text, lineno, inliner, options={}, content=[]): # fallback: everything after '<' is the target target = text[brace+1:] title = text[:brace] - # special target for Python object cross-references + # special target for Python object cross-references if typ in ('data', 'exc', 'func', 'class', 'const', 'attr', 'meth', 'mod', 'obj'): # fix-up parentheses in link title if titleistarget: @@ -166,9 +165,17 @@ def xfileref_role(typ, rawtext, text, lineno, inliner, options={}, content=[]): target = target[1:] pnode['refspecific'] = True # some other special cases for the target - elif typ == 'option' and target[0] in '-/': - # strip option marker from target - target = target[1:] + elif typ == 'option': + program = env.currprogram + if titleistarget: + if ' ' in title and not (title.startswith('/') or title.startswith('-')): + program, target = re.split(' (?=-|--|/)', title, 1) + program = ws_re.sub('-', program) + target = target.strip() + elif ' ' in target: + program, target = re.split(' (?=-|--|/)', target, 1) + program = ws_re.sub('-', program) + pnode['refprogram'] = program elif typ == 'term': # normalize whitespace in definition terms (if the term reference is # broken over a line, a newline will be in target) @@ -217,21 +224,21 @@ specific_docroles = { 'obj': xfileref_role, 'cfunc' : xfileref_role, 'cmember': xfileref_role, - 'cdata' : xfileref_role, - 'ctype' : xfileref_role, - 'cmacro' : xfileref_role, + 'cdata': xfileref_role, + 'ctype': xfileref_role, + 'cmacro': xfileref_role, - 'mod' : xfileref_role, + 'mod': xfileref_role, 'keyword': xfileref_role, 'ref': xfileref_role, - 'token' : xfileref_role, + 'token': xfileref_role, 'term': xfileref_role, 'option': xfileref_role, - 'menuselection' : menusel_role, - 'file' : emph_literal_role, - 'samp' : emph_literal_role, + 'menuselection': menusel_role, + 'file': emph_literal_role, + 'samp': emph_literal_role, } for rolename, func in specific_docroles.iteritems(): diff --git a/sphinx/util/__init__.py b/sphinx/util/__init__.py index de89c4854..bf9c45eb6 100644 --- a/sphinx/util/__init__.py +++ b/sphinx/util/__init__.py @@ -18,6 +18,11 @@ import traceback from os import path +# Generally useful regular expressions. +ws_re = re.compile(r'\s+') +caption_ref_re = re.compile(r'^([^<]+?)\s*<(.+)>$') + + # SEP separates path elements in the canonical file names # # Define SEP as a manifest constant, not so much because we expect it to change