Skip to content

Commit f12b09c

Browse files
authored
Add supported-table directive that validates whether all different targets we support are documented (#116)
This adds a .. dissect-supported-table directive which verifies whether our tables in supported-targets.rst are still up to date The directive itself provides the following options: - `source-path`: where it needs to start looking for modules within the directory specified by the `dissect_projects_path` (required) - `blacklist`: a comma separated list that tells the plugin which modules to ignore - `glob-pattern`: a pattern to search for inside `source-path` if not specified it uses *.py by default
1 parent 5b79cc9 commit f12b09c

File tree

3 files changed

+148
-8
lines changed

3 files changed

+148
-8
lines changed
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
from __future__ import annotations
2+
3+
from pathlib import Path
4+
from typing import Any
5+
from typing_extensions import override
6+
7+
from sphinx.application import Sphinx
8+
from sphinx.util.logging import getLogger
9+
from sphinx.util.console import colorize
10+
from sphinx.addnodes import pending_xref
11+
from docutils.parsers.rst.directives.tables import ListTable
12+
from docutils.parsers.rst import directives
13+
from docutils import nodes
14+
15+
16+
LOGGER = getLogger(__name__)
17+
DISSECT_PREFIX = colorize("bold", "[Dissect] ")
18+
19+
20+
class SupportedTargetTable(ListTable):
21+
"""Extended list-table directive that validates if we have all our supported target modules documented."""
22+
23+
option_spec = ListTable.option_spec.copy()
24+
option_spec["source-path"] = directives.unchanged_required
25+
option_spec["blacklist"] = directives.unchanged
26+
option_spec["glob-pattern"] = directives.unchanged
27+
28+
@override
29+
def run(self):
30+
# Call the parent directive to get the table node
31+
result = super().run()
32+
33+
module_references = []
34+
table_name = ""
35+
# Perform custom checks on the table
36+
if result and isinstance(result[0], nodes.table):
37+
table_node = result[0]
38+
table_name = table_node[0].astext()
39+
LOGGER.info(
40+
DISSECT_PREFIX
41+
+ colorize("darkgreen", "Gathering references from table '%s'"),
42+
table_name,
43+
)
44+
module_references = self._gather_table_references(table_node)
45+
46+
if module_references:
47+
self.validate_references(table_name, module_references)
48+
49+
return result
50+
51+
def _gather_table_references(self, table_node: nodes.table) -> list[str]:
52+
"""Gather all the references inside the table."""
53+
references = []
54+
for ref in table_node.findall(pending_xref):
55+
target = ref.get("reftarget")
56+
split_names = target.rsplit(".", 2)
57+
if target.endswith("._os"):
58+
modname = split_names[-2]
59+
else:
60+
modname = split_names[-1]
61+
references.append(modname)
62+
63+
return references
64+
65+
def validate_references(self, table_name: str, module_references: list[str]):
66+
LOGGER.info(
67+
DISSECT_PREFIX
68+
+ colorize("darkgreen", "Validating module references from table '%s'"),
69+
table_name,
70+
)
71+
check_path: str = self.options.get("source-path")
72+
# Get the environment for the sphinx app
73+
environment = self.state.document.settings.env
74+
dissect_projects_dir = environment.config.dissect_projects_path
75+
76+
search_path = dissect_projects_dir / check_path
77+
78+
black_list = set()
79+
black_list.update(["__init__"])
80+
black_list.update(
81+
module for module in self.options.get("blacklist", "").split(",") if module
82+
)
83+
84+
glob = self.options.get("glob-pattern", "*.py")
85+
for file in search_path.glob(glob):
86+
if file.name == "_os.py":
87+
file = file.parent
88+
89+
relative_file = file.relative_to(dissect_projects_dir)
90+
91+
if file.stem in black_list:
92+
LOGGER.debug(
93+
DISSECT_PREFIX + colorize("darkgrey", "Skipping %s"), relative_file
94+
)
95+
continue
96+
97+
if file.stem not in module_references:
98+
LOGGER.warning(
99+
DISSECT_PREFIX
100+
+ colorize(
101+
"darkred", "Missing documentation entry for %s in table '%s'"
102+
),
103+
relative_file,
104+
table_name,
105+
)
106+
107+
LOGGER.info(
108+
DISSECT_PREFIX + colorize("darkgreen", "Done validating table '%s'"),
109+
table_name,
110+
)
111+
112+
113+
def setup(app: Sphinx) -> dict[str, Any]:
114+
app.add_config_value(
115+
"dissect_projects_path", Path(__file__).parent.parent.parent.parent, "html"
116+
)
117+
app.add_directive("dissect-supported-table", SupportedTargetTable)
118+
return {
119+
"version": "0.1",
120+
"parallel_read_safe": True,
121+
"parallel_write_safe": True,
122+
}

docs/source/conf.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
"sphinx_copybutton",
5252
"sphinx_design",
5353
"dissect_plugins",
54+
"dissect_supported_table",
5455
]
5556

