From c44c8721c9ec2a2040edbda97c610afc405436a0 Mon Sep 17 00:00:00 2001 From: lethosor Date: Wed, 27 Jul 2022 02:17:21 -0400 Subject: [PATCH 01/20] Initial attempt at dfhack-tool directive Doesn't appear to produce headings that can be used as link targets... --- conf.py | 1 + docs/plugins/3dveins.rst | 3 ++ docs/sphinx_extensions/dfhack/tool_docs.py | 52 ++++++++++++++++++++++ docs/sphinx_extensions/dfhack/util.py | 13 ++++++ 4 files changed, 69 insertions(+) create mode 100644 docs/sphinx_extensions/dfhack/tool_docs.py diff --git a/conf.py b/conf.py index dd7be9792..e8ff8392b 100644 --- a/conf.py +++ b/conf.py @@ -237,6 +237,7 @@ extensions = [ 'sphinx.ext.extlinks', 'dfhack.changelog', 'dfhack.lexer', + 'dfhack.tool_docs', ] sphinx_major_version = sphinx.version_info[0] diff --git a/docs/plugins/3dveins.rst b/docs/plugins/3dveins.rst index 680d44c6e..9cd678709 100644 --- a/docs/plugins/3dveins.rst +++ b/docs/plugins/3dveins.rst @@ -3,6 +3,9 @@ **Tags:** `tag/fort`, `tag/mod`, `tag/map` :dfhack-keybind:`3dveins` +.. dfhack-tool:: 3dveins + :tags: foo, bar + :index:`Rewrite layer veins to expand in 3D space. <3dveins; Rewrite layer veins to expand in 3D space.>` Existing, flat veins are removed and new 3D veins that naturally span z-levels are generated in diff --git a/docs/sphinx_extensions/dfhack/tool_docs.py b/docs/sphinx_extensions/dfhack/tool_docs.py new file mode 100644 index 000000000..ac06a6576 --- /dev/null +++ b/docs/sphinx_extensions/dfhack/tool_docs.py @@ -0,0 +1,52 @@ +# useful references: +# https://www.sphinx-doc.org/en/master/extdev/appapi.html +# https://www.sphinx-doc.org/en/master/development/tutorials/recipe.html +# https://www.sphinx-doc.org/en/master/usage/restructuredtext/basics.html#rst-directives + +import docutils.nodes as nodes +# import docutils.parsers.rst.directives as rst_directives +import sphinx +import sphinx.directives + +import dfhack.util + +class DFHackToolDirective(sphinx.directives.ObjectDescription): + has_content = False + required_arguments = 1 + option_spec = { + 'tags': dfhack.util.directive_arg_str_list, + } + + def run(self): + tool_name = self.get_signatures()[0] + + tag_nodes = [nodes.strong(text='Tags: ')] + for tag in self.options['tags']: + tag_nodes += [ + nodes.literal(tag, tag), + nodes.inline(text=' | '), + ] + tag_nodes.pop() + + return [ + nodes.title(text=tool_name), + nodes.paragraph('', '', *tag_nodes), + ] + + # simpler but always renders as "inline code" (and less customizable) + # def handle_signature(self, sig, signode): + # signode += addnodes.desc_name(text=sig) + # return sig + + +def register(app): + app.add_directive('dfhack-tool', DFHackToolDirective) + +def setup(app): + app.connect('builder-inited', register) + + return { + 'version': '0.1', + 'parallel_read_safe': True, + 'parallel_write_safe': True, + } diff --git a/docs/sphinx_extensions/dfhack/util.py b/docs/sphinx_extensions/dfhack/util.py index 71a432da4..61f38b043 100644 --- a/docs/sphinx_extensions/dfhack/util.py +++ b/docs/sphinx_extensions/dfhack/util.py @@ -5,3 +5,16 @@ DOCS_ROOT = os.path.join(DFHACK_ROOT, 'docs') if not os.path.isdir(DOCS_ROOT): raise ReferenceError('docs root not found: %s' % DOCS_ROOT) + +# directive argument helpers (supplementing docutils.parsers.rst.directives) +def directive_arg_str_list(argument): + """ + Converts a space- or comma-separated list of values into a Python list + of strings. + (Directive option conversion function.) + """ + if ',' in argument: + entries = argument.split(',') + else: + entries = argument.split() + return [entry.strip() for entry in entries] From d96260556eb7e92d3f0b3f61defdecbe38892a41 Mon Sep 17 00:00:00 2001 From: lethosor Date: Wed, 27 Jul 2022 03:18:51 -0400 Subject: [PATCH 02/20] Make title visible by putting it in its own section --- docs/plugins/3dveins.rst | 6 +++--- docs/sphinx_extensions/dfhack/tool_docs.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/plugins/3dveins.rst b/docs/plugins/3dveins.rst index 9cd678709..7ae2324eb 100644 --- a/docs/plugins/3dveins.rst +++ b/docs/plugins/3dveins.rst @@ -1,10 +1,10 @@ 3dveins ======= -**Tags:** `tag/fort`, `tag/mod`, `tag/map` -:dfhack-keybind:`3dveins` .. dfhack-tool:: 3dveins - :tags: foo, bar + :tags: fort, mod, map + +:dfhack-keybind:`3dveins` :index:`Rewrite layer veins to expand in 3D space. <3dveins; Rewrite layer veins to expand in 3D space.>` Existing, flat veins diff --git a/docs/sphinx_extensions/dfhack/tool_docs.py b/docs/sphinx_extensions/dfhack/tool_docs.py index ac06a6576..dcda81d8d 100644 --- a/docs/sphinx_extensions/dfhack/tool_docs.py +++ b/docs/sphinx_extensions/dfhack/tool_docs.py @@ -29,7 +29,7 @@ class DFHackToolDirective(sphinx.directives.ObjectDescription): tag_nodes.pop() return [ - nodes.title(text=tool_name), + nodes.section('', nodes.title(text=tool_name)), nodes.paragraph('', '', *tag_nodes), ] From 89a88e94a9794bc488114c73e670de0080e539c0 Mon Sep 17 00:00:00 2001 From: lethosor Date: Wed, 27 Jul 2022 22:02:08 -0400 Subject: [PATCH 03/20] Allow empty :tags:, give section a name to prevent errors --- docs/sphinx_extensions/dfhack/tool_docs.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/sphinx_extensions/dfhack/tool_docs.py b/docs/sphinx_extensions/dfhack/tool_docs.py index dcda81d8d..79516e0d8 100644 --- a/docs/sphinx_extensions/dfhack/tool_docs.py +++ b/docs/sphinx_extensions/dfhack/tool_docs.py @@ -21,7 +21,7 @@ class DFHackToolDirective(sphinx.directives.ObjectDescription): tool_name = self.get_signatures()[0] tag_nodes = [nodes.strong(text='Tags: ')] - for tag in self.options['tags']: + for tag in self.options.get('tags', []): tag_nodes += [ nodes.literal(tag, tag), nodes.inline(text=' | '), @@ -29,7 +29,7 @@ class DFHackToolDirective(sphinx.directives.ObjectDescription): tag_nodes.pop() return [ - nodes.section('', nodes.title(text=tool_name)), + nodes.section('', nodes.title(text=tool_name), ids=[tool_name]), nodes.paragraph('', '', *tag_nodes), ] From e47c681e9c252f0f4599f5c2f7b41ea13e4f05f3 Mon Sep 17 00:00:00 2001 From: lethosor Date: Thu, 4 Aug 2022 02:07:52 -0400 Subject: [PATCH 04/20] Use write_file_if_changed() in changelog.py Speeds up incremental builds significantly --- conf.py | 25 ++++------------------ docs/sphinx_extensions/dfhack/changelog.py | 4 ++-- docs/sphinx_extensions/dfhack/util.py | 20 +++++++++++++++++ 3 files changed, 26 insertions(+), 23 deletions(-) diff --git a/conf.py b/conf.py index e8ff8392b..49d005f46 100644 --- a/conf.py +++ b/conf.py @@ -14,9 +14,7 @@ serve to show the default. # pylint:disable=redefined-builtin -import contextlib import datetime -import io import os import re import shlex # pylint:disable=unused-import @@ -46,6 +44,10 @@ if os.environ.get('DFHACK_DOCS_BUILD_OFFLINE'): from docutils import nodes from docutils.parsers.rst import roles +sys.path.append(os.path.join(os.path.abspath(os.path.dirname(__file__)), 'docs', 'sphinx_extensions')) +from dfhack.util import write_file_if_changed + + def get_keybinds(root, files, keybindings): """Add keybindings in the specified files to the given keybindings dict. @@ -145,23 +147,6 @@ def get_tags(): return tags -@contextlib.contextmanager -def write_file_if_changed(path): - with io.StringIO() as buffer: - yield buffer - new_contents = buffer.getvalue() - - try: - with open(path, 'r') as infile: - old_contents = infile.read() - except IOError: - old_contents = None - - if old_contents != new_contents: - with open(path, 'w') as outfile: - outfile.write(new_contents) - - def generate_tag_indices(): os.makedirs('docs/tags', mode=0o755, exist_ok=True) with write_file_if_changed('docs/tags/index.rst') as topidx: @@ -225,8 +210,6 @@ generate_tag_indices() # -- General configuration ------------------------------------------------ -sys.path.append(os.path.join(os.path.abspath(os.path.dirname(__file__)), 'docs', 'sphinx_extensions')) - # If your documentation needs a minimal Sphinx version, state it here. needs_sphinx = '1.8' diff --git a/docs/sphinx_extensions/dfhack/changelog.py b/docs/sphinx_extensions/dfhack/changelog.py index 2f27590fd..0cd732988 100644 --- a/docs/sphinx_extensions/dfhack/changelog.py +++ b/docs/sphinx_extensions/dfhack/changelog.py @@ -6,7 +6,7 @@ import sys from sphinx.errors import ExtensionError, SphinxError, SphinxWarning -from dfhack.util import DFHACK_ROOT, DOCS_ROOT +from dfhack.util import DFHACK_ROOT, DOCS_ROOT, write_file_if_changed CHANGELOG_PATHS = ( 'docs/changelog.txt', @@ -172,7 +172,7 @@ def consolidate_changelog(all_entries): def print_changelog(versions, all_entries, path, replace=True, prefix=''): # all_entries: version -> section -> entry - with open(path, 'w') as f: + with write_file_if_changed(path) as f: def write(line): if replace: line = replace_text(line, REPLACEMENTS) diff --git a/docs/sphinx_extensions/dfhack/util.py b/docs/sphinx_extensions/dfhack/util.py index 61f38b043..91f0accbe 100644 --- a/docs/sphinx_extensions/dfhack/util.py +++ b/docs/sphinx_extensions/dfhack/util.py @@ -1,3 +1,5 @@ +import contextlib +import io import os DFHACK_ROOT = os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))) @@ -6,6 +8,24 @@ DOCS_ROOT = os.path.join(DFHACK_ROOT, 'docs') if not os.path.isdir(DOCS_ROOT): raise ReferenceError('docs root not found: %s' % DOCS_ROOT) + +@contextlib.contextmanager +def write_file_if_changed(path): + with io.StringIO() as buffer: + yield buffer + new_contents = buffer.getvalue() + + try: + with open(path, 'r') as infile: + old_contents = infile.read() + except IOError: + old_contents = None + + if old_contents != new_contents: + with open(path, 'w') as outfile: + outfile.write(new_contents) + + # directive argument helpers (supplementing docutils.parsers.rst.directives) def directive_arg_str_list(argument): """ From de5f4d356679db170f823d806c81b2b1f3b2ef18 Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 6 Aug 2022 16:24:56 -0400 Subject: [PATCH 05/20] Default to document basename in dfhack-tool directive --- docs/plugins/3dveins.rst | 2 +- docs/sphinx_extensions/dfhack/tool_docs.py | 12 +++++------- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/docs/plugins/3dveins.rst b/docs/plugins/3dveins.rst index 7ae2324eb..f1149eb98 100644 --- a/docs/plugins/3dveins.rst +++ b/docs/plugins/3dveins.rst @@ -1,7 +1,7 @@ 3dveins ======= -.. dfhack-tool:: 3dveins +.. dfhack-tool:: :tags: fort, mod, map :dfhack-keybind:`3dveins` diff --git a/docs/sphinx_extensions/dfhack/tool_docs.py b/docs/sphinx_extensions/dfhack/tool_docs.py index 79516e0d8..fd6ed0946 100644 --- a/docs/sphinx_extensions/dfhack/tool_docs.py +++ b/docs/sphinx_extensions/dfhack/tool_docs.py @@ -12,13 +12,16 @@ import dfhack.util class DFHackToolDirective(sphinx.directives.ObjectDescription): has_content = False - required_arguments = 1 + required_arguments = 0 option_spec = { 'tags': dfhack.util.directive_arg_str_list, } def run(self): - tool_name = self.get_signatures()[0] + if self.arguments: + tool_name = self.arguments[0] + else: + tool_name = self.env.docname.split('/')[-1] tag_nodes = [nodes.strong(text='Tags: ')] for tag in self.options.get('tags', []): @@ -33,11 +36,6 @@ class DFHackToolDirective(sphinx.directives.ObjectDescription): nodes.paragraph('', '', *tag_nodes), ] - # simpler but always renders as "inline code" (and less customizable) - # def handle_signature(self, sig, signode): - # signode += addnodes.desc_name(text=sig) - # return sig - def register(app): app.add_directive('dfhack-tool', DFHackToolDirective) From bb2ca0cc1666c8a33ef3dcfb4f11073c4830b746 Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 6 Aug 2022 16:59:45 -0400 Subject: [PATCH 06/20] Render dfhack-tool as admonition Getting a section header integrated is complicated, so might as well emulate Mediawiki with a box-like element instead --- docs/sphinx_extensions/dfhack/tool_docs.py | 9 +++++++-- docs/styles/dfhack.css | 5 +++++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/docs/sphinx_extensions/dfhack/tool_docs.py b/docs/sphinx_extensions/dfhack/tool_docs.py index fd6ed0946..809bd48b8 100644 --- a/docs/sphinx_extensions/dfhack/tool_docs.py +++ b/docs/sphinx_extensions/dfhack/tool_docs.py @@ -32,8 +32,13 @@ class DFHackToolDirective(sphinx.directives.ObjectDescription): tag_nodes.pop() return [ - nodes.section('', nodes.title(text=tool_name), ids=[tool_name]), - nodes.paragraph('', '', *tag_nodes), + nodes.admonition('', *[ + nodes.paragraph('', '', *[ + nodes.strong('', 'Tool: '), + nodes.inline('', tool_name), + ]), + nodes.paragraph('', '', *tag_nodes), + ], classes=['dfhack-tool-summary']), ] diff --git a/docs/styles/dfhack.css b/docs/styles/dfhack.css index 9b6e523ef..e24ce1c67 100644 --- a/docs/styles/dfhack.css +++ b/docs/styles/dfhack.css @@ -60,3 +60,8 @@ div.body { span.pre { overflow-wrap: break-word; } + +.dfhack-tool-summary p { + margin-top: 0; + margin-bottom: 0.5em; +} From 12b3363b2ca1c8417c2a1fb20f5806709bc58f76 Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 6 Aug 2022 17:26:33 -0400 Subject: [PATCH 07/20] Make dfhack-tool tags link to tag descriptions --- docs/sphinx_extensions/dfhack/tool_docs.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/docs/sphinx_extensions/dfhack/tool_docs.py b/docs/sphinx_extensions/dfhack/tool_docs.py index 809bd48b8..65da95181 100644 --- a/docs/sphinx_extensions/dfhack/tool_docs.py +++ b/docs/sphinx_extensions/dfhack/tool_docs.py @@ -6,6 +6,7 @@ import docutils.nodes as nodes # import docutils.parsers.rst.directives as rst_directives import sphinx +import sphinx.addnodes as addnodes import sphinx.directives import dfhack.util @@ -26,7 +27,13 @@ class DFHackToolDirective(sphinx.directives.ObjectDescription): tag_nodes = [nodes.strong(text='Tags: ')] for tag in self.options.get('tags', []): tag_nodes += [ - nodes.literal(tag, tag), + addnodes.pending_xref(tag, nodes.inline(text=tag), **{ + 'reftype': 'ref', + 'refdomain': 'std', + 'reftarget': 'tag/' + tag, + 'refexplicit': False, + 'refwarn': True, + }), nodes.inline(text=' | '), ] tag_nodes.pop() From d19ffa18060807dd58ede04e12cb54867d156950 Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 6 Aug 2022 23:08:51 -0400 Subject: [PATCH 08/20] Add stub dfhack-command directive, refactor to support --- docs/plugins/3dveins.rst | 2 + docs/sphinx_extensions/dfhack/tool_docs.py | 49 +++++++++++++++------- 2 files changed, 37 insertions(+), 14 deletions(-) diff --git a/docs/plugins/3dveins.rst b/docs/plugins/3dveins.rst index f1149eb98..86a00b51c 100644 --- a/docs/plugins/3dveins.rst +++ b/docs/plugins/3dveins.rst @@ -4,6 +4,8 @@ .. dfhack-tool:: :tags: fort, mod, map +.. dfhack-command:: + :dfhack-keybind:`3dveins` :index:`Rewrite layer veins to expand in 3D space. diff --git a/docs/sphinx_extensions/dfhack/tool_docs.py b/docs/sphinx_extensions/dfhack/tool_docs.py index 65da95181..9100375b3 100644 --- a/docs/sphinx_extensions/dfhack/tool_docs.py +++ b/docs/sphinx_extensions/dfhack/tool_docs.py @@ -11,19 +11,37 @@ import sphinx.directives import dfhack.util -class DFHackToolDirective(sphinx.directives.ObjectDescription): +class DFHackToolDirectiveBase(sphinx.directives.ObjectDescription): has_content = False required_arguments = 0 - option_spec = { - 'tags': dfhack.util.directive_arg_str_list, - } - def run(self): + def get_name_or_docname(self): if self.arguments: - tool_name = self.arguments[0] + return self.arguments[0] else: - tool_name = self.env.docname.split('/')[-1] + return self.env.docname.split('/')[-1] + + def make_labeled_paragraph(self, label, content): + return nodes.paragraph('', '', *[ + nodes.strong('', '{}: '.format(label)), + nodes.inline('', content), + ]) + + def make_nodes(self): + raise NotImplementedError + + def run(self): + return [ + nodes.admonition('', *self.make_nodes(), classes=['dfhack-tool-summary']), + ] + +class DFHackToolDirective(DFHackToolDirectiveBase): + option_spec = { + 'tags': dfhack.util.directive_arg_str_list, + } + + def make_nodes(self): tag_nodes = [nodes.strong(text='Tags: ')] for tag in self.options.get('tags', []): tag_nodes += [ @@ -39,18 +57,21 @@ class DFHackToolDirective(sphinx.directives.ObjectDescription): tag_nodes.pop() return [ - nodes.admonition('', *[ - nodes.paragraph('', '', *[ - nodes.strong('', 'Tool: '), - nodes.inline('', tool_name), - ]), - nodes.paragraph('', '', *tag_nodes), - ], classes=['dfhack-tool-summary']), + self.make_labeled_paragraph('Tool', self.get_name_or_docname()), + nodes.paragraph('', '', *tag_nodes), + ] + + +class DFHackCommandDirective(DFHackToolDirectiveBase): + def make_nodes(self): + return [ + self.make_labeled_paragraph('Command', self.get_name_or_docname()), ] def register(app): app.add_directive('dfhack-tool', DFHackToolDirective) + app.add_directive('dfhack-command', DFHackCommandDirective) def setup(app): app.connect('builder-inited', register) From b3d79f87cbb77e49d688400b6d588cd1f6b94f7e Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 6 Aug 2022 23:12:26 -0400 Subject: [PATCH 09/20] Fix optional name override --- docs/sphinx_extensions/dfhack/tool_docs.py | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/sphinx_extensions/dfhack/tool_docs.py b/docs/sphinx_extensions/dfhack/tool_docs.py index 9100375b3..4911bd454 100644 --- a/docs/sphinx_extensions/dfhack/tool_docs.py +++ b/docs/sphinx_extensions/dfhack/tool_docs.py @@ -14,6 +14,7 @@ import dfhack.util class DFHackToolDirectiveBase(sphinx.directives.ObjectDescription): has_content = False required_arguments = 0 + optional_arguments = 1 def get_name_or_docname(self): if self.arguments: From 39e928845879cea0f2ed9461f22dcde1403ad081 Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 6 Aug 2022 23:11:12 -0400 Subject: [PATCH 10/20] Render commands as literals --- docs/sphinx_extensions/dfhack/tool_docs.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/sphinx_extensions/dfhack/tool_docs.py b/docs/sphinx_extensions/dfhack/tool_docs.py index 4911bd454..35e4de700 100644 --- a/docs/sphinx_extensions/dfhack/tool_docs.py +++ b/docs/sphinx_extensions/dfhack/tool_docs.py @@ -22,10 +22,10 @@ class DFHackToolDirectiveBase(sphinx.directives.ObjectDescription): else: return self.env.docname.split('/')[-1] - def make_labeled_paragraph(self, label, content): + def make_labeled_paragraph(self, label, content, label_class=nodes.strong, content_class=nodes.inline): return nodes.paragraph('', '', *[ - nodes.strong('', '{}: '.format(label)), - nodes.inline('', content), + label_class('', '{}: '.format(label)), + content_class('', content), ]) def make_nodes(self): @@ -66,7 +66,7 @@ class DFHackToolDirective(DFHackToolDirectiveBase): class DFHackCommandDirective(DFHackToolDirectiveBase): def make_nodes(self): return [ - self.make_labeled_paragraph('Command', self.get_name_or_docname()), + self.make_labeled_paragraph('Command', self.get_name_or_docname(), content_class=nodes.literal), ] From 5ef36d210fae15c4715d87680421f9e1559f6f65 Mon Sep 17 00:00:00 2001 From: lethosor Date: Sun, 7 Aug 2022 01:03:15 -0400 Subject: [PATCH 11/20] Render implicit dfhack-command alongside dfhack-tool unless :no-command: is passed --- docs/plugins/3dveins.rst | 2 -- docs/sphinx_extensions/dfhack/tool_docs.py | 28 +++++++++++++++------- 2 files changed, 20 insertions(+), 10 deletions(-) diff --git a/docs/plugins/3dveins.rst b/docs/plugins/3dveins.rst index 86a00b51c..f1149eb98 100644 --- a/docs/plugins/3dveins.rst +++ b/docs/plugins/3dveins.rst @@ -4,8 +4,6 @@ .. dfhack-tool:: :tags: fort, mod, map -.. dfhack-command:: - :dfhack-keybind:`3dveins` :index:`Rewrite layer veins to expand in 3D space. diff --git a/docs/sphinx_extensions/dfhack/tool_docs.py b/docs/sphinx_extensions/dfhack/tool_docs.py index 35e4de700..28876c36a 100644 --- a/docs/sphinx_extensions/dfhack/tool_docs.py +++ b/docs/sphinx_extensions/dfhack/tool_docs.py @@ -3,8 +3,10 @@ # https://www.sphinx-doc.org/en/master/development/tutorials/recipe.html # https://www.sphinx-doc.org/en/master/usage/restructuredtext/basics.html#rst-directives +from typing import List + import docutils.nodes as nodes -# import docutils.parsers.rst.directives as rst_directives +import docutils.parsers.rst.directives as rst_directives import sphinx import sphinx.addnodes as addnodes import sphinx.directives @@ -22,27 +24,31 @@ class DFHackToolDirectiveBase(sphinx.directives.ObjectDescription): else: return self.env.docname.split('/')[-1] - def make_labeled_paragraph(self, label, content, label_class=nodes.strong, content_class=nodes.inline): + @staticmethod + def make_labeled_paragraph(label, content, label_class=nodes.strong, content_class=nodes.inline) -> nodes.paragraph: return nodes.paragraph('', '', *[ label_class('', '{}: '.format(label)), content_class('', content), ]) - def make_nodes(self): + @staticmethod + def wrap_box(*children: List[nodes.Node]) -> nodes.Admonition: + return nodes.admonition('', *children, classes=['dfhack-tool-summary']) + + def render_content(self) -> List[nodes.Node]: raise NotImplementedError def run(self): - return [ - nodes.admonition('', *self.make_nodes(), classes=['dfhack-tool-summary']), - ] + return [self.wrap_box(*self.render_content())] class DFHackToolDirective(DFHackToolDirectiveBase): option_spec = { 'tags': dfhack.util.directive_arg_str_list, + 'no-command': rst_directives.flag, } - def make_nodes(self): + def render_content(self) -> List[nodes.Node]: tag_nodes = [nodes.strong(text='Tags: ')] for tag in self.options.get('tags', []): tag_nodes += [ @@ -62,9 +68,15 @@ class DFHackToolDirective(DFHackToolDirectiveBase): nodes.paragraph('', '', *tag_nodes), ] + def run(self): + out = DFHackToolDirectiveBase.run(self) + if 'no-command' not in self.options: + out += [self.wrap_box(*DFHackCommandDirective.render_content(self))] + return out + class DFHackCommandDirective(DFHackToolDirectiveBase): - def make_nodes(self): + def render_content(self) -> List[nodes.Node]: return [ self.make_labeled_paragraph('Command', self.get_name_or_docname(), content_class=nodes.literal), ] From ed95db27f5bd7b13e556fc2ca15b4f68f38cdcb7 Mon Sep 17 00:00:00 2001 From: lethosor Date: Sun, 7 Aug 2022 01:37:14 -0400 Subject: [PATCH 12/20] Move dfhack-keybind role to tool_docs.py and call from dfhack-command --- conf.py | 67 +--------------------- docs/sphinx_extensions/dfhack/tool_docs.py | 66 ++++++++++++++++++++- 2 files changed, 67 insertions(+), 66 deletions(-) diff --git a/conf.py b/conf.py index 49d005f46..775f94b68 100644 --- a/conf.py +++ b/conf.py @@ -21,6 +21,8 @@ import shlex # pylint:disable=unused-import import sphinx import sys +sys.path.append(os.path.join(os.path.abspath(os.path.dirname(__file__)), 'docs', 'sphinx_extensions')) +from dfhack.util import write_file_if_changed if os.environ.get('DFHACK_DOCS_BUILD_OFFLINE'): # block attempted image downloads, particularly for the PDF builder @@ -38,71 +40,6 @@ if os.environ.get('DFHACK_DOCS_BUILD_OFFLINE'): requests.get = request_disabled -# -- Support :dfhack-keybind:`command` ------------------------------------ -# this is a custom directive that pulls info from default keybindings - -from docutils import nodes -from docutils.parsers.rst import roles - -sys.path.append(os.path.join(os.path.abspath(os.path.dirname(__file__)), 'docs', 'sphinx_extensions')) -from dfhack.util import write_file_if_changed - - -def get_keybinds(root, files, keybindings): - """Add keybindings in the specified files to the - given keybindings dict. - """ - for file in files: - with open(os.path.join(root, file)) as f: - lines = [l.replace('keybinding add', '').strip() for l in f.readlines() - if l.startswith('keybinding add')] - for k in lines: - first, command = k.split(' ', 1) - bind, context = (first.split('@') + [''])[:2] - if ' ' not in command: - command = command.replace('"', '') - tool = command.split(' ')[0].replace('"', '') - keybindings[tool] = keybindings.get(tool, []) + [ - (command, bind.split('-'), context)] - -def get_all_keybinds(root_dir): - """Get the implemented keybinds, and return a dict of - {tool: [(full_command, keybinding, context), ...]}. - """ - keybindings = dict() - for root, _, files in os.walk(root_dir): - get_keybinds(root, files, keybindings) - return keybindings - -KEYBINDS = get_all_keybinds('data/init') - - -# pylint:disable=unused-argument,dangerous-default-value,too-many-arguments -def dfhack_keybind_role_func(role, rawtext, text, lineno, inliner, - options={}, content=[]): - """Custom role parser for DFHack default keybinds.""" - roles.set_classes(options) - if text not in KEYBINDS: - return [], [] - newnode = nodes.paragraph() - for cmd, key, ctx in KEYBINDS[text]: - n = nodes.paragraph() - newnode += n - n += nodes.strong('Keybinding:', 'Keybinding:') - n += nodes.inline(' ', ' ') - for k in key: - n += nodes.inline(k, k, classes=['kbd']) - if cmd != text: - n += nodes.inline(' -> ', ' -> ') - n += nodes.literal(cmd, cmd, classes=['guilabel']) - if ctx: - n += nodes.inline(' in ', ' in ') - n += nodes.literal(ctx, ctx) - return [newnode], [] - - -roles.register_canonical_role('dfhack-keybind', dfhack_keybind_role_func) - # -- Autodoc for DFhack plugins and scripts ------------------------------- def doc_dir(dirname, files, prefix): diff --git a/docs/sphinx_extensions/dfhack/tool_docs.py b/docs/sphinx_extensions/dfhack/tool_docs.py index 28876c36a..d1bdec8bf 100644 --- a/docs/sphinx_extensions/dfhack/tool_docs.py +++ b/docs/sphinx_extensions/dfhack/tool_docs.py @@ -3,6 +3,7 @@ # https://www.sphinx-doc.org/en/master/development/tutorials/recipe.html # https://www.sphinx-doc.org/en/master/usage/restructuredtext/basics.html#rst-directives +import os from typing import List import docutils.nodes as nodes @@ -13,6 +14,63 @@ import sphinx.directives import dfhack.util + +_KEYBINDS = {} + +def scan_keybinds(root, files, keybindings): + """Add keybindings in the specified files to the + given keybindings dict. + """ + for file in files: + with open(os.path.join(root, file)) as f: + lines = [l.replace('keybinding add', '').strip() for l in f.readlines() + if l.startswith('keybinding add')] + for k in lines: + first, command = k.split(' ', 1) + bind, context = (first.split('@') + [''])[:2] + if ' ' not in command: + command = command.replace('"', '') + tool = command.split(' ')[0].replace('"', '') + keybindings[tool] = keybindings.get(tool, []) + [ + (command, bind.split('-'), context)] + +def scan_all_keybinds(root_dir): + """Get the implemented keybinds, and return a dict of + {tool: [(full_command, keybinding, context), ...]}. + """ + keybindings = dict() + for root, _, files in os.walk(root_dir): + scan_keybinds(root, files, keybindings) + return keybindings + + +def render_dfhack_keybind(command) -> List[nodes.paragraph]: + if command not in _KEYBINDS: + return [] + newnode = nodes.paragraph() + for keycmd, key, ctx in _KEYBINDS[command]: + n = nodes.paragraph() + newnode += n + n += nodes.strong('Keybinding:', 'Keybinding:') + n += nodes.inline(' ', ' ') + for k in key: + n += nodes.inline(k, k, classes=['kbd']) + if keycmd != command: + n += nodes.inline(' -> ', ' -> ') + n += nodes.literal(keycmd, keycmd, classes=['guilabel']) + if ctx: + n += nodes.inline(' in ', ' in ') + n += nodes.literal(ctx, ctx) + return [newnode] + + +# pylint:disable=unused-argument,dangerous-default-value,too-many-arguments +def dfhack_keybind_role(role, rawtext, text, lineno, inliner, + options={}, content=[]): + """Custom role parser for DFHack default keybinds.""" + return render_dfhack_keybind(text), [] + + class DFHackToolDirectiveBase(sphinx.directives.ObjectDescription): has_content = False required_arguments = 0 @@ -77,14 +135,20 @@ class DFHackToolDirective(DFHackToolDirectiveBase): class DFHackCommandDirective(DFHackToolDirectiveBase): def render_content(self) -> List[nodes.Node]: + command = self.get_name_or_docname() return [ - self.make_labeled_paragraph('Command', self.get_name_or_docname(), content_class=nodes.literal), + self.make_labeled_paragraph('Command', command, content_class=nodes.literal), + *render_dfhack_keybind(command), ] def register(app): app.add_directive('dfhack-tool', DFHackToolDirective) app.add_directive('dfhack-command', DFHackCommandDirective) + app.add_role('dfhack-keybind', dfhack_keybind_role) + + _KEYBINDS.update(scan_all_keybinds(os.path.join(dfhack.util.DFHACK_ROOT, 'data', 'init'))) + def setup(app): app.connect('builder-inited', register) From 5a14992aca93e896c334980ae0b104128508d0bd Mon Sep 17 00:00:00 2001 From: lethosor Date: Sun, 7 Aug 2022 01:44:02 -0400 Subject: [PATCH 13/20] Use new directives for a few plugins --- docs/plugins/3dveins.rst | 2 -- docs/plugins/autodump.rst | 11 +++++++---- docs/plugins/autogems.rst | 8 ++++++-- docs/plugins/automelt.rst | 5 ++++- 4 files changed, 17 insertions(+), 9 deletions(-) diff --git a/docs/plugins/3dveins.rst b/docs/plugins/3dveins.rst index f1149eb98..b14f6145d 100644 --- a/docs/plugins/3dveins.rst +++ b/docs/plugins/3dveins.rst @@ -4,8 +4,6 @@ .. dfhack-tool:: :tags: fort, mod, map -:dfhack-keybind:`3dveins` - :index:`Rewrite layer veins to expand in 3D space. <3dveins; Rewrite layer veins to expand in 3D space.>` Existing, flat veins are removed and new 3D veins that naturally span z-levels are generated in diff --git a/docs/plugins/autodump.rst b/docs/plugins/autodump.rst index 53ed7f1ba..dc60946b1 100644 --- a/docs/plugins/autodump.rst +++ b/docs/plugins/autodump.rst @@ -1,9 +1,12 @@ autodump ======== -**Tags:** `tag/fort`, `tag/auto`, `tag/fps`, `tag/items`, `tag/stockpiles` -:dfhack-keybind:`autodump` -:dfhack-keybind:`autodump-destroy-here` -:dfhack-keybind:`autodump-destroy-item` + +.. dfhack-tool:: + :tags: fort, auto, fps, items, stockpiles + +.. dfhack-command:: autodump-destroy-here + +.. dfhack-command:: autodump-destroy-item :index:`Automatically set items in a stockpile to be dumped. ` When diff --git a/docs/plugins/autogems.rst b/docs/plugins/autogems.rst index bcff93ba6..3ceed0bec 100644 --- a/docs/plugins/autogems.rst +++ b/docs/plugins/autogems.rst @@ -1,7 +1,11 @@ autogems ======== -**Tags:** `tag/fort`, `tag/auto`, `tag/jobs` -:dfhack-keybind:`autogems-reload` + +.. dfhack-tool:: autogems + :tags: fort, auto, jobs + :no-command: + +.. dfhack-command:: autogems-reload :index:`Automatically cut rough gems. ` This plugin periodically scans your stocks of rough gems and creates manager diff --git a/docs/plugins/automelt.rst b/docs/plugins/automelt.rst index 190bffc06..0ca9f82e9 100644 --- a/docs/plugins/automelt.rst +++ b/docs/plugins/automelt.rst @@ -1,6 +1,9 @@ automelt ======== -**Tags:** `tag/fort`, `tag/auto`, `tag/items`, `tag/stockpiles` + +.. dfhack-tool:: + :tags: fort, auto, items, stockpiles + :no-command: :index:`Quickly designate items to be melted. ` When `enabled `, this From 7651d301d244033fc392eafef2985f245e9e4dd5 Mon Sep 17 00:00:00 2001 From: lethosor Date: Sun, 7 Aug 2022 01:51:38 -0400 Subject: [PATCH 14/20] Remove extra paragraph around keybindings --- docs/sphinx_extensions/dfhack/tool_docs.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/sphinx_extensions/dfhack/tool_docs.py b/docs/sphinx_extensions/dfhack/tool_docs.py index d1bdec8bf..920bef98e 100644 --- a/docs/sphinx_extensions/dfhack/tool_docs.py +++ b/docs/sphinx_extensions/dfhack/tool_docs.py @@ -45,12 +45,11 @@ def scan_all_keybinds(root_dir): def render_dfhack_keybind(command) -> List[nodes.paragraph]: + out = [] if command not in _KEYBINDS: - return [] - newnode = nodes.paragraph() + return out for keycmd, key, ctx in _KEYBINDS[command]: n = nodes.paragraph() - newnode += n n += nodes.strong('Keybinding:', 'Keybinding:') n += nodes.inline(' ', ' ') for k in key: @@ -61,7 +60,8 @@ def render_dfhack_keybind(command) -> List[nodes.paragraph]: if ctx: n += nodes.inline(' in ', ' in ') n += nodes.literal(ctx, ctx) - return [newnode] + out.append(n) + return out # pylint:disable=unused-argument,dangerous-default-value,too-many-arguments From 6b32e008b3bfd9eb73535ae69ea82fbf73cc14d0 Mon Sep 17 00:00:00 2001 From: lethosor Date: Sun, 7 Aug 2022 02:16:38 -0400 Subject: [PATCH 15/20] Attempt to port keybinding documentation verification to new extension Likely requires a sphinx Domain to work with parallel builds properly --- conf.py | 15 --------------- docs/sphinx_extensions/dfhack/tool_docs.py | 15 +++++++++++++++ 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/conf.py b/conf.py index 775f94b68..ff4cdc59d 100644 --- a/conf.py +++ b/conf.py @@ -126,24 +126,9 @@ def write_tool_docs(): outfile.write(include) -def all_keybinds_documented(): - """Check that all keybindings are documented with the :dfhack-keybind: - directive somewhere.""" - undocumented_binds = set(KEYBINDS) - tools = set(i[0] for i in DOC_ALL_DIRS) - for t in tools: - with open(('./docs/tools/{}.rst').format(t)) as f: - tool_binds = set(re.findall(':dfhack-keybind:`(.*?)`', f.read())) - undocumented_binds -= tool_binds - if undocumented_binds: - raise ValueError('The following DFHack commands have undocumented ' - 'keybindings: {}'.format(sorted(undocumented_binds))) - - # Actually call the docs generator and run test write_tool_docs() generate_tag_indices() -#all_keybinds_documented() # comment out while we're transitioning # -- General configuration ------------------------------------------------ diff --git a/docs/sphinx_extensions/dfhack/tool_docs.py b/docs/sphinx_extensions/dfhack/tool_docs.py index 920bef98e..8c843dce0 100644 --- a/docs/sphinx_extensions/dfhack/tool_docs.py +++ b/docs/sphinx_extensions/dfhack/tool_docs.py @@ -3,6 +3,7 @@ # https://www.sphinx-doc.org/en/master/development/tutorials/recipe.html # https://www.sphinx-doc.org/en/master/usage/restructuredtext/basics.html#rst-directives +import logging import os from typing import List @@ -15,7 +16,10 @@ import sphinx.directives import dfhack.util +logger = sphinx.util.logging.getLogger(__name__) + _KEYBINDS = {} +_KEYBINDS_RENDERED = set() # commands whose keybindings have been rendered def scan_keybinds(root, files, keybindings): """Add keybindings in the specified files to the @@ -45,6 +49,7 @@ def scan_all_keybinds(root_dir): def render_dfhack_keybind(command) -> List[nodes.paragraph]: + _KEYBINDS_RENDERED.add(command) out = [] if command not in _KEYBINDS: return out @@ -64,6 +69,13 @@ def render_dfhack_keybind(command) -> List[nodes.paragraph]: return out +def check_missing_keybinds(): + # FIXME: _KEYBINDS_RENDERED is empty in the parent process under parallel builds + # consider moving to a sphinx Domain to solve this properly + for missing_command in sorted(set(_KEYBINDS.keys()) - _KEYBINDS_RENDERED): + logger.warning('Undocumented keybindings for command: %s', missing_command) + + # pylint:disable=unused-argument,dangerous-default-value,too-many-arguments def dfhack_keybind_role(role, rawtext, text, lineno, inliner, options={}, content=[]): @@ -153,6 +165,9 @@ def register(app): def setup(app): app.connect('builder-inited', register) + # TODO: re-enable once detection is corrected + # app.connect('build-finished', lambda *_: check_missing_keybinds()) + return { 'version': '0.1', 'parallel_read_safe': True, From 1e7ce2602e492e8cbe8b466686f2403a24bf6f1f Mon Sep 17 00:00:00 2001 From: lethosor Date: Sun, 7 Aug 2022 18:18:51 -0400 Subject: [PATCH 16/20] Shrink tool/command boxes somewhat --- docs/styles/dfhack.css | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/docs/styles/dfhack.css b/docs/styles/dfhack.css index e24ce1c67..4102e6412 100644 --- a/docs/styles/dfhack.css +++ b/docs/styles/dfhack.css @@ -61,7 +61,13 @@ span.pre { overflow-wrap: break-word; } -.dfhack-tool-summary p { +div.dfhack-tool-summary { + margin: 10px 0; + padding: 10px 15px; +} + +div.dfhack-tool-summary p { margin-top: 0; margin-bottom: 0.5em; + line-height: 1em; } From 6e29ddf2d312fb157105d340ff37aade0cd1fbac Mon Sep 17 00:00:00 2001 From: lethosor Date: Mon, 8 Aug 2022 02:19:07 -0400 Subject: [PATCH 17/20] Move space out of node for better text rendering --- docs/sphinx_extensions/dfhack/tool_docs.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/sphinx_extensions/dfhack/tool_docs.py b/docs/sphinx_extensions/dfhack/tool_docs.py index 8c843dce0..04318816b 100644 --- a/docs/sphinx_extensions/dfhack/tool_docs.py +++ b/docs/sphinx_extensions/dfhack/tool_docs.py @@ -97,7 +97,8 @@ class DFHackToolDirectiveBase(sphinx.directives.ObjectDescription): @staticmethod def make_labeled_paragraph(label, content, label_class=nodes.strong, content_class=nodes.inline) -> nodes.paragraph: return nodes.paragraph('', '', *[ - label_class('', '{}: '.format(label)), + label_class('', '{}:'.format(label)), + nodes.inline(text=' '), content_class('', content), ]) @@ -119,7 +120,7 @@ class DFHackToolDirective(DFHackToolDirectiveBase): } def render_content(self) -> List[nodes.Node]: - tag_nodes = [nodes.strong(text='Tags: ')] + tag_nodes = [nodes.strong(text='Tags:'), nodes.inline(text=' ')] for tag in self.options.get('tags', []): tag_nodes += [ addnodes.pending_xref(tag, nodes.inline(text=tag), **{ From daf3bc516be17e6fc953cad9e8274fc7504568d6 Mon Sep 17 00:00:00 2001 From: lethosor Date: Mon, 8 Aug 2022 02:29:22 -0400 Subject: [PATCH 18/20] Switch to to fix line breaks in text output No visible change in HTML output; PDF looks different but still acceptable --- docs/sphinx_extensions/dfhack/tool_docs.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sphinx_extensions/dfhack/tool_docs.py b/docs/sphinx_extensions/dfhack/tool_docs.py index 04318816b..42d995ff2 100644 --- a/docs/sphinx_extensions/dfhack/tool_docs.py +++ b/docs/sphinx_extensions/dfhack/tool_docs.py @@ -104,7 +104,7 @@ class DFHackToolDirectiveBase(sphinx.directives.ObjectDescription): @staticmethod def wrap_box(*children: List[nodes.Node]) -> nodes.Admonition: - return nodes.admonition('', *children, classes=['dfhack-tool-summary']) + return nodes.topic('', *children, classes=['dfhack-tool-summary']) def render_content(self) -> List[nodes.Node]: raise NotImplementedError From e6b5d5b0c1904f0a8f384aff70117a80f2f3f721 Mon Sep 17 00:00:00 2001 From: lethosor Date: Mon, 8 Aug 2022 17:35:58 -0400 Subject: [PATCH 19/20] Remove commas from tag lists --- docs/plugins/3dveins.rst | 2 +- docs/plugins/autodump.rst | 2 +- docs/plugins/autogems.rst | 2 +- docs/plugins/automelt.rst | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/plugins/3dveins.rst b/docs/plugins/3dveins.rst index b14f6145d..63f6cfd8d 100644 --- a/docs/plugins/3dveins.rst +++ b/docs/plugins/3dveins.rst @@ -2,7 +2,7 @@ ======= .. dfhack-tool:: - :tags: fort, mod, map + :tags: fort mod map :index:`Rewrite layer veins to expand in 3D space. <3dveins; Rewrite layer veins to expand in 3D space.>` Existing, flat veins diff --git a/docs/plugins/autodump.rst b/docs/plugins/autodump.rst index dc60946b1..3e8d291a0 100644 --- a/docs/plugins/autodump.rst +++ b/docs/plugins/autodump.rst @@ -2,7 +2,7 @@ autodump ======== .. dfhack-tool:: - :tags: fort, auto, fps, items, stockpiles + :tags: fort auto fps items stockpiles .. dfhack-command:: autodump-destroy-here diff --git a/docs/plugins/autogems.rst b/docs/plugins/autogems.rst index 3ceed0bec..11575aa7a 100644 --- a/docs/plugins/autogems.rst +++ b/docs/plugins/autogems.rst @@ -2,7 +2,7 @@ autogems ======== .. dfhack-tool:: autogems - :tags: fort, auto, jobs + :tags: fort auto jobs :no-command: .. dfhack-command:: autogems-reload diff --git a/docs/plugins/automelt.rst b/docs/plugins/automelt.rst index 0ca9f82e9..6c42cbb7e 100644 --- a/docs/plugins/automelt.rst +++ b/docs/plugins/automelt.rst @@ -2,7 +2,7 @@ automelt ======== .. dfhack-tool:: - :tags: fort, auto, items, stockpiles + :tags: fort auto items stockpiles :no-command: :index:`Quickly designate items to be melted. From 2d60c543fd867f787e891061531ca7fd4b289572 Mon Sep 17 00:00:00 2001 From: lethosor Date: Mon, 8 Aug 2022 21:22:55 -0400 Subject: [PATCH 20/20] Remove "Tool:" line --- docs/sphinx_extensions/dfhack/tool_docs.py | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/sphinx_extensions/dfhack/tool_docs.py b/docs/sphinx_extensions/dfhack/tool_docs.py index 42d995ff2..ff4bfc9b1 100644 --- a/docs/sphinx_extensions/dfhack/tool_docs.py +++ b/docs/sphinx_extensions/dfhack/tool_docs.py @@ -135,7 +135,6 @@ class DFHackToolDirective(DFHackToolDirectiveBase): tag_nodes.pop() return [ - self.make_labeled_paragraph('Tool', self.get_name_or_docname()), nodes.paragraph('', '', *tag_nodes), ]