mirror of
https://github.com/affaan-m/everything-claude-code.git
synced 2026-05-14 02:10:07 +08:00
feat: add ios-icon-gen skill (#1356)
* feat: add ios-icon-gen skill for Xcode asset catalog icon generation Add a skill that generates PNG icon imagesets (1x, 2x, 3x) for Xcode asset catalogs from two sources: - Iconify API: 275k+ open source icons from 200+ collections (Material Design, Phosphor, Tabler, Lucide, etc.) - SF Symbols: 5k+ Apple-native symbols (macOS only) Includes search, preview, and generation scripts with customizable size, color, weight, and direct output to asset catalogs. * fix: address PR review feedback for ios-icon-gen skill Security: - Fix shell injection in iconify_gen.sh by passing query via sys.argv instead of interpolating into Python string literal Robustness: - Replace all try!/force-unwrap with do/try/catch and guard let in generate_icons.swift for graceful error handling - Add option value validation (require_value/requireOptionValue) in both scripts to prevent crashes on missing flag values - Add curl timeouts (--connect-timeout 10, --max-time 30) to all network calls - Add sips conversion failure warnings instead of silent suppression - Add error handling for curl in list_collections Documentation: - Rename SKILL.md sections to "When to Use", "How It Works", "Examples" to match repo conventions * fix: restore canonical SKILL.md headers and validate color/weight CLI inputs - Revert SKILL.md section headers back to "When to Activate" and "Core Principles" per CONTRIBUTING.md and SKILL-DEVELOPMENT-GUIDE.md (the prior rename to "When to Use"/"How It Works" was incorrect) - Validate --color as a 6-digit hex code at parse time instead of silently falling back to the default gray - Validate --weight against the known set of font weights instead of silently falling back to thin --------- Co-authored-by: Quang Tran <16215255+trmquang93@users.noreply.github.com>
This commit is contained in:
parent
f01929c31a
commit
754bdbf440
157
skills/ios-icon-gen/SKILL.md
Normal file
157
skills/ios-icon-gen/SKILL.md
Normal file
@ -0,0 +1,157 @@
|
||||
---
|
||||
name: ios-icon-gen
|
||||
description: Generate iOS app icons as PNG imagesets for Xcode asset catalogs from SF Symbols (5000+ Apple-native) or Iconify API (275k+ open source icons from 200+ collections). Use when generating icons, creating icon assets, adding icons to asset catalog, or searching for icons for iOS projects.
|
||||
origin: community
|
||||
---
|
||||
|
||||
# iOS Icon Generator
|
||||
|
||||
Generate PNG icon imagesets for Xcode asset catalogs from two sources.
|
||||
|
||||
## When to Activate
|
||||
|
||||
- Generating icon assets for an iOS/macOS Xcode project
|
||||
- Searching for icons across open source collections
|
||||
- Creating PNG imagesets (1x, 2x, 3x) for asset catalogs
|
||||
- Replacing placeholder icons with production-quality assets
|
||||
- Matching existing icon styles in an Xcode project
|
||||
|
||||
## Core Principles
|
||||
|
||||
### 1. Two Sources, One Output Format
|
||||
Both sources produce identical Xcode-compatible imagesets. Choose based on need:
|
||||
|
||||
| Source | Icons | Requires | Best for |
|
||||
|--------|-------|----------|----------|
|
||||
| **Iconify API** | 275,000+ from 200+ collections | Internet | Wide selection, specific styles, open source icons |
|
||||
| **SF Symbols** | 5,000+ Apple symbols | macOS only | Apple-native style, offline use |
|
||||
|
||||
### 2. Always Match Existing Style
|
||||
Before generating, check the project's existing icons for size, color, and weight consistency.
|
||||
|
||||
### 3. Output Structure
|
||||
Both methods produce a complete Xcode imageset:
|
||||
|
||||
```
|
||||
<output-dir>/<asset-name>.imageset/
|
||||
Contents.json
|
||||
<asset-name>.png # 1x (68px default)
|
||||
<asset-name>@2x.png # 2x (136px default)
|
||||
<asset-name>@3x.png # 3x (204px default)
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
### Step 1: Assess Requirements
|
||||
|
||||
Determine icon needs: what the icon represents, preferred style, target color, and size.
|
||||
|
||||
If the project already has icons, check existing style:
|
||||
```bash
|
||||
# Check dimensions of existing icon
|
||||
sips -g pixelWidth -g pixelHeight path/to/existing@2x.png
|
||||
```
|
||||
|
||||
### Step 2: Search for Icons
|
||||
|
||||
**Iconify API (recommended for wide selection):**
|
||||
```bash
|
||||
# Search all collections
|
||||
$SKILL_DIR/scripts/iconify_gen.sh search "receipt"
|
||||
|
||||
# Search within a specific collection
|
||||
$SKILL_DIR/scripts/iconify_gen.sh search "business card" --prefix mdi
|
||||
|
||||
# List available collections
|
||||
$SKILL_DIR/scripts/iconify_gen.sh collections
|
||||
```
|
||||
|
||||
**SF Symbols (for Apple-native style):**
|
||||
Browse the SF Symbols app or reference common names:
|
||||
|
||||
| Use Case | Symbol Name |
|
||||
|----------|-------------|
|
||||
| Document | `doc.text`, `doc.fill` |
|
||||
| Receipt | `doc.text.below.ecg`, `receipt` |
|
||||
| Person | `person.crop.rectangle`, `person.text.rectangle` |
|
||||
| Camera | `camera`, `camera.fill` |
|
||||
| Scan | `doc.viewfinder`, `qrcode.viewfinder` |
|
||||
| Settings | `gearshape`, `slider.horizontal.3` |
|
||||
|
||||
### Step 3: Preview (Optional)
|
||||
|
||||
```bash
|
||||
# Iconify preview
|
||||
$SKILL_DIR/scripts/iconify_gen.sh preview mdi:receipt-text-outline
|
||||
```
|
||||
|
||||
### Step 4: Generate
|
||||
|
||||
**Iconify API:**
|
||||
```bash
|
||||
# Basic generation
|
||||
$SKILL_DIR/scripts/iconify_gen.sh mdi:receipt-text-outline editTool_expenseReport
|
||||
|
||||
# Custom color and output location
|
||||
$SKILL_DIR/scripts/iconify_gen.sh mdi:receipt-text-outline myIcon --color 007AFF --output ./Assets.xcassets/icons
|
||||
```
|
||||
|
||||
Options: `--size <pt>` (default: 68), `--color <hex>` (default: 8E8E93), `--output <dir>` (default: /tmp/icons)
|
||||
|
||||
**SF Symbols:**
|
||||
```bash
|
||||
# Basic generation
|
||||
swift $SKILL_DIR/scripts/generate_icons.swift doc.text.below.ecg editTool_expenseReport
|
||||
|
||||
# Custom color, weight, and output
|
||||
swift $SKILL_DIR/scripts/generate_icons.swift person.crop.rectangle myIcon --color 007AFF --weight regular --output ./Assets.xcassets/icons
|
||||
```
|
||||
|
||||
Options: `--size <pt>` (default: 68), `--color <hex>` (default: 8E8E93), `--weight <name>` (default: thin), `--output <dir>` (default: /tmp/icons)
|
||||
|
||||
### Step 5: Verify and Integrate
|
||||
|
||||
1. Read the generated @2x PNG to verify visually
|
||||
2. Copy to asset catalog if not output there directly:
|
||||
```bash
|
||||
cp -r /tmp/icons/<name>.imageset path/to/Assets.xcassets/<group>/
|
||||
```
|
||||
3. Build the project to verify Xcode picks up the new assets
|
||||
|
||||
## Popular Iconify Collections
|
||||
|
||||
| Prefix | Name | Count | Style |
|
||||
|--------|------|-------|-------|
|
||||
| `mdi` | Material Design Icons | 7400+ | Filled + outline variants |
|
||||
| `ph` | Phosphor | 9000+ | 6 weights per icon |
|
||||
| `solar` | Solar | 7400+ | Bold, linear, outline |
|
||||
| `tabler` | Tabler Icons | 6000+ | Consistent stroke width |
|
||||
| `lucide` | Lucide | 1700+ | Clean, minimal |
|
||||
| `ri` | Remix Icon | 3100+ | Filled + line variants |
|
||||
| `carbon` | Carbon | 2400+ | IBM design language |
|
||||
| `heroicons` | HeroIcons | 1200+ | Tailwind CSS companion |
|
||||
|
||||
Browse all: https://icon-sets.iconify.design/
|
||||
|
||||
## Scripts Reference
|
||||
|
||||
| Script | Source | Path |
|
||||
|--------|--------|------|
|
||||
| `iconify_gen.sh` | Iconify API (275k+ icons) | `$SKILL_DIR/scripts/iconify_gen.sh` |
|
||||
| `generate_icons.swift` | SF Symbols (5k+ icons) | `$SKILL_DIR/scripts/generate_icons.swift` |
|
||||
|
||||
## Best Practices
|
||||
|
||||
- **Search before generating** -- browse available icons to find the best match
|
||||
- **Match existing project style** -- check dimensions, color, and weight of existing icons before generating new ones
|
||||
- **Use Iconify for variety** -- 200+ collections means you can find the exact style you need
|
||||
- **Use SF Symbols for Apple consistency** -- they match system UI perfectly
|
||||
- **Generate directly to asset catalog** -- use `--output ./Assets.xcassets/icons` to skip manual copying
|
||||
- **Verify visually** -- always preview the @2x PNG before committing
|
||||
|
||||
## Anti-Patterns
|
||||
|
||||
- Generating icons without checking existing project icon style
|
||||
- Using default colors when the project has a defined color palette
|
||||
- Generating at wrong sizes (check existing icons first)
|
||||
- Committing generated icons without visual verification
|
||||
258
skills/ios-icon-gen/scripts/generate_icons.swift
Executable file
258
skills/ios-icon-gen/scripts/generate_icons.swift
Executable file
@ -0,0 +1,258 @@
|
||||
#!/usr/bin/env swift
|
||||
|
||||
import AppKit
|
||||
import Foundation
|
||||
|
||||
// MARK: - Configuration
|
||||
|
||||
struct IconSpec {
|
||||
let symbolName: String
|
||||
let assetName: String
|
||||
let baseSize: CGFloat
|
||||
let color: NSColor
|
||||
let weight: NSFont.Weight
|
||||
}
|
||||
|
||||
func parseColor(_ hex: String) -> NSColor {
|
||||
var hex = hex.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
if hex.hasPrefix("#") { hex.removeFirst() }
|
||||
guard hex.count == 6, let value = UInt64(hex, radix: 16) else {
|
||||
return NSColor(red: 142/255, green: 142/255, blue: 147/255, alpha: 1.0)
|
||||
}
|
||||
return NSColor(
|
||||
red: CGFloat((value >> 16) & 0xFF) / 255,
|
||||
green: CGFloat((value >> 8) & 0xFF) / 255,
|
||||
blue: CGFloat(value & 0xFF) / 255,
|
||||
alpha: 1.0
|
||||
)
|
||||
}
|
||||
|
||||
func parseWeight(_ name: String) -> NSFont.Weight {
|
||||
switch name.lowercased() {
|
||||
case "ultralight": return .ultraLight
|
||||
case "thin": return .thin
|
||||
case "light": return .light
|
||||
case "regular": return .regular
|
||||
case "medium": return .medium
|
||||
case "semibold": return .semibold
|
||||
case "bold": return .bold
|
||||
case "heavy": return .heavy
|
||||
case "black": return .black
|
||||
default: return .thin
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Generation
|
||||
|
||||
enum IconError: Error, CustomStringConvertible {
|
||||
case directoryCreation(String)
|
||||
case symbolNotFound(String)
|
||||
case configurationFailed(String)
|
||||
case pngCreation(String)
|
||||
case fileWrite(String)
|
||||
|
||||
var description: String {
|
||||
switch self {
|
||||
case .directoryCreation(let msg): return msg
|
||||
case .symbolNotFound(let msg): return msg
|
||||
case .configurationFailed(let msg): return msg
|
||||
case .pngCreation(let msg): return msg
|
||||
case .fileWrite(let msg): return msg
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func generateIcon(_ spec: IconSpec, outputDir: String) throws {
|
||||
let dir = "\(outputDir)/\(spec.assetName).imageset"
|
||||
do {
|
||||
try FileManager.default.createDirectory(atPath: dir, withIntermediateDirectories: true)
|
||||
} catch {
|
||||
throw IconError.directoryCreation("Could not create output directory '\(dir)': \(error.localizedDescription)")
|
||||
}
|
||||
|
||||
let scales: [(suffix: String, multiplier: CGFloat)] = [("", 1), ("@2x", 2), ("@3x", 3)]
|
||||
|
||||
for scale in scales {
|
||||
let pixelSize = spec.baseSize * scale.multiplier
|
||||
let imageSize = NSSize(width: pixelSize, height: pixelSize)
|
||||
|
||||
let config = NSImage.SymbolConfiguration(
|
||||
pointSize: pixelSize * 0.40,
|
||||
weight: spec.weight,
|
||||
scale: .large
|
||||
)
|
||||
|
||||
guard let symbol = NSImage(systemSymbolName: spec.symbolName, accessibilityDescription: nil) else {
|
||||
throw IconError.symbolNotFound("SF Symbol '\(spec.symbolName)' not found. Run 'SF Symbols' app to browse available names.")
|
||||
}
|
||||
|
||||
guard let configured = symbol.withSymbolConfiguration(config) else {
|
||||
throw IconError.configurationFailed("Could not apply symbol configuration to '\(spec.symbolName)'")
|
||||
}
|
||||
|
||||
let image = NSImage(size: imageSize, flipped: false) { rect in
|
||||
let symSize = configured.size
|
||||
let x = (rect.width - symSize.width) / 2
|
||||
let y = (rect.height - symSize.height) / 2
|
||||
let drawRect = NSRect(x: x, y: y, width: symSize.width, height: symSize.height)
|
||||
|
||||
let tinted = NSImage(size: symSize, flipped: false) { tintRect in
|
||||
configured.draw(in: tintRect)
|
||||
spec.color.set()
|
||||
tintRect.fill(using: .sourceAtop)
|
||||
return true
|
||||
}
|
||||
|
||||
tinted.draw(in: drawRect, from: .zero, operation: .sourceOver, fraction: 1.0)
|
||||
return true
|
||||
}
|
||||
|
||||
guard let tiffData = image.tiffRepresentation,
|
||||
let bitmap = NSBitmapImageRep(data: tiffData),
|
||||
let pngData = bitmap.representation(using: .png, properties: [:]) else {
|
||||
throw IconError.pngCreation("Failed to create PNG for \(spec.assetName)\(scale.suffix)")
|
||||
}
|
||||
|
||||
let fileName = "\(spec.assetName)\(scale.suffix).png"
|
||||
do {
|
||||
try pngData.write(to: URL(fileURLWithPath: "\(dir)/\(fileName)"))
|
||||
} catch {
|
||||
throw IconError.fileWrite("Failed to write \(fileName): \(error.localizedDescription)")
|
||||
}
|
||||
print(" \(fileName) (\(Int(pixelSize))x\(Int(pixelSize)))")
|
||||
}
|
||||
|
||||
// Write Contents.json
|
||||
let json = """
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "\(spec.assetName).png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "\(spec.assetName)@2x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "\(spec.assetName)@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
"""
|
||||
do {
|
||||
try json.write(toFile: "\(dir)/Contents.json", atomically: true, encoding: .utf8)
|
||||
} catch {
|
||||
throw IconError.fileWrite("Failed to write Contents.json: \(error.localizedDescription)")
|
||||
}
|
||||
}
|
||||
|
||||
func requireOptionValue(_ args: [String], at index: Int, flag: String) -> String {
|
||||
guard index < args.count else {
|
||||
fputs("ERROR: Missing value for \(flag)\n", stderr)
|
||||
exit(1)
|
||||
}
|
||||
let value = args[index]
|
||||
if value.hasPrefix("--") {
|
||||
fputs("ERROR: Missing value for \(flag)\n", stderr)
|
||||
exit(1)
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
// MARK: - CLI
|
||||
|
||||
let args = CommandLine.arguments
|
||||
|
||||
if args.count < 3 || args.contains("--help") || args.contains("-h") {
|
||||
print("""
|
||||
Usage: generate_icons.swift <sf-symbol-name> <asset-name> [options]
|
||||
|
||||
Options:
|
||||
--size <pt> Base size in points (default: 68)
|
||||
--color <hex> Color hex code (default: 8E8E93)
|
||||
--weight <name> Font weight: ultralight|thin|light|regular|medium|semibold|bold|heavy|black (default: thin)
|
||||
--output <dir> Output directory (default: /tmp/icons)
|
||||
|
||||
Examples:
|
||||
generate_icons.swift doc.text.below.ecg editTool_expenseReport
|
||||
generate_icons.swift person.crop.rectangle editTool_businessCard --color 007AFF --weight regular
|
||||
generate_icons.swift receipt myReceipt --size 48 --output ./Assets.xcassets/icons
|
||||
|
||||
Browse SF Symbol names: open the SF Symbols app (free from Apple) or https://developer.apple.com/sf-symbols/
|
||||
""")
|
||||
exit(0)
|
||||
}
|
||||
|
||||
let symbolName = args[1]
|
||||
let assetName = args[2]
|
||||
|
||||
var baseSize: CGFloat = 68
|
||||
var colorHex = "8E8E93"
|
||||
var weightName = "thin"
|
||||
var outputDir = "/tmp/icons"
|
||||
|
||||
var i = 3
|
||||
while i < args.count {
|
||||
switch args[i] {
|
||||
case "--size":
|
||||
let raw = requireOptionValue(args, at: i + 1, flag: "--size")
|
||||
guard let size = Double(raw), size > 0 else {
|
||||
fputs("ERROR: --size must be a positive number\n", stderr)
|
||||
exit(1)
|
||||
}
|
||||
baseSize = CGFloat(size)
|
||||
i += 2
|
||||
continue
|
||||
case "--color":
|
||||
colorHex = requireOptionValue(args, at: i + 1, flag: "--color")
|
||||
let stripped = colorHex.hasPrefix("#") ? String(colorHex.dropFirst()) : colorHex
|
||||
guard stripped.count == 6, UInt64(stripped, radix: 16) != nil else {
|
||||
fputs("ERROR: --color must be a 6-digit hex code (e.g. 007AFF)\n", stderr)
|
||||
exit(1)
|
||||
}
|
||||
i += 2
|
||||
continue
|
||||
case "--weight":
|
||||
weightName = requireOptionValue(args, at: i + 1, flag: "--weight")
|
||||
let validWeights = ["ultralight", "thin", "light", "regular", "medium", "semibold", "bold", "heavy", "black"]
|
||||
guard validWeights.contains(weightName.lowercased()) else {
|
||||
fputs("ERROR: --weight must be one of: \(validWeights.joined(separator: ", "))\n", stderr)
|
||||
exit(1)
|
||||
}
|
||||
i += 2
|
||||
continue
|
||||
case "--output":
|
||||
outputDir = requireOptionValue(args, at: i + 1, flag: "--output")
|
||||
i += 2
|
||||
continue
|
||||
default:
|
||||
fputs("WARNING: Unknown option \(args[i])\n", stderr)
|
||||
}
|
||||
i += 1
|
||||
}
|
||||
|
||||
let spec = IconSpec(
|
||||
symbolName: symbolName,
|
||||
assetName: assetName,
|
||||
baseSize: baseSize,
|
||||
color: parseColor(colorHex),
|
||||
weight: parseWeight(weightName)
|
||||
)
|
||||
|
||||
print("Generating \(assetName) from SF Symbol '\(symbolName)':")
|
||||
do {
|
||||
try generateIcon(spec, outputDir: outputDir)
|
||||
print("Output: \(outputDir)/\(assetName).imageset/")
|
||||
} catch {
|
||||
fputs("ERROR: \(error)\n", stderr)
|
||||
exit(1)
|
||||
}
|
||||
235
skills/ios-icon-gen/scripts/iconify_gen.sh
Executable file
235
skills/ios-icon-gen/scripts/iconify_gen.sh
Executable file
@ -0,0 +1,235 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# Generate iOS icon imagesets from Iconify API (275k+ open source icons)
|
||||
# Uses: curl (download SVG) + sips (SVG->PNG conversion, built into macOS)
|
||||
#
|
||||
# Usage:
|
||||
# iconify_gen.sh <icon-id> <asset-name> [options]
|
||||
# iconify_gen.sh search <query> [--prefix <collection>] [--limit <n>]
|
||||
#
|
||||
# Examples:
|
||||
# iconify_gen.sh mdi:receipt-text-outline myExpenseIcon
|
||||
# iconify_gen.sh search "business card"
|
||||
# iconify_gen.sh search receipt --prefix mdi
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
API_BASE="https://api.iconify.design"
|
||||
readonly CURL_OPTS=(--fail --silent --show-error --connect-timeout 10 --max-time 30)
|
||||
|
||||
# Defaults
|
||||
SIZE=68
|
||||
COLOR="8E8E93"
|
||||
OUTPUT="/tmp/icons"
|
||||
LIMIT=20
|
||||
|
||||
require_value() {
|
||||
local flag="$1"
|
||||
local value="${2-}"
|
||||
if [[ -z "$value" || "$value" == --* ]]; then
|
||||
echo "ERROR: ${flag} requires a value" >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
usage() {
|
||||
cat <<'EOF'
|
||||
Usage:
|
||||
iconify_gen.sh <icon-id> <asset-name> [options] Generate an icon imageset
|
||||
iconify_gen.sh search <query> [options] Search for icons
|
||||
iconify_gen.sh preview <icon-id> Download preview SVG
|
||||
iconify_gen.sh collections List popular icon collections
|
||||
|
||||
Generate Options:
|
||||
--size <pt> Base size in points (default: 68)
|
||||
--color <hex> Color hex without # (default: 8E8E93)
|
||||
--output <dir> Output directory (default: /tmp/icons)
|
||||
|
||||
Search Options:
|
||||
--prefix <name> Filter by collection (e.g., mdi, lucide, tabler, ph)
|
||||
--limit <n> Max results (default: 20)
|
||||
|
||||
Icon ID Format: <collection>:<icon-name>
|
||||
Examples: mdi:receipt-text-outline, lucide:credit-card, ph:address-book
|
||||
|
||||
Popular Collections:
|
||||
mdi Material Design Icons (7400+ icons)
|
||||
lucide Lucide (1700+ icons)
|
||||
tabler Tabler Icons (6000+ icons)
|
||||
ph Phosphor (9000+ icons)
|
||||
ri Remix Icon (2800+ icons)
|
||||
carbon Carbon (2100+ icons)
|
||||
EOF
|
||||
exit 0
|
||||
}
|
||||
|
||||
search_icons() {
|
||||
local query="$1"
|
||||
shift
|
||||
local prefix=""
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--prefix) require_value --prefix "${2-}"; prefix="$2"; shift 2 ;;
|
||||
--limit) require_value --limit "${2-}"; LIMIT="$2"; shift 2 ;;
|
||||
*) shift ;;
|
||||
esac
|
||||
done
|
||||
|
||||
local encoded_query
|
||||
encoded_query="$(python3 -c "import urllib.parse, sys; print(urllib.parse.quote(sys.argv[1]))" "$query")"
|
||||
local url="${API_BASE}/search?query=${encoded_query}&limit=${LIMIT}"
|
||||
if [[ -n "$prefix" ]]; then
|
||||
url="${url}&prefix=${prefix}"
|
||||
fi
|
||||
|
||||
local response
|
||||
response=$(curl "${CURL_OPTS[@]}" "$url") || { echo "ERROR: Search request failed"; exit 1; }
|
||||
|
||||
local total
|
||||
total=$(echo "$response" | python3 -c "import sys,json; print(json.load(sys.stdin).get('total',0))")
|
||||
|
||||
echo "Found ${total} icons for '${query}':"
|
||||
echo ""
|
||||
echo "$response" | python3 -c "
|
||||
import sys, json
|
||||
data = json.load(sys.stdin)
|
||||
for icon in data.get('icons', []):
|
||||
print(f' {icon}')
|
||||
"
|
||||
echo ""
|
||||
echo "Generate with: iconify_gen.sh <icon-id> <asset-name>"
|
||||
echo "Preview with: iconify_gen.sh preview <icon-id>"
|
||||
}
|
||||
|
||||
list_collections() {
|
||||
echo "Popular Iconify collections:"
|
||||
echo ""
|
||||
local resp
|
||||
resp=$(curl "${CURL_OPTS[@]}" "${API_BASE}/collections") || { echo "ERROR: Failed to fetch collections list"; exit 1; }
|
||||
echo "$resp" | python3 -c "
|
||||
import sys, json
|
||||
data = json.load(sys.stdin)
|
||||
popular = ['mdi','lucide','tabler','ph','ri','carbon','solar','heroicons','bi','octicon','ion','fe','charm','ci','iconoir','basil','uil','mingcute','flowbite','mynaui']
|
||||
for k in popular:
|
||||
if k in data:
|
||||
v = data[k]
|
||||
name = v.get('name','')
|
||||
total = v.get('total',0)
|
||||
print(f' {k:12s} {name} ({total} icons)')
|
||||
"
|
||||
echo ""
|
||||
echo "Full list: https://icon-sets.iconify.design/"
|
||||
}
|
||||
|
||||
preview_icon() {
|
||||
local icon_id="$1"
|
||||
local collection="${icon_id%%:*}"
|
||||
local name="${icon_id#*:}"
|
||||
local url="${API_BASE}/${collection}/${name}.svg?width=136&height=136&color=%23${COLOR}"
|
||||
local outfile="/tmp/iconify_preview_${collection}_${name}.svg"
|
||||
|
||||
curl "${CURL_OPTS[@]}" "$url" -o "$outfile" || { echo "ERROR: Icon '${icon_id}' not found"; exit 1; }
|
||||
echo "Preview SVG: ${outfile}"
|
||||
echo "URL: ${url}"
|
||||
|
||||
# Also convert to PNG for visual check
|
||||
local pngfile="/tmp/iconify_preview_${collection}_${name}.png"
|
||||
sips -s format png "$outfile" --out "$pngfile" >/dev/null 2>&1 || echo "WARNING: sips conversion failed; PNG may be incorrect"
|
||||
echo "Preview PNG: ${pngfile}"
|
||||
}
|
||||
|
||||
generate_icon() {
|
||||
local icon_id="$1"
|
||||
local asset_name="$2"
|
||||
shift 2
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--size) require_value --size "${2-}"; SIZE="$2"; shift 2 ;;
|
||||
--color) require_value --color "${2-}"; COLOR="$2"; shift 2 ;;
|
||||
--output) require_value --output "${2-}"; OUTPUT="$2"; shift 2 ;;
|
||||
*) shift ;;
|
||||
esac
|
||||
done
|
||||
|
||||
local collection="${icon_id%%:*}"
|
||||
local name="${icon_id#*:}"
|
||||
local imageset_dir="${OUTPUT}/${asset_name}.imageset"
|
||||
|
||||
mkdir -p "$imageset_dir"
|
||||
|
||||
echo "Generating ${asset_name} from Iconify '${icon_id}':"
|
||||
|
||||
local scales=("1:${SIZE}" "2:$((SIZE * 2))" "3:$((SIZE * 3))")
|
||||
|
||||
for scale_info in "${scales[@]}"; do
|
||||
local scale="${scale_info%%:*}"
|
||||
local px="${scale_info#*:}"
|
||||
local suffix=""
|
||||
[[ "$scale" != "1" ]] && suffix="@${scale}x"
|
||||
|
||||
local svg_url="${API_BASE}/${collection}/${name}.svg?width=${px}&height=${px}&color=%23${COLOR}"
|
||||
local svg_file="${imageset_dir}/${asset_name}${suffix}.svg"
|
||||
local png_file="${imageset_dir}/${asset_name}${suffix}.png"
|
||||
|
||||
curl "${CURL_OPTS[@]}" "$svg_url" -o "$svg_file" || { echo "ERROR: Failed to download icon '${icon_id}'"; exit 1; }
|
||||
sips -s format png "$svg_file" --out "$png_file" >/dev/null 2>&1 || echo "WARNING: sips conversion may have failed for ${svg_file}"
|
||||
rm "$svg_file"
|
||||
|
||||
echo " ${asset_name}${suffix}.png (${px}x${px})"
|
||||
done
|
||||
|
||||
# Write Contents.json
|
||||
cat > "${imageset_dir}/Contents.json" <<JSONEOF
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "${asset_name}.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "${asset_name}@2x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "${asset_name}@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
JSONEOF
|
||||
|
||||
echo "Output: ${imageset_dir}/"
|
||||
}
|
||||
|
||||
# Main
|
||||
[[ $# -eq 0 ]] && usage
|
||||
[[ "$1" == "--help" || "$1" == "-h" ]] && usage
|
||||
|
||||
case "$1" in
|
||||
search)
|
||||
shift
|
||||
[[ $# -eq 0 ]] && { echo "Usage: iconify_gen.sh search <query>"; exit 1; }
|
||||
search_icons "$@"
|
||||
;;
|
||||
preview)
|
||||
shift
|
||||
[[ $# -eq 0 ]] && { echo "Usage: iconify_gen.sh preview <icon-id>"; exit 1; }
|
||||
preview_icon "$1"
|
||||
;;
|
||||
collections)
|
||||
list_collections
|
||||
;;
|
||||
*)
|
||||
[[ $# -lt 2 ]] && { echo "Usage: iconify_gen.sh <icon-id> <asset-name> [options]"; exit 1; }
|
||||
generate_icon "$@"
|
||||
;;
|
||||
esac
|
||||
Loading…
x
Reference in New Issue
Block a user