pattern_scanner

Scan Python code for replaceable CSS class patterns

Data Structures

Define data structures for pattern scanning:


ClsPattern


def ClsPattern(
    line_number:int, full_expression:str, css_classes:List, context:str, uses_combine_classes:bool
)->None:

Represents a cls= pattern found in code.

AST Pattern Finder

Use Python’s AST to find cls= patterns in code:


ClsPatternVisitor


def ClsPatternVisitor(
    source_lines:List, # Source code lines for extracting context around patterns
):

AST visitor to find cls= patterns in Python code.

Pattern Scanning Functions

Main functions to scan code for patterns:


scan_python_code


def scan_python_code(
    code:str, # Python source code as a string
)->List: # List of ClsPattern objects found in the code

Scan Python code for cls= patterns.

Enhanced CSS Class Extraction

More sophisticated extraction handling different patterns:


extract_css_classes_from_node


def extract_css_classes_from_node(
    node:AST, # AST node to extract CSS classes from
)->List: # List of CSS class strings found in the node

Recursively extract CSS classes from an AST node. Handles various patterns including combine_classes calls.

Testing Pattern Extraction

Let’s test with the example code from your description:

# Test with your example code
test_code = '''
from fasthtml.common import Div, Header, Nav, Main, Article, Aside, Footer, Img, Button, H1, H2, P

# Flexbox centered navigation
nav = Nav(
    Div("Logo", cls="font-bold"),
    Div(
        "Home", "About", "Contact",
        cls=combine_classes("flex", gap(4))
    ),
    Button("Sign In"),
    cls=combine_classes(
        "flex", 
        justify.between, 
        items.center, 
        "px-6", 
        "py-4", "items-center"
    )
)
'''

# Scan the code
patterns = scan_python_code(test_code)

# Display results
print(f"Found {len(patterns)} cls= patterns:\n")
for i, pattern in enumerate(patterns, 1):
    print(f"Pattern {i}:")
    print(f"  Line: {pattern.line_number}")
    print(f"  Expression: {pattern.full_expression}")
    print(f"  Uses combine_classes: {pattern.uses_combine_classes}")
    print(f"  CSS Classes: {pattern.css_classes}")
    print(f"  Context: {pattern.context}")
    print()
Found 3 cls= patterns:

