Intelligent CSS merging with conflict resolution and multiple strategies
Project description
CSSCade 🎨
Intelligent CSS merging with conflict resolution and multiple strategies
CSSCade is a Python library that intelligently merges CSS properties from different sources, handling conflicts, preserving specificity, and supporting various merge strategies. Perfect for theme customization, runtime CSS manipulation, and CSS-in-JS implementations.
Features ✨
- 3 Merge Modes: Permanent, Component, and Replace strategies
- Multi-Rule Support: Process all CSS rules including pseudo-selectors with
rule_selection='all' - Selective Application: Target specific rules with the
apply_toparameter - Intelligent Conflict Resolution: Handle !important, shorthand properties, and duplicates
- Smart Property Merging: Configurable shorthand strategies (cascade, smart, expand)
- CSS Validation: Optional property and value validation with helpful warnings
- Flexible Naming: Multiple class naming strategies (semantic, hash, sequential)
- Production Ready: Used in real-world applications for dynamic theming
Installation
pip install csscade
Minimal Dependencies: CSSCade has only ONE runtime dependency (cssutils) making it lightweight and fast to install!
Quick Start 🚀
Basic Usage
from csscade import CSSMerger
# Create a merger (defaults to 'component' mode)
merger = CSSMerger()
# Merge CSS
source_css = ".btn { color: red; padding: 10px; }"
overrides = {"color": "blue", "margin": "5px"}
result = merger.merge(source_css, overrides)
# Result structure (all fields always present):
print(result['css']) # List of generated CSS strings
print(result['add']) # Classes to add: ['csscade-btn-xxxx']
print(result['remove']) # Classes to remove: []
print(result['preserve']) # Classes to keep: ['btn']
print(result['warnings']) # Any warnings: []
print(result['info']) # Informational messages: []
Output:
{
'css': ['.csscade-btn-3f4a { color: blue; padding: 10px; margin: 5px; }'],
'add': ['csscade-btn-3f4a'],
'remove': [],
'preserve': ['btn'],
'warnings': [],
'info': ['Created override class .csscade-btn-3f4a with merged properties']
}
Multi-Rule Support
Process multiple CSS rules including pseudo-selectors:
# Process only first CSS rule/class (default)
merger = CSSMerger(rule_selection='first')
result = merger.merge(
".btn { color: red; } .btn:hover { color: darkred; }",
{"background": "blue"}
)
# Only .btn is processed, warning about ignored rules
# Process ALL rules including pseudo-selectors
merger = CSSMerger(rule_selection='all')
result = merger.merge(
".btn { color: red; } .btn:hover { color: darkred; }",
{"background": "blue"}
)
# Both .btn and .btn:hover get background: blue
Merge Modes
1. Permanent Mode
Directly modifies the original CSS rule. Best for build-time CSS generation.
merger = CSSMerger(mode='permanent') # Required parameter
result = merger.merge(
".card { color: red; padding: 10px; }",
{"color": "blue", "margin": "20px"}
)
# Output:
print(result['css'][0])
# .card {
# color: blue; /* Changed */
# padding: 10px; /* Preserved */
# margin: 20px; /* Added */
# }
# Usage: Apply the modified CSS directly
# No class changes needed
2. Component Mode (Default)
Creates an override class while preserving the original. Perfect for theming systems.
merger = CSSMerger(mode='component') # Optional (this is default)
result = merger.merge(
".btn { color: red; padding: 10px; }",
{"color": "blue", "margin": "5px"}
)
# Output:
print(result['css'][0])
# .csscade-btn-3f4a {
# color: blue;
# padding: 10px;
# margin: 5px;
# }
print(result['add']) # ['csscade-btn-3f4a']
print(result['preserve']) # ['btn']
# Usage: element.className = "btn csscade-btn-3f4a"
3. Replace Mode
Creates a complete replacement class. Best for total style replacement.
merger = CSSMerger(mode='replace') # Required parameter
result = merger.merge(
".old-style { color: red; padding: 10px; }",
{"color": "blue", "margin": "5px"}
)
# Output:
print(result['css'][0])
# .csscade-8d2f {
# color: blue;
# padding: 10px;
# margin: 5px;
# }
print(result['add']) # ['csscade-8d2f']
print(result['remove']) # ['old-style']
# Usage: element.className = "csscade-8d2f"
Apply To Parameter (Selective Override)
The apply_to parameter lets you target specific rules when using rule_selection='all'.
Basic Example
merger = CSSMerger(rule_selection='all')
source = """
.btn { background: blue; color: white; }
.btn:hover { background: darkblue; }
"""
# Apply to all rules (default)
result = merger.merge(source, {"border": "2px solid red"}, apply_to='all')
# Both .btn and .btn:hover get the border
# Apply to base rule only
result = merger.merge(source, {"border": "2px solid red"}, apply_to='base')
# Only .btn gets the border, .btn:hover remains unchanged
# Apply to specific pseudo-selector
result = merger.merge(source, {"border": "2px solid red"}, apply_to=[':hover'])
# Only .btn:hover gets the border
Advanced Example with Multiple Targets
source = """
.btn { background: blue; }
.btn:hover { background: darkblue; }
.btn:active { background: navy; }
.btn:focus { outline: none; }
"""
# Target multiple specific states
result = merger.merge(
source,
{"box-shadow": "0 2px 4px rgba(0,0,0,0.2)"},
apply_to=[':hover', ':focus']
)
# Only :hover and :focus states get the box-shadow
Available Apply To Options
'all'- Apply to all rules (default)'base'- Apply to base rule only (no pseudo-selectors)[':hover']- Apply to specific pseudo-selector[':hover', ':active']- Apply to multiple pseudo-selectors['.btn']- Apply to specific class['.btn:hover']- Apply to specific class with pseudo-selector
Conflict Resolution
!important Handling
CSSCade provides 5 strategies for handling !important declarations:
# 'match' strategy (default) - Add !important if original had it
merger = CSSMerger(conflict_resolution={'important': 'match'})
result = merger.merge(
".text { color: blue !important; }",
{"color": "red"}
)
# Output: .text { color: red !important; }
# 'respect' strategy - Never override !important
merger = CSSMerger(conflict_resolution={'important': 'respect'})
result = merger.merge(
".text { color: blue !important; }",
{"color": "red"}
)
# Output: .text { color: blue !important; } # Original preserved
# Other strategies:
# 'override': Override but don't add !important
# 'force': Always add !important to overrides
# 'strip': Remove all !important declarations
Shorthand Properties
CSSCade offers three strategies for handling shorthand properties:
1. Cascade Strategy (Default)
Simple CSS cascade - later properties override
merger = CSSMerger(shorthand_strategy='cascade')
result = merger.merge(
".box { margin: 10px; padding: 20px; }",
{"margin-top": "30px", "padding": "15px"}
)
# Output: .box {
# margin: 10px;
# padding: 15px; /* Fully replaced */
# margin-top: 30px; /* Cascades over margin */
# }
2. Smart Strategy
Intelligent merging for margin/padding, cascade for complex properties
merger = CSSMerger(shorthand_strategy='smart')
result = merger.merge(
".box { margin: 10px; padding: 20px; }",
{"margin-top": "30px", "padding": "15px"}
)
# Output: .box {
# margin: 30px 10px 10px; /* Smart merge: top changed, others preserved */
# padding: 15px; /* Fully replaced */
# }
3. Expand Strategy
Full expansion of all shorthands
merger = CSSMerger(shorthand_strategy='expand')
result = merger.merge(
".box { border: 1px solid red; }",
{"border-width": "3px"}
)
# Output: .box {
# border-top-width: 3px;
# border-right-width: 3px;
# border-bottom-width: 3px;
# border-left-width: 3px;
# border-top-style: solid;
# border-right-style: solid;
# /* ... all properties expanded ... */
# }
Naming Configuration
Control how override classes are generated:
# Semantic naming (default) - Readable class names
merger = CSSMerger(naming={
'strategy': 'semantic', # my-btn-3f4a
'prefix': 'my-',
'suffix': '-override'
})
# Output: my-btn-3f4a-override
# Hash naming - Content-based unique identifiers
merger = CSSMerger(naming={
'strategy': 'hash', # css-7a9f2c
'hash_length': 6
})
# Output: css-7a9f2c (same content = same hash)
# Sequential naming - Simple counters
merger = CSSMerger(naming={
'strategy': 'sequential' # style-1, style-2, style-3
})
# Output: style-1
Default naming configuration:
{
'strategy': 'semantic', # Readable names
'prefix': 'csscade-', # Default prefix
'suffix': '', # No suffix by default
'hash_length': 8 # For hash strategy
}
Validation Configuration
Catch CSS errors and typos with optional validation:
# Development - Helpful warnings
merger = CSSMerger(validation={
'enabled': True,
'check_values': True # Validate color values, units, etc.
})
result = merger.merge(
".card { padding: 10px; }",
{
"fake-property": "value", # Unknown property
"color": "not-a-color", # Invalid value
"margin": "10px",
"margin-top": "20px" # Duplicate warning
}
)
# Warnings: [
# "Unknown CSS property: 'fake-property'",
# "Invalid color value: 'not-a-color'",
# "Potential duplicate: 'margin-top'"
# ]
# Production - Strict validation (throws errors)
merger = CSSMerger(validation={
'enabled': True,
'strict': True # Raises exception on invalid CSS
})
# Minimal - Just enable validation
merger = CSSMerger(validation={'enabled': True})
Default validation configuration:
{
'enabled': False, # Off by default (backwards compatible)
'strict': False, # Warnings, not errors
'check_properties': True, # Check property names when enabled
'check_values': False, # Don't check values by default (expensive)
'allow_vendor': True, # Allow -webkit-, -moz-, etc.
'allow_custom': True, # Allow --css-variables
'check_duplicates': True # Warn about duplicate properties
}
Complete Configuration Examples 🎯
Production Configuration
merger = CSSMerger(
mode='component',
rule_selection='all',
naming={
'strategy': 'semantic',
'prefix': 'app-',
'suffix': ''
},
conflict_resolution={
'important': 'match'
},
shorthand_strategy='smart',
validation={
'enabled': True,
'strict': False,
'check_properties': True
}
)
Development Configuration
merger = CSSMerger(
mode='component',
rule_selection='all',
naming={'strategy': 'sequential', 'prefix': 'dev-'},
validation={
'enabled': True,
'check_values': True,
'strict': False
},
shorthand_strategy='expand', # See all properties
debug=True
)
Advanced Usage 🔧
Real-World Example: Bootstrap Customization
Customize Bootstrap components while preserving all states:
merger = CSSMerger(
mode='component',
rule_selection='all',
shorthand_strategy='smart'
)
bootstrap_button = """
.btn-primary {
background: #007bff;
color: white;
padding: 0.375rem 0.75rem;
border: 1px solid #007bff;
}
.btn-primary:hover {
background: #0056b3;
border-color: #004085;
}
.btn-primary:active {
background: #004085;
}
"""
brand_overrides = {
"background": "#28a745", # Green instead of blue
"border-color": "#28a745",
"font-weight": "bold"
}
result = merger.merge(bootstrap_button, brand_overrides, apply_to='all')
# Output:
# .app-btn-primary-x1a3 {
# background: #28a745;
# color: white;
# padding: 0.375rem 0.75rem;
# border-color: #28a745;
# font-weight: bold;
# }
# .app-btn-primary-x1a3:hover {
# background: #28a745;
# border-color: #28a745;
# font-weight: bold;
# }
# .app-btn-primary-x1a3:active {
# background: #28a745;
# border-color: #28a745;
# font-weight: bold;
# }
# Usage: <button class="btn-primary app-btn-primary-x1a3">
Runtime CSS Manipulation (Inline Styling)
Generate inline styles for dynamic theming:
merger = CSSMerger(mode='permanent')
# User's theme preferences
user_theme = {
"primary-color": "#FF5722",
"font-size": "18px"
}
# Generate inline styles
result = merger.merge(
"body { color: #333; font-size: 16px; }",
user_theme
)
# Apply dynamically
element.style.cssText = result['css'][0]
Batch Operations
Process multiple CSS operations efficiently:
merger = CSSMerger(mode='component')
batch = merger.batch()
# Queue multiple operations
batch.add(".header { color: black; }", {"background": "white"})
batch.add(".footer { padding: 20px; }", {"border-top": "1px solid gray"})
batch.add(".sidebar { width: 200px; }", {"background": "#f5f5f5"})
# Execute all at once
results = batch.execute()
for i, result in enumerate(results):
print(f"Operation {i+1}: {result['add']}")
Result Dictionary Structure
All merge operations return a consistent structure:
{
'css': [], # List of generated CSS strings (always list)
'add': [], # Classes to add to element (always list)
'remove': [], # Classes to remove from element (always list)
'preserve': [], # Original classes to keep (always list)
'warnings': [], # Validation/conflict warnings (always list)
'info': [] # Informational messages (always list)
}
API Reference
CSSMerger Constructor
CSSMerger(
mode='component', # 'permanent'|'component'|'replace'
rule_selection='first', # 'first'|'all'
naming={ # Class naming configuration
'strategy': 'semantic', # 'semantic'|'hash'|'sequential'
'prefix': 'csscade-',
'suffix': '',
'hash_length': 8
},
conflict_resolution={ # Conflict handling
'important': 'match' # 'match'|'respect'|'override'|'force'|'strip'
},
shorthand_strategy='cascade', # 'cascade'|'smart'|'expand'
validation={ # CSS validation
'enabled': False,
'strict': False,
'check_properties': True,
'check_values': False,
'allow_vendor': True,
'allow_custom': True,
'check_duplicates': True
},
debug=False # Enable debug output
)
merge() Method
result = merger.merge(
source, # CSS string, rule, or properties dict
override, # Properties dict or CSS string
component_id=None, # Optional unique identifier
apply_to='all' # Which rules to apply overrides to
)
Default Values Reference
| Parameter | Default Value | Description |
|---|---|---|
mode |
'component' |
Merge strategy |
rule_selection |
'first' |
Process first CSS rule/class only |
shorthand_strategy |
'cascade' |
Simple CSS cascade |
naming.strategy |
'semantic' |
Readable class names |
naming.prefix |
'csscade-' |
Class name prefix |
validation.enabled |
False |
Validation off by default |
conflict_resolution.important |
'match' |
Match original !important |
Testing
Run the comprehensive test suite:
# Basic test
python _test_comprehensive.py
# Run all tests
python -m pytest tests/
Contributing
Contributions are welcome! Please check out our Contributing Guide for details.
License
MIT License - see LICENSE file for details.
Links
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file csscade-0.2.0.tar.gz.
File metadata
- Download URL: csscade-0.2.0.tar.gz
- Upload date:
- Size: 98.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c6f5075d3356ec3c2389a2a98469ea5aeb3600c39f236375fb5005aaa97d0238
|
|
| MD5 |
1c6e731eff039589127506e205287535
|
|
| BLAKE2b-256 |
cf4af9ad89161c0bd62cd032f141eea88c7e92e72c28346b746b0e97cfb9b730
|
File details
Details for the file csscade-0.2.0-py3-none-any.whl.
File metadata
- Download URL: csscade-0.2.0-py3-none-any.whl
- Upload date:
- Size: 108.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
83cc33a83d1aa4f2ff46413b7534a4b3b1d91e3d2a605d0d9081cde373949910
|
|
| MD5 |
2ed2c6a3e6a4dca055398b76ca00376d
|
|
| BLAKE2b-256 |
5c2814195e8b0334963279499e4727e35ea82fb6735ecd79039e9c1986a675fb
|