Skip to content

Consolidate Mermaid rendering helpers#12

Merged
leynos merged 2 commits intomainfrom
codex/refactor-file-writing-and-cli-helpers
Aug 7, 2025
Merged

Consolidate Mermaid rendering helpers#12
leynos merged 2 commits intomainfrom
codex/refactor-file-writing-and-cli-helpers

Conversation

@leynos
Copy link
Copy Markdown
Owner

@leynos leynos commented Aug 7, 2025

Summary

  • inline diagram file generation and mermaid-cli invocation in new _render_diagram
  • streamline render_block to use _render_diagram
  • add unit tests covering _render_diagram

Testing

  • make check-fmt
  • make lint
  • make typecheck
  • make test

https://chatgpt.com/codex/tasks/task_e_6894ef7712f88322b33b7dc4fe9285b9

Summary by Sourcery

Consolidate Mermaid diagram rendering by merging file creation and CLI invocation into a single helper and updating render_block accordingly.

Enhancements:

  • Merge .mmd file creation and mermaid-cli execution into a new _render_diagram helper
  • Refactor render_block to delegate rendering to _render_diagram and remove the verbose flag
  • Remove the old _write_diagram_file and _run_mermaid_cli helpers

Tests:

  • Add and adapt unit tests for _render_diagram to verify file writing, command logging, and error handling
  • Update test suite by renaming test files and removing obsolete tests for deleted helpers

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Aug 7, 2025

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Warning

Rate limit exceeded

@leynos has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 2 minutes and 40 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between 9b5b677 and e4ba5b7.

📒 Files selected for processing (1)
  • nixie/cli.py (4 hunks)

Summary by CodeRabbit

  • Refactor

    • Streamlined the diagram rendering workflow into a single asynchronous function, improving clarity and reducing duplication.
    • Simplified logging and error handling during diagram rendering.
  • Tests

    • Updated tests to align with the new rendering workflow.
    • Removed obsolete tests related to the old file-writing function.

Walkthrough

Refactor the diagram rendering workflow in nixie/cli.py by consolidating file writing and CLI invocation into a single asynchronous function. Update error handling and logging, streamline the control flow, and remove now-redundant helper functions. Adjust related unit tests to target the new function and delete obsolete tests.

Changes

Cohort / File(s) Change Summary
Diagram Rendering Refactor
nixie/cli.py
Merge file writing and CLI invocation into _render_diagram; remove _write_diagram_file and _run_mermaid_cli; update render_block signature and error handling.
Unit Test Updates
nixie/unittests/test_render_diagram.py
Update tests to use _render_diagram instead of _run_mermaid_cli; adjust assertions and test names accordingly.
Obsolete Test Removal
nixie/unittests/test_write_diagram_file.py
Delete test file for removed function _write_diagram_file.

Sequence Diagram(s)

sequenceDiagram
    participant Caller
    participant _render_diagram
    participant FileSystem
    participant MermaidCLI

    Caller->>_render_diagram: Call with block, paths, semaphore, timeout
    _render_diagram->>FileSystem: Write .mmd file
    _render_diagram->>MermaidCLI: Invoke CLI with semaphore (async)
    MermaidCLI-->>_render_diagram: Return result or error
    _render_diagram-->>Caller: Complete or raise error
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~18 minutes

Possibly related PRs

  • Refactor mermaid rendering helpers #10: Refactored by extracting _write_diagram_file and _run_mermaid_cli; this PR merges those helpers back into a single function, directly modifying and replacing the earlier approach.

Poem

Refactor’s wind sweeps through the code,
Two helpers merge, a lighter load.
Async flows and errors tamed,
Old tests gone, new ones named.
Diagrams rendered in a single stride—
With semaphore, the bugs subside!
🦑✨

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch codex/refactor-file-writing-and-cli-helpers

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai generate unit tests to generate unit tests for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@sourcery-ai
Copy link
Copy Markdown
Contributor

sourcery-ai Bot commented Aug 7, 2025

Reviewer's Guide

Refactor diagram rendering by merging file writing and CLI invocation into a single helper, updating render_block to use it, and adjusting tests accordingly.

Class diagram for consolidated Mermaid rendering helpers

