Source code for evariste.plugins.action

# Copyright Louis Paternault 2017-2024
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

"""Actions performed to compile files.

The result of an :class:`action <Action>`
(Was it sucessful? Which files were used? What is the log? etc.)
is stored as a :class:`report <Report>`.

If you plan to write your own action plugin, see :ref:`write_action`.

:class:`Action`
---------------

.. autoclass:: Action
   :members:

:class:`Report`
---------------

.. autoclass:: Report
   :members:
"""

# Can be removed starting with python3.11
from __future__ import annotations

import abc
import contextlib
import io
import logging
import os
import pathlib
import threading
import typing

from ... import errors, plugins
from ...hooks import contexthook

if typing.TYPE_CHECKING:
    from ...tree import Tree

LOGGER = logging.getLogger(__name__)


################################################################################
# Actions


[docs] class Action(plugins.Plugin, metaclass=abc.ABCMeta): """Generic action Subclass this to create a new action (see :ref:`write_action`). """ # pylint: disable=too-many-instance-attributes, too-few-public-methods plugin_type = "action" #: A lock shared by every action. #: Can be used for parts of the compilation which are not thread-safe. lock: threading.Lock = threading.Lock()
[docs] @abc.abstractmethod def compile(self, path: Tree) -> Report: """Compile ``path``. This function *must* be thread-safe. It can use :attr:`Action.lock` if necessary. """ raise NotImplementedError()
[docs] def match(self, value: Tree) -> bool: """Return ``True`` if ``value`` can be compiled by this action.""" # pylint: disable=unused-argument return False
class DirectoryAction(Action): """Fake action on directories.""" # pylint: disable=abstract-method, too-few-public-methods keyword = "action.directory" def compile(self, path): success = self._success(path) if success: message = "" else: message = "At least one file in this directory failed." return Report(path, success=success, log=message, targets=[]) @staticmethod def _success(path): """Return ``True`` if compilation of all subpath succeeded.""" for sub in path: if not path[sub].report.success: return False return True def match(self, dummy): return False ################################################################################ # Reports
[docs] class Report: """Report of an action. Mainly a namespace with very few methods.""" def __init__(self, path, *, targets=None, success=False, log=None, depends=None): # pylint: disable=too-many-arguments self.depends = depends if self.depends is None: self.depends = set() if log is None: self.log = "" else: self.log = log self.path = path if targets is None: self.targets = [] else: self.targets = targets self._success = success @property def full_depends(self) -> set[pathlib.Path]: """Set of files this action depends on, including ``self.path``.""" return self.depends | {self.path.from_fs} @property def success(self) -> bool: """Was compilation sucessful?""" return self._success @success.setter def success(self, value): """Success setter.""" self._success = value