Program Structure
=================

There are two concepts important to Stylist's function: Rules and Styles.

A rule is a single check which may be made against a piece of source code.
e.g. Has the implicit keyword been specified in all appropriate places?

A style, on the other hand, is a set of rules of interest to a particular
group of developers. For instance the LFRic style includes the "missing
implicit" rule and the "trailing white space rule."

Rules
~~~~~

The abstract base of all rules is :py:class:`stylist.rule.Rule`. Classes
derived  from this implement an :py:func:`stylist.rule.Rule.examine` method
which is passed a :py:class:`stylist.source.SourceText` object. This allows
the source to be treated as a plain text file.

Rules relating to Fortran source may be found in the
:py:mod:`stylist.fortran` module. An abstract
:py:class:`stylist.fortran.FortranRule` is provided which requires an
:py:meth:`stylist.fortran.FortranRule.examine_fortran` method which is
passed a :py:class:`stylist.source.FortranSource` object. Thus the rule may
examine the Fortran parse tree.

Writing a New Text Rule
-----------------------

Just sub-class :py:class:`stylist.rule.Rule` and implement the
:py:meth:`stylist.rule.Rule.examine` method. Use the
:py:meth:`stylist.source.SourceText.get_text` method on the passed
:py:class:`stylist.source.SourceText` object to obtain the text of the source
file.

The "trailing white space" rule gives an example of using RegEx on the source
text. Meanwhile the "Fortran characterset" rule shows a more elaborate text
rule which implements a state-machine to test validity.

Writing a Fortran Rule
----------------------

An abstract :py:class:`stylist.rule.FortranRule` is provided, any new rule
should inherit from this and implement the
:py:meth:`stlist.rule.FortranRule.examine_fortran` method. Use the
:py:meth:`stylist.source.FortranSource.get_tree` method to obtain an fparser2
parse tree.

The :py:class:`stylist.source.FortranSource` class also provides some methods
for traversing the parse tree. It is expected that this functionality will
eventually become available through fparser2 and so will be removed from
stylist.

Documenting the Rule
--------------------

Don't forget to add your new rule to the user documentation. This is a
slightly clunky process involving adding some boilerplate to
`documentation/source/user-manual/rule-list.rst`.

Styles
~~~~~~

Styles are defined in stylist/style.py and derive from the abstract Style class. They tend to contain only a constructor which defines a list of rules and passes it up to their parent.
Extending an Existing Style

Just add an instance of the rule you want to add to the appropriate list. You may need to import the module it lives in, or not if it is already there.

For instance:

 class LFRicStyle(Style):
    '''
    LFRic project's list of rules.
    '''
    def __init__(self):
        rules = [stylist.fortran.FortranCharacterset(),
                 stylist.rule.TrailingWhitespace(),
-                stylist.fortran.MissingImplicit('none')
+                stylist.fortran.MissingImplicit('none'),
+                stylist.fortran.MissingOnly(ignore=['pfunit_mod', 'xios'])]
        super(LFRicStyle, self).__init__(rules)

Adding a New Style

As intimated above, this a simple matter of creating a new class inheriting from the Style parent.

class SpecialStyle(Style):
    '''
    LFRic project's list of rules.
    '''
    def __init__(self):
        rules = [stylist.fortran.FortranCharacterset(),
                 stylist.rule.TrailingWhitespace(),
                 stylist.fortran.MissingImplicit('none')]
        super(SpecialStyle, self).__init__(rules)

With that in place function process(...) in __main__.py needs to be updated. The new style can either replace LFRicStyle or be run in addition.

 def process(candidates: List[str]) -> List[Issue]:
     '''
     Examines files for style compliance.

     Any directories specified will be descended looking for files to examine.
     '''
-    styles = [LFRicStyle()]
+    styles = [LFRicStyle(), SpecialStyle()]
     ...

Future Work

Obviously having the styles hardcoded like this is not desirable. It is okay for initial development, to get things going, but eventually we would want to move to a more flexible approach.

Tickets #8, #13 and #14 cover this work.