classDiagram
    class _render_diagram {
        +async __call__(block: str, tmpdir: Path, cfg_path: Path, path: Path, idx: int, sem: asyncio.Semaphore, timeout: float) None
        """Write block to disk and invoke mermaid-cli."""
    }
    class render_block {
        +async __call__(block: str, tmpdir: Path, cfg_path: Path, path: Path, idx: int, semaphore: asyncio.Semaphore, timeout: float = 30.0) bool
        """Render a single mermaid block using the CLI asynchronously."""
    }
    render_block --> _render_diagram : uses

    %% Removed helpers
    class _write_diagram_file {
        -__call__(block: str, tmpdir: Path, path: Path, idx: int) -> tuple[Path, Path]
    }
    class _run_mermaid_cli {
        -async __call__(cmd: list[str], semaphore: asyncio.Semaphore, path: Path, idx: int, timeout: float) None
    }
    %% _render_diagram replaces _write_diagram_file and _run_mermaid_cli
    _render_diagram <.. _write_diagram_file : replaces
    _render_diagram <.. _run_mermaid_cli : replaces
Loading

File-Level Changes

Change Details Files
Consolidated diagram file handling and CLI invocation into _render_diagram
  • Introduced _render_diagram to write .mmd files, build commands, and execute mermaid-cli
  • Removed separate _write_diagram_file and _run_mermaid_cli functions
  • Parameterized concurrency control and timeout in the new helper
nixie/cli.py
Streamlined render_block to leverage _render_diagram
  • Eliminated verbose flag and inline cmd construction
  • Replaced direct file writing and subprocess logic with a single call to _render_diagram
  • Simplified FileNotFoundError handling and error messaging
nixie/cli.py
Updated unit tests to target the new rendering helper
  • Renamed test_run_mermaid_cli.py to test_render_diagram.py and refactored tests to call _render_diagram
  • Adjusted assertions to verify .mmd file content and correct logging of mmdc command
  • Removed obsolete test for write_diagram_file
nixie/unittests/test_render_diagram.py
nixie/unittests/test_write_diagram_file.py

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link
Copy Markdown
Contributor

@sourcery-ai sourcery-ai Bot left a comment

Choose a reason for hiding this comment

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

Hey @leynos - I've reviewed your changes and found some issues that need to be addressed.

  • Extend the semaphore context in _render_diagram to cover the wait_for_proc call so that the concurrency limit truly applies to the entire rendering process, not just the subprocess spawn.
  • Removing the verbose parameter from render_block changes its public signature—consider bumping the version or providing a compatibility shim if downstream code relies on it.
  • Consider replacing the direct print in the FileNotFoundError handler with logger.error to keep CLI error reporting consistent.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- Extend the semaphore context in _render_diagram to cover the wait_for_proc call so that the concurrency limit truly applies to the entire rendering process, not just the subprocess spawn.
- Removing the verbose parameter from render_block changes its public signature—consider bumping the version or providing a compatibility shim if downstream code relies on it.
- Consider replacing the direct print in the FileNotFoundError handler with logger.error to keep CLI error reporting consistent.

## Individual Comments

