-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
3 changed files
with
285 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,6 +5,49 @@ tkldev-detective is a simple framework for linting turnkeylinux appliances, it | |
leverages existing linting tools, as well as allowing for custom handmade lints | ||
and provides a unified output format/interface for utilizing these tools. | ||
|
||
Dependencies | ||
~~~~~~~~~~~~ | ||
|
||
tkldev-detective has no mandatory dependencies | ||
|
||
Currently though there is 1 optional dependency: | ||
|
||
pyyaml - enables yaml lint | ||
|
||
Installation | ||
------------ | ||
|
||
tkldev-detective can be installed as follows | ||
|
||
1. Clone somewhere, I recommend cloning into ``/turnkey/public`` (creating | ||
intermediate directories as required) | ||
|
||
.. code-block:: bash | ||
mkdir -p /turnkey/public | ||
git clone [email protected]:turnkeylinux/tkldev-detective /turnkey/public/tkldev-detective | ||
2. Add ``tkldev-detective`` to your path: | ||
|
||
.. code-block:: bash | ||
ln -s /turnkey/public/tkldev-detective/tkldev-detective /usr/local/bin | ||
Usage | ||
----- | ||
|
||
Using ``tkldev-detective`` is as simple as running | ||
``tkldev-detective lint <appliance>`` where ``<appliance>`` can be the name of | ||
an appliance or the path to an appliance, provided you have the appliance | ||
build-code present on your tkldev. | ||
|
||
E.g. | ||
|
||
``tkldev-detective lint zoneminder`` | ||
|
||
For more information on how it works and how to develop more functionality, see | ||
`overview`_ and `custom modules`_ | ||
|
||
Copyright | ||
--------- | ||
|
||
|
@@ -15,3 +58,6 @@ tkldev-detective is free software: you can redistribute it and/or modify it unde | |
tkldev-detective 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 General Public License for more details. | ||
|
||
You should have received a copy of the GNU General Public License along with tkldev-detective. If not, see <https://www.gnu.org/licenses/>. | ||
|
||
.. _overview: ./docs/overview.rst | ||
.. _custom modules: ./docs/custom_modules.rst |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,171 @@ | ||
TKLDev-Detective Custom Modules | ||
=============================== | ||
|
||
Custom modules are simply python files, with 1 or more class definitions that | ||
subclass ``FileClassifier`` or ``FileLinter`` residing inside of the | ||
``tkldet_modules`` directory. | ||
|
||
(Non-file classifiers and linters might be possible but are not yet | ||
officially supported) | ||
|
||
XXX ReportFilters are also possible to subclass once fully implemented | ||
|
||
Common Notes | ||
------------ | ||
|
||
One thing that all custom operational models share is a concept of "weight" | ||
which is used to determine the order that each check is done in. | ||
|
||
By default all of these classes contain a class-variable ``WEIGHT: int``. | ||
|
||
Counter-intuitively classes are used in ascending order of weight, the | ||
recommended weight for situations that do not require specific ordering is | ||
``100``. | ||
|
||
Custom Classifiers | ||
------------------ | ||
|
||
Classifiers are very simple by design, here's a very simple example of a | ||
classifier which tags directories, it's only job is to take an ``Item`` | ||
and if it refers to a directory, tag it with a relevant tag. | ||
|
||
.. code-block:: python3 | ||
from libtkldet.classifier import FileClassifier, FileItem, register_classifier | ||
from os.path import isdir | ||
@register_classifier # this line registers our custom class to be used as a classifier | ||
class DirectoryClassifier(FileClassifier): | ||
WEIGHT: int = 80 | ||
# we want this to run earlier than most classifier, | ||
# but not necessarily first so we reduce the weight by 20 | ||
def classify(self, item: FileItem): | ||
# classify is the only method you MUST implement when deriving from | ||
# FileClassifier, it does not return anything | ||
if isdir(item): | ||
item.add_tags(self, ['directory']) | ||
# we pass a reference to ourselves, so the item can keep track | ||
# of which tags came from which classifiers | ||
# | ||
# we also pass a list of as many tags as we wish to add to this | ||
# item. The exact value of our tag is unimportant, only that it | ||
# doesn't conflict with any other tags. | ||
See ``filetype.py``, ``shebang.py`` for more examples of custom classifiers. | ||
|
||
Helper Classifiers | ||
~~~~~~~~~~~~~~~~~~ | ||
|
||
There are currently 2 helper classifiers, ``ExactPathClassifier`` and | ||
``SubdirClassifier``. | ||
|
||
ExactPathClassifier classifies only a single file item with a specific path, to | ||
use it you simply need to set a few required class variables: | ||
|
||
.. code-block:: python3 | ||
from libtkldet.classifier import ExactPathClassifier, register_classifier | ||
@register_classifier | ||
class CustomExactPathClassifier(ExactPathClassifier): | ||
path: str = "path/to/some/file" | ||
# exact location of file, relative to appliance root | ||
tags: list[str] = [ "tag1", "tag2" ] | ||
# all these tags are added to the specific file matched | ||
SubdirClassifier is only marginally more complex, it will tag all files that are | ||
directory descendants of the given path, optionally recursively. | ||
|
||
.. code-block:: python3 | ||
from libtkldet.classifier import SubdirClassifier, register_classifier | ||
@register_classifier | ||
class CustomSubdirClassifier(SubdirClassifier): | ||
path: str = "path/to/some/file" | ||
# exact location of parent directory, relative to appliance root | ||
tags: list[str] = [ "tag1", "tag2" ] | ||
# all these tags are added to children of directory | ||
recursive: bool = True | ||
# recurse into child directories? | ||
Too see examples of these helper classifiers see the ``appliance_files.py`` | ||
module. | ||
|
||
Custom Linters | ||
-------------- | ||
|
||
Linters are a little more complex, but ultimately quite similar. | ||
|
||
Here's an example of a linter that produces an info report for every line | ||
that contains "TODO" | ||
|
||
.. code-block:: python3 | ||
from libtkldet.linter import FileLinter, register_linter | ||
from libtkldet.report import FileReport, ReportLevel | ||
from typing import Generator | ||
@register_linter | ||
class TodoLinter(FileLinter): | ||
ENABLE_TAGS: set[str] = set() | ||
DISABLE_TAGS: set[str] = set() | ||
# by default, linter will only run when either: | ||
# | ||
# 1. ENABLE_TAGS is empty and there is no overlap between item tags and | ||
# DISABLE_TAGS | ||
# 2. ENABLE_TAGS is not-empty, at least 1 tag from ENABLE_TAGS is | ||
# included in the given item, and no items from DISABLE_TAGS are | ||
# present | ||
WEIGHT: int = 100 | ||
# weight here works the same as classifiers | ||
def check(self, item: Item) -> Generator[Report, None, None]: | ||
# note this is a generator, we "yield" reports | ||
with open(item.abspath) as fob: | ||
for i, line in enumerate(fob): | ||
if 'TODO' in line: | ||
yield FileReport( | ||
item=item, # same item as input | ||
line=i, | ||
column=line.find('TODO'), | ||
# both line or column can be a single integer, a | ||
# tuple of integers or ommited entirely. However | ||
# if line number is ommited, column should be too | ||
location_metadata=None, | ||
# currently populated by meta info that doessn't | ||
# fit elseware, not output currently | ||
message = 'Found todo NOTE: " + line.split('TODO', 1)[1], | ||
# the message shown to user | ||
fix = None, | ||
# suggested fix as a string if known/relevant | ||
# otherwise None | ||
source = 'TodoLinter', | ||
# arbitrary string to represent the linter that | ||
# found this issue | ||
level = ReportLevel.INFO | ||
# kind of report level, 1 for all the standard log | ||
# levels as well as "CONVENTION" and "REFACTOR" | ||
# (possibly more as time goes on) | ||
) | ||
If the logic surrounding ``ENABLE_TAGS`` and ``DISABLE_TAGS`` is insufficient to | ||
determine if the linter should run you can override ``Linter.should_check`` | ||
method which actually performs those checks it takes the ``Item`` as an argument | ||
and returns a boolean indicating if the linter should check the item. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
TKLDev-Detective Overview | ||
========================= | ||
|
||
tkldev-detective is actually pretty simple, it boils down to 6 concepts, 2 data | ||
models and 4 "operational" models which function in serial. | ||
|
||
Data models | ||
----------- | ||
|
||
Item | ||
An item is just a piece of unspecified data accompanied by a set of tags. | ||
|
||
Subtypes of item are used to distinguish between concrete concepts, | ||
currently the only type of ``Item`` supported is ``FileItem`` which (as you | ||
might assume) contains a path as it's data. | ||
|
||
Report | ||
A report is the final piece of data to be shown to the user. It contains | ||
various pieces of information about a potential issue, how serious it is, | ||
information relating to where it is, what potential fixes might be | ||
available and what linter found this issue. | ||
|
||
Just like Item's this type is intended to be subclassed for other types of | ||
data, and just like ``FileItem``; ``FileReport`` is currently the only such | ||
subclass. It provides sufficient information to show file snippets related | ||
to issues in a variety of ways contextual to how much information it's given | ||
about the location of the issue inside the file. | ||
|
||
Operational models | ||
------------------ | ||
|
||
Locator | ||
the "Locator" determines which files are checked in the first place | ||
in the future this will provide a broader concept of "checkable thing" than | ||
just file, for now though we can safely only consider files. | ||
|
||
The locator produces "Items", these are a model that contains some kind of | ||
"data" (here because they are just files, the data is the path itself) | ||
and can be tagged. | ||
|
||
Classifier | ||
the "Classifier" determines various qualities about a certain file, these | ||
qualities or classifications are referred to internally as "tags". | ||
|
||
An example of a tag might be ``shebang:/usr/bin/python3`` which asserts that | ||
this file has a shebang, and that the shebang is for python3. Note, the use | ||
of ``shebang:`` prefix as there is infinite variation in shebangs. | ||
|
||
Some tags though have no variation, such is the case for | ||
``appliance-makefile`` which is only set for the top-level ``Makefile`` | ||
found in an appliance. | ||
|
||
Linter | ||
the "Linter" performs checks on the inputted "items" and yields any number | ||
of "Reports". Linter's are the most diverse of these groups. | ||
|
||
Examples of linters go from ``PyLinter`` and ``Shellcheck``, linter's which | ||
wrap the 3rd-party linters "pylint" and "shellcheck" respectively, all the | ||
way through ``JsonLint`` which just loads json files and yields a report if | ||
loading fails. | ||
|
||
ReportFilter | ||
the "ReportFilter" is the last stop for reports before they are presented | ||
to the user. A ``ReportFilter`` is mostly intended to remove false | ||
positives but can also be used to add additional context to reports in a | ||
way that might not make sense in the linter itself. | ||
|
||
XXX: currently report filters are untested |