.. _tutorial:

========
Tutorial
========

This tutorial is all about approaching coding standards with little or no
knowledge of in-depth programming or the code standards themselves.  It's the
equivalent of skipping the manual and jumping right in.

The command line prompt for these examples is:

.. sourcecode:: console

  tutor Desktop$

.. _PEP 8: https://peps.python.org/pep-0008/

Getting Started
---------------

Running Pylint with the ``--help`` arguments will give you an idea of the arguments
available. Do that now, i.e.:

.. sourcecode:: console

  pylint --help


A couple of the options that we'll focus on here are: ::

  Commands:
    --help-msg=<msg-id>
    --generate-toml-config
  Messages control:
    --disable=<msg-ids>
  Reports:
    --reports=<y or n>
    --output-format=<format>

If you need more detail, you can also ask for an even longer help message: ::

  pylint --long-help

Pay attention to the last bit of this longer help output. This gives you a
hint of what Pylint is going to ``pick on``: ::

  Output:
     Using the default text output, the message format is :
    MESSAGE_TYPE: LINE_NUM:[OBJECT:] MESSAGE
    There are 5 kind of message types :
    * (C) convention, for programming standard violation
    * (R) refactor, for bad code smell
    * (W) warning, for python specific problems
    * (E) error, for probable bugs in the code
    * (F) fatal, if an error occurred which prevented pylint from doing
    further processing.

When Pylint is first run on a fresh piece of code, a common complaint is that it
is too ``noisy``.  The default configuration enforce a lot of warnings.
We'll use some of the options we noted above to make it suit your
preferences a bit better.

Your First Pylint'ing
---------------------

We'll use a basic Python script with `black`_ already applied on it,
as fodder for our tutorial. The starting code we will use is called
``simplecaesar.py`` and is here in its entirety:

.. _`black`: https://github.com/psf/black

.. sourcecode:: python

    #!/usr/bin/env python3

    import string

    shift = 3
    choice = input("would you like to encode or decode?")
    word = input("Please enter text")
    letters = string.ascii_letters + string.punctuation + string.digits
    encoded = ""
    if choice == "encode":
        for letter in word:
            if letter == " ":
                encoded = encoded + " "
            else:
                x = letters.index(letter) + shift
                encoded = encoded + letters[x]
    if choice == "decode":
        for letter in word:
            if letter == " ":
                encoded = encoded + " "
            else:
                x = letters.index(letter) - shift
                encoded = encoded + letters[x]

    print(encoded)


Let's get started. If we run this:

.. sourcecode:: console

    tutor Desktop$ pylint simplecaesar.py
    ************* Module simplecaesar
    simplecaesar.py:1:0: C0114: Missing module docstring (missing-module-docstring)
    simplecaesar.py:5:0: C0103: Constant name "shift" doesn't conform to UPPER_CASE naming style (invalid-name)
    simplecaesar.py:8:0: C0103: Constant name "letters" doesn't conform to UPPER_CASE naming style (invalid-name)
    simplecaesar.py:9:0: C0103: Constant name "encoded" doesn't conform to UPPER_CASE naming style (invalid-name)
    simplecaesar.py:13:12: C0103: Constant name "encoded" doesn't conform to UPPER_CASE naming style (invalid-name)
    simplecaesar.py:15:12: C0103: Constant name "x" doesn't conform to UPPER_CASE naming style (invalid-name)
    simplecaesar.py:16:12: C0103: Constant name "encoded" doesn't conform to UPPER_CASE naming style (invalid-name)
    simplecaesar.py:20:12: C0103: Constant name "encoded" doesn't conform to UPPER_CASE naming style (invalid-name)
    simplecaesar.py:22:12: C0103: Constant name "x" doesn't conform to UPPER_CASE naming style (invalid-name)
    simplecaesar.py:23:12: C0103: Constant name "encoded" doesn't conform to UPPER_CASE naming style (invalid-name)

    -----------------------------------
    Your code has been rated at 4.74/10


We can see the second line is: ::

  "simplecaesar.py:1:0: C0114: Missing module docstring (missing-module-docstring)"

This basically means that line 1 at column 0 violates the convention ``C0114``.
Another piece of information is the message symbol between parens,
``missing-module-docstring``.

If we want to read up a bit more about that, we can go back to the
command line and try this:

.. sourcecode:: console

  tutor Desktop$ pylint --help-msg=missing-module-docstring
  :missing-module-docstring (C0114): *Missing module docstring*
    Used when a module has no docstring.Empty modules do not require a docstring.
    This message belongs to the basic checker.

That one was a bit of a no-brainer, but we can also run into error messages
where we are unfamiliar with the underlying code theory.

The Next Step
-------------

