#!/usr/bin/env python3
"""Script to generate Windows shell documentation."""
import argparse
import logging
import os
import sys
import winshlrc
from winshlrc import versions
from winshlrc import yaml_definitions_file
[docs]
class ControlPanelItemsIndexRstOutputWriter:
"""Control panel items folder Index.rst output writer."""
[docs]
def __init__(self, path):
"""Initializes a control panel items index.rst output writer."""
super().__init__()
self._file_object = None
self._path = path
[docs]
def __enter__(self):
"""Make this work with the 'with' statement."""
self._file_object = open(self._path, "w", encoding="utf-8")
text = "\n".join(
[
"###################",
"Control Panel Items",
"###################",
"",
"Below is a list of control panel item identifiers obtained from:",
"",
(
"* `Canonical Names of Control Panel Items "
"<https://learn.microsoft.com/en-us/windows/win32/shell/"
"controlpanel-canonical-names>`_"
),
(
"* `Windows Registry <https://winreg-kb.readthedocs.io/en/latest/"
"sources/explorer-keys/Control-panel-item-identifiers.html>`_"
),
"",
".. toctree::",
" :maxdepth: 1",
"",
"",
]
)
self._file_object.write(text)
return self
[docs]
def __exit__(self, exception_type, value, traceback):
"""Make this work with the 'with' statement."""
self._file_object.close()
self._file_object = None
[docs]
def WriteControlPanelItem(self, control_panel_item_identifier):
"""Writes a control panel item to the index.rst file.
Args:
control_panel_item_identifier (str): control panel item identifier.
"""
self._file_object.write(
f" {control_panel_item_identifier:s} "
f"<{control_panel_item_identifier:s}>\n"
)
[docs]
class ControlPanelItemMarkdownOutputWriter:
"""Control panel item Markdown output writer."""
_WINDOWS_VERSIONS_KEY_FUNCTION = versions.WindowsVersions.KeyFunction
[docs]
def __init__(self, path):
"""Initializes a control panel item Markdown output writer."""
super().__init__()
self._file_object = None
self._path = path
[docs]
def __enter__(self):
"""Make this work with the 'with' statement."""
self._file_object = open(self._path, "w", encoding="utf-8")
return self
[docs]
def __exit__(self, exception_type, value, traceback):
"""Make this work with the 'with' statement."""
self._file_object.close()
self._file_object = None
[docs]
def WriteControlPanelItem(self, control_panel_item_definition):
"""Writes a control panel item to a Markdown file.
Args:
control_panel_item_definition (ControlPanelItemDefinition): control panel
item definition.
"""
lines = [f"## {control_panel_item_definition.identifier:s}", ""]
if control_panel_item_definition.windows_versions:
versions_per_prefix = {}
for version in sorted(control_panel_item_definition.windows_versions):
for prefix in ("Windows 10", "Windows 11", None):
if prefix and version.startswith(prefix):
break
if not prefix:
versions_per_prefix[version] = []
else:
if prefix not in versions_per_prefix:
versions_per_prefix[prefix] = []
versions_per_prefix[prefix].append(version[len(prefix) + 2 : -1])
lines.append("Seen on:")
for prefix, sub_versions in sorted(
versions_per_prefix.items(),
key=lambda item: self._WINDOWS_VERSIONS_KEY_FUNCTION(item[0]),
):
if not sub_versions:
line = f"* {prefix:s}"
else:
sub_versions_string = ", ".join(sub_versions)
line = f"* {prefix:s} ({sub_versions_string:s})"
lines.append(line)
lines.append("")
lines.extend(['<table border="1" class="docutils">', " <tbody>"])
if control_panel_item_definition.name:
lines.extend(
[
" <tr>",
" <td><b>Name:</b></td>",
f" <td>{control_panel_item_definition.name:s}</td>",
" </tr>",
]
)
module_name = control_panel_item_definition.module_name or " "
lines.extend(
[
" <tr>",
" <td><b>Module name:</b></td>",
f" <td>{module_name:s}</td>",
" </tr>",
]
)
if control_panel_item_definition.alternate_module_names:
for index, name in enumerate(
sorted(control_panel_item_definition.alternate_module_names)
):
if index == 0:
lines.extend(
[
" <tr>",
" <td><b>Alternate module name(s):</b></td>",
f" <td>{name:s}</td>",
" </tr>",
]
)
else:
lines.extend(
[
" <tr>",
" <td> </b></td>",
f" <td>{name:s}</td>",
" </tr>",
]
)
lines.extend([" </tbody>", "</table>", "", ""])
text = "\n".join(lines)
self._file_object.write(text)
[docs]
class KnownFoldersIndexRstOutputWriter:
"""Known folders Index.rst output writer."""
[docs]
def __init__(self, path):
"""Initializes a known folders index.rst output writer."""
super().__init__()
self._file_object = None
self._path = path
[docs]
def __enter__(self):
"""Make this work with the 'with' statement."""
self._file_object = open(self._path, "w", encoding="utf-8")
text = "\n".join(
[
"#############",
"Known Folders",
"#############",
"",
"Below is a list of known folder identifiers obtained from:",
"",
(
"* `KNOWNFOLDERID <https://learn.microsoft.com/en-us/windows/win32/"
"shell/knownfolderid>`_"
),
"* KnownFolders.h",
(
"* `Windows Registry <https://winreg-kb.readthedocs.io/en/latest/"
"sources/explorer-keys/Known-folder-identifiers.html>`_"
),
"",
".. toctree::",
" :maxdepth: 1",
"",
"",
]
)
self._file_object.write(text)
return self
[docs]
def __exit__(self, exception_type, value, traceback):
"""Make this work with the 'with' statement."""
self._file_object.close()
self._file_object = None
[docs]
def WriteKnownFolder(self, known_folder_identifier):
"""Writes a known folder to the index.rst file.
Args:
known_folder_identifier (str): known folder identifier.
"""
self._file_object.write(
f" {known_folder_identifier:s} <{known_folder_identifier:s}>\n"
)
[docs]
class KnownFolderMarkdownOutputWriter:
"""Known folder Markdown output writer."""
_WINDOWS_VERSIONS_KEY_FUNCTION = versions.WindowsVersions.KeyFunction
[docs]
def __init__(self, path):
"""Initializes a known folder Markdown output writer."""
super().__init__()
self._file_object = None
self._path = path
[docs]
def __enter__(self):
"""Make this work with the 'with' statement."""
self._file_object = open(self._path, "w", encoding="utf-8")
return self
[docs]
def __exit__(self, exception_type, value, traceback):
"""Make this work with the 'with' statement."""
self._file_object.close()
self._file_object = None
[docs]
def WriteKnownFolder(self, known_folder_definition):
"""Writes a known folder to a Markdown file.
Args:
known_folder_definition (KnownFolderDefinition): known folder definition.
"""
lines = [f"## {known_folder_definition.identifier:s}", ""]
if known_folder_definition.windows_versions:
versions_per_prefix = {}
for version in sorted(known_folder_definition.windows_versions):
for prefix in ("Windows 10", "Windows 11", None):
if prefix and version.startswith(prefix):
break
if not prefix:
versions_per_prefix[version] = []
else:
if prefix not in versions_per_prefix:
versions_per_prefix[prefix] = []
versions_per_prefix[prefix].append(version[len(prefix) + 2 : -1])
lines.append("Seen on:")
for prefix, sub_versions in sorted(
versions_per_prefix.items(),
key=lambda item: self._WINDOWS_VERSIONS_KEY_FUNCTION(item[0]),
):
if not sub_versions:
line = f"* {prefix:s}"
else:
sub_versions_string = ", ".join(sub_versions)
line = f"* {prefix:s} ({sub_versions_string:s})"
lines.append(line)
lines.append("")
lines.extend(['<table border="1" class="docutils">', " <tbody>"])
if known_folder_definition.name:
lines.extend(
[
" <tr>",
" <td><b>Name:</b></td>",
f" <td>{known_folder_definition.name:s}</td>",
" </tr>",
]
)
display_name = known_folder_definition.display_name or " "
lines.extend(
[
" <tr>",
" <td><b>Display name:</b></td>",
f" <td>{display_name:s}</td>",
" </tr>",
]
)
if known_folder_definition.alternate_display_names:
for index, name in enumerate(
sorted(known_folder_definition.alternate_display_names)
):
if index == 0:
lines.extend(
[
" <tr>",
" <td><b>Alternate name(s):</b></td>",
f" <td>{name:s}</td>",
" </tr>",
]
)
else:
lines.extend(
[
" <tr>",
" <td> </b></td>",
f" <td>{name:s}</td>",
" </tr>",
]
)
lines.extend([" </tbody>", "</table>", "", ""])
text = "\n".join(lines)
self._file_object.write(text)
[docs]
class ShellFoldersIndexRstOutputWriter:
"""Shell folders Index.rst output writer."""
[docs]
def __init__(self, path):
"""Initializes a shell folders index.rst output writer."""
super().__init__()
self._file_object = None
self._path = path
[docs]
def __enter__(self):
"""Make this work with the 'with' statement."""
self._file_object = open(self._path, "w", encoding="utf-8")
text = "\n".join(
[
"#############",
"Shell Folders",
"#############",
"",
"Below is a list of shell folder identifiers obtained from:",
"",
(
"* `Windows Registry <https://winreg-kb.readthedocs.io/en/latest/"
"sources/system-keys/Shell-folder-identifiers.html>`_"
),
"",
".. toctree::",
" :maxdepth: 1",
"",
"",
]
)
self._file_object.write(text)
return self
[docs]
def __exit__(self, exception_type, value, traceback):
"""Make this work with the 'with' statement."""
self._file_object.close()
self._file_object = None
[docs]
def WriteShellFolder(self, shell_folder_identifier):
"""Writes a shell folder to the index.rst file.
Args:
shell_folder_identifier (str): shell folder identifier.
"""
self._file_object.write(
f" {shell_folder_identifier:s} <{shell_folder_identifier:s}>\n"
)
[docs]
class ShellFolderMarkdownOutputWriter:
"""Shell folder Markdown output writer."""
_WINDOWS_VERSIONS_KEY_FUNCTION = versions.WindowsVersions.KeyFunction
[docs]
def __init__(self, path):
"""Initializes a shell folder Markdown output writer."""
super().__init__()
self._file_object = None
self._path = path
[docs]
def __enter__(self):
"""Make this work with the 'with' statement."""
self._file_object = open(self._path, "w", encoding="utf-8")
return self
[docs]
def __exit__(self, exception_type, value, traceback):
"""Make this work with the 'with' statement."""
self._file_object.close()
self._file_object = None
[docs]
def WriteShellFolder(self, shell_folder_definition):
"""Writes a shell folder to a Markdown file.
Args:
shell_folder_definition (ShellFolderDefinition): shell folder definition.
"""
lines = [f"## {shell_folder_definition.identifier:s}", ""]
if shell_folder_definition.windows_versions:
versions_per_prefix = {}
for version in sorted(shell_folder_definition.windows_versions):
for prefix in ("Windows 10", "Windows 11", None):
if prefix and version.startswith(prefix):
break
if not prefix:
versions_per_prefix[version] = []
else:
if prefix not in versions_per_prefix:
versions_per_prefix[prefix] = []
versions_per_prefix[prefix].append(version[len(prefix) + 2 : -1])
lines.append("Seen on:")
for prefix, sub_versions in sorted(
versions_per_prefix.items(),
key=lambda item: self._WINDOWS_VERSIONS_KEY_FUNCTION(item[0]),
):
if not sub_versions:
line = f"* {prefix:s}"
else:
sub_versions_string = ", ".join(sub_versions)
line = f"* {prefix:s} ({sub_versions_string:s})"
lines.append(line)
lines.append("")
lines.extend(['<table border="1" class="docutils">', " <tbody>"])
class_name = shell_folder_definition.class_name or " "
name = shell_folder_definition.name or " "
lines.extend(
[
" <tr>",
" <td><b>Class name:</b></td>",
f" <td>{class_name:s}</td>",
" </tr>",
" <tr>",
" <td><b>Name:</b></td>",
f" <td>{name:s}</td>",
" </tr>",
]
)
if shell_folder_definition.alternate_names:
for index, name in enumerate(
sorted(shell_folder_definition.alternate_names)
):
if index == 0:
lines.extend(
[
" <tr>",
" <td><b>Alternate name(s):</b></td>",
f" <td>{name:s}</td>",
" </tr>",
]
)
else:
lines.extend(
[
" <tr>",
" <td> </b></td>",
f" <td>{name:s}</td>",
" </tr>",
]
)
lines.extend([" </tbody>", "</table>", "", ""])
text = "\n".join(lines)
self._file_object.write(text)
[docs]
def Main():
"""Entry point of console script to generate Windows shell documentation.
Returns:
int: exit code that is provided to sys.exit().
"""
argument_parser = argparse.ArgumentParser(
description=("Generated Windows shell documentation.")
)
argument_parser.parse_args()
logging.basicConfig(level=logging.INFO, format="[%(levelname)s] %(message)s")
data_path = os.path.join(os.path.dirname(winshlrc.__file__), "data")
definitions_file = yaml_definitions_file.YAMLControlPanelItemsDefinitionsFile()
control_panel_items = {}
path = os.path.join(data_path, "observed_controlpanel_items.yaml")
for control_panel_item_definition in definitions_file.ReadFromFile(path):
# TODO: merge observed control panel items with defined control panel items.
control_panel_items[control_panel_item_definition.identifier] = (
control_panel_item_definition
)
output_directory = os.path.join("docs", "sources", "control-panel-items")
os.makedirs(output_directory, exist_ok=True)
index_rst_file_path = os.path.join(output_directory, "index.rst")
with ControlPanelItemsIndexRstOutputWriter(index_rst_file_path) as index_rst_writer:
for identifier, control_panel_item_definition in sorted(
control_panel_items.items()
):
index_rst_writer.WriteControlPanelItem(identifier)
markdown_file_path = os.path.join(output_directory, f"{identifier:s}.md")
with ControlPanelItemMarkdownOutputWriter(
markdown_file_path
) as markdown_writer:
markdown_writer.WriteControlPanelItem(control_panel_item_definition)
definitions_file = yaml_definitions_file.YAMLKnownFoldersDefinitionsFile()
known_folders = {}
path = os.path.join(data_path, "defined_knownfolders.yaml")
for known_folder_definition in definitions_file.ReadFromFile(path):
if not known_folder_definition.identifier:
continue
lookup_key = known_folder_definition.identifier
if lookup_key in known_folders:
known_folders[lookup_key].Merge(known_folder_definition)
else:
known_folders[known_folder_definition.identifier] = known_folder_definition
path = os.path.join(data_path, "observed_knownfolders.yaml")
for known_folder_definition in definitions_file.ReadFromFile(path):
lookup_key = known_folder_definition.identifier
if lookup_key in known_folders:
known_folders[lookup_key].Merge(known_folder_definition)
else:
known_folders[known_folder_definition.identifier] = known_folder_definition
output_directory = os.path.join("docs", "sources", "known-folders")
os.makedirs(output_directory, exist_ok=True)
index_rst_file_path = os.path.join(output_directory, "index.rst")
with KnownFoldersIndexRstOutputWriter(index_rst_file_path) as index_rst_writer:
for identifier, known_folder_definition in sorted(known_folders.items()):
index_rst_writer.WriteKnownFolder(identifier)
markdown_file_path = os.path.join(output_directory, f"{identifier:s}.md")
with KnownFolderMarkdownOutputWriter(markdown_file_path) as markdown_writer:
markdown_writer.WriteKnownFolder(known_folder_definition)
definitions_file = yaml_definitions_file.YAMLShellFoldersDefinitionsFile()
shell_folders = {}
path = os.path.join(data_path, "observed_shellfolders.yaml")
for shell_folder_definition in definitions_file.ReadFromFile(path):
shell_folders[shell_folder_definition.identifier] = shell_folder_definition
output_directory = os.path.join("docs", "sources", "shell-folders")
os.makedirs(output_directory, exist_ok=True)
index_rst_file_path = os.path.join(output_directory, "index.rst")
with ShellFoldersIndexRstOutputWriter(index_rst_file_path) as index_rst_writer:
for identifier, shell_folder_definition in sorted(shell_folders.items()):
index_rst_writer.WriteShellFolder(identifier)
markdown_file_path = os.path.join(output_directory, f"{identifier:s}.md")
with ShellFolderMarkdownOutputWriter(markdown_file_path) as markdown_writer:
markdown_writer.WriteShellFolder(shell_folder_definition)
return 0
if __name__ == "__main__":
sys.exit(Main())