Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,6 @@ indent_style = tab
[*.bat]
end_of_line = crlf
#max_line_length = 80

[*.sha256]
insert_final_newline = false
18 changes: 18 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,23 @@ repos:
description: check hooks apply to the repository
- repo: local
hooks:
- id: generate-rake-docs
name: generate Rake task documentation
description: generates a Markdown report of the output when running 'rake -T'
# The command to execute. 'python generate_rake_docs.py' will run our script.
entry: python scripts/generate_rake_docs.py
# 'language: system' means pre-commit will execute the 'entry' command
# directly in your system's environment. Ensure Python is in your PATH.
language: system
Comment on lines +27 to +29

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Using language: system relies on the developer's system PATH, potentially causing inconsistencies. Switching to language: python and specifying a language_version ensures a consistent environment by managing a dedicated virtual environment for the hook.

        language: python
        language_version: python3.8

# 'files: \.rake$' specifies that this hook should only run if any
# file ending with '.rake' (e.g., your Rakefiles) is staged for commit.
files: \.rake$
# 'pass_filenames: false' means the hook script does not receive
# the names of the staged files as arguments. Our script doesn't need them.
pass_filenames: false
# 'always_run: false' (default) means the hook only runs if files matching
# the 'files' regex are staged. This is desired behavior for this hook.
# If set to 'true', it would run on every commit regardless of file changes.
- id: prettier
name: run prettier
description: format files with prettier
Expand Down Expand Up @@ -59,6 +76,7 @@ repos:
args: [--allow-missing-credentials]
- id: detect-private-key
- id: end-of-file-fixer
exclude: \.sha256$
- id: file-contents-sorter
args: [--unique]
files: ^\.github/linters/codespell\.txt$
Expand Down
1 change: 1 addition & 0 deletions doc/guides/checksums/rake-tasks.md.sha256
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
698a9cc526b7f92d1479b5a61721f3b9d02220e89b2f89dea5dc76b770ada009
40 changes: 40 additions & 0 deletions doc/guides/rake-tasks.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# Rake Tasks

This file is automatically generated by a pre-commit hook. Do not edit manually.

```
rake all # build all targets, install (locally) in-repo
rake benchmark # run benchmark tests
rake check # run all pre-commit hooks against all files
rake checkinstall # install the pre-commit hooks
rake checkupdate # check the pre-commit hooks for updates
rake clean # clean all built and in-repo installed artifacts
rake composecheck # run all pre-commit hooks against all files with docker-compose
rake composetest # build and run all mruby tests with docker-compose
rake deep_clean # clean everything
rake doc # generate document
rake doc:api # generate yard docs
rake doc:capi # generate doxygen docs
rake doc:clean # clean all built docs
rake doc:clean:api # clean yard docs
rake doc:clean:capi # clean doxygen docs
rake doc:update-opcode.md # update doc/internal/opcode.md
rake doc:view:api # open yard docs
rake doc:view:capi # open doxygen docs
rake generate_active_gems_txt # generate the active gems text files
rake install # install compiled products (on host)
rake install:bin # install compiled executable (all build targets)
rake install:full # install compiled products (all build targets)
rake install_bin # install compiled executable (on host)
rake test # build and run all mruby tests
rake test:bin # build and run command binaries tests
rake test:build # build all mruby tests
rake test:build:lib # build library tests
rake test:lib # build and run library tests
rake test:run # run all mruby tests
rake test:run:bin # run command binaries tests
rake test:run:lib # run library tests
rake test:run:serial # run all mruby tests serially
rake test:run:serial:bin # run library tests serially
rake test:run:serial:lib # run command binaries tests serially
```
131 changes: 131 additions & 0 deletions scripts/generate_rake_docs.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
#!/usr/bin/env python3

import subprocess
import hashlib
import os
import sys

# --- Configuration ---
# Define the directory where the Markdown and checksum files will be stored.
# This will create 'doc/guides' relative to your repository root.
DOCS_DIR = os.path.join("doc", "guides")

# Define the directory for checksums within the docs directory.
# This will create 'doc/guides/checksums'.
CHECKSUMS_DIR = os.path.join(DOCS_DIR, "checksums")

# Define the filename for the generated Rake tasks Markdown file.
RAKE_MD_FILENAME = "rake-tasks.md"

# Construct the full path for the Markdown file.
RAKE_MD_PATH = os.path.join(DOCS_DIR, RAKE_MD_FILENAME)

# Define the filename for the checksum file.
CHECKSUM_FILENAME = f"{RAKE_MD_FILENAME}.sha256"

# Construct the full path for the checksum file.
CHECKSUM_PATH = os.path.join(CHECKSUMS_DIR, CHECKSUM_FILENAME)

# --- Helper Functions ---

def run_rake_tasks():
"""
Executes the 'rake -T' command and captures its standard output.
Handles cases where 'rake' is not found or the command fails.
"""
print("Running 'rake -T' to capture task list...")
try:
# subprocess.run executes the command.
# capture_output=True redirects stdout and stderr.
# text=True decodes stdout/stderr as text using default encoding.
# check=True raises CalledProcessError if the command returns a non-zero exit code.
result = subprocess.run(["rake", "-T"], capture_output=True, text=True, check=True)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The subprocess.run call uses text=True without specifying an encoding. Explicitly specify encoding="utf-8" to ensure consistent behavior across systems.

