refactor: Still trying to reorganize edit mode to be more robust
Some checks failed
Test Suite / code-quality (push) Has been cancelled
Test Suite / unit-tests (3.11) (push) Has been cancelled
Test Suite / unit-tests (3.12) (push) Has been cancelled
Test Suite / security-scan (push) Has been cancelled
Test Suite / integration-tests (push) Has been cancelled
Test Suite / e2e-tests (push) Has been cancelled
Test Suite / performance-tests (push) Has been cancelled
Test Suite / test-summary (push) Has been cancelled
Some checks failed
Test Suite / code-quality (push) Has been cancelled
Test Suite / unit-tests (3.11) (push) Has been cancelled
Test Suite / unit-tests (3.12) (push) Has been cancelled
Test Suite / security-scan (push) Has been cancelled
Test Suite / integration-tests (push) Has been cancelled
Test Suite / e2e-tests (push) Has been cancelled
Test Suite / performance-tests (push) Has been cancelled
Test Suite / test-summary (push) Has been cancelled
This commit is contained in:
@@ -1978,10 +1978,18 @@ def md_list_command(ctx, output_format, names_only):
|
||||
help='Copy referenced assets to output directory')
|
||||
@click.option('--no-ship-assets', is_flag=True,
|
||||
help='Don\'t copy referenced assets to output directory')
|
||||
@click.option('--verbose', '-v', is_flag=True,
|
||||
help='Show detailed output including asset operations')
|
||||
@click.option('--silent', '-s', is_flag=True,
|
||||
help='Suppress non-essential output')
|
||||
@click.option('--image-max-width', type=str, default=None,
|
||||
help='Maximum width for images (default: 12cm, supports px, em, %, cm, in, etc.)')
|
||||
@click.option('--image-max-height', type=str, default=None,
|
||||
help='Maximum height for images (default: 20cm, supports px, em, %, cm, in, etc.)')
|
||||
@click.pass_context
|
||||
def md_render_command(ctx, input_file, output, theme, css, edit, insert, editor_theme,
|
||||
keyboard_shortcuts, use_publication_dir, dont_use_publication_dir, nodogtag,
|
||||
ship_assets, no_ship_assets):
|
||||
ship_assets, no_ship_assets, verbose, silent, image_max_width, image_max_height):
|
||||
"""
|
||||
Render a markdown file to HTML with basic templates and live preview capabilities.
|
||||
|
||||
@@ -2013,10 +2021,35 @@ def md_render_command(ctx, input_file, output, theme, css, edit, insert, editor_
|
||||
if edit and insert:
|
||||
raise click.BadParameter("Cannot use both --edit and --insert flags simultaneously. Choose one mode.")
|
||||
|
||||
# Check environment variables for edit/insert modes (if not set via CLI flags)
|
||||
import os
|
||||
if not edit and not insert:
|
||||
if os.environ.get('MARKITECT_EDIT_MODE', '').lower() in ('true', '1', 'yes'):
|
||||
edit = True
|
||||
elif os.environ.get('MARKITECT_INSERT_MODE', '').lower() in ('true', '1', 'yes'):
|
||||
insert = True
|
||||
|
||||
# Validate asset shipping flags
|
||||
if ship_assets and no_ship_assets:
|
||||
raise click.BadParameter("Cannot use both --ship-assets and --no-ship-assets flags simultaneously.")
|
||||
|
||||
# Validate verbosity flags
|
||||
if verbose and silent:
|
||||
raise click.BadParameter("Cannot use both --verbose and --silent flags simultaneously.")
|
||||
|
||||
# Handle image size configuration with environment variable support
|
||||
import os
|
||||
|
||||
# Get image max width (CLI > ENV > default)
|
||||
final_image_max_width = image_max_width
|
||||
if final_image_max_width is None:
|
||||
final_image_max_width = os.environ.get('MARKITECT_IMAGE_MAX_WIDTH', '12cm')
|
||||
|
||||
# Get image max height (CLI > ENV > default)
|
||||
final_image_max_height = image_max_height
|
||||
if final_image_max_height is None:
|
||||
final_image_max_height = os.environ.get('MARKITECT_IMAGE_MAX_HEIGHT', '20cm')
|
||||
|
||||
# Determine output path with environment variable support
|
||||
if output:
|
||||
output_path = Path(output)
|
||||
@@ -2066,7 +2099,7 @@ def md_render_command(ctx, input_file, output, theme, css, edit, insert, editor_
|
||||
if should_ship_assets:
|
||||
if output_is_directory:
|
||||
# For directory output, ship to the same directory as the HTML file
|
||||
_ship_assets(input_path, output_path.parent, config.get('verbose', False))
|
||||
_ship_assets(input_path, output_path.parent, verbose, silent)
|
||||
# For file output, we don't ship assets (shouldn't reach here anyway)
|
||||
|
||||
# Initialize clean document manager
|
||||
@@ -2081,11 +2114,14 @@ def md_render_command(ctx, input_file, output, theme, css, edit, insert, editor_
|
||||
edit_mode=True,
|
||||
editor_theme=editor_theme,
|
||||
keyboard_shortcuts=keyboard_shortcuts,
|
||||
nodogtag=nodogtag)
|
||||
nodogtag=nodogtag,
|
||||
image_max_width=final_image_max_width,
|
||||
image_max_height=final_image_max_height)
|
||||
|
||||
click.echo(f"✓ Rendered with interactive editing capabilities to: {output_path}")
|
||||
if not silent:
|
||||
click.echo(f"✓ Rendered with interactive editing capabilities to: {output_path}")
|
||||
|
||||
if config.get('verbose', False):
|
||||
if verbose:
|
||||
click.echo(f"Editor theme: {editor_theme}")
|
||||
click.echo(f"Keyboard shortcuts: {'enabled' if keyboard_shortcuts else 'disabled'}")
|
||||
click.echo(f"Theme: {theme or 'default'}")
|
||||
@@ -2097,11 +2133,14 @@ def md_render_command(ctx, input_file, output, theme, css, edit, insert, editor_
|
||||
insert_mode=True,
|
||||
editor_theme=editor_theme,
|
||||
keyboard_shortcuts=keyboard_shortcuts,
|
||||
nodogtag=nodogtag)
|
||||
nodogtag=nodogtag,
|
||||
image_max_width=final_image_max_width,
|
||||
image_max_height=final_image_max_height)
|
||||
|
||||
click.echo(f"✓ Rendered with interactive insert capabilities to: {output_path}")
|
||||
if not silent:
|
||||
click.echo(f"✓ Rendered with interactive insert capabilities to: {output_path}")
|
||||
|
||||
if config.get('verbose', False):
|
||||
if verbose:
|
||||
click.echo(f"Editor theme: {editor_theme}")
|
||||
click.echo(f"Keyboard shortcuts: {'enabled' if keyboard_shortcuts else 'disabled'}")
|
||||
click.echo(f"Heading protection: levels 1-3 read-only")
|
||||
@@ -2113,10 +2152,13 @@ def md_render_command(ctx, input_file, output, theme, css, edit, insert, editor_
|
||||
template=theme, css=css,
|
||||
edit_mode=False,
|
||||
insert_mode=False,
|
||||
nodogtag=nodogtag)
|
||||
click.echo(f"✓ Rendered to: {output_path}")
|
||||
nodogtag=nodogtag,
|
||||
image_max_width=final_image_max_width,
|
||||
image_max_height=final_image_max_height)
|
||||
if not silent:
|
||||
click.echo(f"✓ Rendered to: {output_path}")
|
||||
|
||||
if config.get('verbose', False):
|
||||
if verbose:
|
||||
click.echo(f"Theme: {theme or 'default'}")
|
||||
click.echo(f"CSS: {css or 'default'}")
|
||||
|
||||
@@ -3482,18 +3524,28 @@ class FilenameDecoder:
|
||||
return [self.decode(filename) for filename in filenames]
|
||||
|
||||
|
||||
def _ship_assets(input_path: Path, output_dir: Path, verbose: bool = False):
|
||||
def _ship_assets(input_path: Path, output_dir: Path, verbose: bool = False, silent: bool = False):
|
||||
"""
|
||||
Ship (copy) assets referenced in markdown file to output directory.
|
||||
|
||||
Args:
|
||||
input_path: Path to the markdown file
|
||||
output_dir: Directory where assets should be copied
|
||||
verbose: Whether to print verbose output
|
||||
verbose: Whether to print detailed output
|
||||
silent: Whether to suppress non-essential output
|
||||
"""
|
||||
import shutil
|
||||
import hashlib
|
||||
from markitect.assets.discovery import discover_assets_from_markdown
|
||||
|
||||
def get_file_hash(file_path):
|
||||
"""Get SHA-256 hash of file content for content comparison."""
|
||||
hash_sha256 = hashlib.sha256()
|
||||
with open(file_path, "rb") as f:
|
||||
for chunk in iter(lambda: f.read(4096), b""):
|
||||
hash_sha256.update(chunk)
|
||||
return hash_sha256.hexdigest()
|
||||
|
||||
try:
|
||||
# Read the markdown content
|
||||
markdown_content = input_path.read_text(encoding='utf-8')
|
||||
@@ -3524,14 +3576,49 @@ def _ship_assets(input_path: Path, output_dir: Path, verbose: bool = False):
|
||||
# Create destination directory
|
||||
dest_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# Check if we need to copy (timestamp-based)
|
||||
# Check if we need to copy (smart comparison for cross-filesystem compatibility)
|
||||
should_copy = True
|
||||
if dest_path.exists():
|
||||
source_mtime = asset_ref.resolved_path.stat().st_mtime
|
||||
dest_mtime = dest_path.stat().st_mtime
|
||||
if source_mtime <= dest_mtime:
|
||||
should_copy = False
|
||||
skipped_count += 1
|
||||
source_stat = asset_ref.resolved_path.stat()
|
||||
dest_stat = dest_path.stat()
|
||||
|
||||
# Detect if we're in a cross-filesystem scenario where timestamps might be unreliable
|
||||
# Heuristics: different filesystems, or timestamps that don't make sense
|
||||
is_cross_fs = (
|
||||
# Different device IDs suggests different filesystems
|
||||
source_stat.st_dev != dest_stat.st_dev or
|
||||
# Destination path starts with /mnt/ (common WSL Windows mount)
|
||||
str(dest_path).startswith('/mnt/') or
|
||||
# Very large timestamp differences (>1 hour) for same content suggest sync issues
|
||||
abs(source_stat.st_mtime - dest_stat.st_mtime) > 3600
|
||||
)
|
||||
|
||||
if is_cross_fs:
|
||||
# Use content-based comparison for cross-filesystem scenarios
|
||||
if source_stat.st_size == dest_stat.st_size:
|
||||
try:
|
||||
source_hash = get_file_hash(asset_ref.resolved_path)
|
||||
dest_hash = get_file_hash(dest_path)
|
||||
|
||||
if source_hash == dest_hash:
|
||||
should_copy = False
|
||||
skipped_count += 1
|
||||
if verbose:
|
||||
click.echo(f" → Content verified (cross-fs): {asset_ref.asset_path}")
|
||||
# If hashes differ, should_copy remains True
|
||||
except (OSError, IOError):
|
||||
if verbose:
|
||||
click.echo(f" ⚠ Could not verify content, will copy: {asset_ref.asset_path}")
|
||||
pass
|
||||
# If sizes differ, should_copy remains True
|
||||
else:
|
||||
# Use fast timestamp comparison for same-filesystem scenarios
|
||||
if source_stat.st_mtime <= dest_stat.st_mtime and source_stat.st_size == dest_stat.st_size:
|
||||
should_copy = False
|
||||
skipped_count += 1
|
||||
if verbose:
|
||||
click.echo(f" → Timestamp verified: {asset_ref.asset_path}")
|
||||
# If timestamp suggests newer source or different size, should_copy remains True
|
||||
|
||||
if should_copy:
|
||||
shutil.copy2(asset_ref.resolved_path, dest_path)
|
||||
@@ -3541,12 +3628,21 @@ def _ship_assets(input_path: Path, output_dir: Path, verbose: bool = False):
|
||||
elif verbose:
|
||||
click.echo(f" → Skipped (up-to-date): {asset_ref.asset_path}")
|
||||
|
||||
# Summary
|
||||
if verbose or shipped_count > 0:
|
||||
# Summary - provide feedback based on verbosity settings
|
||||
total_assets = shipped_count + skipped_count + missing_count
|
||||
|
||||
if total_assets > 0 and not silent:
|
||||
if shipped_count > 0:
|
||||
click.echo(f"✓ Shipped {shipped_count} assets")
|
||||
if skipped_count > 0:
|
||||
click.echo(f" → Skipped {skipped_count} up-to-date assets")
|
||||
elif skipped_count > 0:
|
||||
click.echo(f"✓ All {skipped_count} assets up-to-date")
|
||||
|
||||
# Additional details for verbose or when there are mixed results
|
||||
if verbose or (shipped_count > 0 and skipped_count > 0):
|
||||
if skipped_count > 0 and shipped_count > 0:
|
||||
click.echo(f" → {skipped_count} already up-to-date")
|
||||
|
||||
# Always show missing assets as it's important information
|
||||
if missing_count > 0:
|
||||
click.echo(f" ⚠ {missing_count} assets not found", err=True)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user