=================
How to contribute
=================

`Leer en español </es/stable/contributing.html>`_

To contribute, just fork this repository, make a new branch and open a `pull request`_.

.. _`pull request`: https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/creating-a-pull-request

**cpanel-cli** is written in Python (version 3.11 or later required). I organized the code into a standard tree::

    cpanel-cli
    ├── API.md
    ├── CONTRIBUTING.rst
    ├── cpanel
    │   ├── cli.py
    │   ├── core.py
    │   ├── __init__.py
    │   ├── __main__.py
    │   ├── REFERENCE
    │   └── USAGE
    ├── doc
    │   ├── conf.py
    │   ├── contributing.rst
    │   ├── index.rst
    │   ├── locale
    │   │   └── es
    │   │       └── LC_MESSAGES
    │   │           ├── contributing.po
    │   │           ├── index.po
    │   │           ├── installation.po
    │   │           ├── reference.po
    │   │           └── reference
    │   │               └─── *.po
    │   ├── reference.sh
    │   ├── requirements.txt
    │   └── _static
    │       ├─── *.svg
    │       └─── *.png
    ├── hatch.py
    ├── LICENSE
    ├── Makefile
    ├── pyproject.toml
    ├── pyrightconfig.json
    ├── README.rst
    ├── .readthedocs.yaml
    ├── test
    │   ├── cpanelrc.test.example
    │   └── test_uapi.py
    └── tox.ini

``cpanel`` contains the main source code. Files ``REFERENCE`` and ``USAGE`` contain the actual
text for the ``--help`` and ``--version`` flags and the ``help`` command. (I keep them in
external files to make them easier to change. Also, I can automatically parse ``REFERENCE`` to
generate ``*.rst`` files for the Sphinx documentation builder.
See the ``reference.sh`` script below.)

Standard ``pyproject.toml`` contains the project metadata, development and release dependencies,
and build backend definitions. (See `Writing your pyproject.toml`_ for further info.)

.. _`Writing your pyproject.toml`: https://packaging.python.org/en/latest/guides/writing-pyproject-toml/

I’m using the `Hatchling`_ build backend, with a small custom ``hatch.py`` script to get the
``dynamic`` metadata properties in ``pyproject.toml``. The custom ``hatch.py`` script works by parsing the
``cpanel/__init__.py`` source file.

.. _`Hatchling`: https://pypi.org/project/hatchling/

``pyrightconfig.json`` is the configuration file for the `Pyright`_ static type checker.

``test`` contains a set of unit API tests. They’re written using the `tox automation framework`_.
The code driving the tests is in ``test/test_uapi.py``; the main tox configuration file is ``tox.ini``.
These are *not* simple standalone unit tests, but API tests running against
a *live* cPanel instance. See `Running tests`_ below for further details.

.. _`tox automation framework`: https://tox.wiki/en/latest/index.html

``doc`` contains the documentation sources, written in `reStructuredText`_ and processed using `Sphinx`_.
The main configuration file for Sphinx is ``doc/conf.py``. The Sphinx version and theme used
to build the documentation are in ``doc/requirements.txt``.

.. _`reStructuredText`: https://www.sphinx-doc.org/en/master/usage/restructuredtext/basics.html
.. _Sphinx: https://www.sphinx-doc.org/