Suggested change
result = subprocess.run(["rake", "-T"], capture_output=True, text=True, check=True)
result = subprocess.run(["rake", "-T"], capture_output=True, text=True, check=True, encoding="utf-8")

print("Successfully captured Rake tasks.")
return result.stdout
except FileNotFoundError:
# Error if 'rake' command is not found in the system's PATH.
print("Error: 'rake' command not found. Please ensure Rake is installed and in your PATH.", file=sys.stderr)
sys.exit(1) # Exit with a non-zero code to indicate failure to the pre-commit hook.
except subprocess.CalledProcessError as e:
# Error if 'rake -T' command itself fails (e.g., syntax errors in Rakefiles).
print(f"Error running 'rake -T':\n{e.stderr}", file=sys.stderr)
sys.exit(1) # Exit with a non-zero code.

def generate_markdown_file(output):
"""
Creates or updates the RAKE_TASKS.md file with the captured Rake output.
The content includes a header and the raw output embedded in a Markdown code block.
"""
print(f"Generating Markdown file: {RAKE_MD_PATH}...")
# Ensure the directory structure exists. `exist_ok=True` prevents an error if it already exists.
os.makedirs(DOCS_DIR, exist_ok=True)

# Construct the full Markdown content.
markdown_content = f"""# Rake Tasks

This file is automatically generated by a pre-commit hook. Do not edit manually.

```
{output.strip()}
```
"""
# Write the content to the Markdown file.
with open(RAKE_MD_PATH, "w") as f:
f.write(markdown_content)
print(f"Successfully generated {RAKE_MD_PATH}")

def calculate_sha256(filepath):
"""
Calculates the SHA256 hash of a given file.
Reads the file in chunks to handle potentially large files efficiently.
"""
hasher = hashlib.sha256()
# Open the file in binary read mode.
with open(filepath, "rb") as f:
# Read the file in 8KB chunks until the end.
while chunk := f.read(8192):
hasher.update(chunk) # Update the hash with each chunk.
return hasher.hexdigest() # Return the hexadecimal representation of the hash.

def generate_checksum_file():
"""
Generates a SHA256 checksum file for RAKE_TASKS.md.
The checksum is stored in a separate file in the 'checksums' subdirectory.
"""
print(f"Generating checksum file: {CHECKSUM_PATH}...")
# Ensure the checksums directory exists.
os.makedirs(CHECKSUMS_DIR, exist_ok=True)

# Calculate the checksum of the Markdown file.
checksum = calculate_sha256(RAKE_MD_PATH)

# Write the checksum to its dedicated file.
with open(CHECKSUM_PATH, "w") as f:
f.write(checksum)
print(f"Successfully generated {CHECKSUM_PATH}")

# --- Main Execution ---

def main():
"""
Main function to orchestrate the Rake documentation generation process.
"""
# Step 1: Run 'rake -T' and get its output.
rake_output = run_rake_tasks()

# Step 2: Generate the Markdown file with the captured Rake output.
generate_markdown_file(rake_output)

# Step 3: Generate the checksum file for the newly created Markdown file.
generate_checksum_file()

# If the script reaches this point, it means all operations (running rake,
# generating markdown, and generating checksum) were successful.
# The pre-commit framework will automatically check if these newly generated
# files are unstaged. If they are, it will fail the commit and prompt the
# user to 'git add' them. This is the standard and recommended behavior.
print("\nRake documentation generation complete.")
sys.exit(0) # Exit with 0 to indicate success to the pre-commit hook.

if __name__ == "__main__":
main()
Comment on lines +54 to +131

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The script regenerates documentation and checksum files on every run, even if the rake -T output is unchanged. This can be inefficient. Refactor to compare checksums and only write files if content changes. Also, specify encoding="utf-8" for file I/O.

def get_current_checksum():
    """Reads the checksum from the existing file, if it exists."""
    try:
        with open(CHECKSUM_PATH, "r", encoding="utf-8") as f:
            return f.read().strip()
    except FileNotFoundError:
        return None

def write_docs(content, checksum):
    """Writes the markdown and checksum files."""
    print("Writing Rake documentation files...")
    os.makedirs(CHECKSUMS_DIR, exist_ok=True)

    with open(RAKE_MD_PATH, "w", encoding="utf-8") as f:
        f.write(content)
    print(f"Successfully generated {RAKE_MD_PATH}")

    with open(CHECKSUM_PATH, "w", encoding="utf-8") as f:
        f.write(checksum)
    print(f"Successfully generated {CHECKSUM_PATH}")


def main():
    """
    Main function to orchestrate the Rake documentation generation process.
    It only updates files if the Rake task list has changed.
    """
    rake_output = run_rake_tasks()

    markdown_content = f"""# Rake Tasks

This file is automatically generated by a pre-commit hook. Do not edit manually.

{rake_output.strip()}

"""
    new_checksum = hashlib.sha256(markdown_content.encode("utf-8")).hexdigest()

    old_checksum = get_current_checksum()

    if new_checksum == old_checksum:
        print("Rake documentation is already up-to-date.")
        sys.exit(0)

    print("Rake documentation is outdated. Regenerating files...")
    write_docs(markdown_content, new_checksum)

    print("\nRake documentation generation complete.")
    sys.exit(0)

if __name__ == "__main__":
    main()

Loading