Pattern 1:
  Line: 12
  Expression: combine_classes('flex', justify.between, items.center, 'px-6', 'py-4', 'items-center')
  Uses combine_classes: True
  CSS Classes: ['flex', 'px-6', 'py-4', 'items-center']
  Context: cls=combine_classes(

Pattern 2:
  Line: 6
  Expression: 'font-bold'
  Uses combine_classes: False
  CSS Classes: ['font-bold']
  Context: Div("Logo", cls="font-bold"),

Pattern 3:
  Line: 9
  Expression: combine_classes('flex', gap(4))
  Uses combine_classes: True
  CSS Classes: ['flex']
  Context: cls=combine_classes("flex", gap(4))
# Test with various patterns
test_cases = '''
# Simple string
div1 = Div(cls="flex items-center justify-between")

# Multiple classes in one string
div2 = Div(cls="bg-blue-500 text-white px-4 py-2 rounded-lg")

# String concatenation
div3 = Div(cls="flex " + "items-center")

# Empty cls
div4 = Div(cls="")

# combine_classes with mixed content
div5 = Div(cls=combine_classes(
    "absolute",
    "top-0",
    p(4),  # This is already using the library
    "bg-white"
))
'''

patterns = scan_python_code(test_cases)
print(f"Found {len(patterns)} patterns in test cases:\n")

for i, pattern in enumerate(patterns, 1):
    print(f"Pattern {i}: Line {pattern.line_number}")
    print(f"  CSS Classes: {pattern.css_classes}")
    print(f"  Expression: {pattern.full_expression}")
    print()
Found 5 patterns in test cases:

Pattern 1: Line 3
  CSS Classes: ['flex', 'items-center', 'justify-between']
  Expression: 'flex items-center justify-between'

Pattern 2: Line 6
  CSS Classes: ['bg-blue-500', 'text-white', 'px-4', 'py-2', 'rounded-lg']
  Expression: 'bg-blue-500 text-white px-4 py-2 rounded-lg'

Pattern 3: Line 9
  CSS Classes: ['flex', 'items-center']
  Expression: 'flex ' + 'items-center'

Pattern 4: Line 12
  CSS Classes: []
  Expression: ''

Pattern 5: Line 15
  CSS Classes: ['absolute', 'top-0', 'bg-white']
  Expression: combine_classes('absolute', 'top-0', p(4), 'bg-white')

Display Utilities

Functions to display scan results:


display_patterns


def display_patterns(
    patterns:List, # List of ClsPattern objects to display
    show_context:bool=True, # Whether to show the code context
):

Display found patterns in a formatted way.


get_unique_css_classes


def get_unique_css_classes(
    patterns:List, # List of ClsPattern objects
)->Set: # Set of unique CSS class strings

Extract all unique CSS classes from a list of patterns.

# Test the display function with the original example
patterns = scan_python_code(test_code)
display_patterns(patterns)
Found 3 cls= patterns:

Pattern 1 (Line 12):
  Context: cls=combine_classes(
  Expression: combine_classes('flex', justify.between, items.center, 'px-6', 'py-4', 'items-center')
  ✓ Uses combine_classes
  CSS Classes (4):
    - flex
    - px-6
    - py-4
    - items-center

Pattern 2 (Line 6):
  Context: Div("Logo", cls="font-bold"),
  Expression: 'font-bold'
  CSS Classes (1):
    - font-bold

Pattern 3 (Line 9):
  Context: cls=combine_classes("flex", gap(4))
  Expression: combine_classes('flex', gap(4))
  ✓ Uses combine_classes
  CSS Classes (1):
    - flex
# Test extracting unique classes
all_patterns = scan_python_code(test_code) + scan_python_code(test_cases)
unique_classes = get_unique_css_classes(all_patterns)

print(f"Total unique CSS classes found: {len(unique_classes)}")
print("\nUnique classes:")
unique_classes
Total unique CSS classes found: 14

Unique classes:
{'absolute',
 'bg-blue-500',
 'bg-white',
 'flex',
 'font-bold',
 'items-center',
 'justify-between',
 'px-4',
 'px-6',
 'py-2',
 'py-4',
 'rounded-lg',
 'text-white',
 'top-0'}

Assertion Pattern Extraction

Extract patterns from test assertion statements:


AssertionPattern


def AssertionPattern(
    css_class:str, factory_expression:str, module_name:str, example_name:str
)->None:

Represents a pattern extracted from a test assertion.


get_available_css_classes


def get_available_css_classes(
    assertion_patterns:List, # List of assertion patterns from test examples
)->Set: # Set of unique CSS class strings available in the library

Extract all unique CSS classes from assertion patterns. This handles multi-class assertion strings by splitting them.


extract_assertion_patterns


def extract_assertion_patterns(
    source_code:str, # Source code of the test function
    module_name:str, # Name of the module containing the test
    example_name:str, # Name of the test function
)->List: # List of AssertionPattern objects

Extract assertion patterns from test example source code.


collect_all_assertion_patterns


def collect_all_assertion_patterns(
    
)->List: # List of AssertionPattern objects from all modules

Collect assertion patterns from all test examples in the library.

assertion_patterns = collect_all_assertion_patterns()
assertion_classes = get_available_css_classes(assertion_patterns)
list(assertion_classes)[:20]
['snap-mandatory',
 'break-normal',
 '-hue-rotate-60',
 'shadow-zinc-500',
 'select-all',
 'transition-none',
 '-mask-linear-180',
 'inset-ring-green-500',
 'opacity-95',
 'col-span-full',
 'text-6xl',
 'decoration-yellow-500',
 'inset-ring-green-950',
 'drop-shadow-black',
 'row-start-2',
 'fill-white',
 'align-top',
 'rounded-s-lg',
 '-mask-linear-45',
 'p-0']

Pattern Matching

Functions to match extracted CSS classes against available library classes:


CSSClassMatch


def CSSClassMatch(
    css_class:str, match_type:MatchType, matched_pattern:Optional=None, similar_classes:List=None,
    suggested_replacement:Optional=None
)->None:

Represents a match result for a CSS class.


MatchType


def MatchType(
    args:VAR_POSITIONAL, kwds:VAR_KEYWORD
):

Type of match found for a CSS class.


tokenize_css_class


def tokenize_css_class(
    css_class:str, # CSS class string (e.g., "bg-blue-500" or "hover:text-white")
)->List: # List of tokens (e.g., ["bg", "blue", "500"] or ["hover:text", "white"])

Tokenize a CSS class by splitting on hyphens. Handles modifiers (hover:, focus:, etc.) separately.


find_pattern_matches


def find_pattern_matches(
    css_class:str, # CSS class to match (e.g., "px-8" or "hover:text-white")
    available_classes:Set, # Set of available CSS classes from the library
)->Tuple: # Tuple of (matched_pattern, similar_classes) - matched_pattern: Pattern prefix that matches (e.g., "px" for "px-8") - similar_classes: List of similar classes with the same pattern

Find pattern matches for a CSS class by progressively reducing tokens.


match_css_class


def match_css_class(
    css_class:str, # CSS class to match
    available_classes:Set, # Set of available CSS classes from the library
)->CSSClassMatch: # CSSClassMatch object with match details

Match a CSS class against available library classes.


match_css_classes


def match_css_classes(
    css_classes:List, # List of CSS classes to match
    available_classes:Set, # Set of available CSS classes from the library
)->Dict: # Dictionary mapping CSS classes to their match results

Match multiple CSS classes against available library classes.


display_match_results


def display_match_results(
    matches:Dict, # Dictionary of CSS classes to their match results
):

Display match results in a formatted way.


analyze_code_patterns


def analyze_code_patterns(
    code:str, # Python source code to analyze
)->Dict: # Dictionary with analysis results including patterns found and suggestions

Analyze Python code for replaceable CSS patterns.


display_code_analysis


def display_code_analysis(
    code:str, # Python source code to analyze
):

Analyze and display replaceable patterns in Python code.

Migration Suggestions

Functions to provide migration suggestions based on test examples:


find_assertion_for_class


def find_assertion_for_class(
    css_class:str, # The CSS class to find (e.g., "px-6")
    assertion_patterns:List, # List of all assertion patterns from tests
)->Optional: # AssertionPattern if found, None otherwise

Find the assertion pattern that demonstrates how to use a specific CSS class. Prioritizes exact single-class matches over multi-class assertions.


find_pattern_examples


def find_pattern_examples(
    pattern_prefix:str, # Pattern prefix to match (e.g., "px" for px-* pattern)
    assertion_patterns:List, # List of all assertion patterns from tests
)->List: # List of AssertionPattern objects that match the pattern

Find assertion examples that match a pattern prefix.


get_migration_suggestions


def get_migration_suggestions(
    matches:Dict, # Dictionary of CSS class matches
    assertion_patterns:List, # List of all assertion patterns from tests
    config:Optional=None, # Optional configuration
)->Dict: # Dictionary mapping CSS classes to their migration suggestions

Generate migration suggestions for matched CSS classes.


display_migration_suggestions


def display_migration_suggestions(
    code:str, # Python source code to analyze
):

Analyze code and display migration suggestions.


analyze_and_suggest


def analyze_and_suggest(
    code:str, # Python source code to analyze
):

Perform complete analysis of code with migration suggestions.

Testing Pattern Matching

Test the matching logic with various CSS classes:

# Test updated tokenization
print("Testing updated tokenization:")
test_classes = [
    "flex", 
    "px-6", 
    "bg-blue-500", 
    "-mt-4", 
    "hover:text-white",
    "lg:grid-cols-4",
    "focus:ring-2",
    "hover:bg-blue-600"
]
for cls in test_classes:
    tokens = tokenize_css_class(cls)
    print(f"  {cls:<20}{tokens}")
Testing updated tokenization:
  flex                 → ['flex']
  px-6                 → ['px', '6']
  bg-blue-500          → ['bg', 'blue', '500']
  -mt-4                → ['-mt', '4']
  hover:text-white     → ['hover:', 'text', 'white']
  lg:grid-cols-4       → ['lg:', 'grid', 'cols', '4']
  focus:ring-2         → ['focus:', 'ring', '2']
  hover:bg-blue-600    → ['hover:', 'bg', 'blue', '600']
# Get available classes from assertions
assertion_patterns = collect_all_assertion_patterns()
available_classes = get_available_css_classes(assertion_patterns)

# Test with the example from the original test code
test_css_classes = ["flex", "font-bold", "items-center", "px-6", "py-4"]

print("\nTesting CSS class matching:")
print("=" * 60)

for css_class in test_css_classes:
    match_result = match_css_class(css_class, available_classes)
    
    print(f"\nClass: '{css_class}'")
    print(f"  Match Type: {match_result.match_type.value}")
    
    if match_result.match_type == MatchType.EXACT:
        print(f"  ✓ Exact match found")
    elif match_result.match_type == MatchType.PATTERN:
        print(f"  ~ Pattern match: '{match_result.matched_pattern}-*'")
        print(f"  Similar classes: {match_result.similar_classes[:3]}...")
    else:
        print(f"  ✗ No match found")

Testing CSS class matching:
============================================================

Class: 'flex'
  Match Type: exact
  ✓ Exact match found

Class: 'font-bold'
  Match Type: exact
  ✓ Exact match found

Class: 'items-center'
  Match Type: exact
  ✓ Exact match found

Class: 'px-6'
  Match Type: exact
  ✓ Exact match found

Class: 'py-4'
  Match Type: exact
  ✓ Exact match found
# Test with more diverse CSS classes
print("\nTesting with additional CSS classes:")
print("=" * 60)

additional_test_classes = [
    # Should be exact matches
    "block", "absolute", "flex",
    # Should be pattern matches
    "px-8", "py-12", "mt-16", "gap-10", 
    # Should be no match
    "font-bold", "text-blue-600", "hover:bg-gray-100",
    # Edge cases
    "-mx-4", "w-1/3", "lg:grid-cols-4"
]

for css_class in additional_test_classes:
    match_result = match_css_class(css_class, available_classes)
    
    status = "✓" if match_result.match_type == MatchType.EXACT else \
             "~" if match_result.match_type == MatchType.PATTERN else "✗"
    
    print(f"{status} {css_class:<20}{match_result.match_type.value:<10}", end="")
    
    if match_result.matched_pattern:
        print(f" (pattern: {match_result.matched_pattern})")
    else:
        print()

Testing with additional CSS classes:
============================================================
✓ block                → exact     
✓ absolute             → exact     
✓ flex                 → exact     
✓ px-8                 → exact     
~ py-12                → pattern    (pattern: py)
~ mt-16                → pattern    (pattern: mt)
~ gap-10               → pattern    (pattern: gap)
✓ font-bold            → exact     
~ text-blue-600        → pattern    (pattern: text-blue)
✗ hover:bg-gray-100    → no_match  
~ -mx-4                → pattern    (pattern: -mx)
~ w-1/3                → pattern    (pattern: w)
✓ lg:grid-cols-4       → exact     
# Test batch matching with the original example
print("\nBatch matching for original example:")
print("=" * 60)

# Extract unique CSS classes from the test code
patterns = scan_python_code(test_code)
unique_classes_in_code = get_unique_css_classes(patterns)

# Match all classes
matches = match_css_classes(list(unique_classes_in_code), available_classes)

# Display results
display_match_results(matches)

Batch matching for original example:
============================================================
CSS Class Analysis Results:
============================================================

✓ Exact Matches (5):
  - flex
  - font-bold
  - items-center
  - px-6
  - py-4

Summary: 5/5 classes are potentially replaceable
# Test complete code analysis
print("Complete Code Analysis:")
print()
display_code_analysis(test_code)
Complete Code Analysis:

Code Analysis Report
============================================================
Total cls= patterns found: 3
Unique CSS classes: 5

Replaceable Classes: 5/5
  - Exact matches: 5
  - Pattern matches: 0
  - No matches: 0

CSS Class Analysis Results:
============================================================

✓ Exact Matches (5):
  - flex
  - font-bold
  - items-center
  - px-6
  - py-4

Summary: 5/5 classes are potentially replaceable

Patterns by Line:
------------------------------------------------------------
Line 12: ✓ combine_classes('flex', justify.between, items.center, 'px-6', 'py-4', 'items-center')
         ↳ Already uses combine_classes
Line 6: ✓ 'font-bold'
Line 9: ✓ combine_classes('flex', gap(4))
         ↳ Already uses combine_classes
# Test with a more complex example
complex_example = '''
from fasthtml.common import Div, Button, Section

# Hero section with various utilities
hero = Section(
    Div(
        "Welcome to our site",
        cls="text-6xl font-bold text-gray-900 mb-4"
    ),
    Div(
        "Build amazing things with FastHTML",
        cls="text-xl text-gray-600 mb-8"
    ),
    Button(
        "Get Started",
        cls="px-8 py-3 bg-blue-600 text-white rounded-lg hover:bg-blue-700"
    ),
    cls="flex flex-col items-center justify-center min-h-screen px-4"
)
'''

print("\n\nComplex Example Analysis:")
print()
display_code_analysis(complex_example)


Complex Example Analysis:

Code Analysis Report
============================================================
Total cls= patterns found: 4
Unique CSS classes: 19

Replaceable Classes: 18/19
  - Exact matches: 12
  - Pattern matches: 6
  - No matches: 1

CSS Class Analysis Results:
============================================================

✓ Exact Matches (12):
  - flex
  - flex-col
  - font-bold
  - items-center
  - justify-center
  - mb-4
  - min-h-screen
  - px-8
  - rounded-lg
  - text-6xl
  - text-white
  - text-xl

~ Pattern Matches (6):
  - bg-blue-600 → matches pattern 'bg-blue-*'
    Examples: bg-blue-300, bg-blue-300/75, bg-blue-500
  - mb-8 → matches pattern 'mb-*'
    Examples: mb-4
  - px-4 → matches pattern 'px-*'
    Examples: px-6, px-8
  - py-3 → matches pattern 'py-*'
    Examples: py-4, py-8
  - text-gray-600 → matches pattern 'text-gray-*'
    Examples: text-gray-500
  - text-gray-900 → matches pattern 'text-gray-*'
    Examples: text-gray-500

✗ No Matches (1):
  - hover:bg-blue-700

Summary: 18/19 classes are potentially replaceable

Patterns by Line:
------------------------------------------------------------
Line 18: ✓ 'flex flex-col items-center justify-center min-h-screen px-4'
Line 8: ✓ 'text-6xl font-bold text-gray-900 mb-4'
Line 12: ✓ 'text-xl text-gray-600 mb-8'
Line 16: ✓ 'px-8 py-3 bg-blue-600 text-white rounded-lg hover:bg-blue-700'
# Test migration suggestions with original example
print("Migration Suggestions for Original Example:")
print()
display_migration_suggestions(test_code)
Migration Suggestions for Original Example:

Migration Suggestions
============================================================

✓ flex:
  → View example: cjm-tailwind-explore example flexbox_and_grid display

✓ font-bold:
  → View example: cjm-tailwind-explore example typography font_weight

✓ items-center:
  → View example: cjm-tailwind-explore example flexbox_and_grid align

✓ px-6:
  → View example: cjm-tailwind-explore example spacing helper
  → View helper: cjm-tailwind-explore helper spacing pad

✓ py-4:
  → View example: cjm-tailwind-explore example spacing helper
  → View helper: cjm-tailwind-explore helper spacing pad
# Test with complex example
print("\n\nMigration Suggestions for Complex Example:")
print()
display_migration_suggestions(complex_example)


Migration Suggestions for Complex Example:

Migration Suggestions
============================================================

~ bg-blue-600:
  → Pattern example 1: cjm-tailwind-explore example backgrounds color
  → Pattern example 2: cjm-tailwind-explore example backgrounds opacity

✓ flex:
  → View example: cjm-tailwind-explore example flexbox_and_grid display

✓ flex-col:
  → View example: cjm-tailwind-explore example flexbox_and_grid direction

✓ font-bold:
  → View example: cjm-tailwind-explore example typography font_weight

✓ items-center:
  → View example: cjm-tailwind-explore example flexbox_and_grid align

✓ justify-center:
  → View example: cjm-tailwind-explore example flexbox_and_grid justify

✓ mb-4:
  → View example: cjm-tailwind-explore example spacing margin_directional

~ mb-8:
  → Pattern example 1: cjm-tailwind-explore example spacing margin_directional

✓ min-h-screen:
  → View example: cjm-tailwind-explore example sizing min_height

~ px-4:
  → Pattern example 1: cjm-tailwind-explore example spacing directional
  → Pattern example 2: cjm-tailwind-explore example spacing helper

✓ px-8:
  → View example: cjm-tailwind-explore example spacing directional

~ py-3:
  → Pattern example 1: cjm-tailwind-explore example spacing directional
  → Pattern example 2: cjm-tailwind-explore example spacing helper

✓ rounded-lg:
  → View example: cjm-tailwind-explore example borders radius

✓ text-6xl:
  → View example: cjm-tailwind-explore example typography font_size

~ text-gray-600:
  → Pattern example 1: cjm-tailwind-explore example typography text_color

~ text-gray-900:
  → Pattern example 1: cjm-tailwind-explore example typography text_color

✓ text-white:
  → View example: cjm-tailwind-explore example typography text_color

✓ text-xl:
  → View example: cjm-tailwind-explore example typography font_size
# Test analysis and migration suggestions with original example
print("Analysis & Migration Suggestions for Original Example:")
print()
analyze_and_suggest(test_code)
Analysis & Migration Suggestions for Original Example:

Code Analysis Report
============================================================
Total cls= patterns found: 3
Unique CSS classes: 5

Replaceable Classes: 5/5
  - Exact matches: 5
  - Pattern matches: 0
  - No matches: 0

CSS Class Analysis Results:
============================================================

✓ Exact Matches (5):
  - flex
  - font-bold
  - items-center
  - px-6
  - py-4

Summary: 5/5 classes are potentially replaceable

Patterns by Line:
------------------------------------------------------------
Line 12: ✓ combine_classes('flex', justify.between, items.center, 'px-6', 'py-4', 'items-center')
         ↳ Already uses combine_classes
Line 6: ✓ 'font-bold'
Line 9: ✓ combine_classes('flex', gap(4))
         ↳ Already uses combine_classes


Migration Suggestions
============================================================

✓ flex:
  → View example: cjm-tailwind-explore example flexbox_and_grid display

✓ font-bold:
  → View example: cjm-tailwind-explore example typography font_weight

✓ items-center:
  → View example: cjm-tailwind-explore example flexbox_and_grid align

✓ px-6:
  → View example: cjm-tailwind-explore example spacing helper
  → View helper: cjm-tailwind-explore helper spacing pad

✓ py-4:
  → View example: cjm-tailwind-explore example spacing helper
  → View helper: cjm-tailwind-explore helper spacing pad

File Input Support

Functions to scan Python files and Jupyter notebooks:


scan_python_file


def scan_python_file(
    file_path:str, # Path to the Python file
)->List: # List of ClsPattern objects found in the file

Scan a Python file for cls= patterns.


scan_jupyter_notebook


def scan_jupyter_notebook(
    notebook_path:str, # Path to the Jupyter notebook (.ipynb)
)->List: # List of ClsPattern objects found in the notebook

Scan a Jupyter notebook for cls= patterns.


detect_input_type


def detect_input_type(
    input_source:str, # Code string or file path
)->InputType: # InputType enum value

Detect the type of input based on the source string.


InputType


def InputType(
    args:VAR_POSITIONAL, kwds:VAR_KEYWORD
):

Type of input being scanned.


scan_input


def scan_input(
    input_source:str, # Code string, Python file path, or notebook path
    input_type:Optional=None, # Optional explicit input type. If None, will auto-detect.
)->List: # List of ClsPattern objects found

Scan various input types for cls= patterns.


analyze_input


def analyze_input(
    input_source:str, # Code string, Python file path, or notebook path
    input_type:Optional=None, # Optional explicit input type. If None, will auto-detect.
)->Dict: # Dictionary with analysis results

Analyze any input type for replaceable CSS patterns.


display_input_analysis


def display_input_analysis(
    input_source:str, # Code string, Python file path, or notebook path
    input_type:Optional=None, # Optional explicit input type. If None, will auto-detect.
):

Analyze and display replaceable patterns from any input type.


analyze_and_suggest_input


def analyze_and_suggest_input(
    input_source:str, # Code string, Python file path, or notebook path
    input_type:Optional=None, # Optional explicit input type. If None, will auto-detect.
):

Perform complete analysis with migration suggestions for any input type.

Testing File Input Support

Test the unified interface with different input types:

# Test auto-detection of input types
test_inputs = [
    'print("hello")',  # Code string
    'example.py',      # Python file
    'notebook.ipynb',  # Jupyter notebook
    '/path/to/file.py',  # Full path
    'Some("code", cls="flex")'  # Code with cls
]

print("Testing input type detection:")
for inp in test_inputs:
    detected = detect_input_type(inp)
    print(f"  '{inp}' → {detected.value}")
Testing input type detection:
  'print("hello")' → code
  'example.py' → python_file
  'notebook.ipynb' → notebook
  '/path/to/file.py' → python_file
  'Some("code", cls="flex")' → code
# Test unified scanning interface with code string
print("Testing unified scan_input with code string:")
print("=" * 60)

test_code_unified = '''
from fasthtml.common import Div

card = Div(
    Div("Title", cls="text-2xl font-semibold mb-4"),
    Div("Content", cls="text-gray-700"),
    cls="p-6 bg-white rounded-lg shadow-md"
)
'''

patterns = scan_input(test_code_unified)
print(f"Found {len(patterns)} patterns")
for pattern in patterns:
    print(f"  Line {pattern.line_number}: {pattern.css_classes}")
# Create a temporary Python file for testing
import tempfile

test_py_content = '''
from fasthtml.common import *

def create_navbar():
    return Nav(
        Div("Logo", cls="font-bold text-xl"),
        Ul(
            Li("Home", cls="px-4 py-2"),
            Li("About", cls="px-4 py-2"),
            Li("Contact", cls="px-4 py-2"),
            cls="flex gap-4"
        ),
        cls="flex justify-between items-center p-4 bg-gray-100"
    )
'''

# Write to temporary file
with tempfile.NamedTemporaryFile(mode='w', suffix='.py', delete=False) as f:
    f.write(test_py_content)
    temp_py_file = f.name

print(f"Testing Python file scanning:")
print(f"Temporary file: {temp_py_file}")
print("=" * 60)

# Scan the Python file
patterns = scan_input(temp_py_file)
print(f"Found {len(patterns)} patterns")
for pattern in patterns:
    print(f"  Line {pattern.line_number}: {pattern.css_classes}")

# Clean up
import os
os.unlink(temp_py_file)
Testing Python file scanning:
Temporary file: /tmp/tmpee2b37ob.py
============================================================
Found 6 patterns
  Line 13: ['flex', 'justify-between', 'items-center', 'p-4', 'bg-gray-100']
  Line 6: ['font-bold', 'text-xl']
  Line 11: ['flex', 'gap-4']
  Line 8: ['px-4', 'py-2']
  Line 9: ['px-4', 'py-2']
  Line 10: ['px-4', 'py-2']
# Test complete analysis with code string
print("\nTesting complete analysis with display_input_analysis:")
print()
display_input_analysis(test_code)

Testing complete analysis with display_input_analysis:

Pattern Analysis Report
============================================================
Input Type: code
Source: 
from fasthtml.common import Div, Header, Nav, Mai...
Total cls= patterns found: 3
Unique CSS classes: 5

Replaceable Classes: 5/5
  - Exact matches: 5
  - Pattern matches: 0
  - No matches: 0

CSS Class Analysis Results:
============================================================

✓ Exact Matches (5):
  - flex
  - font-bold
  - items-center
  - px-6
  - py-4

Summary: 5/5 classes are potentially replaceable

Patterns by Location:
------------------------------------------------------------
Line 12: ✓ combine_classes('flex', justify.between, items.center, 'px-6', 'py-4', 'items-center')
         ↳ Already uses combine_classes
Line 6: ✓ 'font-bold'
Line 9: ✓ combine_classes('flex', gap(4))
         ↳ Already uses combine_classes
# Test with suggestions
print("\nTesting analysis with suggestions:")
print()
analyze_and_suggest_input(test_code_unified)

Testing analysis with suggestions:

Pattern Analysis Report
============================================================
Input Type: code
Source: 
from fasthtml.common import Div, Card

card = Car...
Total cls= patterns found: 3
Unique CSS classes: 8

Replaceable Classes: 8/8
  - Exact matches: 6
  - Pattern matches: 2
  - No matches: 0

CSS Class Analysis Results:
============================================================

✓ Exact Matches (6):
  - bg-white
  - font-semibold
  - mb-4
  - rounded-lg
  - shadow-md
  - text-2xl

~ Pattern Matches (2):
  - p-6 → matches pattern 'p-*'
    Examples: p-0, p-2.5, p-4
  - text-gray-700 → matches pattern 'text-gray-*'
    Examples: text-gray-500

Summary: 8/8 classes are potentially replaceable

Patterns by Location:
------------------------------------------------------------
Line 7: ✓ 'p-6 bg-white rounded-lg shadow-md'
Line 5: ✓ 'text-2xl font-semibold mb-4'
Line 6: ✓ 'text-gray-700'


Migration Suggestions
============================================================

✓ bg-white:
  → View example: cjm-tailwind-explore example backgrounds color

✓ font-semibold:
  → View example: cjm-tailwind-explore example typography font_weight

✓ mb-4:
  → View example: cjm-tailwind-explore example spacing margin_directional

~ p-6:
  → Pattern example 1: cjm-tailwind-explore example spacing basic
  → Pattern example 2: cjm-tailwind-explore example spacing helper

✓ rounded-lg:
  → View example: cjm-tailwind-explore example borders radius

✓ shadow-md:
  → View example: cjm-tailwind-explore example effects shadow_size

✓ text-2xl:
  → View example: cjm-tailwind-explore example typography font_size

~ text-gray-700:
  → Pattern example 1: cjm-tailwind-explore example typography text_color

Export