The source files for the documentation are in ``doc/*.rst``. The ``index.rst`` file is the main
page. You will notice a ``doc/reference`` folder and a series of ``*.rst`` files on it.
These are programmatically generated by the ``reference.sh`` script and need *not* be edited manually.
(I’m committing them to the source tree because they serve as the actual source files for Sphinx.)

The ``reference.sh`` script generates ``doc/reference.rst`` and the files inside ``doc/reference/``
by parsing the ``REFERENCE`` text file, splitting it into sections and converting them to restructuredText.
This allows me to keep ``REFERENCE`` as a single source of truth for the documentation
and keep the Sphinx documents up to date.

``_static`` contains the images and SVG files used in the documentation.

``.readthedocs.yaml`` is a `configuration file for Read the Docs`_. The remote Sphinx build system
uses this file.

.. _`configuration file for Read the Docs`: https://docs.readthedocs.io/en/stable/config-file/index.html

I maintain a Spanish translation of the documentation, generated using strings from a series of
catalog files (``*.po``) inside ``locale/es/LC_MESSAGES/``. See `Translations`_ for further information.

Finally, I’m using a ``Makefile`` to automate all phases of the development life cycle.
(`Make and Makefiles are awesome`_.)

.. _`Make and Makefiles are awesome`: https://mplanchard.com/posts/make-and-makefiles-are-awesome.html


Development environment
=======================

I developed **cpanel-cli** on Ubuntu Linux 23.10 “Mantic” with Python 3.11.
**cpanel-cli**, however, has no special requirements, so any Linux distro
supporting at least Python 3.11 should work. You can also use macOS “Ventura”
or a later macOS release.

*To create a development environment on macOS*:

Install Python 3:

.. code:: sh

    $ brew install python

Homebrew will install the latest Python version available. If you want to install a specific
version, you can use the excellent `Pyenv`_ utility to manage different releases of Python
side by side.

.. _`Pyenv`: https://github.com/pyenv/pyenv

Install GNU Make:

.. code:: sh

    $ brew install make

*To create a development environment on Linux:*

On a Debian-based distro (Ubuntu, Mint), install Python 3 using:

.. code:: sh

    $ sudo apt install python3 python3-pip python3-venv

On a RPM-based distro (RHEL, Fedora), install Python 3 using:

.. code:: sh

    $ sudo dnf install python3 python3-pip

``apt`` and ``dnf`` will install the latest Python version available. If you want to
install a specific version, you can use the excellent `Pyenv`_ utility to manage
different releases of Python side by side.

GNU Make is installed by default on most Linux distros. Check its availability using:

.. code:: sh

    $ make --version

Building a local ``cpanel-cli`` package from source
===================================================

Build and install a local ``cpanel-cli`` package:

.. code:: sh

    $ make install

This will:

1. Create a new virtual Python 3 environment in a ``venv`` directory

2. Locally install in ``venv`` the development packages listed on the ``[project.optional-dependencies]`` section of ``pyproject.toml``

3. Build a local Python package ``cpanel-cli``

Running the local executable
============================

To run the executable, first activate the virtual environment
(you need to run this only once per session):

.. code:: sh

    $ source venv/bin/activate

Then run the ``cpanel`` utility:

.. code:: sh

    $ cpanel --help

If you edit the sources, just run ``pip3 install .`` (note the dot ``.``) to rebuild
the local package.

Running the (optional) type checker
===================================

*Running the type checker is optional — you can ignore this step if you want.*

The Python source code is annotated using type hints. I use them
to improve the readability of Python code. Read the `Python Type Checking Guide`_ for
an excellent introduction to the use of type hints in Python.

.. _`Python Type Checking Guide`: https://realpython.com/python-type-checking/

Type hints are not actually checked by the Python runtime — you need a
third party *type checker* utility.
For this project I use Pyright_, which is my Python type checker of choice.

.. _Pyright: https://github.com/Microsoft/pyright

To install Pyright:

.. code:: sh

    $ pip3 install --user pyright

Run it using:

.. code:: sh

    $ make typecheck

The type checker configuration is in ``pyrightconfig.json``.

Note that Pyright is based on Node.js, so that pip will indirectly install it and pull a
lot of JavaScript dependencies required by Pyright.

Running tests
=============

I’m using the `tox automation framework`_ for a series of unit API tests.
The main code driving the tests is in ``test/test_uapi.py``; the main tox configuration file is
``tox.ini``.

These are *not* simple unit tests, but unit API tests running against a *live* cPanel instance.
To run the tests, you need access to a cPanel instance running on another host reachable from
the host you’re running the tests on.

To set the remote hosts credentials, make a copy of the provided ``cpanelrc.test.example`` file
and name it ``cpanelrc.test`` (keep in the ``test`` directory):

.. code:: sh

    $ cp test/cpanelrc.test.example test/cpanelrc.test

Then edit ``cpanelrc.test`` and set:

- The hostname of your cPanel instance
- The username of your cPanel account
- An `API token`_ associated to that username

**Token-based authentication is the only supported authentication method.**

.. _`API token`: https://docs.cpanel.net/knowledge-base/security/how-to-use-cpanel-api-tokens/

To run the tests, use:

.. code:: sh

    $ make test

The above command will hit the `cPanel UAPI REST interface`_ with most of the functions
implemented in **cpanel-cli**.

**The remote state of cPanel is left unchanged, i.e., the tests are strictly non-destructive.**

.. _`cPanel UAPI REST interface`: https://api.docs.cpanel.net/cpanel/introduction/

Packaging
=========

Packaging is done via the `Hatchling`_ build backend, as specified on the ``[build-system]``
section of ``pyproject.toml``.

To run the packager, use:

.. code:: sh

    $ make package

The above command should generate the following two distribution files in the
temporary ``dist`` directory:

.. code:: sh

    cpanel_cli-<version>-py3-none-any.whl
    cpanel-cli-<version>.tar.gz

where ``<version>`` is the release number set in ``cpanel/__init__.py``.

The tarball is the source archive; the wheel file is the built distribution archive. The
included files for these distribution packages are listed on the ``[tool.hatch.build.targets.sdist]`` and
``[tool.hatch.build.targets.wheel]`` sections of ``pyproject.toml`` respectively.

These packages are ready to be uploaded to the `Python Package Index`_.

.. _`Python Package Index`: https://pypi.org/

Building the documentation
==========================

The API documentation source files are in the ``doc`` directory. These comprise `reStructuredText`_
(``.rst``) files which are processed using `Sphinx`_ into groups of static HTML trees.

To build the documentation, use:

.. code:: sh

    $ make doc

The above command will generate several static HTML trees in ``doc/build/html``.
For example, it generates the default English documentation in ``doc/build/html/en`` —
the start page is a conventional ``index.html`` file.

This GitHub repository is currently connected to my `Read the Docs`_ account, so that
any committed (or merged) change that updates the documentation sources will automatically
trigger a remote Sphinx rebuild. The resulting updated HTML documentation will always be
available at https://cpanel-cli.readthedocs.io/en/stable/

.. _`Read the Docs`: https://readthedocs.org/

The main configuration file for Sphinx is ``doc/conf.py``. The Sphinx version and theme used
to build the documentation are in ``doc/requirements.txt``.

Translations
============

The English language ``*.rst`` files in ``doc`` are the source documentation files. Any
translation is based on these documents. Translation is done on a string-by-string basis,
using the original English string as a key (``msgid``), and the corresponding translated
string as a value (``msgstr``). For example, for Spanish:

.. code::

    msgid "To be, or not to be, that is the question"
    msgstr "Ser o no ser, he ahí el dilema"

These ``msgid`` and ``msgstr`` pairs are kept in a *catalog* file (``*.po``), which is a
simple text file. These catalog files are stored in the ``doc/locale`` subdirectory.

I personally maintain a Spanish translation of the documentation in catalog files
``doc/locale/es/LC_MESSAGES/*.po``.

Catalog ``.po`` files are compiled into ``.mo`` files using the Sphinx internationalization
utility. These compiled ``.mo`` files are later used to compose translated versions when
`Building the documentation`_.

Adding a translation
--------------------

To add a new translation:

1. Create a new catalog using:

   .. code:: sh

       $ make locale iso=<language code>

   where ``<language code>`` is the `ISO 639-1 code`_ corresponding to the new language. For
   example, to add a French translation you would use:

   .. code:: sh

       $ make locale iso=fr

   This would add a new ``locale/fr/LC_MESSAGES/index.po`` directory with several ``.po``
   files in it.

2. Edit the ``.po`` files created in step 1 and insert the translated strings as
   ``msgstr`` fields. For example:

   .. code:: sh

       msgid "Indices and tables"
       msgstr "Indices et tableaux"

3. Rebuild the documentation:

   .. code:: sh

       $ make doc

   The above command will create a new static HTML tree in ``doc/build/html/<language code>``.
   For example, for French, it will create a new tree in ``doc/build/html/fr``.

Correcting and expanding an existing translation
------------------------------------------------

if you edit the original ``doc/*.rst`` source documentation files, you need to update the
translations as well:

1. Run the following to update the catalog files:

   .. code:: sh

       $ make locale iso=<language code>

   where ``<language code>`` is the `ISO 639-1 code`_. You need to run it for every
   translated language.

2. The previous step will emit a report telling you which ``.po`` files need to be updated,
   for example:

   .. code::

       Update: doc/locale/es/LC_MESSAGES/reference.po +5, -2
       Update: doc/locale/es/LC_MESSAGES/contributing.po +9, -0

   Open the mentioned ``.po`` files and edit or add new ``msgstr`` strings. Be advised that some
   entries might get annotated as ``#, fuzzy``, which means the internationalization
   engine is not sure if there already exists a translation for the entry because of similarities
   with another entry. Just edit the ``msgstr`` text and delete the ``fuzzy`` line.

For further information, see the `Internationalization Guide`_

.. _`ISO 639-1 code`: https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes
.. _`Internationalization Guide`: https://www.sphinx-doc.org/en/master/usage/advanced/intl.html
