From 1d2f8ed5675a6958e40f60e711cc1275954a291b Mon Sep 17 00:00:00 2001 From: Miauwkeru Date: Thu, 9 Apr 2026 11:17:13 +0000 Subject: [PATCH 1/6] Add supported-table directive that validates whether all different targets we support are documented --- docs/source/_ext/supported_table.py | 97 +++++++++++++++++++++++++++++ docs/source/conf.py | 1 + docs/source/supported-targets.rst | 31 ++++++--- 3 files changed, 121 insertions(+), 8 deletions(-) create mode 100644 docs/source/_ext/supported_table.py diff --git a/docs/source/_ext/supported_table.py b/docs/source/_ext/supported_table.py new file mode 100644 index 0000000..f7cfa15 --- /dev/null +++ b/docs/source/_ext/supported_table.py @@ -0,0 +1,97 @@ +from pathlib import Path +from typing import Any +from typing_extensions import override + +from sphinx.application import Sphinx +from sphinx.util.logging import getLogger +from sphinx.addnodes import pending_xref +from docutils.parsers.rst.directives.tables import ListTable +from docutils.parsers.rst import directives +from docutils import nodes + + +LOGGER = getLogger(__name__) + + +class SupportedTargetTable(ListTable): + """Extended list-table directive that validates if we have all our supported target modules documented.""" + + option_spec = ListTable.option_spec.copy() + option_spec["submodule-path"] = directives.unchanged_required + option_spec["blacklist"] = directives.unchanged + option_spec["glob-pattern"] = directives.unchanged + + @override + def run(self): + # Call the parent directive to get the table node + result = super().run() + + module_references = [] + table_name = "" + # Perform custom checks on the table + if result and isinstance(result[0], nodes.table): + table_node = result[0] + table_name = table_node[0].astext() + LOGGER.info("Gathering references from table '%s'", table_name) + module_references = self._gather_table_references(table_node) + + if module_references: + self.validate_references(table_name, module_references) + + return result + + def _gather_table_references(self, table_node: nodes.table) -> list[str]: + """Gather all the references inside the table.""" + references = [] + for ref in table_node.findall(pending_xref): + target = ref.get("reftarget") + split_names = target.rsplit(".", 2) + if target.endswith("._os"): + modname = split_names[-2] + else: + modname = split_names[-1] + references.append(modname) + + return references + + def validate_references(self, table_name: str, module_references: list[str]): + LOGGER.info("Validating module references from table '%s'", table_name) + check_path: str = self.options.get("submodule-path") + root = Path(__file__).parent.parent.parent.parent + + submodule_dir = root / "submodules" + search_path = submodule_dir / check_path + + black_list = set() + black_list.update(["__init__"]) + black_list.update( + module for module in self.options.get("blacklist", "").split(",") if module + ) + + glob = self.options.get("glob-pattern", "*.py") + for file in search_path.glob(glob): + if file.name == "_os.py": + file = file.parent + + relative_file = file.relative_to(submodule_dir) + + if file.stem in black_list: + LOGGER.debug("Skipping %s", relative_file) + continue + if file.stem not in module_references: + LOGGER.warning( + "Missing documentation entry for %s in table '%s'", + relative_file, + table_name, + ) + + LOGGER.info("Done validating table '%s'", table_name) + + +def setup(app: Sphinx) -> dict[str, Any]: + app.add_directive("supported-table", SupportedTargetTable) + return { + "version": "0.1", + "parallel_read_safe": True, + "parallel_write_safe": True, + } diff --git a/docs/source/conf.py b/docs/source/conf.py index 4fdf8c9..1cb195f 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -51,6 +51,7 @@ "sphinx_copybutton", "sphinx_design", "dissect_plugins", + "supported_table", ] # Define the canonical URL if you are using a custom domain on Read the Docs diff --git a/docs/source/supported-targets.rst b/docs/source/supported-targets.rst index 0c28bc5..3e5f139 100644 --- a/docs/source/supported-targets.rst +++ b/docs/source/supported-targets.rst @@ -32,9 +32,11 @@ If needed, you can choose the loader yourself by using ``-L `` opti :ref:`supported filesystems `. Whether a target is supported as a loader, a container or a filesystem depends on implementation details for that specific format. -.. list-table:: Supported loaders +.. supported-table:: Supported loaders :header-rows: 1 :widths: 20 15 5 + :submodule-path: dissect.target/dissect/target/loaders + :blacklist: cyber,phobos,itunes,target,vb,asdf,log,vmsupport,res,direct,profile,pvs * - Description - Format @@ -168,9 +170,11 @@ These can be virtual machine files, forensic containers or a hard disk itself. Dissect can select the appropriate container automatically based on either the file extension or file magic. 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"``. -.. list-table:: Supported containers +.. supported-table:: Supported containers :header-rows: 1 :widths: 15 5 5 + :submodule-path: dissect.target/dissect/target/containers + :blacklist: raw,asdf,hds,split * - Description - Format @@ -211,9 +215,10 @@ Partition Schemes and Volume Systems Dissect supports most common partition schemes. Nested partitions are supported as well. -.. list-table:: Supported Partition Schemes +.. supported-table:: Supported Partition Schemes :header-rows: 1 :widths: 20 5 + :submodule-path: dissect.volume/dissect/volume/disk/schemes * - Description - API @@ -232,9 +237,11 @@ Besides these standard partition schemes, Dissect supports disks in RAID configu For more details, see :doc:`volumes `. -.. list-table:: Supported volume systems +.. supported-table:: Supported volume systems :header-rows: 1 :widths: 20 5 + :submodule-path: dissect.target/dissect/target/volumes + :blacklist: disk,luks,bde * - Description - API @@ -250,9 +257,11 @@ Besides these standard partition schemes, Dissect supports disks in RAID configu Dissect also has decryption capability for some well known systems. This functionality can be accessed with a keychain file (specified with ``-K``) with multiple passphrases or a keychain value (``-Kv``) in most Dissect tools. -.. list-table:: Supported encrypted volume systems +.. supported-table:: Supported encrypted volume systems :header-rows: 1 :widths: 20 5 + :submodule-path: dissect.target/dissect/target/volumes + :blacklist: disk,ddf,lvm,md,vmfs * - Description - API @@ -275,9 +284,11 @@ or need implementation in different areas to work correctly. For more details, see :doc:`Filesystems `. -.. list-table:: Supported filesystems +.. supported-table:: Supported filesystems :header-rows: 1 :widths: 20 5 + :submodule-path: dissect.target/dissect/target/filesystems + :blacklist: zip,smb,itunes,cb,overlay,ntds,tar,dir * - Description - API @@ -324,9 +335,12 @@ Operating Systems Dissect tries to automatically figure out what operating system is available on the target, based on known file locations and structures. Once the operating system is known, it enables you to get more accurate information from the system, for example, the user or network configuration. -.. list-table:: Supported operating systems +.. supported-table:: Supported operating systems :header-rows: 1 :widths: 20 5 + :submodule-path: dissect.target/dissect/target/plugins + :glob-pattern: **/_os.py + :blacklist: default * - Description - API @@ -379,9 +393,10 @@ It can do this recursively, and look for *child targets* inside the *child targe For more details, see :ref:`Child targets `. -.. list-table:: Supported child targets +.. supported-table:: Supported child targets :header-rows: 1 :widths: 20 5 + :submodule-path: dissect.target/dissect/target/plugins/child * - Description - API From cb2c307d0e27e3936bc1ef0e0dbbad89672b0ee6 Mon Sep 17 00:00:00 2001 From: Miauwkeru Date: Thu, 9 Apr 2026 12:33:53 +0000 Subject: [PATCH 2/6] Rename supported_table to dissect_supported_table --- .../_ext/{supported_table.py => dissect_supported_table.py} | 0 docs/source/conf.py | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename docs/source/_ext/{supported_table.py => dissect_supported_table.py} (100%) diff --git a/docs/source/_ext/supported_table.py b/docs/source/_ext/dissect_supported_table.py similarity index 100% rename from docs/source/_ext/supported_table.py rename to docs/source/_ext/dissect_supported_table.py diff --git a/docs/source/conf.py b/docs/source/conf.py index 1cb195f..f86bf8f 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -51,7 +51,7 @@ "sphinx_copybutton", "sphinx_design", "dissect_plugins", - "supported_table", + "dissect_supported_table", ] # Define the canonical URL if you are using a custom domain on Read the Docs From 26c529ca5dffd285590fc89b7355c63b220c5d88 Mon Sep 17 00:00:00 2001 From: Miauwkeru Date: Thu, 9 Apr 2026 12:53:27 +0000 Subject: [PATCH 3/6] Add "[Dissect]" prefix to log messages --- docs/source/_ext/dissect_supported_table.py | 28 +++++++++++++++++---- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/docs/source/_ext/dissect_supported_table.py b/docs/source/_ext/dissect_supported_table.py index f7cfa15..4afc1c2 100644 --- a/docs/source/_ext/dissect_supported_table.py +++ b/docs/source/_ext/dissect_supported_table.py @@ -4,6 +4,7 @@ from sphinx.application import Sphinx from sphinx.util.logging import getLogger +from sphinx.util.console import colorize from sphinx.addnodes import pending_xref from docutils.parsers.rst.directives.tables import ListTable from docutils.parsers.rst import directives @@ -11,6 +12,7 @@ LOGGER = getLogger(__name__) +DISSECT_PREFIX = colorize("bold", "[Dissect] ") class SupportedTargetTable(ListTable): @@ -32,7 +34,11 @@ def run(self): if result and isinstance(result[0], nodes.table): table_node = result[0] table_name = table_node[0].astext() - LOGGER.info("Gathering references from table '%s'", table_name) + LOGGER.info( + DISSECT_PREFIX + + colorize("darkgreen", "Gathering references from table '%s'"), + table_name, + ) module_references = self._gather_table_references(table_node) if module_references: @@ -55,7 +61,11 @@ def _gather_table_references(self, table_node: nodes.table) -> list[str]: return references def validate_references(self, table_name: str, module_references: list[str]): - LOGGER.info("Validating module references from table '%s'", table_name) + LOGGER.info( + DISSECT_PREFIX + + colorize("darkgreen", "Validating module references from table '%s'"), + table_name, + ) check_path: str = self.options.get("submodule-path") root = Path(__file__).parent.parent.parent.parent @@ -76,16 +86,24 @@ def validate_references(self, table_name: str, module_references: list[str]): relative_file = file.relative_to(submodule_dir) if file.stem in black_list: - LOGGER.debug("Skipping %s", relative_file) + LOGGER.debug( + DISSECT_PREFIX + colorize("darkgrey", "Skipping %s"), relative_file + ) continue if file.stem not in module_references: LOGGER.warning( - "Missing documentation entry for %s in table '%s'", + DISSECT_PREFIX + + colorize( + "darkred", "Missing documentation entry for %s in table '%s'" + ), relative_file, table_name, ) - LOGGER.info("Done validating table '%s'", table_name) + LOGGER.info( + DISSECT_PREFIX + colorize("darkgreen", "Done validating table '%s'"), + table_name, + ) def setup(app: Sphinx) -> dict[str, Any]: From d255ba9370e714741106f5eaa271d707980bb25f Mon Sep 17 00:00:00 2001 From: Miauwkeru Date: Thu, 9 Apr 2026 20:48:30 +0000 Subject: [PATCH 4/6] Give the directive the `dissect-` prefix --- docs/source/_ext/dissect_supported_table.py | 2 +- docs/source/supported-targets.rst | 16 ++++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/docs/source/_ext/dissect_supported_table.py b/docs/source/_ext/dissect_supported_table.py index 4afc1c2..9763f35 100644 --- a/docs/source/_ext/dissect_supported_table.py +++ b/docs/source/_ext/dissect_supported_table.py @@ -107,7 +107,7 @@ def validate_references(self, table_name: str, module_references: list[str]): def setup(app: Sphinx) -> dict[str, Any]: - app.add_directive("supported-table", SupportedTargetTable) + app.add_directive("dissect-supported-table", SupportedTargetTable) return { "version": "0.1", "parallel_read_safe": True, diff --git a/docs/source/supported-targets.rst b/docs/source/supported-targets.rst index 3e5f139..c692a02 100644 --- a/docs/source/supported-targets.rst +++ b/docs/source/supported-targets.rst @@ -32,7 +32,7 @@ If needed, you can choose the loader yourself by using ``-L `` opti :ref:`supported filesystems `. Whether a target is supported as a loader, a container or a filesystem depends on implementation details for that specific format. -.. supported-table:: Supported loaders +.. dissect-supported-table:: Supported loaders :header-rows: 1 :widths: 20 15 5 :submodule-path: dissect.target/dissect/target/loaders @@ -170,7 +170,7 @@ These can be virtual machine files, forensic containers or a hard disk itself. Dissect can select the appropriate container automatically based on either the file extension or file magic. 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"``. -.. supported-table:: Supported containers +.. dissect-supported-table:: Supported containers :header-rows: 1 :widths: 15 5 5 :submodule-path: dissect.target/dissect/target/containers @@ -215,7 +215,7 @@ Partition Schemes and Volume Systems Dissect supports most common partition schemes. Nested partitions are supported as well. -.. supported-table:: Supported Partition Schemes +.. dissect-supported-table:: Supported Partition Schemes :header-rows: 1 :widths: 20 5 :submodule-path: dissect.volume/dissect/volume/disk/schemes @@ -237,7 +237,7 @@ Besides these standard partition schemes, Dissect supports disks in RAID configu For more details, see :doc:`volumes `. -.. supported-table:: Supported volume systems +.. dissect-supported-table:: Supported volume systems :header-rows: 1 :widths: 20 5 :submodule-path: dissect.target/dissect/target/volumes @@ -257,7 +257,7 @@ Besides these standard partition schemes, Dissect supports disks in RAID configu Dissect also has decryption capability for some well known systems. This functionality can be accessed with a keychain file (specified with ``-K``) with multiple passphrases or a keychain value (``-Kv``) in most Dissect tools. -.. supported-table:: Supported encrypted volume systems +.. dissect-supported-table:: Supported encrypted volume systems :header-rows: 1 :widths: 20 5 :submodule-path: dissect.target/dissect/target/volumes @@ -284,7 +284,7 @@ or need implementation in different areas to work correctly. For more details, see :doc:`Filesystems `. -.. supported-table:: Supported filesystems +.. dissect-supported-table:: Supported filesystems :header-rows: 1 :widths: 20 5 :submodule-path: dissect.target/dissect/target/filesystems @@ -335,7 +335,7 @@ Operating Systems Dissect tries to automatically figure out what operating system is available on the target, based on known file locations and structures. Once the operating system is known, it enables you to get more accurate information from the system, for example, the user or network configuration. -.. supported-table:: Supported operating systems +.. dissect-supported-table:: Supported operating systems :header-rows: 1 :widths: 20 5 :submodule-path: dissect.target/dissect/target/plugins @@ -393,7 +393,7 @@ It can do this recursively, and look for *child targets* inside the *child targe For more details, see :ref:`Child targets `. -.. supported-table:: Supported child targets +.. dissect-supported-table:: Supported child targets :header-rows: 1 :widths: 20 5 :submodule-path: dissect.target/dissect/target/plugins/child From fa8b48993225ab5a0321f85e1532a7915b9c2952 Mon Sep 17 00:00:00 2001 From: Miauwkeru Date: Mon, 13 Apr 2026 14:02:39 +0000 Subject: [PATCH 5/6] Resolve comments --- docs/source/_ext/dissect_supported_table.py | 21 ++++++++++++++------- docs/source/conf.py | 2 ++ docs/source/supported-targets.rst | 18 +++++++++--------- 3 files changed, 25 insertions(+), 16 deletions(-) diff --git a/docs/source/_ext/dissect_supported_table.py b/docs/source/_ext/dissect_supported_table.py index 9763f35..85c0364 100644 --- a/docs/source/_ext/dissect_supported_table.py +++ b/docs/source/_ext/dissect_supported_table.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from pathlib import Path from typing import Any from typing_extensions import override @@ -19,7 +21,7 @@ class SupportedTargetTable(ListTable): """Extended list-table directive that validates if we have all our supported target modules documented.""" option_spec = ListTable.option_spec.copy() - option_spec["submodule-path"] = directives.unchanged_required + option_spec["source-path"] = directives.unchanged_required option_spec["blacklist"] = directives.unchanged option_spec["glob-pattern"] = directives.unchanged @@ -66,11 +68,12 @@ def validate_references(self, table_name: str, module_references: list[str]): + colorize("darkgreen", "Validating module references from table '%s'"), table_name, ) - check_path: str = self.options.get("submodule-path") - root = Path(__file__).parent.parent.parent.parent + check_path: str = self.options.get("source-path") + # Get the environment for the sphinx app + environment = self.state.document.settings.env + dissect_projects_dir = environment.config.dissect_projects_path - submodule_dir = root / "submodules" - search_path = submodule_dir / check_path + search_path = dissect_projects_dir / check_path black_list = set() black_list.update(["__init__"]) @@ -79,17 +82,18 @@ def validate_references(self, table_name: str, module_references: list[str]): ) glob = self.options.get("glob-pattern", "*.py") - for file in search_path.glob(glob): + for file in search_path.glob(glob.strip("'\"")): if file.name == "_os.py": file = file.parent - relative_file = file.relative_to(submodule_dir) + relative_file = file.relative_to(dissect_projects_dir) if file.stem in black_list: LOGGER.debug( DISSECT_PREFIX + colorize("darkgrey", "Skipping %s"), relative_file ) continue + if file.stem not in module_references: LOGGER.warning( DISSECT_PREFIX @@ -107,6 +111,9 @@ def validate_references(self, table_name: str, module_references: list[str]): def setup(app: Sphinx) -> dict[str, Any]: + app.add_config_value( + "dissect_projects_path", Path(__file__).parent.parent.parent.parent, "html" + ) app.add_directive("dissect-supported-table", SupportedTargetTable) return { "version": "0.1", diff --git a/docs/source/conf.py b/docs/source/conf.py index f86bf8f..721709f 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -183,6 +183,8 @@ "ref.python", ] +dissect_projects_path = Path(__file__).parent.parent.parent / "submodules" + def autoapi_skip_hook(app: Sphinx, what: str, name: str, obj, skip: bool, options: list[str]) -> bool: # Do not skip OS modules in dissect.target (caught by `private-members`) diff --git a/docs/source/supported-targets.rst b/docs/source/supported-targets.rst index c692a02..163a727 100644 --- a/docs/source/supported-targets.rst +++ b/docs/source/supported-targets.rst @@ -35,7 +35,7 @@ If needed, you can choose the loader yourself by using ``-L `` opti .. dissect-supported-table:: Supported loaders :header-rows: 1 :widths: 20 15 5 - :submodule-path: dissect.target/dissect/target/loaders + :source-path: dissect.target/dissect/target/loaders :blacklist: cyber,phobos,itunes,target,vb,asdf,log,vmsupport,res,direct,profile,pvs * - Description @@ -173,7 +173,7 @@ For example, the QCOW2 container gets selected if the file extension is ``.qcow2 .. dissect-supported-table:: Supported containers :header-rows: 1 :widths: 15 5 5 - :submodule-path: dissect.target/dissect/target/containers + :source-path: dissect.target/dissect/target/containers :blacklist: raw,asdf,hds,split * - Description @@ -218,7 +218,7 @@ Dissect supports most common partition schemes. Nested partitions are supported .. dissect-supported-table:: Supported Partition Schemes :header-rows: 1 :widths: 20 5 - :submodule-path: dissect.volume/dissect/volume/disk/schemes + :source-path: dissect.volume/dissect/volume/disk/schemes * - Description - API @@ -240,7 +240,7 @@ Besides these standard partition schemes, Dissect supports disks in RAID configu .. dissect-supported-table:: Supported volume systems :header-rows: 1 :widths: 20 5 - :submodule-path: dissect.target/dissect/target/volumes + :source-path: dissect.target/dissect/target/volumes :blacklist: disk,luks,bde * - Description @@ -260,7 +260,7 @@ This functionality can be accessed with a keychain file (specified with ``-K``) .. dissect-supported-table:: Supported encrypted volume systems :header-rows: 1 :widths: 20 5 - :submodule-path: dissect.target/dissect/target/volumes + :source-path: dissect.target/dissect/target/volumes :blacklist: disk,ddf,lvm,md,vmfs * - Description @@ -287,7 +287,7 @@ or need implementation in different areas to work correctly. .. dissect-supported-table:: Supported filesystems :header-rows: 1 :widths: 20 5 - :submodule-path: dissect.target/dissect/target/filesystems + :source-path: dissect.target/dissect/target/filesystems :blacklist: zip,smb,itunes,cb,overlay,ntds,tar,dir * - Description @@ -338,8 +338,8 @@ Once the operating system is known, it enables you to get more accurate informat .. dissect-supported-table:: Supported operating systems :header-rows: 1 :widths: 20 5 - :submodule-path: dissect.target/dissect/target/plugins - :glob-pattern: **/_os.py + :source-path: dissect.target/dissect/target/plugins + :glob-pattern: "**/_os.py" :blacklist: default * - Description @@ -396,7 +396,7 @@ It can do this recursively, and look for *child targets* inside the *child targe .. dissect-supported-table:: Supported child targets :header-rows: 1 :widths: 20 5 - :submodule-path: dissect.target/dissect/target/plugins/child + :source-path: dissect.target/dissect/target/plugins/child * - Description - API From f49db0e576f90550691462d2f98b87737d8adc57 Mon Sep 17 00:00:00 2001 From: Miauwkeru Date: Mon, 13 Apr 2026 14:24:10 +0000 Subject: [PATCH 6/6] Revert quotes on glob --- docs/source/_ext/dissect_supported_table.py | 2 +- docs/source/supported-targets.rst | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/source/_ext/dissect_supported_table.py b/docs/source/_ext/dissect_supported_table.py index 85c0364..b490ef1 100644 --- a/docs/source/_ext/dissect_supported_table.py +++ b/docs/source/_ext/dissect_supported_table.py @@ -82,7 +82,7 @@ def validate_references(self, table_name: str, module_references: list[str]): ) glob = self.options.get("glob-pattern", "*.py") - for file in search_path.glob(glob.strip("'\"")): + for file in search_path.glob(glob): if file.name == "_os.py": file = file.parent diff --git a/docs/source/supported-targets.rst b/docs/source/supported-targets.rst index 163a727..09ea363 100644 --- a/docs/source/supported-targets.rst +++ b/docs/source/supported-targets.rst @@ -339,7 +339,7 @@ Once the operating system is known, it enables you to get more accurate informat :header-rows: 1 :widths: 20 5 :source-path: dissect.target/dissect/target/plugins - :glob-pattern: "**/_os.py" + :glob-pattern: **/_os.py :blacklist: default * - Description