Add utility scripts collection with auto-generated documentation
This commit introduces a comprehensive collection of utility scripts for shell automation, color manipulation, and documentation generation: Core Scripts: - artifact_github_download.sh: Download GitHub Action artifacts via CLI - css_color_filter.sh: Generate CSS filter values using SPSA algorithm - css_color_palette.sh: Generate comprehensive color palettes (monochromatic, triadic, etc.) - css_json_convert.sh: Convert CSS variables to JSON/YAML formats - doc_bash_generate.sh: Auto-generate README.md with animated GIF demos - doc_rust_generate.sh: Generate Rust project documentation - jinja_template_render.sh: Render Jinja2 templates from CLI - mime_mp4_gif.sh: Convert MP4 videos to GIF format Documentation Features: - Comprehensive README.md with table of contents - 8 animated GIF demos showing real command examples - Sandboxed demo execution in temporary directories - 15-second timeout protection for intensive computations - Automatic example extraction from --help output Technical Implementation: - Pure bash color utilities using only bc for arithmetic - tput-based color codes for portability - IFS-safe string parsing using parameter expansion - Stdout/stderr isolation to prevent contamination - Base64 encoding for multi-line text preservation All scripts include detailed --help documentation with usage examples. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
tmp/
|
||||
691
README.md
Normal file
691
README.md
Normal file
@@ -0,0 +1,691 @@
|
||||
# Utility Scripts Collection
|
||||
|
||||
> Comprehensive documentation with usage examples and demos
|
||||
|
||||
This documentation was auto-generated using [`doc_bash_generate.sh`](https://github.com/yourusername/yourrepo).
|
||||
|
||||
## Table of Contents
|
||||
- [`artifact_github_download.sh`](#artifact-github-download-sh)
|
||||
- [`css_color_filter.sh`](#css-color-filter-sh)
|
||||
- [`css_color_palette.sh`](#css-color-palette-sh)
|
||||
- [`css_json_convert.sh`](#css-json-convert-sh)
|
||||
- [`doc_bash_generate.sh`](#doc-bash-generate-sh)
|
||||
- [`doc_rust_generate.sh`](#doc-rust-generate-sh)
|
||||
- [`jinja_template_render.sh`](#jinja-template-render-sh)
|
||||
- [`mime_mp4_gif.sh`](#mime-mp4-gif-sh)
|
||||
|
||||
|
||||
---
|
||||
|
||||
## `artifact_github_download.sh`
|
||||
|
||||
GitHub Artifact Downloader
|
||||
|
||||
### Demo
|
||||
|
||||

|
||||
|
||||
### Usage
|
||||
|
||||
```bash
|
||||
artifact_github_download.sh [OPTIONS] [ARGUMENTS]
|
||||
```
|
||||
|
||||
### Options
|
||||
|
||||
<details>
|
||||
<summary>Click to expand full help output</summary>
|
||||
|
||||
```
|
||||
GitHub Artifact Downloader
|
||||
|
||||
USAGE:
|
||||
artifact_github_download.sh <REPO> [OPTIONS]
|
||||
|
||||
ARGUMENTS:
|
||||
REPO GitHub repository (owner/repo)
|
||||
|
||||
OPTIONS:
|
||||
-n, --name NAME Artifact name to download (preselect)
|
||||
-o, --output DIR Output directory (default: current directory)
|
||||
-h, --help Show this help message
|
||||
|
||||
EXAMPLES:
|
||||
# Interactive mode - list and select artifacts
|
||||
artifact_github_download.sh valknarness/awesome
|
||||
|
||||
# Preselect artifact by name
|
||||
artifact_github_download.sh valknarness/awesome -n awesome-database-latest
|
||||
|
||||
# Download to specific directory
|
||||
artifact_github_download.sh valknarness/awesome -o ~/downloads
|
||||
|
||||
# Combine options
|
||||
artifact_github_download.sh valknarness/awesome -n awesome-database-latest -o ~/downloads
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
### Examples
|
||||
|
||||
```bash
|
||||
# Interactive mode - list and select artifacts
|
||||
artifact_github_download.sh valknarness/awesome
|
||||
|
||||
# Preselect artifact by name
|
||||
artifact_github_download.sh valknarness/awesome -n awesome-database-latest
|
||||
|
||||
# Download to specific directory
|
||||
artifact_github_download.sh valknarness/awesome -o ~/downloads
|
||||
|
||||
# Combine options
|
||||
artifact_github_download.sh valknarness/awesome -n awesome-database-latest -o ~/downloads
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## `css_color_filter.sh`
|
||||
|
||||
CSS Color Filter Generator
|
||||
|
||||
### Demo
|
||||
|
||||

|
||||
|
||||
### Usage
|
||||
|
||||
```bash
|
||||
css_color_filter.sh [OPTIONS] [ARGUMENTS]
|
||||
```
|
||||
|
||||
### Options
|
||||
|
||||
<details>
|
||||
<summary>Click to expand full help output</summary>
|
||||
|
||||
```
|
||||
CSS Color Filter Generator
|
||||
|
||||
Generate CSS filter values to transform black elements into any target color.
|
||||
|
||||
USAGE:
|
||||
css_color_filter.sh [OPTIONS] [COLOR]
|
||||
|
||||
ARGUMENTS:
|
||||
COLOR Hex color (e.g., #FF0000, ff0000) or RGB (e.g., 255,0,0)
|
||||
|
||||
OPTIONS:
|
||||
-i, --interactive Interactive mode with colored preview
|
||||
-r, --raw Output only the CSS filter (for piping)
|
||||
-c, --copy Copy result to clipboard automatically
|
||||
-h, --help Show this help message
|
||||
|
||||
EXAMPLES:
|
||||
css_color_filter.sh "#FF5733"
|
||||
css_color_filter.sh ff5733
|
||||
css_color_filter.sh "255,87,51"
|
||||
css_color_filter.sh -i
|
||||
|
||||
NOTE:
|
||||
This tool generates filters that work on black elements.
|
||||
To use with non-black elements, prepend: brightness(0) saturate(100%)
|
||||
|
||||
ALGORITHM:
|
||||
Uses SPSA (Simultaneous Perturbation Stochastic Approximation) to find
|
||||
optimal filter combinations that minimize color difference in RGB and HSL.
|
||||
|
||||
DEPENDENCIES:
|
||||
bc For floating-point arithmetic
|
||||
jq For JSON formatting (optional)
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
### Examples
|
||||
|
||||
```bash
|
||||
css_color_filter.sh "#FF5733"
|
||||
css_color_filter.sh ff5733
|
||||
css_color_filter.sh "255,87,51"
|
||||
css_color_filter.sh -i
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## `css_color_palette.sh`
|
||||
|
||||
CSS Color Palette Generator (Pure Bash)
|
||||
|
||||
### Demo
|
||||
|
||||

|
||||
|
||||
### Usage
|
||||
|
||||
```bash
|
||||
css_color_palette.sh [OPTIONS] [ARGUMENTS]
|
||||
```
|
||||
|
||||
### Options
|
||||
|
||||
<details>
|
||||
<summary>Click to expand full help output</summary>
|
||||
|
||||
```
|
||||
CSS Color Palette Generator (Pure Bash)
|
||||
|
||||
Generate comprehensive color palettes without Node.js dependencies.
|
||||
|
||||
USAGE:
|
||||
css_color_palette.sh COLOR [OPTIONS]
|
||||
|
||||
ARGUMENTS:
|
||||
COLOR Base hex color (e.g., #3498db, 3498db)
|
||||
|
||||
OPTIONS:
|
||||
-p, --palette TYPE Palette type: monochromatic, analogous, complementary,
|
||||
split-complementary, triadic, tetradic
|
||||
-o, --output FILE Output file (default: ./colors.yaml)
|
||||
-m, --mode MODE Color mode: light, dark (default: light)
|
||||
-s, --style STYLE Style: shades, tints, tones, all (default: all)
|
||||
-n, --name NAME Palette name (default: auto-generated)
|
||||
--scales N Number of scale steps (default: 11)
|
||||
-i, --interactive Interactive mode
|
||||
-v, --verbose Verbose output with color preview
|
||||
-h, --help Show this help message
|
||||
|
||||
DEPENDENCIES:
|
||||
bc For floating-point arithmetic
|
||||
|
||||
EXAMPLES:
|
||||
css_color_palette.sh "#3498db"
|
||||
css_color_palette.sh "#3498db" -p triadic -o palette.json
|
||||
css_color_palette.sh "ff5733" -p analogous -m dark
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
### Examples
|
||||
|
||||
```bash
|
||||
css_color_palette.sh "#3498db"
|
||||
css_color_palette.sh "#3498db" -p triadic -o palette.json
|
||||
css_color_palette.sh "ff5733" -p analogous -m dark
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## `css_json_convert.sh`
|
||||
|
||||
USAGE:
|
||||
|
||||
### Demo
|
||||
|
||||

|
||||
|
||||
### Usage
|
||||
|
||||
```bash
|
||||
css_json_convert.sh [OPTIONS] [ARGUMENTS]
|
||||
```
|
||||
|
||||
### Options
|
||||
|
||||
<details>
|
||||
<summary>Click to expand full help output</summary>
|
||||
|
||||
```
|
||||
|
||||
================================================================
|
||||
CSS Variable to JSON/YAML Converter
|
||||
Extract CSS custom properties with ease
|
||||
================================================================
|
||||
|
||||
USAGE:
|
||||
css_json_convert.sh [OPTIONS] <input.css>
|
||||
|
||||
DESCRIPTION:
|
||||
Extracts CSS custom properties (variables) from a CSS file and converts
|
||||
them to JSON or YAML format. Automatically detects output format from
|
||||
file extension.
|
||||
|
||||
ARGUMENTS:
|
||||
<input.css> Input CSS file containing CSS variables
|
||||
|
||||
OPTIONS:
|
||||
-o, --output FILE Output file path (default: ./output.yaml)
|
||||
Format auto-detected from extension (.json/.yaml/.yml)
|
||||
-c, --camel-case Convert variable names to camelCase
|
||||
(e.g., --main-color -> mainColor)
|
||||
-v, --verbose Enable verbose output
|
||||
-h, --help Show this help message
|
||||
|
||||
EXAMPLES:
|
||||
# Extract CSS vars to YAML (default)
|
||||
css_json_convert.sh styles.css
|
||||
|
||||
# Extract to JSON with custom output
|
||||
css_json_convert.sh styles.css -o theme.json
|
||||
|
||||
# Convert variable names to camelCase
|
||||
css_json_convert.sh styles.css -o vars.json --camel-case
|
||||
|
||||
CSS VARIABLE FORMAT:
|
||||
The script extracts CSS custom properties in the format:
|
||||
--variable-name: value;
|
||||
|
||||
Example input:
|
||||
:root {
|
||||
--main-color: #e8eaed;
|
||||
--font-size: 16px;
|
||||
}
|
||||
|
||||
Example JSON output:
|
||||
{
|
||||
"main-color": "#e8eaed",
|
||||
"font-size": "16px"
|
||||
}
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
### Examples
|
||||
|
||||
```bash
|
||||
# Extract CSS vars to YAML (default)
|
||||
css_json_convert.sh styles.css
|
||||
|
||||
# Extract to JSON with custom output
|
||||
css_json_convert.sh styles.css -o theme.json
|
||||
|
||||
# Convert variable names to camelCase
|
||||
css_json_convert.sh styles.css -o vars.json --camel-case
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## `doc_bash_generate.sh`
|
||||
|
||||
Bash Documentation Generator with Animated GIFs
|
||||
|
||||
### Demo
|
||||
|
||||

|
||||
|
||||
### Usage
|
||||
|
||||
```bash
|
||||
doc_bash_generate.sh [OPTIONS] [ARGUMENTS]
|
||||
```
|
||||
|
||||
### Options
|
||||
|
||||
<details>
|
||||
<summary>Click to expand full help output</summary>
|
||||
|
||||
```
|
||||
|
||||
================================================================
|
||||
Bash Documentation Generator
|
||||
================================================================
|
||||
|
||||
Bash Documentation Generator with Animated GIFs
|
||||
|
||||
Generate sexy, comprehensive README.md files with embedded asciinema GIFs.
|
||||
|
||||
USAGE:
|
||||
doc_bash_generate.sh [OPTIONS] <executable> [executable...]
|
||||
|
||||
ARGUMENTS:
|
||||
executable One or more executables or glob patterns
|
||||
|
||||
OPTIONS:
|
||||
-o, --output FILE Output README.md path (default: ./README.md)
|
||||
-t, --title TITLE Documentation title (default: auto-generated)
|
||||
--no-gif Skip GIF generation (faster, text only)
|
||||
--gif-only Only generate GIFs, don't update README
|
||||
-h, --help Show this help message
|
||||
|
||||
EXAMPLES:
|
||||
doc_bash_generate.sh css_*.sh
|
||||
doc_bash_generate.sh -o docs/README.md *.sh
|
||||
doc_bash_generate.sh --title "My Awesome Tools" script1.sh script2.sh
|
||||
|
||||
DEPENDENCIES:
|
||||
asciinema Terminal session recorder
|
||||
agg Asciinema to GIF converter (cargo install agg)
|
||||
|
||||
NOTES:
|
||||
Demos are automatically generated by running --help on each command.
|
||||
GIF recordings are created in a temporary directory and cleaned up after.
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
### Examples
|
||||
|
||||
```bash
|
||||
doc_bash_generate.sh css_*.sh
|
||||
doc_bash_generate.sh -o docs/README.md *.sh
|
||||
doc_bash_generate.sh --title "My Awesome Tools" script1.sh script2.sh
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## `doc_rust_generate.sh`
|
||||
|
||||
Rust Documentation Generator with Custom Themes
|
||||
|
||||
### Demo
|
||||
|
||||

|
||||
|
||||
### Usage
|
||||
|
||||
```bash
|
||||
doc_rust_generate.sh [OPTIONS] [ARGUMENTS]
|
||||
```
|
||||
|
||||
### Options
|
||||
|
||||
<details>
|
||||
<summary>Click to expand full help output</summary>
|
||||
|
||||
```
|
||||
Rust Documentation Generator with Custom Themes
|
||||
|
||||
USAGE:
|
||||
doc_rust_generate.sh [OPTIONS] <inputs...>
|
||||
|
||||
DESCRIPTION:
|
||||
Generate beautiful Rust documentation with custom color schemes and styling.
|
||||
Supports various input types including Rust projects, individual files, and more.
|
||||
|
||||
ARGUMENTS:
|
||||
<inputs> Input file(s) or pattern(s) to document:
|
||||
- Rust project directories (containing Cargo.toml)
|
||||
- Individual .rs files
|
||||
- Markdown files (.md)
|
||||
- JSON/TOML configuration files
|
||||
- Glob patterns (e.g., src/**/*.rs)
|
||||
|
||||
OPTIONS:
|
||||
-o, --output DIR Output directory for generated docs
|
||||
(default: $PWD/output)
|
||||
-c, --color COLOR Primary accent color (hex format)
|
||||
(default: #ff69b4)
|
||||
Examples: #3498db, #10b981, #8b5cf6
|
||||
-s, --style STYLE Background style theme
|
||||
Options: slate, zinc, neutral, stone, gray
|
||||
(default: slate)
|
||||
--font-sans FONT Google Font for body text (default: Inter)
|
||||
--font-mono FONT Google Font for code blocks
|
||||
(default: JetBrains Mono)
|
||||
--serve Start HTTP server after generation
|
||||
--open Open documentation in browser (implies --serve)
|
||||
-p, --port PORT Port for HTTP server (default: 8000)
|
||||
-v, --verbose Enable verbose output
|
||||
-d, --dry-run Show what would be done without executing
|
||||
-h, --help Show this help message
|
||||
|
||||
EXAMPLES:
|
||||
# Generate docs for current Rust project
|
||||
doc_rust_generate.sh .
|
||||
|
||||
# Custom color scheme
|
||||
doc_rust_generate.sh . -c "#3498db" -s zinc -o ./docs
|
||||
|
||||
# Document specific files
|
||||
doc_rust_generate.sh src/lib.rs src/main.rs -o ./api-docs
|
||||
|
||||
# Use custom fonts
|
||||
doc_rust_generate.sh . --font-sans "Roboto" --font-mono "Fira Code"
|
||||
|
||||
# Generate and open in browser
|
||||
doc_rust_generate.sh . --open
|
||||
|
||||
NOTES:
|
||||
- Requires: cargo, rustdoc, bc, yq, jq, python3 with jinja2
|
||||
- Colors are automatically generated in light and dark variants
|
||||
- Google Fonts are automatically imported
|
||||
- Mermaid.js diagrams are automatically rendered
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
### Examples
|
||||
|
||||
```bash
|
||||
# Generate docs for current Rust project
|
||||
doc_rust_generate.sh .
|
||||
|
||||
# Custom color scheme
|
||||
doc_rust_generate.sh . -c "#3498db" -s zinc -o ./docs
|
||||
|
||||
# Document specific files
|
||||
doc_rust_generate.sh src/lib.rs src/main.rs -o ./api-docs
|
||||
|
||||
# Use custom fonts
|
||||
doc_rust_generate.sh . --font-sans "Roboto" --font-mono "Fira Code"
|
||||
|
||||
# Generate and open in browser
|
||||
doc_rust_generate.sh . --open
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## `jinja_template_render.sh`
|
||||
|
||||
JINJA2 TEMPLATE RENDERER - NINJA EDITION
|
||||
|
||||
### Demo
|
||||
|
||||

|
||||
|
||||
### Usage
|
||||
|
||||
```bash
|
||||
jinja_template_render.sh [OPTIONS] [ARGUMENTS]
|
||||
```
|
||||
|
||||
### Options
|
||||
|
||||
<details>
|
||||
<summary>Click to expand full help output</summary>
|
||||
|
||||
```
|
||||
JINJA2 TEMPLATE RENDERER - NINJA EDITION
|
||||
|
||||
USAGE:
|
||||
jinja_template_render.sh [OPTIONS] <template> [template...]
|
||||
|
||||
DESCRIPTION:
|
||||
A sophisticated Jinja2 template rendering engine with support for
|
||||
multiple variable sources, glob patterns, and ninja-style operations.
|
||||
|
||||
ARGUMENTS:
|
||||
<template> Template file(s) to render (glob patterns supported)
|
||||
Examples: template.j2, templates/*.j2, **/*.jinja2
|
||||
|
||||
OPTIONS:
|
||||
-o, --output DIR Output directory (default: ./output)
|
||||
-v, --var KEY=VALUE Define template variable (can be used multiple times)
|
||||
-f, --file FILE Load variables from JSON/YAML file (repeatable)
|
||||
-V, --verbose Enable verbose ninja commentary
|
||||
-p, --preview Preview rendered output without saving
|
||||
-s, --strict Enable strict mode (fail on undefined variables)
|
||||
-d, --dry-run Perform dry run without writing files
|
||||
-w, --watch Watch templates and re-render on changes (experimental)
|
||||
-h, --help Show this legendary scroll of knowledge
|
||||
|
||||
VARIABLE SOURCES:
|
||||
Variables are merged in this order (later sources override earlier):
|
||||
1. YAML files (loaded via yq)
|
||||
2. JSON files (loaded via jq)
|
||||
3. CLI variables (-v KEY=VALUE)
|
||||
|
||||
EXAMPLES:
|
||||
# Render single template with CLI variables
|
||||
jinja_template_render.sh template.j2 -v name=Ninja -v level=Master
|
||||
|
||||
# Render multiple templates with YAML config
|
||||
jinja_template_render.sh templates/*.j2 -f config.yaml -o dist/
|
||||
|
||||
# Render with multiple variable sources
|
||||
jinja_template_render.sh app.j2 -f base.yaml -f env.json -v debug=true
|
||||
|
||||
# Preview without saving
|
||||
jinja_template_render.sh template.j2 -f vars.yaml --preview --verbose
|
||||
|
||||
# Strict mode with dry run
|
||||
jinja_template_render.sh *.j2 -f vars.json --strict --dry-run
|
||||
|
||||
TEMPLATE SYNTAX:
|
||||
Jinja2 template example:
|
||||
|
||||
Hello {{ name }}!
|
||||
{% for item in items %}
|
||||
- {{ item }}
|
||||
{% endfor %}
|
||||
{% if debug %}Debug mode enabled{% endif %}
|
||||
|
||||
|
||||
REQUIREMENTS:
|
||||
- Python 3 with jinja2 package
|
||||
- jq (for JSON parsing)
|
||||
- yq (for YAML parsing)
|
||||
|
||||
[NINJA] May your templates be swift and your renders be flawless! >>
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
### Examples
|
||||
|
||||
```bash
|
||||
# Render single template with CLI variables
|
||||
jinja_template_render.sh template.j2 -v name=Ninja -v level=Master
|
||||
|
||||
# Render multiple templates with YAML config
|
||||
jinja_template_render.sh templates/*.j2 -f config.yaml -o dist/
|
||||
|
||||
# Render with multiple variable sources
|
||||
jinja_template_render.sh app.j2 -f base.yaml -f env.json -v debug=true
|
||||
|
||||
# Preview without saving
|
||||
jinja_template_render.sh template.j2 -f vars.yaml --preview --verbose
|
||||
|
||||
# Strict mode with dry run
|
||||
jinja_template_render.sh *.j2 -f vars.json --strict --dry-run
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## `mime_mp4_gif.sh`
|
||||
|
||||
Usage: mime_mp4_gif.sh [OPTIONS] INPUT_FILE [OUTPUT_FILE]
|
||||
|
||||
### Demo
|
||||
|
||||

|
||||
|
||||
### Usage
|
||||
|
||||
```bash
|
||||
mime_mp4_gif.sh [OPTIONS] [ARGUMENTS]
|
||||
```
|
||||
|
||||
### Options
|
||||
|
||||
<details>
|
||||
<summary>Click to expand full help output</summary>
|
||||
|
||||
```
|
||||
Usage: mime_mp4_gif.sh [OPTIONS] INPUT_FILE [OUTPUT_FILE]
|
||||
|
||||
Convert MP4 videos to animated GIFs with advanced frame extraction algorithms.
|
||||
|
||||
Arguments:
|
||||
INPUT_FILE Input MP4 video file (required)
|
||||
OUTPUT_FILE Output GIF file (optional, defaults to INPUT_FILE.gif)
|
||||
|
||||
Options:
|
||||
-k, --keyframes N Number of keyframes to extract (default: 10)
|
||||
-d, --keyframe-duration MS Duration of each frame in milliseconds (default: 100)
|
||||
Valid range: 1-30000 ms
|
||||
Lower values = faster animation
|
||||
Higher values = slower animation
|
||||
-i, --input-schedules N Number of input schedules (default: 1)
|
||||
1 schedule = entire video duration
|
||||
N schedules = divide video into N segments
|
||||
-t, --transition TYPE Interpolation function for frame timing
|
||||
Available: linear sinoid cubic quadratic exponential bounce elastic
|
||||
(default: linear)
|
||||
-s, --schedule TYPE Algorithm to distribute keyframes across schedules
|
||||
Available: uniform front-load back-load center-peak edge-peak fibonacci golden-ratio
|
||||
(default: uniform)
|
||||
-m, --magic TYPE Apply magical effects to the GIF
|
||||
Available: none psychedelic dither-bloom edge-glow temporal-blur chromatic-shift vaporwave
|
||||
(default: none)
|
||||
-v, --verbose Enable verbose output
|
||||
-h, --help Show this help message
|
||||
|
||||
Examples:
|
||||
# Basic conversion with 15 keyframes
|
||||
mime_mp4_gif.sh -k 15 video.mp4
|
||||
|
||||
# Fast animation with 50ms per frame
|
||||
mime_mp4_gif.sh -k 20 -d 50 video.mp4
|
||||
|
||||
# Slow animation with 500ms per frame
|
||||
mime_mp4_gif.sh -k 10 -d 500 video.mp4
|
||||
|
||||
# Use sinusoidal transition with center-peak distribution
|
||||
mime_mp4_gif.sh -t sinoid -s center-peak -k 20 video.mp4
|
||||
|
||||
# Apply psychedelic magic with fibonacci distribution
|
||||
mime_mp4_gif.sh -m psychedelic -s fibonacci -k 13 video.mp4 trippy.gif
|
||||
|
||||
# Complex: 3 schedules with cubic interpolation and edge glow
|
||||
mime_mp4_gif.sh -i 3 -t cubic -s front-load -m edge-glow -k 30 video.mp4
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
---
|
||||
|
||||
## Installation
|
||||
|
||||
All scripts are available in this repository. Make sure they are executable:
|
||||
|
||||
```bash
|
||||
chmod +x *.sh
|
||||
```
|
||||
|
||||
Add them to your PATH for easy access:
|
||||
|
||||
```bash
|
||||
export PATH="$PATH:$(pwd)"
|
||||
```
|
||||
|
||||
## Dependencies
|
||||
|
||||
Common dependencies for these scripts:
|
||||
- `bash` (4.0+)
|
||||
- `bc` - For floating-point arithmetic
|
||||
|
||||
Specific dependencies are listed in each command's help output.
|
||||
|
||||
## Contributing
|
||||
|
||||
Contributions are welcome! Please ensure:
|
||||
- Scripts follow POSIX conventions
|
||||
- Include comprehensive `--help` output with usage examples
|
||||
|
||||
## License
|
||||
|
||||
MIT License - See LICENSE file for details.
|
||||
|
||||
---
|
||||
|
||||
*Documentation generated on 2025-10-30 02:59:22 CET by [`doc_bash_generate.sh`](https://github.com/yourusername/yourrepo)*
|
||||
477
artifact_github_download.sh
Executable file
477
artifact_github_download.sh
Executable file
@@ -0,0 +1,477 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
#############################################################################
|
||||
# GitHub Artifact Downloader
|
||||
#
|
||||
# Download and extract GitHub Actions artifacts with style
|
||||
#
|
||||
# Usage:
|
||||
# artifact_github_download.sh <REPO> [OPTIONS]
|
||||
#
|
||||
# Arguments:
|
||||
# REPO GitHub repository (owner/repo)
|
||||
#
|
||||
# Options:
|
||||
# -n, --name NAME Artifact name to download (preselect)
|
||||
# -o, --output DIR Output directory (default: current directory)
|
||||
# -h, --help Show this help message
|
||||
#
|
||||
# Examples:
|
||||
# artifact_github_download.sh valknarness/awesome
|
||||
# artifact_github_download.sh valknarness/awesome -n awesome-database-latest
|
||||
# artifact_github_download.sh valknarness/awesome -o ~/downloads
|
||||
#############################################################################
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# ============================================================================
|
||||
# Color Definitions
|
||||
# ============================================================================
|
||||
|
||||
# Check if terminal supports colors
|
||||
if [[ -t 1 ]] && command -v tput >/dev/null 2>&1; then
|
||||
COLORS=$(tput colors 2>/dev/null || echo 0)
|
||||
if [[ $COLORS -ge 8 ]]; then
|
||||
# Standard colors
|
||||
RED=$(tput setaf 1)
|
||||
GREEN=$(tput setaf 2)
|
||||
YELLOW=$(tput setaf 3)
|
||||
BLUE=$(tput setaf 4)
|
||||
MAGENTA=$(tput setaf 5)
|
||||
CYAN=$(tput setaf 6)
|
||||
WHITE=$(tput setaf 7)
|
||||
|
||||
# Bright colors
|
||||
BRIGHT_GREEN=$(tput setaf 10 2>/dev/null || tput setaf 2)
|
||||
BRIGHT_YELLOW=$(tput setaf 11 2>/dev/null || tput setaf 3)
|
||||
BRIGHT_BLUE=$(tput setaf 12 2>/dev/null || tput setaf 4)
|
||||
BRIGHT_MAGENTA=$(tput setaf 13 2>/dev/null || tput setaf 5)
|
||||
BRIGHT_CYAN=$(tput setaf 14 2>/dev/null || tput setaf 6)
|
||||
|
||||
# Text formatting
|
||||
BOLD=$(tput bold)
|
||||
DIM=$(tput dim 2>/dev/null || echo "")
|
||||
RESET=$(tput sgr0)
|
||||
else
|
||||
RED="" GREEN="" YELLOW="" BLUE="" MAGENTA="" CYAN="" WHITE=""
|
||||
BRIGHT_GREEN="" BRIGHT_YELLOW="" BRIGHT_BLUE="" BRIGHT_MAGENTA="" BRIGHT_CYAN=""
|
||||
BOLD="" DIM="" RESET=""
|
||||
fi
|
||||
else
|
||||
RED="" GREEN="" YELLOW="" BLUE="" MAGENTA="" CYAN="" WHITE=""
|
||||
BRIGHT_GREEN="" BRIGHT_YELLOW="" BRIGHT_BLUE="" BRIGHT_MAGENTA="" BRIGHT_CYAN=""
|
||||
BOLD="" DIM="" RESET=""
|
||||
fi
|
||||
|
||||
# ============================================================================
|
||||
# Logging Functions
|
||||
# ============================================================================
|
||||
|
||||
log_info() {
|
||||
echo -e "${BRIGHT_BLUE}${BOLD}ℹ${RESET} ${CYAN}$*${RESET}" >&2
|
||||
}
|
||||
|
||||
log_success() {
|
||||
echo -e "${BRIGHT_GREEN}${BOLD}✓${RESET} ${GREEN}$*${RESET}" >&2
|
||||
}
|
||||
|
||||
log_warning() {
|
||||
echo -e "${BRIGHT_YELLOW}${BOLD}⚠${RESET} ${YELLOW}$*${RESET}" >&2
|
||||
}
|
||||
|
||||
log_error() {
|
||||
echo -e "${RED}${BOLD}✗${RESET} ${RED}$*${RESET}" >&2
|
||||
}
|
||||
|
||||
log_step() {
|
||||
echo -e "${BRIGHT_MAGENTA}${BOLD}▸${RESET} ${MAGENTA}$*${RESET}" >&2
|
||||
}
|
||||
|
||||
log_header() {
|
||||
local text="$*"
|
||||
local length=${#text}
|
||||
local line=$(printf '═%.0s' $(seq 1 $length))
|
||||
echo "" >&2
|
||||
echo -e "${BRIGHT_CYAN}${BOLD}╔${line}╗${RESET}" >&2
|
||||
echo -e "${BRIGHT_CYAN}${BOLD}║${RESET}${BOLD}${WHITE}${text}${RESET}${BRIGHT_CYAN}${BOLD}║${RESET}" >&2
|
||||
echo -e "${BRIGHT_CYAN}${BOLD}╚${line}╝${RESET}" >&2
|
||||
echo "" >&2
|
||||
}
|
||||
|
||||
log_data() {
|
||||
local label="$1"
|
||||
local value="$2"
|
||||
echo -e " ${DIM}${label}:${RESET} ${BOLD}${value}${RESET}" >&2
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Helper Functions
|
||||
# ============================================================================
|
||||
|
||||
check_dependencies() {
|
||||
local missing=()
|
||||
|
||||
if ! command -v gh &> /dev/null; then
|
||||
missing+=("gh (GitHub CLI)")
|
||||
fi
|
||||
|
||||
if ! command -v jq &> /dev/null; then
|
||||
missing+=("jq")
|
||||
fi
|
||||
|
||||
if ! command -v unzip &> /dev/null; then
|
||||
missing+=("unzip")
|
||||
fi
|
||||
|
||||
if [[ ${#missing[@]} -gt 0 ]]; then
|
||||
log_error "Missing required dependencies:"
|
||||
for dep in "${missing[@]}"; do
|
||||
echo -e " ${RED}•${RESET} ${dep}"
|
||||
done
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
check_gh_auth() {
|
||||
if ! gh auth status &> /dev/null; then
|
||||
log_error "Not authenticated with GitHub CLI"
|
||||
log_info "Run: ${BOLD}gh auth login${RESET}"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
show_help() {
|
||||
cat << EOF
|
||||
${BOLD}${BRIGHT_CYAN}GitHub Artifact Downloader${RESET}
|
||||
|
||||
${BOLD}USAGE:${RESET}
|
||||
$(basename "$0") ${CYAN}<REPO>${RESET} [${YELLOW}OPTIONS${RESET}]
|
||||
|
||||
${BOLD}ARGUMENTS:${RESET}
|
||||
${CYAN}REPO${RESET} GitHub repository (${DIM}owner/repo${RESET})
|
||||
|
||||
${BOLD}OPTIONS:${RESET}
|
||||
${YELLOW}-n, --name NAME${RESET} Artifact name to download (preselect)
|
||||
${YELLOW}-o, --output DIR${RESET} Output directory (default: current directory)
|
||||
${YELLOW}-h, --help${RESET} Show this help message
|
||||
|
||||
${BOLD}EXAMPLES:${RESET}
|
||||
${DIM}# Interactive mode - list and select artifacts${RESET}
|
||||
$(basename "$0") valknarness/awesome
|
||||
|
||||
${DIM}# Preselect artifact by name${RESET}
|
||||
$(basename "$0") valknarness/awesome -n awesome-database-latest
|
||||
|
||||
${DIM}# Download to specific directory${RESET}
|
||||
$(basename "$0") valknarness/awesome -o ~/downloads
|
||||
|
||||
${DIM}# Combine options${RESET}
|
||||
$(basename "$0") valknarness/awesome -n awesome-database-latest -o ~/downloads
|
||||
|
||||
EOF
|
||||
}
|
||||
|
||||
format_size() {
|
||||
local bytes=$1
|
||||
if (( bytes < 1024 )); then
|
||||
echo "${bytes}B"
|
||||
elif (( bytes < 1048576 )); then
|
||||
awk "BEGIN {printf \"%.1fKB\", $bytes/1024}"
|
||||
elif (( bytes < 1073741824 )); then
|
||||
awk "BEGIN {printf \"%.1fMB\", $bytes/1048576}"
|
||||
else
|
||||
awk "BEGIN {printf \"%.2fGB\", $bytes/1073741824}"
|
||||
fi
|
||||
}
|
||||
|
||||
format_date() {
|
||||
local iso_date="$1"
|
||||
if command -v date &> /dev/null; then
|
||||
if date --version &> /dev/null 2>&1; then
|
||||
# GNU date
|
||||
date -d "$iso_date" "+%Y-%m-%d %H:%M:%S" 2>/dev/null || echo "$iso_date"
|
||||
else
|
||||
# BSD date (macOS)
|
||||
date -j -f "%Y-%m-%dT%H:%M:%SZ" "$iso_date" "+%Y-%m-%d %H:%M:%S" 2>/dev/null || echo "$iso_date"
|
||||
fi
|
||||
else
|
||||
echo "$iso_date"
|
||||
fi
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Main Functions
|
||||
# ============================================================================
|
||||
|
||||
list_artifacts() {
|
||||
local repo="$1"
|
||||
|
||||
log_step "Fetching artifacts from ${BOLD}${repo}${RESET}..."
|
||||
|
||||
# First check if there are any artifacts using gh's built-in jq
|
||||
local count
|
||||
count=$(gh api \
|
||||
-H "Accept: application/vnd.github+json" \
|
||||
-H "X-GitHub-Api-Version: 2022-11-28" \
|
||||
--jq '.artifacts | length' \
|
||||
"/repos/${repo}/actions/artifacts?per_page=100" 2>/dev/null)
|
||||
|
||||
if [[ -z "$count" ]]; then
|
||||
log_error "Failed to fetch artifacts from repository"
|
||||
log_info "Please check that:"
|
||||
echo " • The repository ${BOLD}${repo}${RESET} exists and you have access"
|
||||
echo " • GitHub Actions is enabled for this repository"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ "$count" -eq 0 ]]; then
|
||||
log_warning "No artifacts found in repository ${BOLD}${repo}${RESET}"
|
||||
log_info "This repository may not have any workflow runs that produced artifacts"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Now fetch the full JSON response
|
||||
local artifacts_json
|
||||
artifacts_json=$(gh api \
|
||||
-H "Accept: application/vnd.github+json" \
|
||||
-H "X-GitHub-Api-Version: 2022-11-28" \
|
||||
"/repos/${repo}/actions/artifacts?per_page=100" 2>/dev/null)
|
||||
|
||||
echo "$artifacts_json"
|
||||
}
|
||||
|
||||
select_artifact() {
|
||||
local artifacts_json="$1"
|
||||
local preselect_name="$2"
|
||||
|
||||
# Parse artifacts
|
||||
local artifacts
|
||||
artifacts=$(echo "$artifacts_json" | jq -r '.artifacts[] |
|
||||
"\(.id)|\(.name)|\(.size_in_bytes)|\(.created_at)|\(.workflow_run.id)"')
|
||||
|
||||
# If preselect name is provided, find matching artifact
|
||||
if [[ -n "$preselect_name" ]]; then
|
||||
local selected
|
||||
selected=$(echo "$artifacts" | grep -F "|${preselect_name}|" | head -1)
|
||||
|
||||
if [[ -z "$selected" ]]; then
|
||||
log_error "Artifact '${BOLD}${preselect_name}${RESET}' not found"
|
||||
log_info "Available artifacts:"
|
||||
echo "$artifacts" | while IFS='|' read -r id name size created workflow; do
|
||||
echo " ${CYAN}•${RESET} ${name}"
|
||||
done
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "$selected"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Interactive selection
|
||||
log_info "Available artifacts:"
|
||||
echo ""
|
||||
|
||||
local i=1
|
||||
local -a artifact_array
|
||||
|
||||
while IFS='|' read -r id name size created workflow; do
|
||||
artifact_array+=("$id|$name|$size|$created|$workflow")
|
||||
local formatted_size=$(format_size "$size")
|
||||
local formatted_date=$(format_date "$created")
|
||||
|
||||
printf " ${BOLD}${YELLOW}[%2d]${RESET} ${BRIGHT_CYAN}%s${RESET}\n" "$i" "$name"
|
||||
printf " ${DIM}Size: ${RESET}%s ${DIM}Created: ${RESET}%s\n" "$formatted_size" "$formatted_date"
|
||||
echo ""
|
||||
|
||||
((i++))
|
||||
done <<< "$artifacts"
|
||||
|
||||
# Prompt for selection
|
||||
local selection
|
||||
while true; do
|
||||
echo -n -e "${BRIGHT_MAGENTA}${BOLD}<EFBFBD>${RESET} ${MAGENTA}Select artifact [1-$((i-1))]:${RESET} "
|
||||
read -r selection
|
||||
|
||||
if [[ "$selection" =~ ^[0-9]+$ ]] && [[ "$selection" -ge 1 ]] && [[ "$selection" -lt "$i" ]]; then
|
||||
break
|
||||
else
|
||||
log_warning "Invalid selection. Please enter a number between 1 and $((i-1))"
|
||||
fi
|
||||
done
|
||||
|
||||
echo "${artifact_array[$((selection-1))]}"
|
||||
}
|
||||
|
||||
download_artifact() {
|
||||
local repo="$1"
|
||||
local artifact_id="$2"
|
||||
local artifact_name="$3"
|
||||
local output_dir="$4"
|
||||
|
||||
log_step "Downloading artifact ${BOLD}${artifact_name}${RESET}..."
|
||||
|
||||
# Create output directory if it doesn't exist
|
||||
mkdir -p "$output_dir"
|
||||
|
||||
# Download artifact using gh
|
||||
local zip_file="${output_dir}/${artifact_name}.zip"
|
||||
|
||||
if gh api \
|
||||
-H "Accept: application/vnd.github+json" \
|
||||
-H "X-GitHub-Api-Version: 2022-11-28" \
|
||||
"/repos/${repo}/actions/artifacts/${artifact_id}/zip" \
|
||||
> "$zip_file" 2>/dev/null; then
|
||||
|
||||
log_success "Downloaded to ${BOLD}${zip_file}${RESET}"
|
||||
echo "$zip_file"
|
||||
else
|
||||
log_error "Failed to download artifact"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
extract_artifact() {
|
||||
local zip_file="$1"
|
||||
local output_dir="$2"
|
||||
|
||||
log_step "Extracting archive..."
|
||||
|
||||
# Create extraction directory
|
||||
local extract_dir="${output_dir}/$(basename "$zip_file" .zip)"
|
||||
mkdir -p "$extract_dir"
|
||||
|
||||
if unzip -q "$zip_file" -d "$extract_dir"; then
|
||||
log_success "Extracted to ${BOLD}${extract_dir}${RESET}"
|
||||
|
||||
# Show extracted files
|
||||
log_info "Extracted files:"
|
||||
find "$extract_dir" -type f -exec basename {} \; | while read -r file; do
|
||||
echo " ${GREEN}•${RESET} ${file}"
|
||||
done
|
||||
|
||||
# Remove zip file
|
||||
rm "$zip_file"
|
||||
log_info "Cleaned up zip file"
|
||||
|
||||
echo "$extract_dir"
|
||||
else
|
||||
log_error "Failed to extract archive"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Main Script
|
||||
# ============================================================================
|
||||
|
||||
main() {
|
||||
local repo=""
|
||||
local artifact_name=""
|
||||
local output_dir="."
|
||||
|
||||
# Parse arguments
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case $1 in
|
||||
-h|--help)
|
||||
show_help
|
||||
exit 0
|
||||
;;
|
||||
-n|--name)
|
||||
artifact_name="$2"
|
||||
shift 2
|
||||
;;
|
||||
-o|--output)
|
||||
output_dir="$2"
|
||||
shift 2
|
||||
;;
|
||||
-*)
|
||||
log_error "Unknown option: $1"
|
||||
echo ""
|
||||
show_help
|
||||
exit 1
|
||||
;;
|
||||
*)
|
||||
if [[ -z "$repo" ]]; then
|
||||
repo="$1"
|
||||
else
|
||||
log_error "Unexpected argument: $1"
|
||||
echo ""
|
||||
show_help
|
||||
exit 1
|
||||
fi
|
||||
shift
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
# Validate required arguments
|
||||
if [[ -z "$repo" ]]; then
|
||||
log_error "Repository argument is required"
|
||||
echo ""
|
||||
show_help
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Validate repository format
|
||||
if [[ ! "$repo" =~ ^[^/]+/[^/]+$ ]]; then
|
||||
log_error "Invalid repository format. Expected: ${BOLD}owner/repo${RESET}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Show header
|
||||
log_header "GitHub Artifact Downloader"
|
||||
|
||||
# Check dependencies
|
||||
log_step "Checking dependencies..."
|
||||
check_dependencies
|
||||
log_success "All dependencies found"
|
||||
|
||||
# Check GitHub authentication
|
||||
log_step "Checking GitHub authentication..."
|
||||
check_gh_auth
|
||||
log_success "Authenticated with GitHub"
|
||||
|
||||
echo ""
|
||||
log_data "Repository" "${BRIGHT_CYAN}${repo}${RESET}"
|
||||
if [[ -n "$artifact_name" ]]; then
|
||||
log_data "Artifact" "${BRIGHT_YELLOW}${artifact_name}${RESET}"
|
||||
fi
|
||||
log_data "Output" "${BRIGHT_GREEN}${output_dir}${RESET}"
|
||||
echo ""
|
||||
|
||||
# List artifacts
|
||||
local artifacts_json
|
||||
artifacts_json=$(list_artifacts "$repo")
|
||||
|
||||
# Select artifact
|
||||
local selected
|
||||
selected=$(select_artifact "$artifacts_json" "$artifact_name")
|
||||
|
||||
IFS='|' read -r artifact_id name size created workflow <<< "$selected"
|
||||
|
||||
echo ""
|
||||
log_info "Selected artifact:"
|
||||
log_data " Name" "${BRIGHT_CYAN}${name}${RESET}"
|
||||
log_data " Size" "$(format_size "$size")"
|
||||
log_data " Created" "$(format_date "$created")"
|
||||
echo ""
|
||||
|
||||
# Download artifact
|
||||
local zip_file
|
||||
zip_file=$(download_artifact "$repo" "$artifact_id" "$name" "$output_dir")
|
||||
|
||||
# Extract artifact
|
||||
local extract_dir
|
||||
extract_dir=$(extract_artifact "$zip_file" "$output_dir")
|
||||
|
||||
# Success summary
|
||||
echo ""
|
||||
log_header "Download Complete!"
|
||||
log_data "Location" "${BOLD}${extract_dir}${RESET}"
|
||||
echo ""
|
||||
|
||||
log_success "All done! 🎉"
|
||||
}
|
||||
|
||||
# Run main function
|
||||
main "$@"
|
||||
90
assets/doc_rust_generate/theme/header.html.jinja
Normal file
90
assets/doc_rust_generate/theme/header.html.jinja
Normal file
@@ -0,0 +1,90 @@
|
||||
<script type="module">
|
||||
import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.esm.min.mjs';
|
||||
|
||||
mermaid.initialize({
|
||||
startOnLoad: false,
|
||||
theme: 'dark',
|
||||
themeVariables: {
|
||||
primaryColor: '{{primaryColor}}',
|
||||
primaryTextColor: '{{primaryTextColor}}',
|
||||
primaryBorderColor: '{{primaryBorderColor}}',
|
||||
lineColor: '{{lineColor}}',
|
||||
secondaryColor: '#{{secondaryColor}}',
|
||||
tertiaryColor: '{{secondaryColor}}',
|
||||
background: '{{background}}',
|
||||
mainBkg: '{{mainBkg}}',
|
||||
secondBkg: '{{secondBkg}}',
|
||||
border1: '{{border1}}',
|
||||
border2: '{{border2}}',
|
||||
note: '{{note}}',
|
||||
noteBkgColor: '{{noteBkgColor}}',
|
||||
noteTextColor: '{{noteTextColor}}',
|
||||
noteBorderColor: '{{noteBorderColor}}',
|
||||
arrowheadColor: '{{arrowheadColor}}',
|
||||
fontFamily: '{{fontFamily}}',
|
||||
fontSize: '{{fontSize}}',
|
||||
darkMode: '{{darkMode}}',
|
||||
edgeLabelBackground: '{{edgeLabelBackground}}',
|
||||
clusterBkg: '{{clusterBkg}}',
|
||||
clusterBorder: '{{clusterBorder}}',
|
||||
defaultLinkColor: '{{defaultLinkColor}}',
|
||||
titleColor: '{{titleColor}}',
|
||||
nodeTextColor: '{{nodeTextColor}}'
|
||||
},
|
||||
flowchart: {
|
||||
htmlLabels: true,
|
||||
curve: 'basis',
|
||||
useMaxWidth: true,
|
||||
padding: 20
|
||||
},
|
||||
securityLevel: 'loose'
|
||||
});
|
||||
|
||||
// Use both DOMContentLoaded and load events for better compatibility
|
||||
function initMermaid() {
|
||||
const mermaidBlocks = document.querySelectorAll(
|
||||
'pre.language-mermaid code'
|
||||
);
|
||||
|
||||
if (mermaidBlocks.length === 0) {
|
||||
console.log('No mermaid blocks found');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`Found ${mermaidBlocks.length} mermaid blocks, converting...`);
|
||||
|
||||
mermaidBlocks.forEach((block, index) => {
|
||||
const mermaidDiv = document.createElement('div');
|
||||
mermaidDiv.className = 'mermaid';
|
||||
mermaidDiv.id = `mermaid-diagram-${index}`;
|
||||
mermaidDiv.textContent = block.textContent;
|
||||
block.parentElement.replaceWith(mermaidDiv);
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
mermaid
|
||||
.run()
|
||||
.then(() => {
|
||||
console.log('Mermaid diagrams rendered successfully');
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error('Mermaid rendering error:', err);
|
||||
});
|
||||
}, 100);
|
||||
}
|
||||
|
||||
// Try multiple event listeners to ensure it runs
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', initMermaid);
|
||||
} else {
|
||||
// DOM already loaded
|
||||
initMermaid();
|
||||
}
|
||||
|
||||
window.addEventListener('load', () => {
|
||||
// Failsafe: run again on window load if diagrams still missing
|
||||
if (document.querySelectorAll('.mermaid svg').length === 0) {
|
||||
initMermaid();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
25
assets/doc_rust_generate/theme/mermaid.yaml
Normal file
25
assets/doc_rust_generate/theme/mermaid.yaml
Normal file
@@ -0,0 +1,25 @@
|
||||
primaryColor: '#ff69b4'
|
||||
primaryTextColor: '#1e272e'
|
||||
primaryBorderColor: '#ff1493'
|
||||
lineColor: '#ff69b4'
|
||||
secondaryColor: '#2f3542'
|
||||
tertiaryColor: '#1e272e'
|
||||
background: '#1e272e'
|
||||
mainBkg: '#2f3542'
|
||||
secondBkg: '#1e272e'
|
||||
border1: '#ff69b4'
|
||||
border2: '#ff1493'
|
||||
note: '#2f3542'
|
||||
noteBkgColor: '#2f3542'
|
||||
noteTextColor: '#ffffff'
|
||||
noteBorderColor: '#ff69b4'
|
||||
arrowheadColor: '#ff69b4'
|
||||
fontFamily: 'Inter system-ui -apple-system sans-serif'
|
||||
fontSize: '16px'
|
||||
darkMode: true
|
||||
edgeLabelBackground: '#1e272e'
|
||||
clusterBkg: '#2f3542'
|
||||
clusterBorder: '#ff69b4'
|
||||
defaultLinkColor: '#ff69b4'
|
||||
titleColor: '#ffffff'
|
||||
nodeTextColor: '#e8eaed'
|
||||
28
assets/doc_rust_generate/theme/theme.css
Normal file
28
assets/doc_rust_generate/theme/theme.css
Normal file
@@ -0,0 +1,28 @@
|
||||
.mermaid {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
padding: 14px;
|
||||
background-color: var(--code-block-background-color);
|
||||
border-radius: var(--code-block-border-radius);
|
||||
}
|
||||
|
||||
.version {
|
||||
color: var(--quote-color);
|
||||
}
|
||||
|
||||
.sidebar-crate h2 a {
|
||||
color: var(--link-color);
|
||||
}
|
||||
|
||||
a.doc-anchor {
|
||||
color: var(--link-color-alternative);
|
||||
}
|
||||
|
||||
pre {
|
||||
font-size: 0.8em;
|
||||
}
|
||||
|
||||
.docblock :not(pre) > code {
|
||||
font-size: 0.8em;
|
||||
padding: 1px 6px;
|
||||
}
|
||||
152
assets/doc_rust_generate/theme/theme.yaml
Normal file
152
assets/doc_rust_generate/theme/theme.yaml
Normal file
@@ -0,0 +1,152 @@
|
||||
main-background-color: "#2f3542"
|
||||
main-color: "#e8eaed"
|
||||
settings-input-color: "#e8eaed"
|
||||
settings-input-border-color: "#57606f"
|
||||
settings-button-color: "#ff69b4"
|
||||
settings-button-border-focus: "#ff1493"
|
||||
link-color: "#ff69b4"
|
||||
code-highlight-kw-color: "#ff69b4"
|
||||
code-highlight-kw-2-color: "#ff1493"
|
||||
code-highlight-kw-3-color: "#ec407a"
|
||||
sidebar-background-color: "#1e272e"
|
||||
sidebar-background-color-hover: "#24272e"
|
||||
sidebar-elems-color: "#e8eaed"
|
||||
sidebar-border-color: "#57606f"
|
||||
sidebar-link-color: "#e8eaed"
|
||||
sidebar-current-link-background-color: rgba(255, 105, 180, 0.15)
|
||||
search-input-focused-border-color: "#ff69b4"
|
||||
search-results-alias-color: "#ff69b4"
|
||||
search-results-grey-color: "#747d8c"
|
||||
search-tab-title-count-color: "#ff69b4"
|
||||
search-background-color: "#2f3542"
|
||||
search-color: "#e8eaed"
|
||||
search-error-code-background-color: "#1e272e"
|
||||
search-results-border-color: "#57606f"
|
||||
search-tab-button-not-selected-border-top-color: "#57606f"
|
||||
search-tab-button-not-selected-background: "#24272e"
|
||||
search-tab-button-selected-border-top-color: "#ff69b4"
|
||||
search-tab-button-selected-background: "#2f3542"
|
||||
search-result-link-focus-background-color: rgba(255, 105, 180, 0.1)
|
||||
search-result-border-color: "#57606f"
|
||||
search-input-border-color: "#57606f"
|
||||
code-block-background-color: "#1e272e"
|
||||
codeblock-error-hover-color: rgba(255, 105, 180, 0.1)
|
||||
codeblock-error-color: rgba(255, 20, 147, 0.8)
|
||||
codeblock-ignore-hover-color: "#ffffff"
|
||||
codeblock-ignore-color: "#e8eaed"
|
||||
codeblock-link-background: "#1e272e"
|
||||
codeblock-link-hover-background: "#24272e"
|
||||
scrollbar-track-background-color: "#1e272e"
|
||||
scrollbar-thumb-background-color: "#ff69b4"
|
||||
scrollbar-color: "#ff69b4 #1e272e"
|
||||
heading-color: "#ff69b4"
|
||||
title-color: "#ff69b4"
|
||||
subtitle-color: "#ff1493"
|
||||
headings-border-bottom-color: "#ff69b4"
|
||||
border-color: "#57606f"
|
||||
table-border-color: "#57606f"
|
||||
table-head-background-color: "#1e272e"
|
||||
button-background-color: "#1e272e"
|
||||
button-border-color: "#57606f"
|
||||
right-side-color: "#e8eaed"
|
||||
tr-even-background-color: "#24272e"
|
||||
tr-odd-background-color: "#2f3542"
|
||||
table-alt-row-background-color: "#24272e"
|
||||
quote-color: "#747d8c"
|
||||
quote-border-color: "#ff69b4"
|
||||
type-link-color: "#ff69b4"
|
||||
trait-link-color: "#ff1493"
|
||||
assoc-item-link-color: "#ec407a"
|
||||
fn-link-color: "#ff69b4"
|
||||
function-link-color: "#ff69b4"
|
||||
macro-link-color: "#f06292"
|
||||
keyword-link-color: "#ff69b4"
|
||||
mod-link-color: "#ff1493"
|
||||
docblock-table-border-color: "#57606f"
|
||||
docblock-code-background-color: "#1e272e"
|
||||
mobile-sidebar-menu-filter: none
|
||||
mobile-sidebar-menu-hover-filter: invert(100%) brightness(1.2)
|
||||
src-line-numbers-span-color: "#57606f"
|
||||
src-line-number-highlighted-background-color: rgba(255, 105, 180, 0.1)
|
||||
src-sidebar-background-selected: rgba(255, 105, 180, 0.15)
|
||||
src-sidebar-background-hover: rgba(255, 105, 180, 0.1)
|
||||
item-table-header-background-color: "#1e272e"
|
||||
item-table-header-border-color: "#57606f"
|
||||
scraped-example-title-color: "#ff69b4"
|
||||
scraped-example-help-border-color: "#57606f"
|
||||
scraped-example-help-color: "#e8eaed"
|
||||
scraped-example-help-hover-border-color: "#ff69b4"
|
||||
scraped-example-help-hover-color: "#ffffff"
|
||||
scraped-example-code-line-highlight: rgba(255, 105, 180, 0.1)
|
||||
scraped-example-code-line-highlight-focus: rgba(255, 105, 180, 0.2)
|
||||
scrape-example-code-line-highlight: rgba(255, 105, 180, 0.1)
|
||||
scrape-example-code-line-highlight-focus: rgba(255, 105, 180, 0.2)
|
||||
scrape-example-help-border-color: "#57606f"
|
||||
scrape-example-help-color: "#e8eaed"
|
||||
scrape-example-help-hover-border-color: "#ff69b4"
|
||||
scrape-example-help-hover-color: "#ffffff"
|
||||
scrape-example-toggle-line-background: rgba(255, 105, 180, 0.05)
|
||||
scrape-example-toggle-line-hover-background: rgba(255, 105, 180, 0.1)
|
||||
scrape-example-code-wrapper-background-start: rgba(30, 39, 46, 0.9)
|
||||
scrape-example-code-wrapper-background-end: rgba(30, 39, 46, 0.6)
|
||||
toggle-filter: invert(100%) brightness(0.8)
|
||||
toggle-plus-minus-background-color: rgba(255, 105, 180, 0.1)
|
||||
notable-trait-tooltip-background-color: "#1e272e"
|
||||
notable-trait-tooltip-border-color: "#ff69b4"
|
||||
copy-button-color: "#e8eaed"
|
||||
copy-button-background-color: "#1e272e"
|
||||
copy-button-hover-color: "#ffffff"
|
||||
copy-button-hover-background-color: "#24272e"
|
||||
copy-button-border-color: "#57606f"
|
||||
copy-path-button-color: "#e8eaed"
|
||||
code-attribute-color: "#ff69b4"
|
||||
code-highlight-lifetime-color: "#f06292"
|
||||
code-highlight-prelude-color: "#ff69b4"
|
||||
code-highlight-prelude-val-color: "#ff1493"
|
||||
code-highlight-number-color: "#f06292"
|
||||
code-highlight-string-color: "#ec407a"
|
||||
code-highlight-literal-color: "#ff69b4"
|
||||
code-highlight-attribute-color: "#ff69b4"
|
||||
code-highlight-self-color: "#ff1493"
|
||||
code-highlight-macro-color: "#f06292"
|
||||
code-highlight-question-mark-color: "#ff69b4"
|
||||
code-highlight-comment-color: "#747d8c"
|
||||
code-highlight-doc-comment-color: "#747d8c"
|
||||
stab-background-color: "#1e272e"
|
||||
stab-code-color: "#e8eaed"
|
||||
stab-unstable-background-color: rgba(255, 105, 180, 0.2)
|
||||
stab-unstable-code-color: "#ff69b4"
|
||||
stab-deprecated-background-color: rgba(255, 69, 0, 0.2)
|
||||
stab-deprecated-code-color: "#ff4500"
|
||||
stab-portability-background-color: rgba(116, 125, 140, 0.2)
|
||||
stab-portability-code-color: "#747d8c"
|
||||
kbd-color: "#e8eaed"
|
||||
kbd-background: "#1e272e"
|
||||
kbd-box-shadow-color: "#57606f"
|
||||
rust-logo-filter: brightness(0) saturate(100%) invert(75%) sepia(100%) saturate(1000%) hue-rotate(290deg) brightness(100%) contrast(100%)
|
||||
crate-search-div-filter: invert(100%)
|
||||
crate-search-div-hover-filter: invert(100%)
|
||||
crate-search-hover-border: "#ff69b4"
|
||||
target-background-color: rgba(255, 105, 180, 0.1)
|
||||
target-border-color: "#ff69b4"
|
||||
code-example-button-color: "#e8eaed"
|
||||
code-example-button-background-color: "#1e272e"
|
||||
code-example-button-hover-background-color: "#24272e"
|
||||
code-example-button-hover-color: "#ffffff"
|
||||
settings-menu-filter: brightness(0.9) invert(65%) sepia(67%) saturate(3704%) hue-rotate(300deg) brightness(108%) contrast(101%)
|
||||
settings-menu-hover-background-color: rgba(255, 105, 180, 0.1)
|
||||
settings-menu-hover-filter: brightness(1.1) invert(65%) sepia(67%) saturate(3704%) hue-rotate(300deg) brightness(108%) contrast(101%)
|
||||
help-border-color: "#57606f"
|
||||
help-background-color: "#2f3542"
|
||||
help-color: "#e8eaed"
|
||||
warning-border-color: "#f59e0b"
|
||||
warning-background-color: rgba(245, 158, 11, 0.1)
|
||||
note-background-color: rgba(59, 130, 246, 0.1)
|
||||
note-border-color: "#3b82f6"
|
||||
toggles-color: "#e8eaed"
|
||||
sidebar-resizer-hover: "#ff69b4"
|
||||
sidebar-resizer-active: "#ff1493"
|
||||
font-family: "'Inter', 'Fira Sans', Arial, sans-serif"
|
||||
font-family-code: "'JetBrains Mono', 'Fira Mono', monospace"
|
||||
link-color-alternative: "#6495ed"
|
||||
|
||||
846
css_color_filter.sh
Executable file
846
css_color_filter.sh
Executable file
@@ -0,0 +1,846 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
#############################################################################
|
||||
# CSS Color Filter Generator
|
||||
#
|
||||
# Generate CSS filter values to transform black elements into any target color
|
||||
# No Node.js required - pure bash implementation
|
||||
#
|
||||
# Usage:
|
||||
# css_color_filter.sh [COLOR]
|
||||
# css_color_filter.sh -i # Interactive mode
|
||||
# css_color_filter.sh --help # Show help
|
||||
#
|
||||
# Arguments:
|
||||
# COLOR Hex color (e.g., #FF0000, ff0000) or RGB (e.g., 255,0,0)
|
||||
#
|
||||
# Options:
|
||||
# -i, --interactive Interactive mode with colored preview
|
||||
# -r, --raw Output only the CSS filter (for piping)
|
||||
# -c, --copy Copy result to clipboard automatically
|
||||
# -h, --help Show this help message
|
||||
#
|
||||
# Examples:
|
||||
# css_color_filter.sh "#FF5733"
|
||||
# css_color_filter.sh ff5733
|
||||
# css_color_filter.sh "255,87,51"
|
||||
# css_color_filter.sh -i
|
||||
#
|
||||
# Dependencies:
|
||||
# bc For floating-point arithmetic
|
||||
# jq For JSON formatting (optional)
|
||||
#
|
||||
#############################################################################
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# ============================================================================
|
||||
# Color Definitions
|
||||
# ============================================================================
|
||||
|
||||
RED="" GREEN="" YELLOW="" BLUE="" MAGENTA="" CYAN="" BOLD="" DIM="" RESET=""
|
||||
COLORS=0
|
||||
|
||||
if [[ -t 1 ]] && command -v tput >/dev/null 2>&1; then
|
||||
COLORS=$(tput colors 2>/dev/null || echo 0)
|
||||
if [[ ${COLORS:-0} -ge 8 ]]; then
|
||||
RED=$(tput setaf 1 2>/dev/null || echo "")
|
||||
GREEN=$(tput setaf 2 2>/dev/null || echo "")
|
||||
YELLOW=$(tput setaf 3 2>/dev/null || echo "")
|
||||
BLUE=$(tput setaf 4 2>/dev/null || echo "")
|
||||
MAGENTA=$(tput setaf 5 2>/dev/null || echo "")
|
||||
CYAN=$(tput setaf 6 2>/dev/null || echo "")
|
||||
BOLD=$(tput bold 2>/dev/null || echo "")
|
||||
DIM=$(tput dim 2>/dev/null || echo "")
|
||||
RESET=$(tput sgr0 2>/dev/null || echo "")
|
||||
fi
|
||||
fi
|
||||
|
||||
# ============================================================================
|
||||
# Helper Functions
|
||||
# ============================================================================
|
||||
|
||||
print_usage() {
|
||||
cat << EOF
|
||||
${BOLD}CSS Color Filter Generator${RESET}
|
||||
|
||||
Generate CSS filter values to transform black elements into any target color.
|
||||
|
||||
${BOLD}USAGE:${RESET}
|
||||
$(basename "$0") [OPTIONS] [COLOR]
|
||||
|
||||
${BOLD}ARGUMENTS:${RESET}
|
||||
COLOR Hex color (e.g., #FF0000, ff0000) or RGB (e.g., 255,0,0)
|
||||
|
||||
${BOLD}OPTIONS:${RESET}
|
||||
-i, --interactive Interactive mode with colored preview
|
||||
-r, --raw Output only the CSS filter (for piping)
|
||||
-c, --copy Copy result to clipboard automatically
|
||||
-h, --help Show this help message
|
||||
|
||||
${BOLD}EXAMPLES:${RESET}
|
||||
$(basename "$0") "#FF5733"
|
||||
$(basename "$0") ff5733
|
||||
$(basename "$0") "255,87,51"
|
||||
$(basename "$0") -i
|
||||
|
||||
${BOLD}NOTE:${RESET}
|
||||
This tool generates filters that work on ${BOLD}black${RESET} elements.
|
||||
To use with non-black elements, prepend: ${DIM}brightness(0) saturate(100%)${RESET}
|
||||
|
||||
${BOLD}ALGORITHM:${RESET}
|
||||
Uses SPSA (Simultaneous Perturbation Stochastic Approximation) to find
|
||||
optimal filter combinations that minimize color difference in RGB and HSL.
|
||||
|
||||
${BOLD}DEPENDENCIES:${RESET}
|
||||
bc For floating-point arithmetic
|
||||
jq For JSON formatting (optional)
|
||||
|
||||
EOF
|
||||
}
|
||||
|
||||
error() {
|
||||
echo "${RED}${BOLD}Error:${RESET} $1" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
info() {
|
||||
echo "${BLUE}${BOLD}==>${RESET} $1"
|
||||
}
|
||||
|
||||
success() {
|
||||
echo "${GREEN}${BOLD}[OK]${RESET} $1"
|
||||
}
|
||||
|
||||
warning() {
|
||||
echo "${YELLOW}${BOLD}[WARN]${RESET} $1"
|
||||
}
|
||||
|
||||
check_dependencies() {
|
||||
if ! command -v bc >/dev/null 2>&1; then
|
||||
error "bc is required but not found. Please install bc (apt-get install bc)"
|
||||
fi
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Math Utilities
|
||||
# ============================================================================
|
||||
|
||||
bc_calc() {
|
||||
local expr="$1"
|
||||
expr=$(echo "$expr" | tr -d ' ')
|
||||
if [[ -z "$expr" ]]; then
|
||||
echo "0"
|
||||
return
|
||||
fi
|
||||
echo "scale=10; $expr" | bc -l 2>/dev/null || echo "0"
|
||||
}
|
||||
|
||||
bc_compare() {
|
||||
local result=$(echo "$1" | bc -l 2>/dev/null || echo "0")
|
||||
[[ "$result" == "1" ]]
|
||||
}
|
||||
|
||||
abs_val() {
|
||||
local val="$1"
|
||||
if bc_compare "$val < 0"; then
|
||||
echo "$(bc_calc "-1 * $val")"
|
||||
else
|
||||
echo "$val"
|
||||
fi
|
||||
}
|
||||
|
||||
min() {
|
||||
local a=$1 b=$2
|
||||
if bc_compare "$a < $b"; then
|
||||
echo "$a"
|
||||
else
|
||||
echo "$b"
|
||||
fi
|
||||
}
|
||||
|
||||
max() {
|
||||
local a=$1 b=$2
|
||||
if bc_compare "$a > $b"; then
|
||||
echo "$a"
|
||||
else
|
||||
echo "$b"
|
||||
fi
|
||||
}
|
||||
|
||||
clamp() {
|
||||
local val=$1 min_val=$2 max_val=$3
|
||||
val=$(max "$val" "$min_val")
|
||||
val=$(min "$val" "$max_val")
|
||||
echo "$val"
|
||||
}
|
||||
|
||||
round() {
|
||||
LC_NUMERIC=C printf "%.0f" "$1"
|
||||
}
|
||||
|
||||
# Generate random number between 0 and 1
|
||||
random_float() {
|
||||
echo "scale=10; $RANDOM / 32767" | bc -l
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Color Validation
|
||||
# ============================================================================
|
||||
|
||||
validate_hex() {
|
||||
local hex="$1"
|
||||
hex="${hex#\#}"
|
||||
if [[ "$hex" =~ ^[0-9A-Fa-f]{3}$ ]] || [[ "$hex" =~ ^[0-9A-Fa-f]{6}$ ]]; then
|
||||
return 0
|
||||
fi
|
||||
return 1
|
||||
}
|
||||
|
||||
validate_rgb() {
|
||||
local rgb="$1"
|
||||
if [[ "$rgb" =~ ^[0-9]{1,3},[0-9]{1,3},[0-9]{1,3}$ ]]; then
|
||||
IFS=',' read -r r g b <<< "$rgb"
|
||||
if [[ $r -le 255 && $g -le 255 && $b -le 255 ]]; then
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
return 1
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Color Conversion
|
||||
# ============================================================================
|
||||
|
||||
hex_to_rgb() {
|
||||
local hex="$1"
|
||||
hex="${hex#\#}"
|
||||
|
||||
if [[ ${#hex} -eq 3 ]]; then
|
||||
hex="${hex:0:1}${hex:0:1}${hex:1:1}${hex:1:1}${hex:2:1}${hex:2:1}"
|
||||
fi
|
||||
|
||||
local r=$((16#${hex:0:2}))
|
||||
local g=$((16#${hex:2:2}))
|
||||
local b=$((16#${hex:4:2}))
|
||||
|
||||
echo "$r $g $b"
|
||||
}
|
||||
|
||||
rgb_to_hex() {
|
||||
local r=$(round "$1")
|
||||
local g=$(round "$2")
|
||||
local b=$(round "$3")
|
||||
|
||||
r=$(clamp "$r" 0 255)
|
||||
g=$(clamp "$g" 0 255)
|
||||
b=$(clamp "$b" 0 255)
|
||||
|
||||
LC_NUMERIC=C printf "#%02X%02X%02X" "$r" "$g" "$b"
|
||||
}
|
||||
|
||||
rgb_to_hsl() {
|
||||
local r=$1 g=$2 b=$3
|
||||
|
||||
r=$(bc_calc "$r / 255")
|
||||
g=$(bc_calc "$g / 255")
|
||||
b=$(bc_calc "$b / 255")
|
||||
|
||||
local max=$(max "$r" "$(max "$g" "$b")")
|
||||
local min=$(min "$r" "$(min "$g" "$b")")
|
||||
local delta=$(bc_calc "$max - $min")
|
||||
|
||||
local h=0 s=0 l
|
||||
l=$(bc_calc "($max + $min) / 2")
|
||||
|
||||
if bc_compare "$delta != 0"; then
|
||||
if bc_compare "$l < 0.5"; then
|
||||
s=$(bc_calc "$delta / ($max + $min)")
|
||||
else
|
||||
s=$(bc_calc "$delta / (2 - $max - $min)")
|
||||
fi
|
||||
|
||||
if bc_compare "$max == $r"; then
|
||||
h=$(bc_calc "(($g - $b) / $delta) + (if ($g < $b) then 6 else 0)")
|
||||
elif bc_compare "$max == $g"; then
|
||||
h=$(bc_calc "(($b - $r) / $delta) + 2")
|
||||
else
|
||||
h=$(bc_calc "(($r - $g) / $delta) + 4")
|
||||
fi
|
||||
|
||||
h=$(bc_calc "$h / 6")
|
||||
fi
|
||||
|
||||
h=$(bc_calc "$h * 100")
|
||||
s=$(bc_calc "$s * 100")
|
||||
l=$(bc_calc "$l * 100")
|
||||
|
||||
echo "$h $s $l"
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Color Class Implementation
|
||||
# ============================================================================
|
||||
|
||||
# Global color state (r, g, b)
|
||||
declare -a COLOR_STATE
|
||||
|
||||
color_set() {
|
||||
local r=$(clamp "$1" 0 255)
|
||||
local g=$(clamp "$2" 0 255)
|
||||
local b=$(clamp "$3" 0 255)
|
||||
COLOR_STATE=("$r" "$g" "$b")
|
||||
}
|
||||
|
||||
color_get_rgb() {
|
||||
echo "${COLOR_STATE[0]} ${COLOR_STATE[1]} ${COLOR_STATE[2]}"
|
||||
}
|
||||
|
||||
# Matrix multiplication for color transformations
|
||||
color_multiply() {
|
||||
local r=${COLOR_STATE[0]}
|
||||
local g=${COLOR_STATE[1]}
|
||||
local b=${COLOR_STATE[2]}
|
||||
|
||||
# Matrix passed as arguments (9 values)
|
||||
local m=("$@")
|
||||
|
||||
local new_r=$(bc_calc "$r * ${m[0]} + $g * ${m[1]} + $b * ${m[2]}")
|
||||
local new_g=$(bc_calc "$r * ${m[3]} + $g * ${m[4]} + $b * ${m[5]}")
|
||||
local new_b=$(bc_calc "$r * ${m[6]} + $g * ${m[7]} + $b * ${m[8]}")
|
||||
|
||||
new_r=$(clamp "$new_r" 0 255)
|
||||
new_g=$(clamp "$new_g" 0 255)
|
||||
new_b=$(clamp "$new_b" 0 255)
|
||||
|
||||
COLOR_STATE=("$new_r" "$new_g" "$new_b")
|
||||
}
|
||||
|
||||
# CSS filter functions
|
||||
|
||||
color_invert() {
|
||||
local value=${1:-1}
|
||||
local r=${COLOR_STATE[0]}
|
||||
local g=${COLOR_STATE[1]}
|
||||
local b=${COLOR_STATE[2]}
|
||||
|
||||
r=$(bc_calc "($value + $r / 255 * (1 - 2 * $value)) * 255")
|
||||
g=$(bc_calc "($value + $g / 255 * (1 - 2 * $value)) * 255")
|
||||
b=$(bc_calc "($value + $b / 255 * (1 - 2 * $value)) * 255")
|
||||
|
||||
color_set "$r" "$g" "$b"
|
||||
}
|
||||
|
||||
color_sepia() {
|
||||
local value=${1:-1}
|
||||
color_multiply \
|
||||
"$(bc_calc "0.393 + 0.607 * (1 - $value)")" \
|
||||
"$(bc_calc "0.769 - 0.769 * (1 - $value)")" \
|
||||
"$(bc_calc "0.189 - 0.189 * (1 - $value)")" \
|
||||
"$(bc_calc "0.349 - 0.349 * (1 - $value)")" \
|
||||
"$(bc_calc "0.686 + 0.314 * (1 - $value)")" \
|
||||
"$(bc_calc "0.168 - 0.168 * (1 - $value)")" \
|
||||
"$(bc_calc "0.272 - 0.272 * (1 - $value)")" \
|
||||
"$(bc_calc "0.534 - 0.534 * (1 - $value)")" \
|
||||
"$(bc_calc "0.131 + 0.869 * (1 - $value)")"
|
||||
}
|
||||
|
||||
color_saturate() {
|
||||
local value=${1:-1}
|
||||
color_multiply \
|
||||
"$(bc_calc "0.213 + 0.787 * $value")" \
|
||||
"$(bc_calc "0.715 - 0.715 * $value")" \
|
||||
"$(bc_calc "0.072 - 0.072 * $value")" \
|
||||
"$(bc_calc "0.213 - 0.213 * $value")" \
|
||||
"$(bc_calc "0.715 + 0.285 * $value")" \
|
||||
"$(bc_calc "0.072 - 0.072 * $value")" \
|
||||
"$(bc_calc "0.213 - 0.213 * $value")" \
|
||||
"$(bc_calc "0.715 - 0.715 * $value")" \
|
||||
"$(bc_calc "0.072 + 0.928 * $value")"
|
||||
}
|
||||
|
||||
color_hue_rotate() {
|
||||
local angle=${1:-0}
|
||||
angle=$(bc_calc "$angle / 180 * 3.14159265359")
|
||||
|
||||
local sin=$(echo "s($angle)" | bc -l)
|
||||
local cos=$(echo "c($angle)" | bc -l)
|
||||
|
||||
color_multiply \
|
||||
"$(bc_calc "0.213 + $cos * 0.787 - $sin * 0.213")" \
|
||||
"$(bc_calc "0.715 - $cos * 0.715 - $sin * 0.715")" \
|
||||
"$(bc_calc "0.072 - $cos * 0.072 + $sin * 0.928")" \
|
||||
"$(bc_calc "0.213 - $cos * 0.213 + $sin * 0.143")" \
|
||||
"$(bc_calc "0.715 + $cos * 0.285 + $sin * 0.140")" \
|
||||
"$(bc_calc "0.072 - $cos * 0.072 - $sin * 0.283")" \
|
||||
"$(bc_calc "0.213 - $cos * 0.213 - $sin * 0.787")" \
|
||||
"$(bc_calc "0.715 - $cos * 0.715 + $sin * 0.715")" \
|
||||
"$(bc_calc "0.072 + $cos * 0.928 + $sin * 0.072")"
|
||||
}
|
||||
|
||||
color_brightness() {
|
||||
local value=${1:-1}
|
||||
local r=${COLOR_STATE[0]}
|
||||
local g=${COLOR_STATE[1]}
|
||||
local b=${COLOR_STATE[2]}
|
||||
|
||||
r=$(bc_calc "$r * $value")
|
||||
g=$(bc_calc "$g * $value")
|
||||
b=$(bc_calc "$b * $value")
|
||||
|
||||
color_set "$r" "$g" "$b"
|
||||
}
|
||||
|
||||
color_contrast() {
|
||||
local value=${1:-1}
|
||||
local slope="$value"
|
||||
local intercept=$(bc_calc "-(0.5 * $value) + 0.5")
|
||||
|
||||
local r=${COLOR_STATE[0]}
|
||||
local g=${COLOR_STATE[1]}
|
||||
local b=${COLOR_STATE[2]}
|
||||
|
||||
r=$(bc_calc "$r * $slope + $intercept * 255")
|
||||
g=$(bc_calc "$g * $slope + $intercept * 255")
|
||||
b=$(bc_calc "$b * $slope + $intercept * 255")
|
||||
|
||||
color_set "$r" "$g" "$b"
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Solver Implementation
|
||||
# ============================================================================
|
||||
|
||||
# Target color
|
||||
declare -a TARGET_RGB
|
||||
declare -a TARGET_HSL
|
||||
|
||||
# Calculate loss between current color and target
|
||||
calculate_loss() {
|
||||
local -a filters=("$@")
|
||||
|
||||
# Reset to black
|
||||
color_set 0 0 0
|
||||
|
||||
# Apply filters
|
||||
color_invert "$(bc_calc "${filters[0]} / 100")"
|
||||
color_sepia "$(bc_calc "${filters[1]} / 100")"
|
||||
color_saturate "$(bc_calc "${filters[2]} / 100")"
|
||||
color_hue_rotate "$(bc_calc "${filters[3]} * 3.6")"
|
||||
color_brightness "$(bc_calc "${filters[4]} / 100")"
|
||||
color_contrast "$(bc_calc "${filters[5]} / 100")"
|
||||
|
||||
# Get resulting color
|
||||
read -r r g b <<< "$(color_get_rgb)"
|
||||
read -r h s l <<< "$(rgb_to_hsl "$r" "$g" "$b")"
|
||||
|
||||
# Calculate color difference
|
||||
local loss=0
|
||||
loss=$(bc_calc "$loss + $(abs_val "$(bc_calc "$r - ${TARGET_RGB[0]}")")")
|
||||
loss=$(bc_calc "$loss + $(abs_val "$(bc_calc "$g - ${TARGET_RGB[1]}")")")
|
||||
loss=$(bc_calc "$loss + $(abs_val "$(bc_calc "$b - ${TARGET_RGB[2]}")")")
|
||||
loss=$(bc_calc "$loss + $(abs_val "$(bc_calc "$h - ${TARGET_HSL[0]}")")")
|
||||
loss=$(bc_calc "$loss + $(abs_val "$(bc_calc "$s - ${TARGET_HSL[1]}")")")
|
||||
loss=$(bc_calc "$loss + $(abs_val "$(bc_calc "$l - ${TARGET_HSL[2]}")")")
|
||||
|
||||
echo "$loss"
|
||||
}
|
||||
|
||||
# Fix filter values to valid ranges
|
||||
fix_filter_value() {
|
||||
local value=$1
|
||||
local idx=$2
|
||||
local max=100
|
||||
|
||||
if [[ $idx -eq 2 ]]; then
|
||||
max=7500 # saturate
|
||||
elif [[ $idx -eq 4 ]] || [[ $idx -eq 5 ]]; then
|
||||
max=200 # brightness, contrast
|
||||
fi
|
||||
|
||||
if [[ $idx -eq 3 ]]; then
|
||||
# hue-rotate: wrap around
|
||||
while bc_compare "$value > $max"; do
|
||||
value=$(bc_calc "$value - $max")
|
||||
done
|
||||
while bc_compare "$value < 0"; do
|
||||
value=$(bc_calc "$value + $max")
|
||||
done
|
||||
else
|
||||
value=$(clamp "$value" 0 "$max")
|
||||
fi
|
||||
|
||||
echo "$value"
|
||||
}
|
||||
|
||||
# SPSA optimization
|
||||
spsa() {
|
||||
local A=$1
|
||||
local c=$2
|
||||
shift 2
|
||||
local -a a=("$1" "$2" "$3" "$4" "$5" "$6")
|
||||
shift 6
|
||||
local -a values=("$1" "$2" "$3" "$4" "$5" "$6")
|
||||
shift 6
|
||||
local iters=$1
|
||||
|
||||
local alpha=1
|
||||
local gamma=0.16666666666666666
|
||||
|
||||
local -a best=("${values[@]}")
|
||||
local best_loss=999999
|
||||
|
||||
for ((k=0; k<iters; k++)); do
|
||||
local ck=$(bc_calc "$c / ($k + 1) ^ $gamma")
|
||||
|
||||
local -a deltas
|
||||
local -a high_args
|
||||
local -a low_args
|
||||
|
||||
for i in {0..5}; do
|
||||
# Random delta: 1 or -1
|
||||
if [[ $((RANDOM % 2)) -eq 0 ]]; then
|
||||
deltas[$i]=1
|
||||
else
|
||||
deltas[$i]=-1
|
||||
fi
|
||||
|
||||
high_args[$i]=$(bc_calc "${values[$i]} + $ck * ${deltas[$i]}")
|
||||
low_args[$i]=$(bc_calc "${values[$i]} - $ck * ${deltas[$i]}")
|
||||
done
|
||||
|
||||
local loss_high=$(calculate_loss "${high_args[@]}")
|
||||
local loss_low=$(calculate_loss "${low_args[@]}")
|
||||
local loss_diff=$(bc_calc "$loss_high - $loss_low")
|
||||
|
||||
for i in {0..5}; do
|
||||
local g=$(bc_calc "$loss_diff / (2 * $ck) * ${deltas[$i]}")
|
||||
local ak=$(bc_calc "${a[$i]} / ($A + $k + 1) ^ $alpha")
|
||||
values[$i]=$(bc_calc "${values[$i]} - $ak * $g")
|
||||
values[$i]=$(fix_filter_value "${values[$i]}" "$i")
|
||||
done
|
||||
|
||||
local loss=$(calculate_loss "${values[@]}")
|
||||
|
||||
if bc_compare "$loss < $best_loss"; then
|
||||
best=("${values[@]}")
|
||||
best_loss="$loss"
|
||||
fi
|
||||
done
|
||||
|
||||
echo "${best[@]} $best_loss"
|
||||
}
|
||||
|
||||
# Solve wide search
|
||||
solve_wide() {
|
||||
local A=5
|
||||
local c=15
|
||||
local -a a=(60 180 18000 600 1.2 1.2)
|
||||
|
||||
local -a best_values
|
||||
local best_loss=999999
|
||||
|
||||
for ((i=0; i<3; i++)); do
|
||||
local -a initial=(50 20 3750 50 100 100)
|
||||
|
||||
read -r -a result <<< "$(spsa $A $c "${a[@]}" "${initial[@]}" 1000)"
|
||||
|
||||
# Last element is loss
|
||||
local loss="${result[6]}"
|
||||
|
||||
if bc_compare "$loss < $best_loss"; then
|
||||
best_values=("${result[@]:0:6}")
|
||||
best_loss="$loss"
|
||||
fi
|
||||
|
||||
# Break if good enough
|
||||
if bc_compare "$best_loss <= 25"; then
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
echo "${best_values[@]} $best_loss"
|
||||
}
|
||||
|
||||
# Solve narrow search
|
||||
solve_narrow() {
|
||||
local -a wide=("$@")
|
||||
local wide_loss="${wide[6]}"
|
||||
|
||||
local A="$wide_loss"
|
||||
local c=2
|
||||
local A1=$(bc_calc "$A + 1")
|
||||
local -a a
|
||||
a[0]=$(bc_calc "0.25 * $A1")
|
||||
a[1]=$(bc_calc "0.25 * $A1")
|
||||
a[2]="$A1"
|
||||
a[3]=$(bc_calc "0.25 * $A1")
|
||||
a[4]=$(bc_calc "0.2 * $A1")
|
||||
a[5]=$(bc_calc "0.2 * $A1")
|
||||
|
||||
local -a values=("${wide[@]:0:6}")
|
||||
|
||||
spsa "$A" "$c" "${a[@]}" "${values[@]}" 500
|
||||
}
|
||||
|
||||
# Main solve function
|
||||
solve_filters() {
|
||||
local target_hex="$1"
|
||||
|
||||
# Set target
|
||||
read -r r g b <<< "$(hex_to_rgb "$target_hex")"
|
||||
TARGET_RGB=("$r" "$g" "$b")
|
||||
read -r h s l <<< "$(rgb_to_hsl "$r" "$g" "$b")"
|
||||
TARGET_HSL=("$h" "$s" "$l")
|
||||
|
||||
# Solve
|
||||
local -a wide
|
||||
read -r -a wide <<< "$(solve_wide)"
|
||||
|
||||
local -a narrow
|
||||
read -r -a narrow <<< "$(solve_narrow "${wide[@]}")"
|
||||
|
||||
# Format output
|
||||
local -a values=("${narrow[@]:0:6}")
|
||||
local loss="${narrow[6]}"
|
||||
|
||||
# Generate CSS
|
||||
local filter="invert($(round "${values[0]}")%)"
|
||||
filter="$filter sepia($(round "${values[1]}")%)"
|
||||
filter="$filter saturate($(round "${values[2]}")%)"
|
||||
filter="$filter hue-rotate($(round "$(bc_calc "${values[3]} * 3.6")")deg)"
|
||||
filter="$filter brightness($(round "${values[4]}")%)"
|
||||
filter="$filter contrast($(round "${values[5]}")%)"
|
||||
|
||||
echo "$filter|$loss"
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Display Functions
|
||||
# ============================================================================
|
||||
|
||||
draw_color_block() {
|
||||
local hex="$1"
|
||||
local label="$2"
|
||||
|
||||
read -r r g b <<< "$(hex_to_rgb "$hex")"
|
||||
|
||||
if [[ ${COLORS:-0} -ge 256 ]]; then
|
||||
local bg_color="\033[48;2;${r};${g};${b}m"
|
||||
local reset="\033[0m"
|
||||
echo -e "${BOLD}${label}${RESET}"
|
||||
echo -e "${bg_color} ${reset}"
|
||||
echo -e "${bg_color} ${reset}"
|
||||
echo -e "${bg_color} ${reset}"
|
||||
echo ""
|
||||
fi
|
||||
}
|
||||
|
||||
copy_to_clipboard() {
|
||||
local text="$1"
|
||||
|
||||
if command -v xclip >/dev/null 2>&1; then
|
||||
echo -n "$text" | xclip -selection clipboard
|
||||
return 0
|
||||
elif command -v xsel >/dev/null 2>&1; then
|
||||
echo -n "$text" | xsel --clipboard
|
||||
return 0
|
||||
elif command -v wl-copy >/dev/null 2>&1; then
|
||||
echo -n "$text" | wl-copy
|
||||
return 0
|
||||
elif command -v pbcopy >/dev/null 2>&1; then
|
||||
echo -n "$text" | pbcopy
|
||||
return 0
|
||||
fi
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
display_result() {
|
||||
local hex="$1"
|
||||
local filter="$2"
|
||||
local loss="$3"
|
||||
local raw_mode="${4:-false}"
|
||||
|
||||
if [[ "$raw_mode" == "true" ]]; then
|
||||
echo "filter: $filter;"
|
||||
return
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "${BOLD}================================================================${RESET}"
|
||||
echo "${BOLD} CSS Color Filter Generator ${RESET}"
|
||||
echo "${BOLD}================================================================${RESET}"
|
||||
echo ""
|
||||
|
||||
read -r r g b <<< "$(hex_to_rgb "$hex")"
|
||||
echo "${BOLD}Target Color:${RESET}"
|
||||
echo " Hex: ${CYAN}${hex}${RESET}"
|
||||
echo " RGB: ${CYAN}rgb($r, $g, $b)${RESET}"
|
||||
echo ""
|
||||
|
||||
if [[ ${COLORS:-0} -ge 256 ]]; then
|
||||
draw_color_block "$hex" "Preview:"
|
||||
fi
|
||||
|
||||
echo "${BOLD}Generated CSS Filter:${RESET}"
|
||||
echo "${GREEN}filter: ${filter};${RESET}"
|
||||
echo ""
|
||||
|
||||
local loss_float=$(LC_NUMERIC=C printf "%.1f" "$loss")
|
||||
echo "${BOLD}Accuracy:${RESET}"
|
||||
echo -n " Loss: ${YELLOW}${loss_float}${RESET} "
|
||||
|
||||
if bc_compare "$loss < 1"; then
|
||||
echo "${GREEN}(Perfect match!)${RESET}"
|
||||
elif bc_compare "$loss < 5"; then
|
||||
echo "${GREEN}(Excellent match)${RESET}"
|
||||
elif bc_compare "$loss < 15"; then
|
||||
echo "${YELLOW}(Good match - consider re-running)${RESET}"
|
||||
else
|
||||
echo "${RED}(Poor match - try running again)${RESET}"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "${BOLD}----------------------------------------------------------------${RESET}"
|
||||
echo "${DIM}Note: This filter works on black elements. For non-black elements,"
|
||||
echo " prepend: brightness(0) saturate(100%)${RESET}"
|
||||
echo "${BOLD}----------------------------------------------------------------${RESET}"
|
||||
echo ""
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Interactive Mode
|
||||
# ============================================================================
|
||||
|
||||
interactive_mode() {
|
||||
echo ""
|
||||
echo "${BOLD}${BLUE}+================================================================+${RESET}"
|
||||
echo "${BOLD}${BLUE}| CSS Color Filter Generator (Interactive) |${RESET}"
|
||||
echo "${BOLD}${BLUE}+================================================================+${RESET}"
|
||||
echo ""
|
||||
|
||||
while true; do
|
||||
echo -n "${BOLD}Enter a color${RESET} ${DIM}(hex or rgb, or 'q' to quit):${RESET} "
|
||||
read -r color_input
|
||||
|
||||
if [[ "$color_input" =~ ^[qQ]$ ]]; then
|
||||
echo ""
|
||||
success "Goodbye!"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [[ -z "$color_input" ]]; then
|
||||
continue
|
||||
fi
|
||||
|
||||
process_color "$color_input" "false" "true"
|
||||
|
||||
echo ""
|
||||
echo -n "${DIM}Press Enter to continue...${RESET}"
|
||||
read -r
|
||||
echo ""
|
||||
done
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Color Processing
|
||||
# ============================================================================
|
||||
|
||||
process_color() {
|
||||
local color_input="$1"
|
||||
local raw_mode="${2:-false}"
|
||||
local auto_copy="${3:-false}"
|
||||
|
||||
local r g b hex
|
||||
|
||||
if validate_hex "$color_input"; then
|
||||
hex="$color_input"
|
||||
[[ "$hex" != \#* ]] && hex="#$hex"
|
||||
# Expand shorthand
|
||||
hex="${hex#\#}"
|
||||
if [[ ${#hex} -eq 3 ]]; then
|
||||
hex="${hex:0:1}${hex:0:1}${hex:1:1}${hex:1:1}${hex:2:1}${hex:2:1}"
|
||||
fi
|
||||
hex="#${hex^^}"
|
||||
elif validate_rgb "$color_input"; then
|
||||
IFS=',' read -r r g b <<< "$color_input"
|
||||
hex=$(rgb_to_hex "$r" "$g" "$b")
|
||||
else
|
||||
error "Invalid color format. Use hex (e.g., #FF0000) or RGB (e.g., 255,0,0)"
|
||||
fi
|
||||
|
||||
if [[ "$raw_mode" != "true" ]]; then
|
||||
info "Calculating optimal CSS filter for $hex..."
|
||||
echo ""
|
||||
fi
|
||||
|
||||
local result
|
||||
result=$(solve_filters "$hex")
|
||||
|
||||
IFS='|' read -r filter loss <<< "$result"
|
||||
|
||||
display_result "$hex" "$filter" "$loss" "$raw_mode"
|
||||
|
||||
if [[ "$auto_copy" == "true" ]]; then
|
||||
if copy_to_clipboard "filter: $filter;"; then
|
||||
success "CSS filter copied to clipboard!"
|
||||
else
|
||||
warning "Could not copy to clipboard (install xclip, xsel, wl-copy, or pbcopy)"
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Main Script Logic
|
||||
# ============================================================================
|
||||
|
||||
main() {
|
||||
check_dependencies
|
||||
|
||||
local color_input=""
|
||||
local interactive=false
|
||||
local raw_mode=false
|
||||
local auto_copy=false
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
-h|--help)
|
||||
print_usage
|
||||
exit 0
|
||||
;;
|
||||
-i|--interactive)
|
||||
interactive=true
|
||||
shift
|
||||
;;
|
||||
-r|--raw)
|
||||
raw_mode=true
|
||||
shift
|
||||
;;
|
||||
-c|--copy)
|
||||
auto_copy=true
|
||||
shift
|
||||
;;
|
||||
-*)
|
||||
error "Unknown option: $1. Use --help for usage information."
|
||||
;;
|
||||
*)
|
||||
color_input="$1"
|
||||
shift
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [[ "$interactive" == "true" ]]; then
|
||||
interactive_mode
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [[ -z "$color_input" ]]; then
|
||||
error "No color specified. Use --help for usage information."
|
||||
fi
|
||||
|
||||
process_color "$color_input" "$raw_mode" "$auto_copy"
|
||||
}
|
||||
|
||||
main "$@"
|
||||
980
css_color_palette.sh
Executable file
980
css_color_palette.sh
Executable file
@@ -0,0 +1,980 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
#############################################################################
|
||||
# CSS Color Palette Generator (Pure Bash)
|
||||
#
|
||||
# Generate comprehensive color palettes with tints, shades, and tones
|
||||
# No Node.js required - pure bash implementation
|
||||
#
|
||||
# Usage:
|
||||
# css_color_palette.sh <COLOR> [OPTIONS]
|
||||
#
|
||||
# Arguments:
|
||||
# COLOR Base hex color (e.g., #3498db, 3498db)
|
||||
#
|
||||
# Options:
|
||||
# -p, --palette TYPE Palette type: monochromatic, analogous, complementary,
|
||||
# split-complementary, triadic, tetradic (default: monochromatic)
|
||||
# -o, --output FILE Output file (default: ./colors.yaml)
|
||||
# -m, --mode MODE Color mode: light, dark (default: light)
|
||||
# -s, --style STYLE Generate style variations: shades, tints, tones, all
|
||||
# -n, --name NAME Color palette name (default: auto-generated)
|
||||
# --scales N Number of scale steps (default: 11)
|
||||
# -i, --interactive Interactive mode
|
||||
# -v, --verbose Verbose output with color preview
|
||||
# -h, --help Show this help message
|
||||
#
|
||||
#############################################################################
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# ============================================================================
|
||||
# Color Definitions
|
||||
# ============================================================================
|
||||
|
||||
RED="" GREEN="" YELLOW="" BLUE="" MAGENTA="" CYAN="" BOLD="" DIM="" RESET=""
|
||||
COLORS=0
|
||||
|
||||
if [[ -t 1 ]] && command -v tput >/dev/null 2>&1; then
|
||||
COLORS=$(tput colors 2>/dev/null || echo 0)
|
||||
if [[ ${COLORS:-0} -ge 8 ]]; then
|
||||
RED=$(tput setaf 1 2>/dev/null || echo "")
|
||||
GREEN=$(tput setaf 2 2>/dev/null || echo "")
|
||||
YELLOW=$(tput setaf 3 2>/dev/null || echo "")
|
||||
BLUE=$(tput setaf 4 2>/dev/null || echo "")
|
||||
MAGENTA=$(tput setaf 5 2>/dev/null || echo "")
|
||||
CYAN=$(tput setaf 6 2>/dev/null || echo "")
|
||||
BOLD=$(tput bold 2>/dev/null || echo "")
|
||||
DIM=$(tput dim 2>/dev/null || echo "")
|
||||
RESET=$(tput sgr0 2>/dev/null || echo "")
|
||||
fi
|
||||
fi
|
||||
|
||||
# ============================================================================
|
||||
# Global Variables
|
||||
# ============================================================================
|
||||
|
||||
BASE_COLOR=""
|
||||
PALETTE_TYPE="monochromatic"
|
||||
OUTPUT_FILE="./colors.yaml"
|
||||
COLOR_MODE="light"
|
||||
STYLE_TYPE="all"
|
||||
PALETTE_NAME=""
|
||||
SCALE_STEPS=11
|
||||
INTERACTIVE=false
|
||||
VERBOSE=false
|
||||
|
||||
# Associative arrays for storing palette data
|
||||
declare -A PALETTE_DATA
|
||||
declare -a COLOR_GROUPS
|
||||
|
||||
# ============================================================================
|
||||
# Helper Functions
|
||||
# ============================================================================
|
||||
|
||||
print_usage() {
|
||||
cat << EOF
|
||||
${BOLD}CSS Color Palette Generator (Pure Bash)${RESET}
|
||||
|
||||
Generate comprehensive color palettes without Node.js dependencies.
|
||||
|
||||
${BOLD}USAGE:${RESET}
|
||||
$(basename "$0") COLOR [OPTIONS]
|
||||
|
||||
${BOLD}ARGUMENTS:${RESET}
|
||||
COLOR Base hex color (e.g., #3498db, 3498db)
|
||||
|
||||
${BOLD}OPTIONS:${RESET}
|
||||
-p, --palette TYPE Palette type: monochromatic, analogous, complementary,
|
||||
split-complementary, triadic, tetradic
|
||||
-o, --output FILE Output file (default: ./colors.yaml)
|
||||
-m, --mode MODE Color mode: light, dark (default: light)
|
||||
-s, --style STYLE Style: shades, tints, tones, all (default: all)
|
||||
-n, --name NAME Palette name (default: auto-generated)
|
||||
--scales N Number of scale steps (default: 11)
|
||||
-i, --interactive Interactive mode
|
||||
-v, --verbose Verbose output with color preview
|
||||
-h, --help Show this help message
|
||||
|
||||
${BOLD}DEPENDENCIES:${RESET}
|
||||
bc For floating-point arithmetic
|
||||
|
||||
${BOLD}EXAMPLES:${RESET}
|
||||
$(basename "$0") "#3498db"
|
||||
$(basename "$0") "#3498db" -p triadic -o palette.json
|
||||
$(basename "$0") "ff5733" -p analogous -m dark
|
||||
|
||||
EOF
|
||||
}
|
||||
|
||||
error() {
|
||||
echo "${RED}${BOLD}Error:${RESET} $1" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
info() {
|
||||
echo "${BLUE}${BOLD}==>${RESET} $1"
|
||||
}
|
||||
|
||||
success() {
|
||||
echo "${GREEN}${BOLD}[OK]${RESET} $1"
|
||||
}
|
||||
|
||||
warning() {
|
||||
echo "${YELLOW}${BOLD}[WARN]${RESET} $1"
|
||||
}
|
||||
|
||||
# Check for bc dependency
|
||||
check_dependencies() {
|
||||
if ! command -v bc >/dev/null 2>&1; then
|
||||
error "bc is required but not found. Please install bc (apt-get install bc)"
|
||||
fi
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Math Utilities
|
||||
# ============================================================================
|
||||
|
||||
# Floating point comparison
|
||||
bc_calc() {
|
||||
local expr="$1"
|
||||
# Remove any leading/trailing whitespace
|
||||
expr=$(echo "$expr" | tr -d ' ')
|
||||
# Check if expression is empty
|
||||
if [[ -z "$expr" ]]; then
|
||||
echo "0"
|
||||
return
|
||||
fi
|
||||
echo "scale=6; $expr" | bc -l 2>/dev/null || echo "0"
|
||||
}
|
||||
|
||||
# Boolean bc comparison (returns 0 for true, 1 for false)
|
||||
bc_compare() {
|
||||
local result=$(echo "$1" | bc -l 2>/dev/null || echo "0")
|
||||
[[ "$result" == "1" ]]
|
||||
}
|
||||
|
||||
min() {
|
||||
local a=$1 b=$2
|
||||
if bc_compare "$a < $b"; then
|
||||
echo "$a"
|
||||
else
|
||||
echo "$b"
|
||||
fi
|
||||
}
|
||||
|
||||
max() {
|
||||
local a=$1 b=$2
|
||||
if bc_compare "$a > $b"; then
|
||||
echo "$a"
|
||||
else
|
||||
echo "$b"
|
||||
fi
|
||||
}
|
||||
|
||||
clamp() {
|
||||
local val=$1 min_val=$2 max_val=$3
|
||||
val=$(max "$val" "$min_val")
|
||||
val=$(min "$val" "$max_val")
|
||||
echo "$val"
|
||||
}
|
||||
|
||||
round() {
|
||||
LC_NUMERIC=C printf "%.0f" "$1"
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Color Validation and Normalization
|
||||
# ============================================================================
|
||||
|
||||
validate_hex() {
|
||||
local hex="$1"
|
||||
hex="${hex#\#}"
|
||||
if [[ "$hex" =~ ^[0-9A-Fa-f]{3}$ ]] || [[ "$hex" =~ ^[0-9A-Fa-f]{6}$ ]]; then
|
||||
return 0
|
||||
fi
|
||||
return 1
|
||||
}
|
||||
|
||||
normalize_hex() {
|
||||
local hex="$1"
|
||||
hex="${hex#\#}"
|
||||
# Expand shorthand
|
||||
if [[ ${#hex} -eq 3 ]]; then
|
||||
hex="${hex:0:1}${hex:0:1}${hex:1:1}${hex:1:1}${hex:2:1}${hex:2:1}"
|
||||
fi
|
||||
echo "#${hex^^}"
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Color Conversion Functions
|
||||
# ============================================================================
|
||||
|
||||
# Convert hex to RGB (returns "r g b")
|
||||
hex_to_rgb() {
|
||||
local hex="$1"
|
||||
hex="${hex#\#}"
|
||||
|
||||
if [[ ${#hex} -eq 3 ]]; then
|
||||
hex="${hex:0:1}${hex:0:1}${hex:1:1}${hex:1:1}${hex:2:1}${hex:2:1}"
|
||||
fi
|
||||
|
||||
local r=$((16#${hex:0:2}))
|
||||
local g=$((16#${hex:2:2}))
|
||||
local b=$((16#${hex:4:2}))
|
||||
|
||||
echo "$r $g $b"
|
||||
}
|
||||
|
||||
# Convert RGB to hex
|
||||
rgb_to_hex() {
|
||||
local r=$(round "$1")
|
||||
local g=$(round "$2")
|
||||
local b=$(round "$3")
|
||||
|
||||
# Clamp values
|
||||
r=$(clamp "$r" 0 255)
|
||||
g=$(clamp "$g" 0 255)
|
||||
b=$(clamp "$b" 0 255)
|
||||
|
||||
# Use C locale to ensure proper number formatting
|
||||
LC_NUMERIC=C printf "#%02X%02X%02X" "$r" "$g" "$b"
|
||||
}
|
||||
|
||||
# Convert RGB to HSL (returns "h s l")
|
||||
rgb_to_hsl() {
|
||||
local r=$1 g=$2 b=$3
|
||||
|
||||
# Normalize to 0-1
|
||||
r=$(bc_calc "$r / 255")
|
||||
g=$(bc_calc "$g / 255")
|
||||
b=$(bc_calc "$b / 255")
|
||||
|
||||
local max=$(max "$r" "$(max "$g" "$b")")
|
||||
local min=$(min "$r" "$(min "$g" "$b")")
|
||||
local delta=$(bc_calc "$max - $min")
|
||||
|
||||
local h=0 s=0 l
|
||||
l=$(bc_calc "($max + $min) / 2")
|
||||
|
||||
if bc_compare "$delta != 0"; then
|
||||
# Calculate saturation
|
||||
if bc_compare "$l < 0.5"; then
|
||||
s=$(bc_calc "$delta / ($max + $min)")
|
||||
else
|
||||
s=$(bc_calc "$delta / (2 - $max - $min)")
|
||||
fi
|
||||
|
||||
# Calculate hue
|
||||
if bc_compare "$max == $r"; then
|
||||
h=$(bc_calc "(($g - $b) / $delta) + (if ($g < $b) then 6 else 0)")
|
||||
elif bc_compare "$max == $g"; then
|
||||
h=$(bc_calc "(($b - $r) / $delta) + 2")
|
||||
else
|
||||
h=$(bc_calc "(($r - $g) / $delta) + 4")
|
||||
fi
|
||||
|
||||
h=$(bc_calc "$h / 6")
|
||||
fi
|
||||
|
||||
# Convert to degrees and percentages
|
||||
h=$(bc_calc "$h * 360")
|
||||
s=$(bc_calc "$s * 100")
|
||||
l=$(bc_calc "$l * 100")
|
||||
|
||||
echo "$h $s $l"
|
||||
}
|
||||
|
||||
# Helper for HSL to RGB conversion
|
||||
hue_to_rgb() {
|
||||
local p=$1 q=$2 t=$3
|
||||
|
||||
# Normalize t to 0-1
|
||||
if bc_compare "$t < 0"; then
|
||||
t=$(bc_calc "$t + 1")
|
||||
fi
|
||||
if bc_compare "$t > 1"; then
|
||||
t=$(bc_calc "$t - 1")
|
||||
fi
|
||||
|
||||
if bc_compare "$t < 0.166666"; then
|
||||
echo "$(bc_calc "$p + ($q - $p) * 6 * $t")"
|
||||
elif bc_compare "$t < 0.5"; then
|
||||
echo "$q"
|
||||
elif bc_compare "$t < 0.666666"; then
|
||||
echo "$(bc_calc "$p + ($q - $p) * (0.666666 - $t) * 6")"
|
||||
else
|
||||
echo "$p"
|
||||
fi
|
||||
}
|
||||
|
||||
# Convert HSL to RGB (returns "r g b")
|
||||
hsl_to_rgb() {
|
||||
local h=$1 s=$2 l=$3
|
||||
|
||||
# Normalize
|
||||
h=$(bc_calc "$h / 360")
|
||||
s=$(bc_calc "$s / 100")
|
||||
l=$(bc_calc "$l / 100")
|
||||
|
||||
local r g b
|
||||
|
||||
if bc_compare "$s == 0"; then
|
||||
# Achromatic (gray)
|
||||
r=$l
|
||||
g=$l
|
||||
b=$l
|
||||
else
|
||||
local q
|
||||
if bc_compare "$l < 0.5"; then
|
||||
q=$(bc_calc "$l * (1 + $s)")
|
||||
else
|
||||
q=$(bc_calc "$l + $s - $l * $s")
|
||||
fi
|
||||
|
||||
local p=$(bc_calc "2 * $l - $q")
|
||||
|
||||
r=$(hue_to_rgb "$p" "$q" "$(bc_calc "$h + 0.333333")")
|
||||
g=$(hue_to_rgb "$p" "$q" "$h")
|
||||
b=$(hue_to_rgb "$p" "$q" "$(bc_calc "$h - 0.333333")")
|
||||
fi
|
||||
|
||||
# Convert to 0-255
|
||||
r=$(bc_calc "$r * 255")
|
||||
g=$(bc_calc "$g * 255")
|
||||
b=$(bc_calc "$b * 255")
|
||||
|
||||
echo "$r $g $b"
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Color Manipulation Functions
|
||||
# ============================================================================
|
||||
|
||||
# Adjust hue (degrees)
|
||||
adjust_hue() {
|
||||
local h=$1 adjustment=$2
|
||||
h=$(bc_calc "$h + $adjustment")
|
||||
|
||||
# Normalize to 0-360
|
||||
while bc_compare "$h < 0"; do
|
||||
h=$(bc_calc "$h + 360")
|
||||
done
|
||||
while bc_compare "$h >= 360"; do
|
||||
h=$(bc_calc "$h - 360")
|
||||
done
|
||||
|
||||
echo "$h"
|
||||
}
|
||||
|
||||
# Generate tint (mix with white)
|
||||
generate_tint() {
|
||||
local hex="$1"
|
||||
local percentage=$2
|
||||
|
||||
read -r r g b <<< "$(hex_to_rgb "$hex")"
|
||||
|
||||
r=$(bc_calc "$r + (255 - $r) * ($percentage / 100)")
|
||||
g=$(bc_calc "$g + (255 - $g) * ($percentage / 100)")
|
||||
b=$(bc_calc "$b + (255 - $b) * ($percentage / 100)")
|
||||
|
||||
rgb_to_hex "$r" "$g" "$b"
|
||||
}
|
||||
|
||||
# Generate shade (mix with black)
|
||||
generate_shade() {
|
||||
local hex="$1"
|
||||
local percentage=$2
|
||||
|
||||
read -r r g b <<< "$(hex_to_rgb "$hex")"
|
||||
|
||||
r=$(bc_calc "$r * (1 - $percentage / 100)")
|
||||
g=$(bc_calc "$g * (1 - $percentage / 100)")
|
||||
b=$(bc_calc "$b * (1 - $percentage / 100)")
|
||||
|
||||
rgb_to_hex "$r" "$g" "$b"
|
||||
}
|
||||
|
||||
# Generate tone (mix with gray)
|
||||
generate_tone() {
|
||||
local hex="$1"
|
||||
local percentage=$2
|
||||
|
||||
read -r r g b <<< "$(hex_to_rgb "$hex")"
|
||||
|
||||
local gray=$(bc_calc "($r + $g + $b) / 3")
|
||||
|
||||
r=$(bc_calc "$r + ($gray - $r) * ($percentage / 100)")
|
||||
g=$(bc_calc "$g + ($gray - $g) * ($percentage / 100)")
|
||||
b=$(bc_calc "$b + ($gray - $b) * ($percentage / 100)")
|
||||
|
||||
rgb_to_hex "$r" "$g" "$b"
|
||||
}
|
||||
|
||||
# Adjust lightness
|
||||
adjust_lightness() {
|
||||
local hex="$1"
|
||||
local adjustment=$2
|
||||
|
||||
read -r r g b <<< "$(hex_to_rgb "$hex")"
|
||||
read -r h s l <<< "$(rgb_to_hsl "$r" "$g" "$b")"
|
||||
|
||||
l=$(bc_calc "$l + $adjustment")
|
||||
l=$(clamp "$l" 0 100)
|
||||
|
||||
read -r r g b <<< "$(hsl_to_rgb "$h" "$s" "$l")"
|
||||
rgb_to_hex "$r" "$g" "$b"
|
||||
}
|
||||
|
||||
# Adjust saturation
|
||||
adjust_saturation() {
|
||||
local hex="$1"
|
||||
local adjustment=$2
|
||||
|
||||
read -r r g b <<< "$(hex_to_rgb "$hex")"
|
||||
read -r h s l <<< "$(rgb_to_hsl "$r" "$g" "$b")"
|
||||
|
||||
s=$(bc_calc "$s + $adjustment")
|
||||
s=$(clamp "$s" 0 100)
|
||||
|
||||
read -r r g b <<< "$(hsl_to_rgb "$h" "$s" "$l")"
|
||||
rgb_to_hex "$r" "$g" "$b"
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Scale Generation
|
||||
# ============================================================================
|
||||
|
||||
generate_color_scale() {
|
||||
local base_hex="$1"
|
||||
local group_name="$2"
|
||||
local style="$3"
|
||||
|
||||
local -a scale_values=(50 100 200 300 400 500 600 700 800 900 950)
|
||||
local base_index=5 # 500 is the base
|
||||
|
||||
# Set base color
|
||||
PALETTE_DATA["${group_name}.500"]="$base_hex"
|
||||
|
||||
# Generate lighter variations (50-400)
|
||||
for i in {4..0}; do
|
||||
local step=$((base_index - i))
|
||||
local scale_val=${scale_values[$i]}
|
||||
local color
|
||||
|
||||
if [[ "$style" == "tints" ]]; then
|
||||
local percentage=$(bc_calc "$step / $base_index * 85")
|
||||
color=$(generate_tint "$base_hex" "$percentage")
|
||||
elif [[ "$style" == "tones" ]]; then
|
||||
read -r r g b <<< "$(hex_to_rgb "$base_hex")"
|
||||
read -r h s l <<< "$(rgb_to_hsl "$r" "$g" "$b")"
|
||||
local new_l=$(bc_calc "95 - $i * 8")
|
||||
local new_s=$(bc_calc "$(max 10 "$(bc_calc "$s - ($base_index - $i) * 5")")")
|
||||
read -r r g b <<< "$(hsl_to_rgb "$h" "$new_s" "$new_l")"
|
||||
color=$(rgb_to_hex "$r" "$g" "$b")
|
||||
else
|
||||
# Default: lighten
|
||||
local adjustment=$(bc_calc "$step * 12")
|
||||
color=$(adjust_lightness "$base_hex" "$adjustment")
|
||||
if [[ $i -le 2 ]]; then
|
||||
local sat_adj=$(bc_calc "-($base_index - $i) * 8")
|
||||
color=$(adjust_saturation "$color" "$sat_adj")
|
||||
fi
|
||||
fi
|
||||
|
||||
PALETTE_DATA["${group_name}.${scale_val}"]="$color"
|
||||
done
|
||||
|
||||
# Generate darker variations (600-950)
|
||||
for i in {6..10}; do
|
||||
local step=$((i - base_index))
|
||||
local scale_val=${scale_values[$i]}
|
||||
local color
|
||||
|
||||
if [[ "$style" == "shades" ]]; then
|
||||
local percentage=$(bc_calc "$step / (${#scale_values[@]} - $base_index - 1) * 75")
|
||||
color=$(generate_shade "$base_hex" "$percentage")
|
||||
elif [[ "$style" == "tones" ]]; then
|
||||
read -r r g b <<< "$(hex_to_rgb "$base_hex")"
|
||||
read -r h s l <<< "$(rgb_to_hsl "$r" "$g" "$b")"
|
||||
local new_l=$(bc_calc "45 - ($i - $base_index) * 7")
|
||||
new_l=$(max 5 "$new_l")
|
||||
local new_s=$(bc_calc "$(max 10 "$(bc_calc "$s - $step * 3")")")
|
||||
read -r r g b <<< "$(hsl_to_rgb "$h" "$new_s" "$new_l")"
|
||||
color=$(rgb_to_hex "$r" "$g" "$b")
|
||||
else
|
||||
# Default: darken
|
||||
local adjustment=$(bc_calc "-$step * 10")
|
||||
color=$(adjust_lightness "$base_hex" "$adjustment")
|
||||
if [[ $i -ge 9 ]]; then
|
||||
local sat_adj=$(bc_calc "-$step * 5")
|
||||
color=$(adjust_saturation "$color" "$sat_adj")
|
||||
fi
|
||||
fi
|
||||
|
||||
PALETTE_DATA["${group_name}.${scale_val}"]="$color"
|
||||
done
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Palette Generation
|
||||
# ============================================================================
|
||||
|
||||
generate_palette() {
|
||||
local base_hex="$1"
|
||||
local palette_type="$2"
|
||||
local style="$3"
|
||||
|
||||
read -r r g b <<< "$(hex_to_rgb "$base_hex")"
|
||||
read -r h s l <<< "$(rgb_to_hsl "$r" "$g" "$b")"
|
||||
|
||||
case "$palette_type" in
|
||||
monochromatic)
|
||||
COLOR_GROUPS=("primary")
|
||||
generate_color_scale "$base_hex" "primary" "$style"
|
||||
;;
|
||||
|
||||
analogous)
|
||||
COLOR_GROUPS=("primary" "analogous1" "analogous2")
|
||||
generate_color_scale "$base_hex" "primary" "$style"
|
||||
|
||||
# Analogous 1: -30 degrees
|
||||
local h1=$(adjust_hue "$h" -30)
|
||||
read -r r g b <<< "$(hsl_to_rgb "$h1" "$s" "$l")"
|
||||
local color1=$(rgb_to_hex "$r" "$g" "$b")
|
||||
generate_color_scale "$color1" "analogous1" "$style"
|
||||
|
||||
# Analogous 2: +30 degrees
|
||||
local h2=$(adjust_hue "$h" 30)
|
||||
read -r r g b <<< "$(hsl_to_rgb "$h2" "$s" "$l")"
|
||||
local color2=$(rgb_to_hex "$r" "$g" "$b")
|
||||
generate_color_scale "$color2" "analogous2" "$style"
|
||||
;;
|
||||
|
||||
complementary)
|
||||
COLOR_GROUPS=("primary" "complement")
|
||||
generate_color_scale "$base_hex" "primary" "$style"
|
||||
|
||||
# Complement: 180 degrees
|
||||
local hc=$(adjust_hue "$h" 180)
|
||||
read -r r g b <<< "$(hsl_to_rgb "$hc" "$s" "$l")"
|
||||
local colorc=$(rgb_to_hex "$r" "$g" "$b")
|
||||
generate_color_scale "$colorc" "complement" "$style"
|
||||
;;
|
||||
|
||||
split-complementary)
|
||||
COLOR_GROUPS=("primary" "split1" "split2")
|
||||
generate_color_scale "$base_hex" "primary" "$style"
|
||||
|
||||
# Split 1: 150 degrees
|
||||
local hs1=$(adjust_hue "$h" 150)
|
||||
read -r r g b <<< "$(hsl_to_rgb "$hs1" "$s" "$l")"
|
||||
local colors1=$(rgb_to_hex "$r" "$g" "$b")
|
||||
generate_color_scale "$colors1" "split1" "$style"
|
||||
|
||||
# Split 2: 210 degrees
|
||||
local hs2=$(adjust_hue "$h" 210)
|
||||
read -r r g b <<< "$(hsl_to_rgb "$hs2" "$s" "$l")"
|
||||
local colors2=$(rgb_to_hex "$r" "$g" "$b")
|
||||
generate_color_scale "$colors2" "split2" "$style"
|
||||
;;
|
||||
|
||||
triadic)
|
||||
COLOR_GROUPS=("primary" "triadic1" "triadic2")
|
||||
generate_color_scale "$base_hex" "primary" "$style"
|
||||
|
||||
# Triadic 1: 120 degrees
|
||||
local ht1=$(adjust_hue "$h" 120)
|
||||
read -r r g b <<< "$(hsl_to_rgb "$ht1" "$s" "$l")"
|
||||
local colort1=$(rgb_to_hex "$r" "$g" "$b")
|
||||
generate_color_scale "$colort1" "triadic1" "$style"
|
||||
|
||||
# Triadic 2: 240 degrees
|
||||
local ht2=$(adjust_hue "$h" 240)
|
||||
read -r r g b <<< "$(hsl_to_rgb "$ht2" "$s" "$l")"
|
||||
local colort2=$(rgb_to_hex "$r" "$g" "$b")
|
||||
generate_color_scale "$colort2" "triadic2" "$style"
|
||||
;;
|
||||
|
||||
tetradic)
|
||||
COLOR_GROUPS=("primary" "tetradic1" "tetradic2" "tetradic3")
|
||||
generate_color_scale "$base_hex" "primary" "$style"
|
||||
|
||||
# Tetradic colors: 90, 180, 270 degrees
|
||||
for deg in 90 180 270; do
|
||||
local idx=$((deg / 90))
|
||||
local hn=$(adjust_hue "$h" "$deg")
|
||||
read -r r g b <<< "$(hsl_to_rgb "$hn" "$s" "$l")"
|
||||
local colorn=$(rgb_to_hex "$r" "$g" "$b")
|
||||
generate_color_scale "$colorn" "tetradic${idx}" "$style"
|
||||
done
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Output Functions
|
||||
# ============================================================================
|
||||
|
||||
generate_yaml_output() {
|
||||
local name="$1"
|
||||
local type="$2"
|
||||
local mode="$3"
|
||||
local style="$4"
|
||||
local base="$5"
|
||||
|
||||
cat << EOF
|
||||
name: '$name'
|
||||
type: '$type'
|
||||
mode: '$mode'
|
||||
style: '$style'
|
||||
base: '$base'
|
||||
colors:
|
||||
EOF
|
||||
|
||||
for group in "${COLOR_GROUPS[@]}"; do
|
||||
echo " ${group}:"
|
||||
for scale in 50 100 200 300 400 500 600 700 800 900 950; do
|
||||
local key="${group}.${scale}"
|
||||
if [[ -n "${PALETTE_DATA[$key]:-}" ]]; then
|
||||
LC_NUMERIC=C printf " %s: '%s'\n" "$scale" "${PALETTE_DATA[$key]}"
|
||||
fi
|
||||
done
|
||||
done
|
||||
|
||||
cat << EOF
|
||||
metadata:
|
||||
generated: '$(date -u +"%Y-%m-%dT%H:%M:%SZ")'
|
||||
generator: 'css_color_palette.sh'
|
||||
version: '1.0.0'
|
||||
EOF
|
||||
}
|
||||
|
||||
generate_json_output() {
|
||||
local name="$1"
|
||||
local type="$2"
|
||||
local mode="$3"
|
||||
local style="$4"
|
||||
local base="$5"
|
||||
|
||||
echo "{"
|
||||
echo " \"name\": \"$name\","
|
||||
echo " \"type\": \"$type\","
|
||||
echo " \"mode\": \"$mode\","
|
||||
echo " \"style\": \"$style\","
|
||||
echo " \"base\": \"$base\","
|
||||
echo " \"colors\": {"
|
||||
|
||||
local group_count=0
|
||||
for group in "${COLOR_GROUPS[@]}"; do
|
||||
((group_count++))
|
||||
echo " \"${group}\": {"
|
||||
|
||||
local scale_count=0
|
||||
for scale in 50 100 200 300 400 500 600 700 800 900 950; do
|
||||
local key="${group}.${scale}"
|
||||
if [[ -n "${PALETTE_DATA[$key]:-}" ]]; then
|
||||
((scale_count++))
|
||||
if [[ $scale_count -lt 11 ]]; then
|
||||
echo " \"$scale\": \"${PALETTE_DATA[$key]}\","
|
||||
else
|
||||
echo " \"$scale\": \"${PALETTE_DATA[$key]}\""
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ $group_count -lt ${#COLOR_GROUPS[@]} ]]; then
|
||||
echo " },"
|
||||
else
|
||||
echo " }"
|
||||
fi
|
||||
done
|
||||
|
||||
echo " },"
|
||||
echo " \"metadata\": {"
|
||||
echo " \"generated\": \"$(date -u +"%Y-%m-%dT%H:%M:%SZ")\","
|
||||
echo " \"generator\": \"css_color_palette_bash.sh\","
|
||||
echo " \"version\": \"1.0.0\""
|
||||
echo " }"
|
||||
echo "}"
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Display Functions
|
||||
# ============================================================================
|
||||
|
||||
draw_color_swatch() {
|
||||
local hex="$1"
|
||||
local label="$2"
|
||||
local hex_clean="${hex#\#}"
|
||||
|
||||
read -r r g b <<< "$(hex_to_rgb "$hex")"
|
||||
|
||||
if [[ ${COLORS:-0} -ge 256 ]]; then
|
||||
local bg="\033[48;2;${r};${g};${b}m"
|
||||
local fg="\033[38;2;${r};${g};${b}m"
|
||||
local reset="\033[0m"
|
||||
|
||||
# Determine text color based on luminance
|
||||
local luminance=$(bc_calc "(0.299*$r + 0.587*$g + 0.114*$b)/255")
|
||||
local text_color
|
||||
if bc_compare "$luminance > 0.5"; then
|
||||
text_color="\033[38;2;0;0;0m"
|
||||
else
|
||||
text_color="\033[38;2;255;255;255m"
|
||||
fi
|
||||
|
||||
LC_NUMERIC=C printf "${bg}${text_color} %-20s ${reset} ${fg}%s${reset} %s\n" "$label" "■" "$hex"
|
||||
else
|
||||
LC_NUMERIC=C printf " %-20s %s\n" "$label" "$hex"
|
||||
fi
|
||||
}
|
||||
|
||||
display_palette_preview() {
|
||||
if [[ "$VERBOSE" != "true" ]]; then
|
||||
return
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "${BOLD}================================================================${RESET}"
|
||||
echo "${BOLD} Color Palette Preview ${RESET}"
|
||||
echo "${BOLD}================================================================${RESET}"
|
||||
echo ""
|
||||
|
||||
echo "${BOLD}Palette Name:${RESET} $PALETTE_NAME"
|
||||
echo "${BOLD}Type:${RESET} $PALETTE_TYPE"
|
||||
echo "${BOLD}Base Color:${RESET} $BASE_COLOR"
|
||||
echo ""
|
||||
|
||||
for group in "${COLOR_GROUPS[@]}"; do
|
||||
echo "${BOLD}${CYAN}${group}:${RESET}"
|
||||
for scale in 50 100 200 300 400 500 600 700 800 900 950; do
|
||||
local key="${group}.${scale}"
|
||||
if [[ -n "${PALETTE_DATA[$key]:-}" ]]; then
|
||||
draw_color_swatch "${PALETTE_DATA[$key]}" "${group}.${scale}"
|
||||
fi
|
||||
done
|
||||
echo ""
|
||||
done
|
||||
|
||||
echo "${BOLD}----------------------------------------------------------------${RESET}"
|
||||
echo ""
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Interactive Mode
|
||||
# ============================================================================
|
||||
|
||||
interactive_mode() {
|
||||
echo ""
|
||||
echo "${BOLD}${BLUE}+================================================================+${RESET}"
|
||||
echo "${BOLD}${BLUE}| CSS Color Palette Generator (Interactive) |${RESET}"
|
||||
echo "${BOLD}${BLUE}+================================================================+${RESET}"
|
||||
echo ""
|
||||
|
||||
while true; do
|
||||
echo -n "${BOLD}Enter base color${RESET} ${DIM}(hex, or 'q' to quit):${RESET} "
|
||||
read -r color_input
|
||||
|
||||
if [[ "$color_input" =~ ^[qQ]$ ]]; then
|
||||
echo ""
|
||||
success "Goodbye!"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [[ -z "$color_input" ]]; then
|
||||
continue
|
||||
fi
|
||||
|
||||
if ! validate_hex "$color_input"; then
|
||||
warning "Invalid hex color format"
|
||||
continue
|
||||
fi
|
||||
|
||||
local hex=$(normalize_hex "$color_input")
|
||||
|
||||
echo ""
|
||||
echo -n "${BOLD}Palette type${RESET} ${DIM}[monochromatic]:${RESET} "
|
||||
read -r palette_input
|
||||
palette_input=${palette_input:-monochromatic}
|
||||
|
||||
echo -n "${BOLD}Output file${RESET} ${DIM}[./colors.yaml]:${RESET} "
|
||||
read -r output_input
|
||||
output_input=${output_input:-./colors.yaml}
|
||||
|
||||
echo -n "${BOLD}Style${RESET} ${DIM}[all]:${RESET} "
|
||||
read -r style_input
|
||||
style_input=${style_input:-all}
|
||||
|
||||
echo ""
|
||||
info "Generating palette..."
|
||||
|
||||
# Reset palette data
|
||||
PALETTE_DATA=()
|
||||
COLOR_GROUPS=()
|
||||
|
||||
BASE_COLOR="$hex"
|
||||
PALETTE_TYPE="$palette_input"
|
||||
OUTPUT_FILE="$output_input"
|
||||
STYLE_TYPE="$style_input"
|
||||
PALETTE_NAME="${palette_input}-${hex//\#/}"
|
||||
|
||||
generate_palette "$BASE_COLOR" "$PALETTE_TYPE" "$STYLE_TYPE"
|
||||
|
||||
VERBOSE=true
|
||||
display_palette_preview
|
||||
VERBOSE=false
|
||||
|
||||
# Generate output
|
||||
local extension="${OUTPUT_FILE##*.}"
|
||||
if [[ "$extension" == "json" ]]; then
|
||||
generate_json_output "$PALETTE_NAME" "$PALETTE_TYPE" "$COLOR_MODE" "$STYLE_TYPE" "$BASE_COLOR" > "$OUTPUT_FILE"
|
||||
else
|
||||
generate_yaml_output "$PALETTE_NAME" "$PALETTE_TYPE" "$COLOR_MODE" "$STYLE_TYPE" "$BASE_COLOR" > "$OUTPUT_FILE"
|
||||
fi
|
||||
|
||||
success "Palette saved to: ${BOLD}$OUTPUT_FILE${RESET}"
|
||||
|
||||
# Count colors
|
||||
local total=0
|
||||
for key in "${!PALETTE_DATA[@]}"; do
|
||||
((total++))
|
||||
done
|
||||
info "Total colors generated: $total"
|
||||
|
||||
echo ""
|
||||
echo -n "${DIM}Press Enter to continue...${RESET}"
|
||||
read -r
|
||||
echo ""
|
||||
done
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Main Script Logic
|
||||
# ============================================================================
|
||||
|
||||
main() {
|
||||
# Check dependencies
|
||||
check_dependencies
|
||||
|
||||
# Parse arguments
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
-h|--help)
|
||||
print_usage
|
||||
exit 0
|
||||
;;
|
||||
-p|--palette)
|
||||
PALETTE_TYPE="$2"
|
||||
shift 2
|
||||
;;
|
||||
-o|--output)
|
||||
OUTPUT_FILE="$2"
|
||||
shift 2
|
||||
;;
|
||||
-m|--mode)
|
||||
COLOR_MODE="$2"
|
||||
shift 2
|
||||
;;
|
||||
-s|--style)
|
||||
STYLE_TYPE="$2"
|
||||
shift 2
|
||||
;;
|
||||
-n|--name)
|
||||
PALETTE_NAME="$2"
|
||||
shift 2
|
||||
;;
|
||||
--scales)
|
||||
SCALE_STEPS="$2"
|
||||
shift 2
|
||||
;;
|
||||
-i|--interactive)
|
||||
INTERACTIVE=true
|
||||
shift
|
||||
;;
|
||||
-v|--verbose)
|
||||
VERBOSE=true
|
||||
shift
|
||||
;;
|
||||
-*)
|
||||
error "Unknown option: $1. Use --help for usage information."
|
||||
;;
|
||||
*)
|
||||
BASE_COLOR="$1"
|
||||
shift
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
# Interactive mode
|
||||
if [[ "$INTERACTIVE" == "true" ]]; then
|
||||
interactive_mode
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Validate inputs
|
||||
if [[ -z "$BASE_COLOR" ]]; then
|
||||
error "No base color specified. Use --help for usage information."
|
||||
fi
|
||||
|
||||
if ! validate_hex "$BASE_COLOR"; then
|
||||
error "Invalid hex color format: $BASE_COLOR"
|
||||
fi
|
||||
|
||||
# Normalize color
|
||||
BASE_COLOR=$(normalize_hex "$BASE_COLOR")
|
||||
|
||||
# Auto-generate palette name if not provided
|
||||
if [[ -z "$PALETTE_NAME" ]]; then
|
||||
PALETTE_NAME="${PALETTE_TYPE}-${BASE_COLOR//\#/}"
|
||||
fi
|
||||
|
||||
# Validate palette type
|
||||
case "$PALETTE_TYPE" in
|
||||
monochromatic|analogous|complementary|split-complementary|triadic|tetradic)
|
||||
;;
|
||||
*)
|
||||
error "Invalid palette type: $PALETTE_TYPE"
|
||||
;;
|
||||
esac
|
||||
|
||||
# Validate style type
|
||||
case "$STYLE_TYPE" in
|
||||
all|shades|tints|tones)
|
||||
;;
|
||||
*)
|
||||
error "Invalid style type: $STYLE_TYPE"
|
||||
;;
|
||||
esac
|
||||
|
||||
# Generate palette
|
||||
if [[ "$VERBOSE" == "true" ]]; then
|
||||
info "Generating $PALETTE_TYPE palette from $BASE_COLOR..."
|
||||
fi
|
||||
|
||||
generate_palette "$BASE_COLOR" "$PALETTE_TYPE" "$STYLE_TYPE"
|
||||
|
||||
# Display preview if verbose
|
||||
display_palette_preview
|
||||
|
||||
# Generate output file
|
||||
local extension="${OUTPUT_FILE##*.}"
|
||||
if [[ "$extension" == "json" ]]; then
|
||||
generate_json_output "$PALETTE_NAME" "$PALETTE_TYPE" "$COLOR_MODE" "$STYLE_TYPE" "$BASE_COLOR" > "$OUTPUT_FILE"
|
||||
else
|
||||
generate_yaml_output "$PALETTE_NAME" "$PALETTE_TYPE" "$COLOR_MODE" "$STYLE_TYPE" "$BASE_COLOR" > "$OUTPUT_FILE"
|
||||
fi
|
||||
|
||||
success "Palette saved to: ${BOLD}$OUTPUT_FILE${RESET}"
|
||||
|
||||
if [[ "$VERBOSE" == "true" ]]; then
|
||||
local total=0
|
||||
for key in "${!PALETTE_DATA[@]}"; do
|
||||
((total++))
|
||||
done
|
||||
info "Total colors generated: $total"
|
||||
fi
|
||||
}
|
||||
|
||||
# Run main function
|
||||
main "$@"
|
||||
347
css_json_convert.sh
Executable file
347
css_json_convert.sh
Executable file
@@ -0,0 +1,347 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
#############################################
|
||||
# CSS Variable to JSON/YAML Converter
|
||||
# Extracts CSS custom properties (--var: value;)
|
||||
# and converts them to JSON or YAML format
|
||||
#############################################
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# Terminal colors using tput
|
||||
RED="" GREEN="" YELLOW="" BLUE="" MAGENTA="" CYAN="" BOLD="" DIM="" RESET=""
|
||||
|
||||
if [[ -t 1 ]] && command -v tput >/dev/null 2>&1; then
|
||||
COLORS=$(tput colors 2>/dev/null || echo 0)
|
||||
if [[ ${COLORS:-0} -ge 8 ]]; then
|
||||
RED=$(tput setaf 1 2>/dev/null || echo "")
|
||||
GREEN=$(tput setaf 2 2>/dev/null || echo "")
|
||||
YELLOW=$(tput setaf 3 2>/dev/null || echo "")
|
||||
BLUE=$(tput setaf 4 2>/dev/null || echo "")
|
||||
MAGENTA=$(tput setaf 5 2>/dev/null || echo "")
|
||||
CYAN=$(tput setaf 6 2>/dev/null || echo "")
|
||||
BOLD=$(tput bold 2>/dev/null || echo "")
|
||||
DIM=$(tput dim 2>/dev/null || echo "")
|
||||
RESET=$(tput sgr0 2>/dev/null || echo "")
|
||||
fi
|
||||
fi
|
||||
|
||||
# Icons for output (simple ASCII)
|
||||
readonly ICON_SUCCESS="[OK]"
|
||||
readonly ICON_ERROR="[ERROR]"
|
||||
readonly ICON_INFO="[INFO]"
|
||||
readonly ICON_CONVERT="==>"
|
||||
|
||||
# Default values
|
||||
OUTPUT_FILE="${PWD}/output.yaml"
|
||||
INPUT_FILE=""
|
||||
CAMEL_CASE=false
|
||||
VERBOSE=false
|
||||
|
||||
#############################################
|
||||
# Functions
|
||||
#############################################
|
||||
|
||||
print_banner() {
|
||||
echo ""
|
||||
echo "${CYAN}${BOLD}================================================================${RESET}"
|
||||
echo "${CYAN}${BOLD} CSS Variable to JSON/YAML Converter${RESET}"
|
||||
echo "${CYAN}${BOLD} Extract CSS custom properties with ease${RESET}"
|
||||
echo "${CYAN}${BOLD}================================================================${RESET}"
|
||||
echo ""
|
||||
}
|
||||
|
||||
print_success() {
|
||||
echo "${GREEN}${ICON_SUCCESS}${RESET} $*"
|
||||
}
|
||||
|
||||
print_error() {
|
||||
echo "${RED}${ICON_ERROR}${RESET} $*" >&2
|
||||
}
|
||||
|
||||
print_info() {
|
||||
echo "${BLUE}${ICON_INFO}${RESET} $*"
|
||||
}
|
||||
|
||||
print_verbose() {
|
||||
if [[ "$VERBOSE" == true ]]; then
|
||||
echo "${DIM} ==> $*${RESET}"
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
show_help() {
|
||||
cat << EOF
|
||||
${BOLD}USAGE:${RESET}
|
||||
$(basename "$0") [OPTIONS] <input.css>
|
||||
|
||||
${BOLD}DESCRIPTION:${RESET}
|
||||
Extracts CSS custom properties (variables) from a CSS file and converts
|
||||
them to JSON or YAML format. Automatically detects output format from
|
||||
file extension.
|
||||
|
||||
${BOLD}ARGUMENTS:${RESET}
|
||||
<input.css> Input CSS file containing CSS variables
|
||||
|
||||
${BOLD}OPTIONS:${RESET}
|
||||
-o, --output FILE Output file path (default: ./output.yaml)
|
||||
Format auto-detected from extension (.json/.yaml/.yml)
|
||||
-c, --camel-case Convert variable names to camelCase
|
||||
(e.g., --main-color -> mainColor)
|
||||
-v, --verbose Enable verbose output
|
||||
-h, --help Show this help message
|
||||
|
||||
${BOLD}EXAMPLES:${RESET}
|
||||
# Extract CSS vars to YAML (default)
|
||||
$(basename "$0") styles.css
|
||||
|
||||
# Extract to JSON with custom output
|
||||
$(basename "$0") styles.css -o theme.json
|
||||
|
||||
# Convert variable names to camelCase
|
||||
$(basename "$0") styles.css -o vars.json --camel-case
|
||||
|
||||
${BOLD}CSS VARIABLE FORMAT:${RESET}
|
||||
The script extracts CSS custom properties in the format:
|
||||
--variable-name: value;
|
||||
|
||||
Example input:
|
||||
:root {
|
||||
--main-color: #e8eaed;
|
||||
--font-size: 16px;
|
||||
}
|
||||
|
||||
Example JSON output:
|
||||
{
|
||||
"main-color": "#e8eaed",
|
||||
"font-size": "16px"
|
||||
}
|
||||
|
||||
EOF
|
||||
}
|
||||
|
||||
# Convert kebab-case to camelCase
|
||||
to_camel_case() {
|
||||
local input="$1"
|
||||
# Remove leading dashes and convert to camelCase
|
||||
echo "$input" | sed -E 's/^--//; s/-(.)/\U\1/g'
|
||||
}
|
||||
|
||||
# Extract CSS variables using advanced sed
|
||||
extract_css_variables() {
|
||||
local input_file="$1"
|
||||
|
||||
print_verbose "Extracting CSS variables from: $input_file" >&2
|
||||
|
||||
# Advanced sed expression to extract CSS custom properties
|
||||
# Matches: --variable-name: value; (with flexible whitespace)
|
||||
sed -n 's/^[[:space:]]*\(--[a-zA-Z0-9_-]\+\)[[:space:]]*:[[:space:]]*\([^;]\+\);.*$/\1|\2/p' "$input_file" \
|
||||
| sed 's/[[:space:]]*$//' \
|
||||
| sed 's/^[[:space:]]*//'
|
||||
}
|
||||
|
||||
# Convert to JSON format
|
||||
convert_to_json() {
|
||||
local -a variables=("$@")
|
||||
local json="{"
|
||||
local first=true
|
||||
|
||||
for var in "${variables[@]}"; do
|
||||
IFS='|' read -r name value <<< "$var"
|
||||
|
||||
# Remove leading dashes
|
||||
name="${name#--}"
|
||||
|
||||
# Convert to camelCase if requested
|
||||
if [[ "$CAMEL_CASE" == true ]]; then
|
||||
name=$(to_camel_case "--$name")
|
||||
fi
|
||||
|
||||
# Trim whitespace from value
|
||||
value=$(echo "$value" | sed 's/^[[:space:]]*//; s/[[:space:]]*$//')
|
||||
|
||||
# Escape quotes in value
|
||||
value="${value//\"/\\\"}"
|
||||
|
||||
if [[ "$first" == true ]]; then
|
||||
first=false
|
||||
json+=$'\n'
|
||||
else
|
||||
json+=","$'\n'
|
||||
fi
|
||||
|
||||
json+=" \"$name\": \"$value\""
|
||||
done
|
||||
|
||||
json+=$'\n'"}"
|
||||
echo "$json"
|
||||
}
|
||||
|
||||
# Convert to YAML format
|
||||
convert_to_yaml() {
|
||||
local -a variables=("$@")
|
||||
local yaml=""
|
||||
|
||||
for var in "${variables[@]}"; do
|
||||
IFS='|' read -r name value <<< "$var"
|
||||
|
||||
# Remove leading dashes
|
||||
name="${name#--}"
|
||||
|
||||
# Convert to camelCase if requested
|
||||
if [[ "$CAMEL_CASE" == true ]]; then
|
||||
name=$(to_camel_case "--$name")
|
||||
fi
|
||||
|
||||
# Trim whitespace from value
|
||||
value=$(echo "$value" | sed 's/^[[:space:]]*//; s/[[:space:]]*$//')
|
||||
|
||||
# Quote value if it contains special characters or starts with special chars
|
||||
if [[ "$value" =~ ^[#\&\*\!\|\>\'\"] ]] || [[ "$value" =~ [:\{\}\[\],] ]]; then
|
||||
value="\"$value\""
|
||||
fi
|
||||
|
||||
yaml+="$name: $value"$'\n'
|
||||
done
|
||||
|
||||
echo "$yaml"
|
||||
}
|
||||
|
||||
# Determine output format from file extension
|
||||
get_output_format() {
|
||||
local file="$1"
|
||||
local ext="${file##*.}"
|
||||
|
||||
case "$ext" in
|
||||
json)
|
||||
echo "json"
|
||||
;;
|
||||
yaml|yml)
|
||||
echo "yaml"
|
||||
;;
|
||||
*)
|
||||
print_error "Unsupported output format: .$ext"
|
||||
print_info "Supported formats: .json, .yaml, .yml"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
#############################################
|
||||
# Main Script
|
||||
#############################################
|
||||
|
||||
main() {
|
||||
print_banner
|
||||
|
||||
# Parse arguments
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case $1 in
|
||||
-o|--output)
|
||||
OUTPUT_FILE="$2"
|
||||
shift 2
|
||||
;;
|
||||
-c|--camel-case)
|
||||
CAMEL_CASE=true
|
||||
shift
|
||||
;;
|
||||
-v|--verbose)
|
||||
VERBOSE=true
|
||||
shift
|
||||
;;
|
||||
-h|--help)
|
||||
show_help
|
||||
exit 0
|
||||
;;
|
||||
-*)
|
||||
print_error "Unknown option: $1"
|
||||
echo "Use -h or --help for usage information"
|
||||
exit 1
|
||||
;;
|
||||
*)
|
||||
INPUT_FILE="$1"
|
||||
shift
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
# Validate input file
|
||||
if [[ -z "$INPUT_FILE" ]]; then
|
||||
print_error "No input file specified"
|
||||
echo "Use -h or --help for usage information"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ ! -f "$INPUT_FILE" ]]; then
|
||||
print_error "Input file not found: $INPUT_FILE"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Show processing info
|
||||
print_info "${ICON_FILE} Input: ${CYAN}$INPUT_FILE${RESET}"
|
||||
print_info "${ICON_FILE} Output: ${CYAN}$OUTPUT_FILE${RESET}"
|
||||
|
||||
if [[ "$CAMEL_CASE" == true ]]; then
|
||||
print_info "${ICON_CONVERT} Mode: ${YELLOW}camelCase conversion enabled${RESET}"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
|
||||
# Extract CSS variables
|
||||
print_info "${ICON_CONVERT} Extracting CSS variables..."
|
||||
|
||||
mapfile -t variables < <(extract_css_variables "$INPUT_FILE")
|
||||
|
||||
if [[ ${#variables[@]} -eq 0 ]]; then
|
||||
print_error "No CSS variables found in $INPUT_FILE"
|
||||
print_info "Expected format: --variable-name: value;"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
print_success "Found ${BOLD}${#variables[@]}${RESET} CSS variable(s)"
|
||||
|
||||
# Show extracted variables in verbose mode
|
||||
if [[ "$VERBOSE" == true ]]; then
|
||||
echo ""
|
||||
for var in "${variables[@]}"; do
|
||||
IFS='|' read -r name value <<< "$var"
|
||||
print_verbose "${MAGENTA}$name${RESET} = ${GREEN}$value${RESET}"
|
||||
done
|
||||
echo ""
|
||||
fi
|
||||
|
||||
# Determine output format
|
||||
OUTPUT_FORMAT=$(get_output_format "$OUTPUT_FILE")
|
||||
print_verbose "Output format: $OUTPUT_FORMAT"
|
||||
|
||||
# Convert and write output
|
||||
print_info "${ICON_CONVERT} Converting to ${BOLD}${OUTPUT_FORMAT^^}${RESET}..."
|
||||
|
||||
case "$OUTPUT_FORMAT" in
|
||||
json)
|
||||
convert_to_json "${variables[@]}" > "$OUTPUT_FILE"
|
||||
;;
|
||||
yaml)
|
||||
convert_to_yaml "${variables[@]}" > "$OUTPUT_FILE"
|
||||
;;
|
||||
esac
|
||||
|
||||
# Success message
|
||||
echo ""
|
||||
print_success "${ICON_ROCKET} Conversion complete!"
|
||||
print_info "Output saved to: ${BOLD}${GREEN}$OUTPUT_FILE${RESET}"
|
||||
|
||||
# Show preview if verbose
|
||||
if [[ "$VERBOSE" == true ]]; then
|
||||
echo ""
|
||||
echo -e "${DIM} | ||||