Docs Developer Guide

This document is for contributors working on the design, templating, deployment, or development of the ODK Docs website.

Tech Overview

ODK Docs uses:

Custom HTML templating

ODK Docs uses the sphinx_rtd_theme, with some minor customizations.

ODK-specific versions of HTML/Jinja templates are in _templates. Any file in that directory will override the file of the same name in the sphinx_rtd_theme source.

So, to customize a portion of the HTML template, copy the source file from sphinx_rtd_theme and then edit it.

Please commit the copied file unchanged before editing, so that it is easy to track what you have changed.

Custom JavaScript

Custom JavaScript should be added in src/_static/js/custom.js. Comment your code with an explanation of what the JS accomplishes, and a reference to the issue number you are working on.

The ODK Docs template includes JQuery, so you can use it in your custom JS.

Custom CSS

Custom CSS should be added in src/_static/css/custom.css. Comment your code with an explanation of what the CSS accomplishes and a reference to the issue number you are working on.

For example:

/* Example CSS PR #xyx */

div[class^='example'] {
  color: black;
}

It is helpful to keep the CSS file organized. There are several sections in the custom.css file:

  • Styling for rst roles and directives

  • Responsive CSS

  • Styling for JS implementation

  • Utility classes

Each of these sections are enclosed in start and end comments. Add your code to the relevant section. If you don't find any section relevant, add a new section and add your code there.

For example:

/* New section starts */

/* Example CSS PR #xyx */

div[class^='example'] {
  color: black;
}

/* New section ends */

Style Guide checks

Proselint is used for style testing the docs. Apart from the built-in tests in proselint, custom checks are added for style guide testing. Following a literate programming. model, style checks are defined in docs-style-guide.rst. After each style rule, you can define a python code-block containing the code for style testing. When the style-test script is run, these python code-blocks are parsed to generate a testing script.

Proselint dependent checks

In most of the custom checks, a new function is written that calls one of the built-in proselint functions as a return value.

All the checks use a decorator memoize() to cache the check for faster execution.

memoize()

Use @memoize above function definition to cache the result.

Proselint provides several functions for defining style tests:

existence_check(text, list, err, msg, ignore_case=True, str=False, max_errors=float('inf'), offset=0, require_padding=True, dotall=False, excluded_topics=None, join=False)

To check for existence of a regex pattern(s) in the text. The parameters offset, excluded_topics and join are not needed for style guide testing.

Parameters:
  • text (str) – Text to be checked

  • list (list) – List of regex expressions

  • err (str) – Name of the test

  • msg (str) – Error or warning message

  • ignore_case (bool) – For using re.IGNORECASE

  • str (bool) – For using re.UNICODE

  • max_errors (float) – Maximum number of errors to be generated

  • require_padding (bool) – To use padding with the specified regex (It is better to set it as False and specify the regex accordingly)

  • dotall (bool) – For using re.DOTALL

Returns:

The error list consisting of error tuples: [(start, end, err, msg, replacement)].

Return type:

list

preferred_forms_check(text, list, err, msg, ignore_case=True, offset=0, max_errors=float('inf'))

To suggest a preferred form of the word used. The parameter offset is not needed for style guide testing.

Parameters:
  • text (str) – Text to be checked

  • list (list) – list of comparison (words or regex): [correct form , incorrect form]

  • err (str) – Name of the test

  • msg (str) – Error or warning message

  • ignore_case (bool) – For using re.IGNORECASE

  • max_errors (float) – Maximum number of errors to be generated

Returns:

The error list consisting of error tuples: [(start, end, err, msg, replacement)].

Return type:

list

consistency_check(text, word_pairs, err, msg, offset=0)

To check for consistency for the given word pairs. The parameters offset is not needed for style guide testing.

Parameters:
  • text (str) – Text to be checked

  • word_pairs (list) – Word pairs to be checked for consistency

  • err (str) – Name of the test

  • msg (str) – Error or warning message

Returns:

The error list consisting of error tuples: [(start, end, err, msg, replacement)].

Return type:

list

Note

The checker functions are used by the built-in proselint function lint() to generate an error list of different format. The returned list finally is: [(check, message, line, column, start, end, end - start, "warning", replacements)]

Example Usage

@memoize
def example(text):
    """Example check."""
    err = "style-guide.example"
    msg = "A demonstration for writing checks."
    regex = "[\.\?!](example)"

    return existence_check(text, [regex], err, msg, ignore_case=False,
                       require_padding=False)

When you define code-blocks which use built-in proselint testing, specify the class style-checks.

.. code-block:: python
  :class: style-checks

The generated file after parsing code for style checks is style-checks.py.

If the test is too large to be defined in the file docs-style-guide.rst, you can use a snippet from the test (as in this Docs Style Guide example). The code-blocks for such snippets should specify the class proselint-extra-checks. Define the complete test in the file /style-guide/proselint-extra-checks.py.

Independent checks

Apart from the checks, which are to be run through proselint, you can add extra checks to be run independently. They are not enabled in proselintrc as well. For example, the checks for finding quote marks and section labels do not use any built-in functions to obtain an error list.

Example Usage

def check_quotes(text):
    """Avoid using straight quotes."""
    err = "style-guide.check-quote"
    msg = "Avoid using quote marks."
    regex = r"\"[a-zA-z0-9 ]{1,15}\""

    errors = []

    for matchobj in re.finditer(regex, text):
        start = matchobj.start()+1
        end = matchobj.end()
        (row, col) = line_and_column(text, start)
        extent = matchobj.end()-matchobj.start()
        errors += [(err, msg, row, col, start, end,
                         extent, "warning", "None")]

    return errors

The code-blocks for extra checks should specify the class extra-checks. The generated file after parsing code for extra checks is extra-checks.py.

Note

Built-in proselint function line_and_column() is used with extra checks to obtain the row and column of the matched text.

line_and_column(text, start)

To find the line number and column of a position in a string.

Parameters:
  • text (str) – Text to be searched for

  • start (int) – Starting position of matched pattern

Returns:

Tuple containing row and column number

Return type:

tuple

Error vs warning

  • Warnings are intended to provide guidance to authors.

  • Errors enforce "hard" rules, and raising an error will stop the build.

You can classify the result of a check as an error if you are sure that no false positives would be produced. The checks classified as errors should return a replacement for fixing the errors. Proselint dependent checks which use the function preferred_forms_check() or consistency_check() always return a preferred form. If you create an independent check which generates an error make sure to return a replacement in the error list.

To generate an error from a check, specify the check name in the list of errors in the function get_errlist() in the file style-test.py.

Excluding built-in proselint checks

To exclude a built-in proselint check, specify the check name in the check list in the function exclude_checks() in the file style-test.py.