mirror of
https://github.com/ChristianLempa/boilerplates.git
synced 2026-08-28 10:04:57 -05:00
* updated version number * chore(config): apply ruff formatting to release/v0.1.0 - Run ruff check --fix to remove unused imports and fix code issues - Run ruff format to apply PEP 8 formatting (4 spaces for Python) - Fix TemplateRenderError import and remove unused defaults variable * fix(core): required sections ignore toggle and always enabled - Modified VariableSection.is_enabled() to return True for required sections - Hide toggle variables from display in required sections - Add warnings when attempting to disable required section toggles via config or CLI - Updated pihole template with better defaults and required network section Fixes #1411 * feat(core): comprehensive improvements to variable dependencies and prompts Major enhancements: - Sort variables by dependencies within sections for logical display/prompt order - Skip prompting for variables with unsatisfied needs - Hide toggle variables in required sections from display - Use standard prompt logic for toggle variables (supports extra text) - Add warnings when setting values for variables with unsatisfied needs via config/CLI - Fix section merge to preserve needs from module spec when template doesn't override - Support semicolon-separated multiple AND conditions in needs syntax Example: needs: 'traefik_enabled=true;network_mode=bridge,macvlan' Fixes and improvements for issues with: - Required sections incorrectly showing as disabled - Variables displayed/prompted in illogical order - Macvlan variables prompted when network_mode=bridge - Toggle descriptions not showing extra text - Section needs being cleared during template merge Related to #1411 * fix(pihole): handle host network mode correctly Remove dependency on network_enabled toggle since Network section is now required. Template now directly checks network_mode value for host/bridge/macvlan logic. * feat(core): remove Jinja2 default() filter extraction (#1410) (#1416) - Removed _extract_jinja_default_values() method from Template class - Removed merge logic for Jinja2 defaults in variables property - Fixed validate command to handle 3-tuple from LibraryManager.find() - All defaults must now be explicitly defined in template/module specs - Updated CHANGELOG.md with removal notice * feat(core): add --var-file support for loading variables from YAML (#1331) - Added _load_var_file() method to parse YAML variable files - Added _apply_var_file() method to apply var file variables with proper precedence - Added --var-file/-f parameter to generate command - Supports both flat (var: value) and nested (section: {var: value}) YAML structures - Proper precedence chain: module < template < config < var-file < CLI --var - Comprehensive error handling for file not found, invalid YAML, and type errors - Updated documentation and examples - All existing templates validate successfully * update changelog * docs(quality): add comprehensive code quality analysis * refactor(collection): add iter_active_sections() helper method - Adds centralized iterator for sections with proper filtering - Eliminates duplicate iteration logic in prompt.py - Supports include_disabled and include_unsatisfied flags - Reduces code duplication by ~30 lines Related to #1364 (High Priority #1) * refactor(module): deduplicate template loading logic - Adds _load_all_templates() helper method with optional filtering - Updates list(), search(), and validate() to use centralized helper - Eliminates ~90 lines of duplicate code - Improves maintainability with single source of truth for template loading Related to #1364 (High Priority #3) * docs: add comprehensive naming and API improvement analysis - Identifies 6 major improvement opportunities - Prioritizes by impact (High/Medium/Low) - Proposes CRUD standardization across ConfigManager - Recommends consolidating duplicate methods - Includes breaking change mitigation strategies - Estimates +1.0 code quality score improvement Related to #1364 * docs: remove analysis documents (not needed for PR) * refactor(display): split DisplayManager into specialized managers - Refactored monolithic DisplayManager (971 lines) into 4 specialized managers: * VariableDisplayManager - variable and section rendering * TemplateDisplayManager - template display and file trees * StatusDisplayManager - status messages and errors * TableDisplayManager - all table types - Maintained 100% backward compatibility via delegation methods - All existing code works without modifications - Follows Single Responsibility Principle - Updated AGENTS.md with new architecture documentation Previous improvements included in this commit: - Renamed display methods for consistency (display_template, display_section) - Reduced variable map lookups in reset_disabled_bool_variables() - Improved exception hierarchy (VariableValidationError, VariableError) - Extracted error context building to TemplateErrorHandler class - Fixed VariableSection forward reference in variable.py All ruff checks pass. Tested with compose list and compose show commands. Relates to #1364 * refactor(display): complete optimization with settings, helpers, and method splitting Major improvements to display.py architecture: 1. DisplaySettings Class (65 lines): - Centralized all hardcoded values (colors, styles, layouts, text labels) - Easy customization via single class - Constants: colors, styles, padding, sizes, labels, etc. 2. Helper Methods in DisplayManager: - _format_library_display() - eliminates duplicate library formatting - _truncate_value() - centralized value truncation logic - _format_file_size() - human-readable size formatting (B, KB, MB) 3. Split render_variables_table() (91 → 56 lines): - Extracted _render_section_header() (20 lines) - Extracted _render_variable_row() (35 lines) - Main method now cleaner coordinator logic 4. Updated All Managers to Use Settings: - VariableDisplayManager: uses all style/color/text constants - TemplateDisplayManager: uses settings and _format_library_display() - StatusDisplayManager: uses color scheme constants - TableDisplayManager: uses helpers and settings throughout 5. Removed Code Duplication: - Library display logic (was in 2 places) - File size formatting (was in 1 place) - Value truncation (was in 2 places with different logic) - Sensitive masking (consolidated) Benefits: - Single source of truth for all display configuration - Easy to theme/customize CLI appearance - Better testability (helpers can be unit tested) - Reduced duplication - More maintainable (change color scheme in one place) File stats: 1343 → 1337 lines (-6 lines despite adding 65-line settings class) All tests pass: compose list, compose show traefik Linting: ruff checks passed Relates to #1364 * refactor(display): split display.py into separate manager modules - Created cli/core/display/ package structure - Split DisplayManager into specialized managers: - VariableDisplayManager: variable rendering - TemplateDisplayManager: template display - StatusDisplayManager: status messages and errors - TableDisplayManager: table rendering - Moved DisplaySettings and IconManager to __init__.py - Maintained backward compatibility through delegation methods - All imports remain unchanged (from cli.core.display import DisplayManager) - Follows Single Responsibility Principle for better maintainability * docs(changelog): add display module refactoring entry * refactor(display): separate DisplaySettings, IconManager, and DisplayManager into individual files - Moved DisplaySettings to display_settings.py - Moved IconManager to icon_manager.py - Moved DisplayManager to display_manager.py - Updated __init__.py to only handle imports/exports (27 lines vs 526) - Each file now has single, clear responsibility - Better adherence to Single Responsibility Principle - Updated AGENTS.md with new structure and ruff formatting instructions * style: apply ruff formatting to entire codebase * fix(display): simplify section disabled label logic and fix table row styling - Fixed table row styling being added as 5th column instead of style parameter - Simplified disabled label logic: show (disabled) if section has toggle and is not enabled - Removed redundant has_dependencies parameter from _render_section_header - Now all disabled toggle sections consistently show (disabled) label * refactor(display): standardize table header styling across CLI - Enforce consistent STYLE_TABLE_HEADER ('bold blue') for all tables via _print_table() - Remove optional style parameter logic from table header styling - Remove separate heading() calls before tables for cleaner output - Ensure uniform table appearance throughout compose, repo, and config commands * updated changelog * updated changelog and description for code quality * feature(ci): Add Ruff linting configuration and GitHub Actions workflow - Add Ruff configuration to pyproject.toml with PEP 8 compliance - Line length: 88 characters - Indentation: 4 spaces (PEP 8 standard) - Enable comprehensive rule sets (pycodestyle, pyflakes, isort, pylint, etc.) - Create .github/workflows/codequality-ruff.yaml - Runs on PRs to main and pushes to main/release/* branches - Checks both linting and formatting (blocking) - Fix yamllint errors in config.yaml and release workflow - Remove whitespace from table_display.py Related to #1318 * fixed some ruff errors * feat(compose): add --var and --var-file support to show command (#1421) - Add --var and --var-file options to show command - Apply same variable precedence as generate command - Update CHANGELOG.md with feature description - Users can now preview variable overrides before generating files * started developing new functions * critical updates to templates * updates to the template tags * n8n template preparation * traefik security headers * version pinning for twingate-connector * template fix * recent template updates * traefik template publish * n8n tags * prometheus update * fix(compose): use CF_API_TOKEN_FILE for Cloudflare API token in Traefik * fix bug in schema 1.1 * renovate draft template * make updates to renovate * code quality updates * feat: Add schema 1.2 with dedicated volume and resources sections - Add spec_v1_2.py with new volume and resources sections - Volume section: Replaces swarm_volume_* vars, works universally - Resources section: CPU/memory limits for production deployments - Ports section: Add ports_http and ports_https variables - Update compose module to support schema 1.2 - Create new v2 archetypes: - service-volumes-v2.j2: Uses volume_mode - volumes-v2.j2: Top-level volumes with new section - service-resources-v1.j2: Resource limits Related: #1519 * fixed #1522 and archetype improvements * fixed issues in archetypes * prepare other templates * prepare other templates * fix(variable): correct email validation regex Fixed malformed email validation regex that was matching literal backslash-s characters instead of whitespace. Changed from r"^[^@\\s]+@[^@\\s]+\\.[^@\\s]+$" to r"^[^@\s]+@[^@\s]+\.[^@\s]+$" Fixes #1481 * fix(variable): replace regex with RFC-compliant email validation Replaced regex-based email validation with email-validator library. Regex cannot properly validate emails per RFC 5322/5321 - it fails on valid addresses like "John Doe"@example.com, user+tag@example.com. Changes: - Added email-validator>=2.0.0 dependency to pyproject.toml - Removed EMAIL_REGEX constant - Updated _convert_email() to use validate_email() function - Returns normalized email addresses - Provides better error messages for invalid emails Fixes #1481 * prepare migration for other modules * ruff fixes * fix(template): gracefully handle missing 'needs' dependencies When a section's 'needs' dependency references a non-existent section, the CLI now logs a warning instead of raising an error. This allows templates to be modified without breaking when dependencies are removed. Closes #1428 * fix(template): skip empty files during generation (#1518) (#1530) * big update * docs: add markdown support to changelog (#1471) * feature(install): add auto-install for dependencies on Linux and macOS - Auto-detects OS and Linux distribution - Installs python3, pip, git, and pipx if missing - Supports Ubuntu, Debian, Fedora, RHEL, CentOS, Rocky, AlmaLinux, openSUSE, Arch, Manjaro, Alpine, and macOS - Adds --no-auto-install flag to skip automatic installation - Improves error messages with clear installation instructions Closes #1517 * fix(install): handle PEP 668 externally-managed environments - Try installing pipx from system packages first - Fall back to pip with --break-system-packages flag for PEP 668 - Add pipx package names for each distro - Improve pipx ensurepath handling * fix(install): improve pipx installation error handling - Properly suppress stderr when trying system package installation - Better conditional logic for pip installation with --break-system-packages - Add success logging for each installation method * fix(install): suppress pip error output by checking success - Use grep to check for 'Successfully installed' instead of exit codes - This suppresses PEP 668 error output when trying pip methods - Provides helpful error message suggesting manual apt install * fix(install): handle distros without VERSION_ID in os-release - Arch Linux and some other rolling distros don't have VERSION_ID - Use parameter expansion to set empty default * fix(install): add support for archarm distribution - OrbStack Arch Linux uses 'archarm' as distribution ID - Add it to the Arch Linux case pattern * chore: add build/ and dist/ to .gitignore - Ignore Python build artifacts - Ignore distribution packages * fix: add 'boilerplates' prefix to command suggestions in help text - Update help messages to show 'boilerplates repo update' instead of 'repo update' - Makes it clearer that commands should be run with the boilerplates CLI prefix - Addresses user feedback from issue #1517 * feat(gitlab): integrate improvements from template/1372 with schema 1.2 - Add .env.j2 for environment variables (root password) - Add container_hostname, root_email, root_password variables - Add initial root user configuration to gitlab.rb - Add default_theme, default_color_mode, disable_usage_data settings - Improve template description and next_steps documentation - Add env_file and swarm configs/secrets support - Update to use schema 1.2 volume section (volume_mode instead of swarm_volume_*) - Fix registry port from 5678 to standard 5000 - Add swarm placement constraints support - Update ports section to include ports_https * archetype validation testing * schema1.2-traefik_domain * feat(traefik): add multiple DNS challenge providers - Add support for Porkbun, GoDaddy, DigitalOcean, Route53 (AWS), Azure, GCP, and Namecheap - Add provider-specific credential variables with conditional visibility - Support both standard and Docker Swarm modes for all providers - Update environment variable handling for each provider Closes #1478 * fix repo and changelog * feature(docs): add GitHub Action to auto-generate wiki variable documentation - Created .github/scripts/generate_wiki_docs.py script - Generates markdown documentation for all module variables - Uses latest schema version for each module - Created workflow to auto-update wiki on schema changes - Workflow triggers on changes to module specs and script - Runs on release/v0.1.0 branch (will switch to main later) Relates to #1316 * docs(wiki): add prominent Contributing section with CONTRIBUTING.md link - Added Contributing section in Developer Documentation area - Links directly to CONTRIBUTING.md in repository - Highlights key points: CLI requires Discord, templates welcome PRs Relates to #1316 * Documentation * template updates * template updates * fix(compose): add Loki batching configuration to Alloy template - Add batch_wait (5s) and batch_size (1MB) to reduce request volume - Add max_backoff (5m) and min_backoff (500ms) for retry reliability - Prevents ingestion rate limit errors with multiple Alloy instances - Reduces HTTP overhead and improves compression efficiency Fixes #1556 * updates * update schema * update * fix ruff * update wiki * fix wiki * fix wiki * refactor(workflows): rename and extend wiki sync workflow - Rename docs-update-wiki-variables.yaml to docs-update-wiki.yaml - Add syncing of static wiki pages from .wiki/ directory - Add .wiki/** to workflow triggers - Improve commit message and workflow description * fix(workflows): improve wiki branch detection for new wikis - Add fallback to current branch if symbolic ref doesn't exist - Prevents 'invalid refspec' error on newly created wikis * fix(workflows): simplify wiki workflow by assuming master branch - Remove complex branch detection that was causing empty variable issues - Hardcode master branch (GitHub wikis default) - Remove unnecessary initialization check (wiki must exist for checkout to succeed) - Simplify commit message * update email settings * fix wiki * big template updates 1 * big template updates 1 * template refactoring 2 * working on templates 2 * working on templates 3 * refactoring updates * release-test-1 * release-test-2 * fix(templates): resolve yamllint errors - add missing newlines and remove duplicate key * fix(templates): resolve yamllint line-length warnings in descriptions
304 lines
11 KiB
Python
304 lines
11 KiB
Python
from __future__ import annotations
|
|
|
|
import logging
|
|
import re
|
|
from typing import TYPE_CHECKING
|
|
|
|
from rich import box
|
|
from rich._loop import loop_first
|
|
from rich.console import Console, ConsoleOptions, RenderResult
|
|
from rich.markdown import Heading, ListItem, Markdown
|
|
from rich.panel import Panel
|
|
from rich.segment import Segment
|
|
from rich.text import Text
|
|
|
|
from .display_icons import IconManager
|
|
from .display_settings import DisplaySettings
|
|
|
|
if TYPE_CHECKING:
|
|
from .display_base import BaseDisplay
|
|
|
|
logger = logging.getLogger(__name__)
|
|
console_err = Console(stderr=True) # Keep for error output
|
|
|
|
|
|
class LeftAlignedHeading(Heading):
|
|
"""Custom Heading element with left alignment and no extra spacing."""
|
|
|
|
def __rich_console__(self, console: Console, options: ConsoleOptions) -> RenderResult:
|
|
text = self.text
|
|
text.justify = "left" # Override center justification
|
|
if self.tag == "h1":
|
|
# Draw a border around h1s (left-aligned)
|
|
yield Panel(
|
|
text,
|
|
box=box.HEAVY,
|
|
style="markdown.h1.border",
|
|
)
|
|
else:
|
|
# Styled text for h2 and beyond (no blank line before h2)
|
|
yield text
|
|
|
|
|
|
class IconListItem(ListItem):
|
|
"""Custom list item that replaces bullets with colored icons from shortcodes."""
|
|
|
|
def render_bullet(self, console: Console, options: ConsoleOptions) -> RenderResult:
|
|
"""Render list item with icon replacement if text starts with :shortcode:."""
|
|
# Get the text content from elements
|
|
text_content = ""
|
|
for element in self.elements:
|
|
if hasattr(element, "text"):
|
|
text_content = element.text.plain
|
|
break
|
|
|
|
icon_used = None
|
|
icon_color = "cyan" # Default color for icons
|
|
shortcode_found = None
|
|
|
|
# Scan for shortcode at the beginning
|
|
for shortcode, icon in IconManager.SHORTCODES.items():
|
|
if text_content.strip().startswith(shortcode):
|
|
icon_used = icon
|
|
shortcode_found = shortcode
|
|
|
|
# Map shortcodes to colors
|
|
shortcode_colors = {
|
|
":warning:": "yellow",
|
|
":error:": "red",
|
|
":check:": "green",
|
|
":success:": "green",
|
|
":info:": "blue",
|
|
":docker:": "blue",
|
|
":kubernetes:": "blue",
|
|
":rocket:": "magenta",
|
|
":star:": "yellow",
|
|
":lightning:": "yellow",
|
|
}
|
|
icon_color = shortcode_colors.get(shortcode, "cyan")
|
|
break
|
|
|
|
if icon_used and shortcode_found:
|
|
# Remove the shortcode from the text in all elements
|
|
for element in self.elements:
|
|
if hasattr(element, "text"):
|
|
# Replace the shortcode in the Text object
|
|
plain_text = element.text.plain
|
|
new_text = plain_text.replace(shortcode_found, "", 1).lstrip()
|
|
# Reconstruct the Text object with the same style
|
|
element.text = Text(new_text, style=element.text.style)
|
|
|
|
# Render with custom colored icon instead of bullet
|
|
render_options = options.update(width=options.max_width - 3)
|
|
lines = console.render_lines(self.elements, render_options, style=self.style)
|
|
bullet_style = console.get_style(icon_color, default="none")
|
|
|
|
bullet = Segment(f" {icon_used} ", bullet_style)
|
|
padding = Segment(" " * 3)
|
|
new_line = Segment("\n")
|
|
|
|
for first, line in loop_first(lines):
|
|
yield bullet if first else padding
|
|
yield from line
|
|
yield new_line
|
|
else:
|
|
# No icon found, use default list item rendering
|
|
yield from super().render_bullet(console, options)
|
|
|
|
|
|
class LeftAlignedMarkdown(Markdown):
|
|
"""Custom Markdown renderer with left-aligned headings and icon list items."""
|
|
|
|
def __init__(self, markup: str, **kwargs):
|
|
"""Initialize with custom heading and list item elements."""
|
|
super().__init__(markup, **kwargs)
|
|
|
|
# Replace heading element to use left alignment
|
|
self.elements["heading_open"] = LeftAlignedHeading
|
|
|
|
# Replace list item element to use icon replacement
|
|
self.elements["list_item_open"] = IconListItem
|
|
|
|
|
|
class StatusDisplay:
|
|
"""Status messages and error display.
|
|
|
|
Provides methods for displaying success, error, warning,
|
|
and informational messages with consistent formatting.
|
|
"""
|
|
|
|
def __init__(self, settings: DisplaySettings, quiet: bool, base: BaseDisplay):
|
|
"""Initialize StatusDisplay.
|
|
|
|
Args:
|
|
settings: Display settings for formatting
|
|
quiet: If True, suppress non-error output
|
|
base: BaseDisplay instance
|
|
"""
|
|
self.settings = settings
|
|
self.quiet = quiet
|
|
self.base = base
|
|
|
|
def _display_message(self, level: str, message: str, context: str | None = None) -> None:
|
|
"""Display a message with consistent formatting.
|
|
|
|
Args:
|
|
level: Message level (error, warning, success, info)
|
|
message: The message to display
|
|
context: Optional context information
|
|
"""
|
|
# Errors and warnings always go to stderr, even in quiet mode
|
|
# Success and info respect quiet mode and go to stdout
|
|
use_stderr = level in ("error", "warning")
|
|
should_print = use_stderr or not self.quiet
|
|
|
|
if not should_print:
|
|
return
|
|
|
|
settings = self.settings
|
|
colors = {
|
|
"error": settings.COLOR_ERROR,
|
|
"warning": settings.COLOR_WARNING,
|
|
"success": settings.COLOR_SUCCESS,
|
|
}
|
|
color = colors.get(level)
|
|
|
|
# Format message based on context
|
|
if context:
|
|
text = (
|
|
f"{level.capitalize()} in {context}: {message}"
|
|
if level in {"error", "warning"}
|
|
else f"{context}: {message}"
|
|
)
|
|
else:
|
|
text = f"{level.capitalize()}: {message}" if level in {"error", "warning"} else message
|
|
|
|
# Only use icons and colors for actual status indicators (error, warning, success)
|
|
# Plain info messages use default terminal color (no markup)
|
|
if level in {"error", "warning", "success"}:
|
|
icon = IconManager.get_status_icon(level)
|
|
formatted_text = f"[{color}]{icon} {text}[/{color}]"
|
|
else:
|
|
formatted_text = text
|
|
|
|
if use_stderr:
|
|
console_err.print(formatted_text)
|
|
else:
|
|
self.base.text(formatted_text)
|
|
|
|
# Log appropriately
|
|
log_message = f"{context}: {message}" if context else message
|
|
log_methods = {
|
|
"error": logger.error,
|
|
"warning": logger.warning,
|
|
"success": logger.info,
|
|
"info": logger.info,
|
|
}
|
|
log_methods.get(level, logger.info)(log_message)
|
|
|
|
def error(self, message: str, context: str | None = None, details: str | None = None) -> None:
|
|
"""Display an error message.
|
|
|
|
Args:
|
|
message: Error message
|
|
context: Optional context
|
|
details: Optional additional details (shown in dim style on same line)
|
|
"""
|
|
if details:
|
|
# Combine message and details on same line with different formatting
|
|
settings = self.settings
|
|
color = settings.COLOR_ERROR
|
|
icon = IconManager.get_status_icon("error")
|
|
|
|
# Format: Icon Error: Message (details in dim)
|
|
formatted = f"[{color}]{icon} Error: {message}[/{color}] [dim]({details})[/dim]"
|
|
console_err.print(formatted)
|
|
|
|
# Log at debug level to avoid duplicate console output (already printed to stderr)
|
|
logger.debug(f"Error displayed: {message} ({details})")
|
|
else:
|
|
# No details, use standard display
|
|
self._display_message("error", message, context)
|
|
|
|
def warning(self, message: str, context: str | None = None, details: str | None = None) -> None:
|
|
"""Display a warning message.
|
|
|
|
Args:
|
|
message: Warning message
|
|
context: Optional context
|
|
details: Optional additional details (shown in dim style on same line)
|
|
"""
|
|
if details:
|
|
# Combine message and details on same line with different formatting
|
|
settings = self.settings
|
|
color = settings.COLOR_WARNING
|
|
icon = IconManager.get_status_icon("warning")
|
|
|
|
# Format: Icon Warning: Message (details in dim)
|
|
formatted = f"[{color}]{icon} Warning: {message}[/{color}] [dim]({details})[/dim]"
|
|
console_err.print(formatted)
|
|
|
|
# Log at debug level to avoid duplicate console output (already printed to stderr)
|
|
logger.debug(f"Warning displayed: {message} ({details})")
|
|
else:
|
|
# No details, use standard display
|
|
self._display_message("warning", message, context)
|
|
|
|
def success(self, message: str, context: str | None = None) -> None:
|
|
"""Display a success message.
|
|
|
|
Args:
|
|
message: Success message
|
|
context: Optional context
|
|
"""
|
|
self._display_message("success", message, context)
|
|
|
|
def info(self, message: str, context: str | None = None) -> None:
|
|
"""Display an informational message.
|
|
|
|
Args:
|
|
message: Info message
|
|
context: Optional context
|
|
"""
|
|
self._display_message("info", message, context)
|
|
|
|
def skipped(self, message: str, reason: str | None = None) -> None:
|
|
"""Display a skipped/disabled message.
|
|
|
|
Args:
|
|
message: The main message to display
|
|
reason: Optional reason why it was skipped
|
|
"""
|
|
if reason:
|
|
self.base.text(f"\n{message} (skipped - {reason})", style="dim")
|
|
else:
|
|
self.base.text(f"\n{message} (skipped)", style="dim")
|
|
|
|
def markdown(self, content: str) -> None:
|
|
"""Render markdown content with left-aligned headings.
|
|
|
|
Replaces emoji-style shortcodes (e.g., :warning:, :info:) with Nerd Font icons
|
|
before rendering, EXCEPT for shortcodes at the start of list items which are
|
|
handled by IconListItem to replace the bullet.
|
|
|
|
Args:
|
|
content: Markdown-formatted text to render (may contain shortcodes)
|
|
"""
|
|
if not self.quiet:
|
|
# Replace shortcodes with Nerd Font icons, but preserve list item shortcodes
|
|
# Pattern: "- :shortcode:" at start of line should NOT be replaced
|
|
lines = content.split("\n")
|
|
processed_lines = []
|
|
|
|
for line in lines:
|
|
# Check if line is a list item starting with a shortcode
|
|
if re.match(r"^\s*-\s+:[a-z]+:", line):
|
|
# Keep the line as-is, IconListItem will handle it
|
|
processed_lines.append(line)
|
|
else:
|
|
# Replace shortcodes normally
|
|
processed_lines.append(IconManager.replace_shortcodes(line))
|
|
|
|
processed_content = "\n".join(processed_lines)
|
|
self.base._print_markdown(LeftAlignedMarkdown(processed_content))
|