### Comment 1
<location> `nixie/cli.py:110` </location>
<code_context>
-    block: str, tmpdir: Path, path: Path, idx: int
-) -> tuple[Path, Path]:
-    """Write ``block`` to a temporary ``.mmd`` file.
+async def _render_diagram(
+    block: str,
+    tmpdir: Path,
</code_context>

<issue_to_address>
Consider splitting _render_diagram into two focused helpers for file writing and CLI invocation to keep each function small and testable.

Consider restoring the two focused helpers and let _render_diagram only orchestrate them. This keeps each piece easy to test and the signature of _render_diagram small:

```python
# isolate file I/O
def _write_diagram_file(
    block: str, tmpdir: Path, path: Path, idx: int
) -> tuple[Path, Path]:
    mmd = tmpdir / f"{path.stem}_{idx}.mmd"
    mmd.write_text(block)
    return mmd, mmd.with_suffix(".svg")


# isolate CLI invocation
async def _run_mermaid_cli(
    mmd: Path,
    svg: Path,
    cfg_path: Path,
    sem: asyncio.Semaphore,
    path: Path,
    idx: int,
    timeout: float,
) -> None:
    cmd = get_mmdc_cmd(mmd, svg, cfg_path)
    logging.getLogger(__name__).info(shlex.join(cmd))
    async with sem:
        proc = await asyncio.create_subprocess_exec(
            *cmd,
            stdout=asyncio.subprocess.PIPE,
            stderr=asyncio.subprocess.PIPE,
        )
        success, stderr = await wait_for_proc(proc, path, idx, timeout)
    if not success:
        raise RuntimeError(f"Error running {shlex.join(cmd)} for {path}")


# only orchestration
async def _render_diagram(
    block: str,
    tmpdir: Path,
    cfg_path: Path,
    path: Path,
    idx: int,
    sem: asyncio.Semaphore,
    timeout: float,
) -> None:
    mmd, svg = _write_diagram_file(block, tmpdir, path, idx)
    await _run_mermaid_cli(mmd, svg, cfg_path, sem, path, idx, timeout)
```

This way:

- _write_diagram_file and _run_mermaid_cli stay small and unit-testable.
- _render_diagram’s signature remains the same but its body is a single “call two helpers” step.
- All behavior is unchanged.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread nixie/cli.py
@leynos
Copy link
Copy Markdown
Owner Author

leynos commented Aug 7, 2025

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Aug 7, 2025

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@leynos
Copy link
Copy Markdown
Owner Author

leynos commented Aug 7, 2025

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Aug 7, 2025

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🔭 Outside diff range comments (1)
nixie/cli.py (1)

1-3: Add module-level docstring.

Add a triple-quoted docstring after the shebang to explain the module's purpose, utility, and usage as required by the coding guidelines.

 #!/usr/bin/env python3
+"""Command-line interface for validating Mermaid diagrams in Markdown files.
+
+This module provides functionality to parse Markdown files, extract Mermaid
+diagram blocks, and validate them using the mermaid-cli tool. It supports
+concurrent processing of multiple diagrams and handles various CLI backends
+(mmdc, npx, bun).
+
+Usage:
+    nixie [--concurrency N] [--verbose] path1.md [path2.md ...]
+"""
 from __future__ import annotations
♻️ Duplicate comments (1)
nixie/cli.py (1)

110-171: Split _render_diagram into focused helpers to maintain single responsibility.

The function violates the single responsibility principle by handling both file I/O and CLI invocation. Split it into two focused functions as previously suggested to improve testability and maintainability.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 906f11d and 9b5b677.

📒 Files selected for processing (3)
  • nixie/cli.py (4 hunks)
  • nixie/unittests/test_render_diagram.py (3 hunks)
  • nixie/unittests/test_write_diagram_file.py (0 hunks)
💤 Files with no reviewable changes (1)
  • nixie/unittests/test_write_diagram_file.py
🧰 Additional context used
📓 Path-based instructions (1)
**/*.py

⚙️ CodeRabbit Configuration File

**/*.py: How about the following?

- Keep cyclomatic complexity ≤ 12
- Follow single responsibility and CQRS (command/query segregation)
- Docstrings must follow the `numpy` style guide. Use a single-line summary for private functions and methods, and full structured docs for all public interfaces.
- Move conditionals with >2 branches to predicate/helper functions
- Avoid `eval`, `exec`, `pickle`, monkey-patching, `ctypes`, unsafe shell
- Every module must begin with a triple-quoted docstring explaining its purpose, utility, and usage, including example calls if appropriate.
- Comments and docs must follow en-GB-oxendict (-ize / -our) spelling and grammar
- Lint suppressions:
  - Blanket `# noqa`, file-level skips, and categories are **forbidden**
  - Only narrow in-line disables (`# noqa: XYZ`) are permitted, and must be accompanied by `FIXME:` or a ticket link, and used only as a last resort.
- Use `pytest` fixtures for shared setup (`conftest.py` or `fixtures/`)
- Replace duplicate tests with `@pytest.mark.parametrize`
- Prefer `pytest-mock` or `unittest.mock` for stubs/mocks
- Use `assert …, "message"` over bare asserts
- Reflect all API/behaviour changes in `docs/` and update roadmap on completion
- Files must not exceed 400 logical lines:
  - Decompose large modules into subpackages
  - Split large `match/case` or dispatch tables by domain and collocate with targets if appropriate
  - Move bulky data (fixtures, templates) to external files for parsing at runtime
- Mutable defaults and shadowed built-ins are forbidden
- All code must have clear type hints using modern style (`A | B`, `list[str]`, `class Foo[A]:`, `type Bar = int`, etc.), with ABC imports drawn from the correct stdlib module.

Files:

  • nixie/unittests/test_render_diagram.py
  • nixie/cli.py
🪛 Ruff (0.12.2)
nixie/unittests/test_render_diagram.py

16-16: Trailing comma missing

Add trailing comma

(COM812)


48-48: Trailing comma missing

Add trailing comma

(COM812)

nixie/cli.py

117-117: Async function definition with a timeout parameter

(ASYNC109)


206-206: Trailing comma missing

Add trailing comma

(COM812)

🔇 Additional comments (1)
nixie/cli.py (1)

205-207: Add trailing comma after the format string.

Add a trailing comma to maintain consistency with Python formatting conventions.

         print(
             "Error: '{0}' not found. Install Node.js with npx or Bun to use @mermaid-js/mermaid-cli.".format(
                 cli
-            ),
+            ),
             file=sys.stderr,
         )

Likely an incorrect or invalid review comment.

Comment thread nixie/unittests/test_render_diagram.py
Comment thread nixie/unittests/test_render_diagram.py
@leynos
Copy link
Copy Markdown
Owner Author

leynos commented Aug 7, 2025

@coderabbitai Have the following now been resolved?

  • Extend the semaphore context in _render_diagram to cover the wait_for_proc call so that the concurrency limit truly applies to the entire rendering process, not just the subprocess spawn.
  • Removing the verbose parameter from render_block changes its public signature—consider bumping the version or providing a compatibility shim if downstream code relies on it.
  • Consider replacing the direct print in the FileNotFoundError handler with logger.error to keep CLI error reporting consistent.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Aug 7, 2025

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

@leynos Let me check each of these issues:

Issue 1: Semaphore context in _render_diagram
Not fully resolved. Looking at the current implementation, the semaphore context only covers the subprocess creation but not the wait_for_proc call:

async with sem:
    proc = await asyncio.create_subprocess_exec(*cmd, ...)
await wait_for_proc(proc, path, idx, timeout)

The semaphore is released before waiting for the process to complete, meaning multiple processes can run concurrently beyond the intended limit.

Issue 2: Verbose parameter removal from render_block
Not addressed. The verbose parameter has been removed from render_block without any version bump or compatibility measures. This is a breaking API change that could affect downstream code.

Issue 3: FileNotFoundError handler using logger.error
Not resolved. The error handling still uses print() instead of logger.error():

except FileNotFoundError as e:
    print(f"ERROR: {filename} not found. Is mermaid-cli installed?")

Summary: None of the three issues have been resolved in the current implementation. The PR still has:

  1. Semaphore context that doesn't cover the full rendering process
  2. A breaking API change without versioning considerations
  3. Inconsistent error logging using print instead of logger

These issues should be addressed before merging to ensure proper concurrency control, API compatibility, and consistent logging practices.


🏁 Script executed:

#!/bin/bash
# Examine the current _render_diagram function implementation to check semaphore usage
ast-grep --pattern 'async def _render_diagram($_) {
  $$$
}'

Length of output: 62


🏁 Script executed:

#!/bin/bash
# Look at the render_block function signature and implementation
ast-grep --pattern 'async def render_block($_) -> $_:
  $$$'

Length of output: 62


🏁 Script executed:

#!/bin/bash
# Search for FileNotFoundError handling in the code
rg -A 5 -B 2 "FileNotFoundError"

Length of output: 794


🏁 Script executed:

#!/bin/bash
# Get the full content of nixie/cli.py to analyze the complete implementation
cat nixie/cli.py

Length of output: 9187


@leynos After examining the current implementation, I can confirm that none of the three issues have been resolved:

1. Semaphore context issue - ❌ NOT RESOLVED
The semaphore context in _render_diagram still only covers the subprocess creation but not the wait_for_proc call:

async with sem:
    proc = await asyncio.create_subprocess_exec(...)
# Semaphore is released here
success, stderr = await wait_for_proc(proc, path, idx, timeout)

This means the concurrency limit only applies to starting processes, not to the entire rendering process. Multiple processes can run simultaneously beyond the intended limit.

2. Breaking API change - ❌ NOT ADDRESSED
The verbose parameter has been completely removed from render_block without any compatibility measures:

async def render_block(
    block: str,
    tmpdir: Path,
    cfg_path: Path,
    path: Path,
    idx: int,
    semaphore: asyncio.Semaphore,
    *,
    timeout: float = 30.0,
) -> bool:

This is a breaking change that could affect downstream code. No version bump or compatibility shim has been implemented.

3. Inconsistent error logging - ❌ NOT RESOLVED
The FileNotFoundError handler still uses print() instead of logger.error():

except FileNotFoundError as exc:
    cli = exc.filename or "mmdc"
    print(
        "Error: '{0}' not found. Install Node.js with npx or Bun to use @mermaid-js/mermaid-cli.".format(
            cli
        ),
        file=sys.stderr,
    )

This is inconsistent with the logging approach used elsewhere in the codebase.

Recommendation: Address these issues before merging to ensure proper concurrency control, maintain API compatibility, and achieve consistent error reporting.

@leynos leynos merged commit 0cad73c into main Aug 7, 2025
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant