mermaid-trace 0.4.1__py3-none-any.whl → 0.6.0.post0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
mermaid_trace/__init__.py CHANGED
@@ -27,16 +27,41 @@ Usage Example:
27
27
  """
28
28
 
29
29
  from .core.decorators import trace_interaction, trace
30
- from .handlers.mermaid_handler import MermaidFileHandler
30
+ from .core.utils import trace_class, patch_object
31
+ from .handlers.mermaid_handler import (
32
+ MermaidFileHandler,
33
+ RotatingMermaidFileHandler,
34
+ TimedRotatingMermaidFileHandler,
35
+ )
31
36
  from .handlers.async_handler import AsyncMermaidHandler
32
37
  from .core.events import Event, FlowEvent
33
38
  from .core.context import LogContext
34
39
  from .core.formatter import BaseFormatter, MermaidFormatter
40
+ from .core.config import config, MermaidConfig
41
+
42
+ __all__ = [
43
+ "trace_interaction",
44
+ "trace",
45
+ "trace_class",
46
+ "patch_object",
47
+ "MermaidFileHandler",
48
+ "RotatingMermaidFileHandler",
49
+ "TimedRotatingMermaidFileHandler",
50
+ "AsyncMermaidHandler",
51
+ "Event",
52
+ "FlowEvent",
53
+ "LogContext",
54
+ "BaseFormatter",
55
+ "MermaidFormatter",
56
+ "config",
57
+ "MermaidConfig",
58
+ "configure_flow",
59
+ ]
35
60
  # We don't import integrations by default to avoid hard dependencies
36
61
  # Integrations (like FastAPI) must be imported explicitly by the user if needed.
37
62
 
38
63
  from importlib.metadata import PackageNotFoundError, version
39
- from typing import List, Optional
64
+ from typing import List, Optional, Dict, Any
40
65
 
41
66
  import logging
42
67
 
@@ -45,8 +70,12 @@ def configure_flow(
45
70
  output_file: str = "flow.mmd",
46
71
  handlers: Optional[List[logging.Handler]] = None,
47
72
  append: bool = False,
73
+ overwrite: bool = True,
48
74
  async_mode: bool = False,
49
- ) -> logging.Logger:
75
+ level: int = logging.INFO,
76
+ config_overrides: Optional[Dict[str, Any]] = None,
77
+ queue_size: Optional[int] = None,
78
+ ) -> logging.Logger: # noqa: PLR0913
50
79
  """
51
80
  Configures the flow logger to output to a Mermaid file.
52
81
 
@@ -64,18 +93,30 @@ def configure_flow(
64
93
  Useful if you want to stream logs to other destinations.
65
94
  append (bool): If True, adds the new handler(s) without removing existing ones.
66
95
  Defaults to False (clears existing handlers to prevent duplicate logging).
96
+ overwrite (bool): If True, overwrites the output file if it already exists.
97
+ If False, appends to the existing file. Defaults to True.
67
98
  async_mode (bool): If True, uses a non-blocking background thread for logging (QueueHandler).
68
99
  Recommended for high-performance production environments to avoid
69
100
  blocking the main execution thread during file I/O.
70
101
  Defaults to False.
102
+ level (int): Logging level. Defaults to logging.INFO.
103
+ config_overrides (Dict[str, Any], optional): Dictionary to override default configuration settings.
104
+ Keys should match MermaidConfig attributes.
105
+ queue_size (int, optional): Size of the async queue. If provided, overrides config.queue_size.
71
106
 
72
107
  Returns:
73
108
  logging.Logger: The configured logger instance used for flow tracing.
74
109
  """
110
+ # Apply configuration overrides
111
+ if config_overrides:
112
+ for k, v in config_overrides.items():
113
+ if hasattr(config, k):
114
+ setattr(config, k, v)
115
+
75
116
  # Get the specific logger used by the tracing decorators
76
117
  # This logger is isolated from the root logger to prevent pollution
77
118
  logger = logging.getLogger("mermaid_trace.flow")
78
- logger.setLevel(logging.INFO)
119
+ logger.setLevel(level)
79
120
 
80
121
  # Remove existing handlers to avoid duplicate logs if configured multiple times
81
122
  # unless 'append' is requested. This ensures idempotency when calling configure_flow multiple times.
@@ -91,15 +132,21 @@ def configure_flow(
91
132
  else:
92
133
  # Create default Mermaid handler
93
134
  # This handler knows how to write the Mermaid header and format events
94
- handler = MermaidFileHandler(output_file)
135
+ mode = "w" if overwrite else "a"
136
+ handler = MermaidFileHandler(output_file, mode=mode)
95
137
  handler.setFormatter(MermaidFormatter())
96
138
  target_handlers = [handler]
97
139
 
98
140
  if async_mode:
141
+ # Determine queue size
142
+ final_queue_size = queue_size if queue_size is not None else config.queue_size
143
+
99
144
  # Wrap the target handlers in an AsyncMermaidHandler (QueueHandler)
100
145
  # The QueueListener will pick up logs from the queue and dispatch to target_handlers
101
146
  # This decouples the application execution from the logging I/O
102
- async_handler = AsyncMermaidHandler(target_handlers)
147
+ async_handler = AsyncMermaidHandler(
148
+ target_handlers, queue_size=final_queue_size
149
+ )
103
150
  logger.addHandler(async_handler)
104
151
  else:
105
152
  # Attach handlers directly to the logger for synchronous logging
@@ -116,18 +163,3 @@ try:
116
163
  except PackageNotFoundError:
117
164
  # Fallback version if the package is not installed (e.g., local development)
118
165
  __version__ = "0.0.0"
119
-
120
-
121
- # Export public API for easy access
122
- __all__ = [
123
- "trace_interaction",
124
- "trace",
125
- "configure_flow",
126
- "MermaidFileHandler",
127
- "AsyncMermaidHandler",
128
- "LogContext",
129
- "Event",
130
- "FlowEvent",
131
- "BaseFormatter",
132
- "MermaidFormatter",
133
- ]
mermaid_trace/cli.py CHANGED
@@ -1,8 +1,20 @@
1
1
  """
2
- Command Line Interface Module
3
-
4
- This module provides command-line functionality for MermaidTrace, primarily for
5
- previewing generated Mermaid diagrams in a web browser with live reload capabilities.
2
+ Command Line Interface (CLI) Module for MermaidTrace.
3
+
4
+ This module serves as the entry point for the MermaidTrace command-line tools.
5
+ It provides functionality to:
6
+ 1. **Serve** Mermaid diagram files (.mmd) via a local HTTP server.
7
+ 2. **Preview** diagrams in a web browser with live-reload capabilities.
8
+ 3. **Monitor** file changes using polling or filesystem events (via `watchdog`).
9
+
10
+ Key Components:
11
+ - `serve`: The primary command function that sets up the HTTP server and file watcher.
12
+ - `_create_handler`: A factory function that generates a custom request handler with access to the target file.
13
+ - `HTML_TEMPLATE`: A self-contained HTML page that renders Mermaid diagrams using the Mermaid.js CDN.
14
+
15
+ Usage:
16
+ Run this module directly or via the `mermaid-trace` command (if installed).
17
+ Example: `python -m mermaid_trace.cli serve diagram.mmd --port 8080`
6
18
  """
7
19
 
8
20
  import argparse
@@ -14,22 +26,28 @@ import os
14
26
  from pathlib import Path
15
27
  from typing import Type, Any
16
28
 
29
+ # Attempt to import `watchdog` for efficient file system monitoring.
30
+ # `watchdog` is an external library that allows the program to react to file events (like modifications) immediately.
31
+ # We handle the ImportError gracefully to allow the CLI to function (via polling) even if `watchdog` is not installed.
17
32
  try:
18
- # Watchdog is an optional dependency for efficient file monitoring
19
- # If installed, it enables instant file change detection
20
33
  from watchdog.observers import Observer
21
34
  from watchdog.events import FileSystemEventHandler
22
35
 
23
36
  HAS_WATCHDOG = True
24
37
  except ImportError:
25
- # Fallback when watchdog is not installed (minimal install case)
38
+ # If watchdog is missing, we fall back to a simpler polling mechanism in the browser
26
39
  HAS_WATCHDOG = False
27
40
 
28
- # HTML Template for the diagram preview page
29
- # Provides a self-contained environment to render Mermaid diagrams with:
30
- # 1. Mermaid.js library from CDN for diagram rendering
31
- # 2. Basic CSS styling for readability and layout
32
- # 3. JavaScript for auto-refreshing when the source file changes
41
+ # HTML Template for the diagram preview page.
42
+ # This string contains the full HTML structure served to the browser.
43
+ #
44
+ # It includes:
45
+ # 1. **Mermaid.js CDN**: Loads the library required to render the diagrams client-side.
46
+ # 2. **CSS Styling**: Basic styles for layout, readability, and the "Refresh" button.
47
+ # 3. **JavaScript Logic**:
48
+ # - Initializes Mermaid.js.
49
+ # - Implements a polling mechanism (`checkUpdate`) that hits the `/_status` endpoint.
50
+ # - Reloads the page automatically if the server reports a newer file modification time.
33
51
  HTML_TEMPLATE = """
34
52
  <!DOCTYPE html>
35
53
  <html lang="en">
@@ -37,20 +55,30 @@ HTML_TEMPLATE = """
37
55
  <meta charset="UTF-8">
38
56
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
39
57
  <title>MermaidTrace Flow Preview</title>
40
- <!-- Load Mermaid.js from CDN -->
58
+ <!-- Load Mermaid.js from CDN (Content Delivery Network) -->
59
+ <!-- This library parses the text-based diagram definition and renders it as an SVG -->
41
60
  <script src="https://cdn.jsdelivr.net/npm/mermaid/dist/mermaid.min.js"></script>
42
61
  <style>
43
62
  /* Basic styling for readability and layout */
44
63
  body {{ font-family: sans-serif; padding: 20px; background: #f4f4f4; }}
64
+
65
+ /* Container for the diagram to give it a "card" look */
45
66
  .container {{ background: white; padding: 20px; border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); }}
67
+
46
68
  h1 {{ color: #333; }}
47
- #diagram {{ overflow-x: auto; }} /* Allow horizontal scrolling for wide diagrams */
69
+
70
+ /* Allow horizontal scrolling for wide diagrams that might overflow the screen */
71
+ #diagram {{ overflow-x: auto; }}
72
+
73
+ /* Floating refresh button for manual reloads */
48
74
  .refresh-btn {{
49
75
  position: fixed; bottom: 20px; right: 20px;
50
76
  padding: 10px 20px; background: #007bff; color: white;
51
77
  border: none; border-radius: 5px; cursor: pointer; font-size: 16px;
52
78
  }}
53
79
  .refresh-btn:hover {{ background: #0056b3; }}
80
+
81
+ /* Status indicator to show the user that the live-reload is active */
54
82
  .status {{
55
83
  position: fixed; bottom: 20px; left: 20px;
56
84
  font-size: 12px; color: #666;
@@ -60,29 +88,38 @@ HTML_TEMPLATE = """
60
88
  <body>
61
89
  <div class="container">
62
90
  <h1>MermaidTrace Flow Preview: {filename}</h1>
63
- <!-- The Mermaid diagram content will be injected here -->
91
+ <!-- The Mermaid diagram content will be injected here by the Python server -->
92
+ <!-- The 'mermaid' class triggers the Mermaid.js library to process this div -->
64
93
  <div class="mermaid" id="diagram">
65
94
  {content}
66
95
  </div>
67
96
  </div>
68
- <!-- Button to manually reload the page/diagram -->
97
+
98
+ <!-- Button to manually reload the page/diagram if auto-reload fails or is slow -->
69
99
  <button class="refresh-btn" onclick="location.reload()">Refresh Diagram</button>
70
100
  <div class="status" id="status">Monitoring for changes...</div>
71
101
 
72
102
  <script>
73
- // Initialize Mermaid with default settings
103
+ // Initialize Mermaid with default settings.
104
+ // 'startOnLoad: true' tells Mermaid to find all .mermaid classes and render them immediately.
74
105
  mermaid.initialize({{ startOnLoad: true, theme: 'default' }});
75
106
 
76
- // Live Reload Logic
77
- // We track the file's modification time (mtime) sent by the server.
107
+ // --- Live Reload Logic ---
108
+
109
+ // We track the file's modification time (mtime) sent by the server during the initial page load.
110
+ // This value is injected into the template by Python.
78
111
  const currentMtime = "{mtime}";
79
112
 
113
+ /**
114
+ * Checks the server for updates to the source file.
115
+ * It fetches the '/_status' endpoint which returns the current file mtime.
116
+ */
80
117
  function checkUpdate() {{
81
- // Poll the /_status endpoint to check if the file has changed on disk
82
118
  fetch('/_status')
83
119
  .then(response => response.text())
84
120
  .then(mtime => {{
85
- // If the server reports a different mtime, reload the page
121
+ // If the server reports a different mtime than what we loaded with,
122
+ // it means the file has changed on disk. We reload the page to see the new diagram.
86
123
  if (mtime && mtime !== currentMtime) {{
87
124
  console.log("File changed, reloading...");
88
125
  location.reload();
@@ -91,8 +128,9 @@ HTML_TEMPLATE = """
91
128
  .catch(err => console.error("Error checking status:", err));
92
129
  }}
93
130
 
94
- // Poll every 1 second
95
- // This is a simple alternative to WebSockets for local dev tools
131
+ // Poll every 1 second (1000 milliseconds).
132
+ // This is a simple, robust alternative to WebSockets for local development tools.
133
+ // It creates minimal load for a local server.
96
134
  setInterval(checkUpdate, 1000);
97
135
  </script>
98
136
  </body>
@@ -106,122 +144,156 @@ def _create_handler(
106
144
  """
107
145
  Factory function to create a custom HTTP request handler class.
108
146
 
109
- This uses a closure to inject `filename` and `path` into the handler's scope,
110
- allowing the `do_GET` method to access them without global variables.
147
+ We use a factory function (a function that returns a class) because `socketserver`
148
+ expects a class type, not an instance. This allows us to "close over" the `filename`
149
+ and `path` variables, making them available to the handler class without using globals.
111
150
 
112
151
  Args:
113
- filename (str): Display name of the file being served
114
- path (Path): Path object pointing to the file on disk
152
+ filename (str): The display name of the file being served (used in the HTML title).
153
+ path (Path): The `pathlib.Path` object pointing to the actual file on disk.
115
154
 
116
155
  Returns:
117
- Type[SimpleHTTPRequestHandler]: A custom request handler class
156
+ Type[SimpleHTTPRequestHandler]: A custom class inheriting from `SimpleHTTPRequestHandler`.
118
157
  """
119
158
 
120
159
  class Handler(http.server.SimpleHTTPRequestHandler):
121
160
  """
122
161
  Custom HTTP Request Handler for serving Mermaid diagram previews.
123
162
 
124
- This handler intercepts GET requests to:
125
- - Serve the HTML wrapper with embedded diagram content at the root path ('/')
126
- - Provide file modification time for live reload at '/_status'
127
- - Fall back to default behavior for other paths
163
+ This handler overrides standard methods to provide two specific endpoints:
164
+ 1. `/` (Root): Serves the HTML wrapper with the embedded diagram content.
165
+ 2. `/_status`: Returns the file's current modification time (used for live reload).
128
166
  """
129
167
 
130
168
  def log_message(self, format: str, *args: Any) -> None:
131
169
  """
132
- Suppress default request logging to keep the console clean.
170
+ Override `log_message` to suppress default HTTP request logging.
133
171
 
134
- Only application logs are shown, not every HTTP request.
172
+ By default, `SimpleHTTPRequestHandler` logs every request to stderr.
173
+ We override this to keep the console output clean, showing only important application logs.
135
174
  """
136
175
  pass
137
176
 
138
177
  def do_GET(self) -> None:
139
178
  """
140
- Handle GET requests for different paths.
179
+ Handle HTTP GET requests.
141
180
 
142
181
  Routes:
143
- - '/' : Serves HTML wrapper with embedded Mermaid content
144
- - '/_status' : Returns current file modification time for live reload
145
- - other paths : Falls back to default SimpleHTTPRequestHandler behavior
182
+ - **/**: Reads the target file, injects it into `HTML_TEMPLATE`, and serves the HTML.
183
+ - **/_status**: Checks the file's modification time and returns it as plain text.
184
+ - **Others**: Falls back to the default file serving behavior (though typically not used here).
146
185
  """
147
186
  if self.path == "/":
148
- # Serve the main HTML page with embedded diagram
187
+ # --- Root Endpoint: Serve the HTML Page ---
149
188
  self.send_response(200)
150
189
  self.send_header("Content-type", "text/html")
151
190
  self.end_headers()
152
191
 
153
192
  try:
154
- # Read current content of the Mermaid file
193
+ # Read the current content of the Mermaid file from disk.
194
+ # We read it every time the page is requested to ensure we get the latest version.
155
195
  content = path.read_text(encoding="utf-8")
196
+ # Get the modification time to embed in the page for the JS poller.
156
197
  mtime = str(path.stat().st_mtime)
157
198
  except Exception as e:
158
- # Fallback if reading fails (e.g., file locked, permission error)
159
- # Display error directly in the diagram area
199
+ # Error Handling:
200
+ # If reading fails (e.g., file locked, permissions, deleted),
201
+ # we render a special Mermaid diagram showing the error message.
202
+ # This provides immediate visual feedback in the browser.
160
203
  content = f"sequenceDiagram\nNote right of Error: Failed to read file: {e}"
161
204
  mtime = "0"
162
205
 
163
- # Inject content into the HTML template
206
+ # Inject variables into the HTML template
164
207
  html = HTML_TEMPLATE.format(
165
208
  filename=filename, content=content, mtime=mtime
166
209
  )
167
210
  self.wfile.write(html.encode("utf-8"))
168
211
 
169
212
  elif self.path == "/_status":
170
- # API endpoint for client-side polling
171
- # Returns current file modification time as plain text
213
+ # --- Status Endpoint: Live Reload Polling ---
214
+ # The client-side JavaScript calls this endpoint periodically.
172
215
  self.send_response(200)
173
216
  self.send_header("Content-type", "text/plain")
174
217
  self.end_headers()
175
218
  try:
219
+ # Return the current modification time as the response body.
176
220
  mtime = str(path.stat().st_mtime)
177
221
  except OSError:
178
- # Fallback if file can't be accessed
222
+ # If the file cannot be accessed (e.g., deleted), return "0".
179
223
  mtime = "0"
180
224
  self.wfile.write(mtime.encode("utf-8"))
181
225
 
182
226
  else:
183
- # Serve other static files if needed, or return 404
227
+ # --- Fallback: Default Behavior ---
228
+ # Useful if the HTML template referenced other static assets (images, css files),
229
+ # though currently everything is embedded.
184
230
  super().do_GET()
185
231
 
186
232
  return Handler
187
233
 
188
234
 
189
- def serve(filename: str, port: int = 8000) -> None:
235
+ def serve(filename: str, port: int = 8000, master: bool = False) -> None:
190
236
  """
191
- Starts a local HTTP server to preview Mermaid diagrams in a web browser.
237
+ Starts the local HTTP server and file watcher to preview a Mermaid diagram.
192
238
 
193
- This function blocks the main thread while running a TCP server. It automatically
194
- opens the default web browser to the preview URL and supports live reload when
195
- the source .mmd file changes.
239
+ This is the core logic for the `serve` command. It sets up the environment,
240
+ opens the browser, and enters a blocking loop to serve requests.
196
241
 
197
- Features:
198
- - Serves .mmd files wrapped in an HTML viewer with Mermaid.js
199
- - Live reload functionality using Watchdog (if available) or client-side polling
200
- - Graceful shutdown handling on Ctrl+C
201
- - Automatic browser opening
242
+ Workflow:
243
+ 1. Validates the input file.
244
+ 2. Sets up a `watchdog` observer (if installed) for console logging of changes.
245
+ 3. Creates the custom HTTP handler using `_create_handler`.
246
+ 4. Opens the user's default web browser to the server URL.
247
+ 5. Starts a threaded TCP server to handle HTTP requests.
202
248
 
203
249
  Args:
204
- filename (str): Path to the .mmd file to serve
205
- port (int, optional): Port to bind the server to. Defaults to 8000.
250
+ filename (str): The path to the .mmd file to serve.
251
+ port (int): The port number to bind the server to (default: 8000).
252
+ master (bool): Whether to use the enhanced Master Preview Server (FastAPI + SSE).
206
253
  """
207
- # Resolve the file path
254
+ # 1. Enhanced Master Mode
255
+ if master:
256
+ try:
257
+ from .server import run_server, HAS_SERVER_DEPS
258
+
259
+ if HAS_SERVER_DEPS:
260
+ # For master mode, we watch the directory of the file
261
+ target_dir = os.path.dirname(os.path.abspath(filename))
262
+ if os.path.isdir(filename):
263
+ target_dir = os.path.abspath(filename)
264
+
265
+ # Open browser first
266
+ webbrowser.open(f"http://localhost:{port}")
267
+ run_server(target_dir, port)
268
+ return
269
+ else:
270
+ print(
271
+ "Warning: FastAPI/Uvicorn not found. Falling back to basic server."
272
+ )
273
+ except ImportError:
274
+ print("Warning: server module not found. Falling back to basic server.")
275
+
276
+ # Create a Path object for robust file path handling
208
277
  path = Path(filename)
278
+
279
+ # 1. Validation
209
280
  if not path.exists():
210
281
  print(f"Error: File '{filename}' not found.")
211
282
  sys.exit(1)
212
283
 
213
- # Setup Watchdog file watcher if available
214
- # Watchdog provides immediate file change notifications in the terminal
215
- # The actual browser reload is handled by client-side polling
284
+ # 2. Watchdog Setup (Optional)
285
+ # If `watchdog` is installed, we use it to print immediate feedback to the console when the file changes.
286
+ # Note: The browser reload is driven by the JS polling the `/_status` endpoint, not by this observer.
287
+ # This observer is primarily for developer feedback in the terminal.
216
288
  observer = None
217
289
  if HAS_WATCHDOG:
218
290
 
219
291
  class FileChangeHandler(FileSystemEventHandler):
220
- """Watchdog event handler for detecting changes to the served file"""
292
+ """Internal handler class for Watchdog events."""
221
293
 
222
294
  def on_modified(self, event: Any) -> None:
223
- """Called when a file is modified"""
224
- # Filter only for modifications to our specific file
295
+ """Triggered when a file is modified in the watched directory."""
296
+ # We only care about modifications to the specific file we are serving.
225
297
  if not event.is_directory and os.path.abspath(event.src_path) == str(
226
298
  path.resolve()
227
299
  ):
@@ -229,6 +301,7 @@ def serve(filename: str, port: int = 8000) -> None:
229
301
 
230
302
  print("Initializing file watcher...")
231
303
  observer = Observer()
304
+ # Watch the directory containing the file, but filter events in the handler
232
305
  observer.schedule(FileChangeHandler(), path=str(path.parent), recursive=False)
233
306
  observer.start()
234
307
  else:
@@ -236,52 +309,55 @@ def serve(filename: str, port: int = 8000) -> None:
236
309
  "Watchdog not installed. Falling back to polling mode (client-side only)."
237
310
  )
238
311
 
239
- # Create the custom HTTP handler
312
+ # 3. Create Server Handler
240
313
  HandlerClass = _create_handler(filename, path)
241
314
 
242
- # Print server information
315
+ # 4. User Feedback
243
316
  print(f"Serving {filename} at http://localhost:{port}")
244
317
  print("Press Ctrl+C to stop.")
245
318
 
246
- # Automatically open the default web browser to the preview URL
319
+ # 5. Open Browser
320
+ # We open the browser *before* the server loop blocks, but the request might fail if the server
321
+ # isn't ready instantly. However, `socketserver` setup is usually fast enough.
247
322
  webbrowser.open(f"http://localhost:{port}")
248
323
 
249
- # Start the TCP server
250
- # Using ThreadingTCPServer to handle multiple requests concurrently
251
- # This ensures browser polling doesn't block the initial page load
324
+ # 6. Start Server
325
+ # We use `ThreadingTCPServer` so that multiple requests (e.g., polling + main page load)
326
+ # can be handled concurrently. This prevents the polling loop from blocking the page load.
252
327
  with socketserver.ThreadingTCPServer(("", port), HandlerClass) as httpd:
253
328
  try:
254
- # Serve forever until interrupted
329
+ # Block and handle requests indefinitely
255
330
  httpd.serve_forever()
256
331
  except KeyboardInterrupt:
257
- # Handle Ctrl+C gracefully
332
+ # 7. Graceful Shutdown
333
+ # Catch Ctrl+C to clean up resources properly
258
334
  print("\nStopping server...")
259
- # Stop the watchdog observer if it was started
260
335
  if observer:
261
336
  observer.stop()
262
337
  observer.join()
263
- # Close the server
264
338
  httpd.server_close()
265
339
 
266
340
 
267
341
  def main() -> None:
268
342
  """
269
- Entry point for the CLI application.
343
+ Main entry point for the CLI application.
270
344
 
271
- Parses command-line arguments and dispatches to the appropriate command handler.
272
- Currently supports only the 'serve' command for previewing Mermaid diagrams.
345
+ Responsibilities:
346
+ 1. **Argument Parsing**: Uses `argparse` to define commands and options.
347
+ 2. **Command Dispatch**: Calls the appropriate function based on the user's command.
273
348
  """
274
- # Create argument parser
349
+ # Initialize the argument parser with a description of the tool
275
350
  parser = argparse.ArgumentParser(
276
351
  description="MermaidTrace CLI - Preview Mermaid diagrams in browser"
277
352
  )
278
353
 
279
- # Add subparsers for different commands
354
+ # Create sub-parsers to handle different commands (currently only 'serve')
280
355
  subparsers = parser.add_subparsers(
281
356
  dest="command", required=True, help="Available commands"
282
357
  )
283
358
 
284
- # Define 'serve' command for previewing diagrams
359
+ # --- 'serve' command ---
360
+ # Defines the 'serve' command which takes a file path and an optional port
285
361
  serve_parser = subparsers.add_parser(
286
362
  "serve", help="Serve a Mermaid file in the browser with live reload"
287
363
  )
@@ -289,15 +365,21 @@ def main() -> None:
289
365
  serve_parser.add_argument(
290
366
  "--port", type=int, default=8000, help="Port to bind to (default: 8000)"
291
367
  )
368
+ serve_parser.add_argument(
369
+ "--master",
370
+ action="store_true",
371
+ help="Use enhanced Master Preview (requires FastAPI)",
372
+ )
292
373
 
293
- # Parse arguments and execute command
374
+ # Parse the arguments provided by the user
294
375
  args = parser.parse_args()
295
376
 
377
+ # Dispatch logic
296
378
  if args.command == "serve":
297
- # Execute the serve command
298
- serve(args.file, args.port)
379
+ # Invoke the serve function with parsed arguments
380
+ serve(args.file, args.port, args.master)
299
381
 
300
382
 
301
383
  if __name__ == "__main__":
302
- # Run the main function when script is executed directly
384
+ # Standard boilerplate to run the main function when the script is executed directly
303
385
  main()
@@ -0,0 +1,55 @@
1
+ """
2
+ Configuration Module for Mermaid Trace
3
+ ======================================
4
+
5
+ This module provides a centralized configuration system for the library.
6
+ It allows users to control behavior globally, such as argument capturing,
7
+ string truncation limits, and logging levels.
8
+ """
9
+
10
+ from dataclasses import dataclass
11
+ import os
12
+
13
+
14
+ @dataclass
15
+ class MermaidConfig:
16
+ """
17
+ Global configuration settings for Mermaid Trace.
18
+
19
+ Attributes:
20
+ capture_args (bool): Whether to capture function arguments and return values.
21
+ Defaults to True. Set to False for performance or privacy.
22
+ max_string_length (int): Maximum length for string representations of objects.
23
+ Defaults to 50. Prevents huge log files.
24
+ max_arg_depth (int): Maximum recursion depth for nested objects (lists/dicts).
25
+ Defaults to 1.
26
+ queue_size (int): Size of the async queue. Defaults to 1000.
27
+ """
28
+
29
+ capture_args: bool = True
30
+ max_string_length: int = 50
31
+ max_arg_depth: int = 1
32
+ queue_size: int = 1000
33
+
34
+ @classmethod
35
+ def from_env(cls) -> "MermaidConfig":
36
+ """
37
+ Loads configuration from environment variables.
38
+
39
+ Env Vars:
40
+ MERMAID_TRACE_CAPTURE_ARGS (bool): "true"/"false"
41
+ MERMAID_TRACE_MAX_STRING_LENGTH (int)
42
+ MERMAID_TRACE_MAX_ARG_DEPTH (int)
43
+ MERMAID_TRACE_QUEUE_SIZE (int)
44
+ """
45
+ return cls(
46
+ capture_args=os.getenv("MERMAID_TRACE_CAPTURE_ARGS", "true").lower()
47
+ == "true",
48
+ max_string_length=int(os.getenv("MERMAID_TRACE_MAX_STRING_LENGTH", "50")),
49
+ max_arg_depth=int(os.getenv("MERMAID_TRACE_MAX_ARG_DEPTH", "1")),
50
+ queue_size=int(os.getenv("MERMAID_TRACE_QUEUE_SIZE", "1000")),
51
+ )
52
+
53
+
54
+ # Global configuration instance
55
+ config = MermaidConfig()