Now that we got some configuration stuff out of the way, let's see what we can
do with the remaining warnings. If we add a docstring to describe what the code
is meant to do that will help. There are ``invalid-name`` messages that we will
get to later. Here is the updated code:

.. sourcecode:: python

    #!/usr/bin/env python3

    """This script prompts a user to enter a message to encode or decode
    using a classic Caesar shift substitution (3 letter shift)"""

    import string

    shift = 3
    choice = input("would you like to encode or decode?")
    word = input("Please enter text")
    letters = string.ascii_letters + string.punctuation + string.digits
    encoded = ""
    if choice == "encode":
        for letter in word:
            if letter == " ":
                encoded = encoded + " "
            else:
                x = letters.index(letter) + shift
                encoded = encoded + letters[x]
    if choice == "decode":
        for letter in word:
            if letter == " ":
                encoded = encoded + " "
            else:
                x = letters.index(letter) - shift
                encoded = encoded + letters[x]

    print(encoded)

Here is what happens when we run it:

.. sourcecode:: console

    tutor Desktop$ pylint simplecaesar.py
    ************* Module simplecaesar
    simplecaesar.py:8:0: C0103: Constant name "shift" doesn't conform to UPPER_CASE naming style (invalid-name)
    simplecaesar.py:11:0: C0103: Constant name "letters" doesn't conform to UPPER_CASE naming style (invalid-name)
    simplecaesar.py:12:0: C0103: Constant name "encoded" doesn't conform to UPPER_CASE naming style (invalid-name)
    simplecaesar.py:16:12: C0103: Constant name "encoded" doesn't conform to UPPER_CASE naming style (invalid-name)
    simplecaesar.py:18:12: C0103: Constant name "x" doesn't conform to UPPER_CASE naming style (invalid-name)
    simplecaesar.py:19:12: C0103: Constant name "encoded" doesn't conform to UPPER_CASE naming style (invalid-name)
    simplecaesar.py:23:12: C0103: Constant name "encoded" doesn't conform to UPPER_CASE naming style (invalid-name)
    simplecaesar.py:25:12: C0103: Constant name "x" doesn't conform to UPPER_CASE naming style (invalid-name)
    simplecaesar.py:26:12: C0103: Constant name "encoded" doesn't conform to UPPER_CASE naming style (invalid-name)

    ------------------------------------------------------------------
    Your code has been rated at 5.26/10 (previous run: 4.74/10, +0.53)

Nice! Pylint told us how much our code rating has improved since our last run,
and we're down to just the ``invalid-name`` messages.

There are fairly well defined conventions around naming things like instance
variables, functions, classes, etc.  The conventions focus on the use of
UPPERCASE and lowercase as well as the characters that separate multiple words
in the name.  This lends itself well to checking via a regular expression, thus
the **should match (([A-Z\_][A-Z1-9\_]*)|(__.*__))$**.

In this case Pylint is telling us that those variables appear to be constants
and should be all UPPERCASE. This is an in-house convention that has lived with Pylint
since its inception. You too can create your own in-house naming
conventions but for the purpose of this tutorial, we want to stick to the `PEP 8`_
standard. In this case, the variables we declared should follow the convention
of all lowercase.  The appropriate rule would be something like:
"should match [a-z\_][a-z0-9\_]{2,30}$".  Notice the lowercase letters in the
regular expression (a-z versus A-Z).

If we run that rule using a ``--const-rgx='[a-z\_][a-z0-9\_]{2,30}$'`` option, it
will now be quite quiet:

.. sourcecode:: console

    tutor Desktop$ pylint simplecaesar.py --const-rgx='[a-z\_][a-z0-9\_]{2,30}$'
    ************* Module simplecaesar
    simplecaesar.py:18:12: C0103: Constant name "x" doesn't conform to '[a-z\\_][a-z0-9\\_]{2,30}$' pattern (invalid-name)
    simplecaesar.py:25:12: C0103: Constant name "x" doesn't conform to '[a-z\\_][a-z0-9\\_]{2,30}$' pattern (invalid-name)

    ------------------------------------------------------------------
    Your code has been rated at 8.95/10 (previous run: 5.26/10, +3.68)

You can `read up`_ on regular expressions or use `a website to help you`_.

.. tip::
 It would really be a pain to specify that regex on the command line all the time, particularly if we're using many other options.
 That's what a configuration file is for. We can configure our Pylint to
 store our options for us so we don't have to declare them on the command line.  Using a configuration file is a nice way of formalizing your rules and
 quickly sharing them with others. Invoking ``pylint --generate-toml-config`` will create a sample ``.toml`` section with all the options set and explained in comments.
 This can then be added to your ``pyproject.toml`` file or any other ``.toml`` file pointed to with the ``--rcfile`` option.

.. _`read up`: https://docs.python.org/library/re.html
.. _`a website to help you`: https://regex101.com/
