diff --git a/.gitignore b/.gitignore index 59241f8..2f5c82b 100644 --- a/.gitignore +++ b/.gitignore @@ -143,6 +143,11 @@ venv.bak/ .dmypy.json dmypy.json +# claude +.claude +*/.claude +*/*/.claude + # Pyre type checker .pyre/ diff --git a/README.md b/README.md index 12072ca..1bb99ce 100644 --- a/README.md +++ b/README.md @@ -24,8 +24,12 @@ pre-commit install ## Examples -### Colchicine (by name) +### Render Colchicine (by name) ```bash $ chemscii colchicine --columns 100 ``` ![colchicine.png](examples/images/colchicine.png) + + +### Use chemscii as a Claude code tool +![claude_code_example.png](examples/images/claude_code_example.png) diff --git a/examples/claude_code/CLAUDE.md b/examples/claude_code/CLAUDE.md index 1922ca1..7e7358a 100644 --- a/examples/claude_code/CLAUDE.md +++ b/examples/claude_code/CLAUDE.md @@ -1,27 +1,28 @@ # chemscii Claude Code Integration -When users ask about chemical structures, molecules, or compounds, render them using the `chemscii` CLI. +When users ask about chemical structures, molecules, drugs, or compounds, render them using the `chemscii` CLI. +If your response to a question includes SMILES, molecules, or ChEMBL IDs render them using the `chemscii` CLI. ## Default Rendering -Use the magic renderer (default) with 50 columns: +Use the magic renderer (default) with 100 columns: ```bash -chemscii "" +chemscii "" --columns 80 ``` Examples: -- `chemscii "caffeine"` - render by common name -- `chemscii "aspirin"` - render by drug name -- `chemscii "CCO"` - render by SMILES notation -- `chemscii "CHEMBL25"` - render by ChEMBL ID +- `chemscii "caffeine" --columns 80` - render by common name +- `chemscii "aspirin" --columns 80` - render by drug name +- `chemscii "CCO" --columns 80` - render by SMILES notation +- `chemscii "CHEMBL25" --columns 80` - render by ChEMBL ID ## Larger Renderings If users ask for a larger, bigger, or more detailed rendering, increase columns to 100: ```bash -chemscii "" --columns 100 +chemscii "" --columns 120 ``` ## Alternative View diff --git a/examples/images/claude_code_example.png b/examples/images/claude_code_example.png new file mode 100644 index 0000000..81e004d Binary files /dev/null and b/examples/images/claude_code_example.png differ diff --git a/src/chemscii/cli.py b/src/chemscii/cli.py index af87fff..2f2b0bd 100644 --- a/src/chemscii/cli.py +++ b/src/chemscii/cli.py @@ -100,13 +100,6 @@ def parse_input(input_type: InputType, value: str) -> str | None: return None -def version_callback(value: bool) -> None: - """Print version and exit.""" - if value: - console.print("chemscii 0.1.0") - raise typer.Exit() - - @app.command() def main( molecule: str = typer.Argument( @@ -137,6 +130,12 @@ def main( "-c", help="Output width for magic renderer.", ), + escape_codes: bool = typer.Option( + False, + "--codes", + "-e", + help="Include escape codes for color rendering after copying text.", + ), width: int = typer.Option( 60, "--width", @@ -149,14 +148,6 @@ def main( "-H", help="Canvas height for ascii/unicode renderers.", ), - version: bool = typer.Option( - False, - "--version", - "-v", - callback=version_callback, - is_eager=True, - help="Show version and exit.", - ), ) -> None: """Render a chemical structure as ASCII/Unicode art. @@ -221,7 +212,7 @@ def main( unicode_renderer.render_molecule(mol) else: # Default to magic - magic_renderer = AsciiMagicRenderer(columns=columns) + magic_renderer = AsciiMagicRenderer(columns=columns, codes=escape_codes) magic_renderer.render_molecule(mol) diff --git a/src/chemscii/renderers/ascii.py b/src/chemscii/renderers/ascii.py index fbd7045..f0d4bfd 100644 --- a/src/chemscii/renderers/ascii.py +++ b/src/chemscii/renderers/ascii.py @@ -4,6 +4,26 @@ from chemscii.renderers.base import BaseRenderer +# ANSI color codes +_COLORS: dict[str, str] = { + "red": "\033[91m", + "blue": "\033[94m", + "yellow": "\033[93m", + "cyan": "\033[96m", + "reset": "\033[0m", +} + +# Element to color mapping +_ELEMENT_COLORS: dict[str, str] = { + "O": "red", + "N": "blue", + "S": "yellow", + "F": "cyan", + "Cl": "cyan", + "Br": "cyan", + "I": "cyan", +} + class AsciiRenderer(BaseRenderer): """Renders chemical structures using basic ASCII characters.""" @@ -14,3 +34,46 @@ class AsciiRenderer(BaseRenderer): _DIAG_DOWN = "\\" _DOUBLE = "=" _TRIPLE = "#" + + def __init__( + self, + width: int = -1, + height: int = -1, + padding: int = 2, + color: bool = True, + ) -> None: + """Initialize the ASCII renderer. + + Args: + width: Canvas width in characters (-1 for auto). + height: Canvas height in characters (-1 for auto). + padding: Padding around the molecule in characters. + color: Whether to colorize element symbols. + """ + super().__init__(width=width, height=height, padding=padding) + self.color = color + + def _draw_atom(self, canvas: list[list[str]], x: int, y: int, symbol: str) -> None: + """Draw an atom symbol on the canvas with optional color. + + Args: + canvas: The character canvas. + x: Canvas x coordinate. + y: Canvas y coordinate. + symbol: Element symbol to draw. + """ + if self.color and symbol in _ELEMENT_COLORS: + color_name = _ELEMENT_COLORS[symbol] + color_code = _COLORS[color_name] + reset_code = _COLORS["reset"] + # Apply color to the entire symbol + for i, char in enumerate(symbol[:2]): + px = x + i + if 0 <= y < self.height and 0 <= px < self.width: + canvas[y][px] = f"{color_code}{char}{reset_code}" + else: + # Default behavior without color + for i, char in enumerate(symbol[:2]): + px = x + i + if 0 <= y < self.height and 0 <= px < self.width: + canvas[y][px] = char diff --git a/src/chemscii/renderers/magic.py b/src/chemscii/renderers/magic.py index 5df4942..86e90c1 100644 --- a/src/chemscii/renderers/magic.py +++ b/src/chemscii/renderers/magic.py @@ -17,13 +17,15 @@ class AsciiMagicRenderer: the image to ASCII art using the ascii_magic library. """ - def __init__(self, columns: int = 120) -> None: + def __init__(self, columns: int = 120, codes: bool = True) -> None: """Initialize the renderer. Args: columns: Width of the ASCII art output in characters. + codes: Include escape codes. """ self.columns = columns + self.codes = codes def render_molecule(self, mol: Mol) -> str: """Render a molecule as ASCII art. @@ -36,7 +38,11 @@ def render_molecule(self, mol: Mol) -> str: """ img = self._mol_to_image(mol) art = AsciiArt.from_pillow_image(img) - txt: str = art.to_terminal(self.columns) + if self.codes: + txt: str = art.to_ascii(self.columns) + print(repr(txt)) + else: + txt = art.to_terminal(self.columns) return txt def _mol_to_image(