Loading...
No commits yet
Not committed History
Blame
_branding.py • 4.8 KB
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Timestamp: 2026-01-27
# File: src/scitex_writer/_branding.py

"""Branding configuration for white-label integration.

This module provides configurable branding for scitex-writer, allowing parent
packages (e.g., scitex.writer) to rebrand documentation and tool descriptions.

Environment Variables
---------------------
SCITEX_WRITER_BRAND : str
    Package name shown in docs (default: "scitex-writer")
SCITEX_WRITER_ALIAS : str
    Import alias shown in examples (default: "sw")

Usage
-----
Parent package sets env vars before importing:

    # scitex/writer/__init__.py
    import os
    os.environ["SCITEX_WRITER_BRAND"] = "scitex.writer"
    os.environ["SCITEX_WRITER_ALIAS"] = "sw"
    from scitex_writer import *

Then docstrings will show:
    >>> import scitex.writer as sw
    >>> sw.compile.manuscript("./my-paper")

Instead of:
    >>> import scitex_writer as sw
    >>> sw.compile.manuscript("./my-paper")
"""

import os
import re
from typing import Optional

# Read branding from environment
BRAND_NAME = os.environ.get("SCITEX_WRITER_BRAND", "scitex-writer")
BRAND_ALIAS = os.environ.get("SCITEX_WRITER_ALIAS", "sw")

# Original values (for reference/restoration)
_ORIGINAL_NAME = "scitex-writer"
_ORIGINAL_MODULE = "scitex_writer"
_ORIGINAL_ALIAS = "sw"


def rebrand_text(text: Optional[str]) -> Optional[str]:
    """Apply branding to a text string (e.g., docstring).

    Parameters
    ----------
    text : str or None
        Text to rebrand.

    Returns
    -------
    str or None
        Rebranded text, or None if input was None.

    Examples
    --------
    >>> os.environ["SCITEX_WRITER_BRAND"] = "mypackage"
    >>> os.environ["SCITEX_WRITER_ALIAS"] = "mp"
    >>> rebrand_text("import scitex_writer as sw")
    'import mypackage as mp'
    """
    if text is None:
        return None

    if BRAND_NAME == _ORIGINAL_NAME and BRAND_ALIAS == _ORIGINAL_ALIAS:
        return text

    result = text

    # Derive module name from brand (e.g., "scitex.writer" -> "scitex.writer")
    brand_module = BRAND_NAME.replace("-", "_")

    # Replace "import scitex_writer as sw" with "import BRAND as ALIAS"
    result = re.sub(
        rf"import\s+{_ORIGINAL_MODULE}\s+as\s+{_ORIGINAL_ALIAS}",
        f"import {brand_module} as {BRAND_ALIAS}",
        result,
    )

    # Replace "from scitex_writer" with "from BRAND"
    result = re.sub(
        rf"from\s+{_ORIGINAL_MODULE}(\s+import|\s*\.)",
        lambda m: f"from {brand_module}{m.group(1)}",
        result,
    )

    # Replace standalone "scitex-writer" (but not in URLs or paths)
    result = re.sub(
        rf"(?<![/.\w]){re.escape(_ORIGINAL_NAME)}(?=\s|$|[,.](?!\w))",
        BRAND_NAME,
        result,
    )

    # Replace " sw." with " ALIAS." (variable usage in examples)
    result = re.sub(
        rf"(\s){_ORIGINAL_ALIAS}\.",
        lambda m: f"{m.group(1)}{BRAND_ALIAS}.",
        result,
    )

    # Replace ">>> sw." with ">>> ALIAS." (doctest examples)
    result = re.sub(
        rf"(>>>\s+){_ORIGINAL_ALIAS}\.",
        lambda m: f"{m.group(1)}{BRAND_ALIAS}.",
        result,
    )

    return result


def rebrand_docstring(obj):
    """Apply branding to an object's docstring in-place.

    Parameters
    ----------
    obj : object
        Object with __doc__ attribute (function, class, module).

    Returns
    -------
    object
        The same object with rebranded docstring.
    """
    if hasattr(obj, "__doc__") and obj.__doc__:
        try:
            obj.__doc__ = rebrand_text(obj.__doc__)
        except AttributeError:
            # Some built-in objects have read-only __doc__
            pass
    return obj


def get_branded_import_example() -> str:
    """Get the branded import statement for documentation.

    Returns
    -------
    str
        Import statement like "import scitex_writer as sw" or "from scitex import writer as sw".
    """
    brand_module = BRAND_NAME.replace("-", "_")

    if BRAND_NAME == _ORIGINAL_NAME:
        return f"import {_ORIGINAL_MODULE} as {BRAND_ALIAS}"

    # For rebranded packages, check if it's a submodule
    if "." in brand_module:
        parts = brand_module.rsplit(".", 1)
        return f"from {parts[0]} import {parts[1]} as {BRAND_ALIAS}"
    else:
        return f"import {brand_module} as {BRAND_ALIAS}"


def get_mcp_server_name() -> str:
    """Get the MCP server name based on branding.

    Returns
    -------
    str
        Server name for MCP registration.
    """
    return BRAND_NAME.replace(".", "-")


def get_mcp_instructions() -> str:
    """Get branded MCP server instructions.

    Returns
    -------
    str
        Instructions text with branding applied.

    Note
    ----
    This function delegates to _usage.get_usage() to avoid circular imports
    while maintaining backward compatibility.
    """
    from ._usage import get_usage

    return get_usage()


# EOF