diff --git a/sphinx/domains/std.py b/sphinx/domains/std.py index 6218de6abe..ad9ef71d17 100644 --- a/sphinx/domains/std.py +++ b/sphinx/domains/std.py @@ -35,7 +35,7 @@ Final = Any # RE for option descriptions -option_desc_re = re.compile(r'((?:/|--|-|\+)?[^\s=]+)(=?\s*.*)') +option_desc_re = re.compile(r'((?:/|--|-|\+)?[^\s={]+)(=?\s*.*)') # RE for grammar tokens token_re = re.compile(r'`((~?\w*:)?\w+)`', re.U) @@ -177,6 +177,8 @@ def handle_signature(self, sig: str, signode: desc_signature) -> str: location=signode) continue optname, args = m.groups() + # replace '#' with '=' in option name + optname = optname.replace('#', '=') if optname[-1] == '[' and args[-1] == ']': # optional value surrounded by brackets (ex. foo[=bar]) optname = optname[:-1] @@ -199,7 +201,7 @@ def handle_signature(self, sig: str, signode: desc_signature) -> str: elif args[0] == ' ': signode += addnodes.desc_sig_space() args = args.strip() - elif args[0] == '=': + elif args[0] in ('=', '#'): signode += addnodes.desc_sig_punctuation('=', '=') args = args[1:] for part in samp_role.parse(args): @@ -940,7 +942,15 @@ def _resolve_option_xref(self, env: "BuildEnvironment", fromdocname: str, node: pending_xref, contnode: Element) -> Optional[Element]: progname = node.get('std:program') target = target.strip() - docname, labelid = self.progoptions.get((progname, target), ('', '')) + needles = [target] + # For :option:`-foo=bar` search for -foo=bar, -foo= or -foo option directives + if '=' in target: + needles.append(target[:target.find('=') + 1]) + needles.append(target[:target.find('=')]) + for needle in needles: + docname, labelid = self.progoptions.get((progname, needle), ('', '')) + if docname: + break if not docname: commands = [] while ws_re.search(target): diff --git a/sphinx/util/nodes.py b/sphinx/util/nodes.py index 0646b44e64..f81ef06306 100644 --- a/sphinx/util/nodes.py +++ b/sphinx/util/nodes.py @@ -466,7 +466,7 @@ def _make_id(string: str) -> str: return str(id) -_non_id_chars = re.compile('[^a-zA-Z0-9._]+') +_non_id_chars = re.compile('[^a-zA-Z0-9._=]+') _non_id_at_ends = re.compile('^[-0-9._]+|-+$') _non_id_translate = { 0x00f8: 'o', # o with stroke diff --git a/tests/test_util_nodes.py b/tests/test_util_nodes.py index 14dcdcd586..3fe441b825 100644 --- a/tests/test_util_nodes.py +++ b/tests/test_util_nodes.py @@ -189,10 +189,11 @@ def test_clean_astext(): ('', '_io.StringIO', 'io.StringIO'), # starts with underscore ('', 'sphinx', 'sphinx'), # alphabets in unicode fullwidth characters ('', '悠好', 'id0'), # multibytes text (in Chinese) - ('', 'Hello=悠好=こんにちは', 'Hello'), # alphabets and multibytes text + ('', 'Hello=悠好=こんにちは', 'Hello=='), # alphabets and multibytes text ('', 'fünf', 'funf'), # latin1 (umlaut) ('', '0sphinx', 'sphinx'), # starts with number ('', 'sphinx-', 'sphinx'), # ends with hyphen + ('', '-foo=bar', 'foo=bar'), # test option value ]) def test_make_id(app, prefix, term, expected): document = create_new_document()