5657
# Define the canonical URL if you are using a custom domain on Read the Docs
@@ -182,6 +183,8 @@
182183
"ref.python",
183184
]
184185

186+
dissect_projects_path = Path(__file__).parent.parent.parent / "submodules"
187+
185188

186189
def autoapi_skip_hook(app: Sphinx, what: str, name: str, obj, skip: bool, options: list[str]) -> bool:
187190
# Do not skip OS modules in dissect.target (caught by `private-members`)

docs/source/supported-targets.rst

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,11 @@ If needed, you can choose the loader yourself by using ``-L <loader type>`` opti
3232
:ref:`supported filesystems <supported-targets:Filesystems>`. Whether a target is supported
3333
as a loader, a container or a filesystem depends on implementation details for that specific format.
3434

35-
.. list-table:: Supported loaders
35+
.. dissect-supported-table:: Supported loaders
3636
:header-rows: 1
3737
:widths: 20 15 5
38+
:source-path: dissect.target/dissect/target/loaders
39+
:blacklist: cyber,phobos,itunes,target,vb,asdf,log,vmsupport,res,direct,profile,pvs
3840

3941
* - Description
4042
- Format
@@ -168,9 +170,11 @@ These can be virtual machine files, forensic containers or a hard disk itself.
168170
Dissect can select the appropriate container automatically based on either the file extension or file magic.
169171
For example, the QCOW2 container gets selected if the file extension is ``.qcow2`` or if the first bytes of the file are ``b"QFI\xfb"``.
170172

171-
.. list-table:: Supported containers
173+
.. dissect-supported-table:: Supported containers
172174
:header-rows: 1
173175
:widths: 15 5 5
176+
:source-path: dissect.target/dissect/target/containers
177+
:blacklist: raw,asdf,hds,split
174178

175179
* - Description
176180
- Format
@@ -211,9 +215,10 @@ Partition Schemes and Volume Systems
211215

212216
Dissect supports most common partition schemes. Nested partitions are supported as well.
213217

214-
.. list-table:: Supported Partition Schemes
218+
.. dissect-supported-table:: Supported Partition Schemes
215219
:header-rows: 1
216220
:widths: 20 5
221+
:source-path: dissect.volume/dissect/volume/disk/schemes
217222

218223
* - Description
219224
- API
@@ -232,9 +237,11 @@ Besides these standard partition schemes, Dissect supports disks in RAID configu
232237

233238
For more details, see :doc:`volumes <advanced/volumes>`.
234239

235-
.. list-table:: Supported volume systems
240+
.. dissect-supported-table:: Supported volume systems
236241
:header-rows: 1
237242
:widths: 20 5
243+
:source-path: dissect.target/dissect/target/volumes
244+
:blacklist: disk,luks,bde
238245

239246
* - Description
240247
- API
@@ -250,9 +257,11 @@ Besides these standard partition schemes, Dissect supports disks in RAID configu
250257
Dissect also has decryption capability for some well known systems.
251258
This functionality can be accessed with a keychain file (specified with ``-K``) with multiple passphrases or a keychain value (``-Kv``) in most Dissect tools.
252259

253-
.. list-table:: Supported encrypted volume systems
260+
.. dissect-supported-table:: Supported encrypted volume systems
254261
:header-rows: 1
255262
:widths: 20 5
263+
:source-path: dissect.target/dissect/target/volumes
264+
:blacklist: disk,ddf,lvm,md,vmfs
256265

257266
* - Description
258267
- API
@@ -275,9 +284,11 @@ or need implementation in different areas to work correctly.
275284

276285
For more details, see :doc:`Filesystems </advanced/filesystems>`.
277286

278-
.. list-table:: Supported filesystems
287+
.. dissect-supported-table:: Supported filesystems
279288
:header-rows: 1
280289
:widths: 20 5
290+
:source-path: dissect.target/dissect/target/filesystems
291+
:blacklist: zip,smb,itunes,cb,overlay,ntds,tar,dir
281292

282293
* - Description
283294
- API
@@ -324,9 +335,12 @@ Operating Systems
324335
Dissect tries to automatically figure out what operating system is available on the target, based on known file locations and structures.
325336
Once the operating system is known, it enables you to get more accurate information from the system, for example, the user or network configuration.
326337

327-
.. list-table:: Supported operating systems
338+
.. dissect-supported-table:: Supported operating systems
328339
:header-rows: 1
329340
:widths: 20 5
341+
:source-path: dissect.target/dissect/target/plugins
342+
:glob-pattern: **/_os.py
343+
:blacklist: default
330344
331345
* - Description
332346
- API
@@ -379,9 +393,10 @@ It can do this recursively, and look for *child targets* inside the *child targe
379393

380394
For more details, see :ref:`Child targets <advanced/targets:Targets in targets>`.
381395

382-
.. list-table:: Supported child targets
396+
.. dissect-supported-table:: Supported child targets
383397
:header-rows: 1
384398
:widths: 20 5
399+
:source-path: dissect.target/dissect/target/plugins/child
385400

386401
* - Description
387402
- API

0 commit comments

Comments
 (0)