From cfc322fb7f3ba361d3efef64cb76de7f1cc7a161 Mon Sep 17 00:00:00 2001 From: PeridexisErrant Date: Wed, 15 Jun 2016 13:22:04 +1000 Subject: [PATCH] Improve handling of in-script documentation Closes issue #823. This allows for clean unification of html docs and the in-terminal help text for scripts, including handling in core rather than on a per-script basis (see issue #947). --- Contributing.rst | 26 ++--- NEWS.rst | 1 + conf.py | 193 +++++++++---------------------------- docs/Authors.rst | 1 + travis/script-in-readme.py | 15 +-- 5 files changed, 70 insertions(+), 166 deletions(-) diff --git a/Contributing.rst b/Contributing.rst index d29c4acef..2c1e7bb45 100644 --- a/Contributing.rst +++ b/Contributing.rst @@ -148,29 +148,32 @@ as well as getting help, you'll be providing valuable feedback that makes it easier for future readers! Scripts can use a custom autodoc function, based on the Sphinx ``include`` -directive and Ruby docstring conventions - any lines between ``=begin`` and -``=end`` are copied into the appropriate scripts documentation page. -They **must** have a heading which exactly matches the command, underlined +directive - anything between the tokens is copied into the appropriate scripts +documentation page. For Ruby, we follow the built-in docstring convention +(``=begin`` and ``=end``). For Lua, the tokens are ``[====[`` and ``]====]`` +- ordinary multi-line strings. It is highly encouraged to reuse this string +as the in-console documentation by (eg.) printing it when a ``-help`` argument +is given. + +The docs **must** have a heading which exactly matches the command, underlined with ``=====`` to the same length. For example, a lua file would have:: - --[[=begin + local helpstr = [====[ add-thought =========== Adds a thought or emotion to the selected unit. Can be used by other scripts, or the gui invoked by running ``add-thought gui`` with a unit selected. - =end]] + ]====] -Ruby scripts use the same syntax, but obviously omit the leading ``--[[`` and -trailing ``]]`` which denote a multiline comment in lua. -``=begin`` and ``=end`` are native syntax (and matched in lua for convenience). Where the heading for a section is also the name of a command, the spelling and case should exactly match the command to enter in the DFHack command line. -Try to keep lines within 80-100 characters, so it's readable in plain text - -Sphinx (our documentation system) will make sure paragraphs flow. +Try to keep lines within 80-100 characters, so it's readable in plain text +in the terminal - Sphinx (our documentation system) will make sure +paragraphs flow. If there aren't many options or examples to show, they can go in a paragraph of text. Use double-backticks to put commands in monospaced font, like this:: @@ -208,8 +211,7 @@ section like this:: ========= Add link targets if you need them, but otherwise plain headings are preferred. -Scripts using the in-source docs option, which should be all of them, have -link targets created automatically. +Scripts have link targets created automatically. Other ways to help ================== diff --git a/NEWS.rst b/NEWS.rst index 781c22b7b..7d9630c32 100644 --- a/NEWS.rst +++ b/NEWS.rst @@ -54,6 +54,7 @@ Misc Improvements - `catsplosion`: now a lua script instead of a plugin - `fix/diplomats`: replaces ``fixdiplomats`` - `fix/merchants`: replaces ``fixmerchants`` +- Unified script documentation and in-terminal help options Removed ------- diff --git a/conf.py b/conf.py index 59a220e26..d841eece2 100644 --- a/conf.py +++ b/conf.py @@ -1,21 +1,25 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -# -# DFHack documentation build configuration file -# -# This file is execfile()d with the current directory set to its -# containing dir. -# -# Note that not all possible configuration values are present in this -# autogenerated file. -# -# All configuration values have a default; values that are commented out -# serve to show the default. +""" +DFHack documentation build configuration file + +This file is execfile()d with the current directory set to its +containing dir. + +Note that not all possible configuration values are present in this +autogenerated file. + +All configuration values have a default; values that are commented out +serve to show the default. +""" + +# pylint:disable=redefined-builtin import fnmatch from io import open +from itertools import starmap import os -import shlex +import shlex # pylint:disable=unused-import import sys @@ -29,10 +33,14 @@ def doc_dir(dirname, files): continue with open(os.path.join(dirname, f), 'r', encoding='utf8') as fstream: text = [l.rstrip() for l in fstream.readlines() if l.strip()] + # Some legacy lua files use the ruby tokens (in 3rdparty scripts) + tokens = ('=begin', '=end') + if f[-4:] == '.lua' and any('[====[' in line for line in text): + tokens = ('[====[', ']====]') command = None for line in text: if command and line == len(line) * '=': - yield command, sdir + '/' + f + yield command, sdir + '/' + f, tokens[0], tokens[1] break command = line @@ -40,9 +48,7 @@ def doc_dir(dirname, files): def document_scripts(): """Autodoc for files with the magic script documentation marker strings. - Creates a file for eack kind of script (base/devel/fix/gui/modtools) - with all the ".. include::" directives to pull out docs between the - magic strings. + Returns a dict of script-kinds to lists of .rst include directives. """ # First, we collect the commands and paths to include in our docs scripts = [] @@ -55,15 +61,19 @@ def document_scripts(): if len(k_fname) == 1: kinds['base'].append(s) else: - kinds.get(k_fname[0], []).append(s) - template = ('.. _{}:\n\n' - '.. include:: /{}\n' - ' :start-after: =begin\n' - ' :end-before: =end\n') - for key, value in kinds.items(): - kinds[key] = [template.format(x[0], x[1]) - for x in sorted(value, key=lambda x: x[0])] - # Finally, we write our _auto/* files for each kind of script + kinds[k_fname[0]].append(s) + template = '.. _{}:\n\n.. include:: /{}\n' +\ + ' :start-after: {}\n :end-before: {}\n' + return {key: '\n\n'.join(starmap(template.format, sorted(value))) + for key, value in kinds.items()} + +def write_script_docs(): + """ + Creates a file for eack kind of script (base/devel/fix/gui/modtools) + with all the ".. include::" directives to pull out docs between the + magic strings. + """ + kinds = document_scripts() if not os.path.isdir('docs/_auto'): os.mkdir('docs/_auto') head = { @@ -76,15 +86,18 @@ def document_scripts(): title = ('.. _{k}:\n\n{l}\n{t}\n{l}\n\n' '.. include:: /scripts/{a}about.txt\n\n' '.. contents::\n\n').format( - k=k, t=head[k], l=len(head[k])*'#', a=('' if k=='base' else k+'/')) + k=k, t=head[k], + l=len(head[k])*'#', + a=('' if k == 'base' else k + '/') + ) mode = 'w' if sys.version_info.major > 2 else 'wb' with open('docs/_auto/{}.rst'.format(k), mode) as outfile: outfile.write(title) - outfile.write('\n\n'.join(kinds[k])) + outfile.write(kinds[k]) # Actually call the docs generator -document_scripts() +write_script_docs() # -- General configuration ------------------------------------------------ @@ -135,7 +148,7 @@ author = 'The DFHack Team' def get_version(): """Return the DFHack version string, from CMakeLists.txt""" - version = release = '' + version = release = '' #pylint:disable=redefined-outer-name try: with open('CMakeLists.txt') as f: for s in f.readlines(): @@ -157,11 +170,8 @@ version = release = get_version() # Usually you set "language" from the command line for these cases. language = None -# There are two options for replacing |today|: either, you set today to some -# non-false value, then it is used: -#today = '' -# Else, today_fmt is used as the format for a strftime call. -#today_fmt = '%B %d, %Y' +# strftime format for |today| and 'Last updated on:' timestamp at page bottom +today_fmt = html_last_updated_fmt = '%Y-%m-%d' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. @@ -202,9 +212,6 @@ html_theme_options = { 'travis_button': False, } -# Add any paths that contain custom themes here, relative to this directory. -#html_theme_path = [] - # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". #html_title = None @@ -226,19 +233,6 @@ html_favicon = 'docs/styles/dfhack-icon.ico' # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['docs/styles'] -# Add any extra paths that contain custom files (such as robots.txt or -# .htaccess) here, relative to this directory. These files are copied -# directly to the root of the documentation. -#html_extra_path = [] - -# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, -# using the given strftime format. -html_last_updated_fmt = '%Y-%m-%d' - -# If true, SmartyPants will be used to convert quotes and dashes to -# typographically correct entities. -#html_use_smartypants = True - # Custom sidebar templates, maps document names to template names. html_sidebars = { '**': [ @@ -249,113 +243,18 @@ html_sidebars = { ] } -# Additional templates that should be rendered to pages, maps page names to -# template names. -#html_additional_pages = {} - # If false, no module index is generated. html_domain_indices = False # If false, no index is generated. html_use_index = False -# If true, the index is split into individual pages for each letter. -#html_split_index = False - -# If true, links to the reST sources are added to the pages. -#html_show_sourcelink = True - -# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. -#html_show_sphinx = True - -# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. -#html_show_copyright = True - -# If true, an OpenSearch description file will be output, and all pages will -# contain a tag referring to it. The value of this option must be the -# base URL from which the finished HTML is served. -#html_use_opensearch = '' - -# Output file base name for HTML help builder. -htmlhelp_basename = 'DFHackdoc' - # -- Options for LaTeX output --------------------------------------------- -latex_elements = { -# The paper size ('letterpaper' or 'a4paper'). -#'papersize': 'letterpaper', - -# The font size ('10pt', '11pt' or '12pt'). -#'pointsize': '10pt', - -# Additional stuff for the LaTeX preamble. -#'preamble': '', - -# Latex figure (float) alignment -#'figure_align': 'htbp', -} - # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ - (master_doc, 'DFHack.tex', 'DFHack Documentation', - 'The DFHack Team', 'manual'), + (master_doc, 'DFHack.tex', 'DFHack Documentation', + 'The DFHack Team', 'manual'), ] - -# The name of an image file (relative to this directory) to place at the top of -# the title page. -#latex_logo = None - -# For "manual" documents, if this is true, then toplevel headings are parts, -# not chapters. -#latex_use_parts = False - -# If true, show page references after internal links. -#latex_show_pagerefs = False - -# If true, show URL addresses after external links. -#latex_show_urls = False - -# Documents to append as an appendix to all manuals. -#latex_appendices = [] - -# If false, no module index is generated. -#latex_domain_indices = True - - -# -- Options for manual page output --------------------------------------- - -# One entry per manual page. List of tuples -# (source start file, name, description, authors, manual section). -man_pages = [ - (master_doc, 'dfhack', 'DFHack Documentation', - [author], 1) -] - -# If true, show URL addresses after external links. -#man_show_urls = False - - -# -- Options for Texinfo output ------------------------------------------- - -# Grouping the document tree into Texinfo files. List of tuples -# (source start file, target name, title, author, -# dir menu entry, description, category) -texinfo_documents = [ - (master_doc, 'DFHack', 'DFHack Documentation', - author, 'DFHack', 'One line description of project.', - 'Miscellaneous'), -] - -# Documents to append as an appendix to all manuals. -#texinfo_appendices = [] - -# If false, no module index is generated. -#texinfo_domain_indices = True - -# How to display URL addresses: 'footnote', 'no', or 'inline'. -#texinfo_show_urls = 'footnote' - -# If true, do not generate a @detailmenu in the "Top" node's menu. -#texinfo_no_detailmenu = False diff --git a/docs/Authors.rst b/docs/Authors.rst index e38d82194..64dec0e9c 100644 --- a/docs/Authors.rst +++ b/docs/Authors.rst @@ -95,6 +95,7 @@ Sebastian Wolfertz Enkrod Seth Woodworth sethwoodworth simon Simon Jackson sizeak +stolencatkarma sv-esk sv-esk Tacomagic Tim Walberg twalberg diff --git a/travis/script-in-readme.py b/travis/script-in-readme.py index 8227dea8d..154304420 100644 --- a/travis/script-in-readme.py +++ b/travis/script-in-readme.py @@ -17,7 +17,7 @@ def check_ls(fname, line): """Check length & existence of leading comment for "ls" builtin command.""" line = line.strip() comment = '--' if fname.endswith('.lua') else '#' - if line.endswith('=begin') or not line.startswith(comment): + if '[====[' in line or not line.startswith(comment): print('Error: no leading comment in ' + fname) return 1 return 0 @@ -25,13 +25,15 @@ def check_ls(fname, line): def check_file(fname): errors, doclines = 0, [] + tok1, tok2 = ('=begin', '=end') if fname.endswith('.rb') else \ + ('[====[', ']====]') with open(fname, errors='ignore') as f: lines = f.readlines() errors += check_ls(fname, lines[0]) for l in lines: - if doclines or l.strip().endswith('=begin'): + if doclines or l.strip().endswith(tok1): doclines.append(l.rstrip()) - if l.startswith('=end'): + if l.startswith(tok2): break else: if doclines: @@ -39,7 +41,8 @@ def check_file(fname): else: print('Error: no documentation in: ' + fname) return 1 - title, underline = [d for d in doclines if d and '=begin' not in d][:2] + title, underline = [d for d in doclines + if d and '=begin' not in d and '[====[' not in d][:2] if underline != '=' * len(title): print('Error: title/underline mismatch:', fname, title, underline) errors += 1 @@ -55,9 +58,7 @@ def main(): err = 0 for root, _, files in os.walk('scripts'): for f in files: - # TODO: remove 3rdparty exemptions from checks - # Requires reading their CMakeLists to only apply to used scripts - if f[-3:] in {'.rb', 'lua'} and '3rdparty' not in root: + if f[-3:] in {'.rb', 'lua'}: err += check_file(join(root, f)) return err