SVG2DrawIOLib 1.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- SVG2DrawIOLib/__about__.py +3 -0
- SVG2DrawIOLib/__init__.py +21 -0
- SVG2DrawIOLib/cli/__init__.py +158 -0
- SVG2DrawIOLib/cli/add.py +241 -0
- SVG2DrawIOLib/cli/create.py +242 -0
- SVG2DrawIOLib/cli/helpers.py +29 -0
- SVG2DrawIOLib/cli/list.py +70 -0
- SVG2DrawIOLib/cli/remove.py +71 -0
- SVG2DrawIOLib/library_manager.py +269 -0
- SVG2DrawIOLib/models.py +129 -0
- SVG2DrawIOLib/svg_processor.py +317 -0
- svg2drawiolib-1.0.0.dist-info/METADATA +220 -0
- svg2drawiolib-1.0.0.dist-info/RECORD +16 -0
- svg2drawiolib-1.0.0.dist-info/WHEEL +4 -0
- svg2drawiolib-1.0.0.dist-info/entry_points.txt +2 -0
- svg2drawiolib-1.0.0.dist-info/licenses/LICENSE.txt +9 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"""SVG2DrawIOLib - Generate DrawIO shape libraries from SVG files."""
|
|
2
|
+
|
|
3
|
+
from SVG2DrawIOLib.__about__ import __version__
|
|
4
|
+
from SVG2DrawIOLib.library_manager import LibraryManager
|
|
5
|
+
from SVG2DrawIOLib.models import (
|
|
6
|
+
DrawIOIcon,
|
|
7
|
+
LibraryMetadata,
|
|
8
|
+
SVGDimensions,
|
|
9
|
+
SVGProcessingOptions,
|
|
10
|
+
)
|
|
11
|
+
from SVG2DrawIOLib.svg_processor import SVGProcessor
|
|
12
|
+
|
|
13
|
+
__all__ = [
|
|
14
|
+
"__version__",
|
|
15
|
+
"DrawIOIcon",
|
|
16
|
+
"LibraryManager",
|
|
17
|
+
"LibraryMetadata",
|
|
18
|
+
"SVGDimensions",
|
|
19
|
+
"SVGProcessingOptions",
|
|
20
|
+
"SVGProcessor",
|
|
21
|
+
]
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
"""Command-line interface for SVG2DrawIOLib with dynamic command loading."""
|
|
2
|
+
|
|
3
|
+
import importlib
|
|
4
|
+
import logging
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
import rich_click as rc
|
|
8
|
+
|
|
9
|
+
# Configure rich-click for beautiful, colorful help output
|
|
10
|
+
rc.rich_click.TEXT_MARKUP = "rich"
|
|
11
|
+
rc.rich_click.SHOW_ARGUMENTS = True
|
|
12
|
+
rc.rich_click.GROUP_ARGUMENTS_OPTIONS = True
|
|
13
|
+
rc.rich_click.OPTIONS_TABLE_COLUMN_TYPES = ["required", "opt_short", "opt_long", "help"]
|
|
14
|
+
rc.rich_click.OPTIONS_TABLE_HELP_SECTIONS = ["help", "deprecated", "envvar", "default", "required"]
|
|
15
|
+
rc.rich_click.STYLE_ERRORS_SUGGESTION = "magenta italic"
|
|
16
|
+
rc.rich_click.ERRORS_SUGGESTION = "Try running the '--help' flag for more information."
|
|
17
|
+
rc.rich_click.ERRORS_EPILOGUE = ""
|
|
18
|
+
rc.rich_click.MAX_WIDTH = 100
|
|
19
|
+
rc.rich_click.COLOR_SYSTEM = "auto"
|
|
20
|
+
|
|
21
|
+
# Style configuration
|
|
22
|
+
rc.rich_click.STYLE_OPTION = "bold cyan"
|
|
23
|
+
rc.rich_click.STYLE_ARGUMENT = "bold yellow"
|
|
24
|
+
rc.rich_click.STYLE_COMMAND = "bold green"
|
|
25
|
+
rc.rich_click.STYLE_SWITCH = "bold blue"
|
|
26
|
+
rc.rich_click.STYLE_METAVAR = "bold magenta"
|
|
27
|
+
rc.rich_click.STYLE_METAVAR_APPEND = "dim"
|
|
28
|
+
rc.rich_click.STYLE_METAVAR_SEPARATOR = "dim"
|
|
29
|
+
rc.rich_click.STYLE_HEADER_TEXT = "bold"
|
|
30
|
+
rc.rich_click.STYLE_FOOTER_TEXT = "dim"
|
|
31
|
+
rc.rich_click.STYLE_USAGE = "bold yellow"
|
|
32
|
+
rc.rich_click.STYLE_USAGE_COMMAND = "bold"
|
|
33
|
+
rc.rich_click.STYLE_DEPRECATED = "red"
|
|
34
|
+
rc.rich_click.STYLE_HELPTEXT_FIRST_LINE = "bold"
|
|
35
|
+
rc.rich_click.STYLE_HELPTEXT = ""
|
|
36
|
+
rc.rich_click.STYLE_OPTION_HELP = ""
|
|
37
|
+
rc.rich_click.STYLE_OPTION_DEFAULT = "dim"
|
|
38
|
+
rc.rich_click.STYLE_REQUIRED_SHORT = "red"
|
|
39
|
+
rc.rich_click.STYLE_REQUIRED_LONG = "dim red"
|
|
40
|
+
rc.rich_click.ALIGN_OPTIONS_PANEL = "left"
|
|
41
|
+
rc.rich_click.ALIGN_ARGUMENTS_PANEL = "left"
|
|
42
|
+
|
|
43
|
+
# Command groups for organized help
|
|
44
|
+
rc.rich_click.COMMAND_GROUPS = {
|
|
45
|
+
"SVG2DrawIOLib": [
|
|
46
|
+
{
|
|
47
|
+
"name": "Library Management Commands",
|
|
48
|
+
"commands": ["create", "add", "remove", "list"],
|
|
49
|
+
"table_styles": {
|
|
50
|
+
"show_lines": True,
|
|
51
|
+
"row_styles": ["none"],
|
|
52
|
+
"border_style": "blue",
|
|
53
|
+
"box": "ROUNDED",
|
|
54
|
+
},
|
|
55
|
+
}
|
|
56
|
+
]
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
# Option groups for better organization
|
|
60
|
+
rc.rich_click.OPTION_GROUPS = {
|
|
61
|
+
"SVG2DrawIOLib create": [
|
|
62
|
+
{
|
|
63
|
+
"name": "Required Options",
|
|
64
|
+
"options": ["--output"],
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
"name": "Sizing Options",
|
|
68
|
+
"options": ["--max-size", "--width", "--height"],
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
"name": "Styling Options",
|
|
72
|
+
"options": ["--css", "--css-color", "--namespace", "--tag"],
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
"name": "General Options",
|
|
76
|
+
"options": ["--recursive", "--verbose", "--quiet", "--help"],
|
|
77
|
+
},
|
|
78
|
+
],
|
|
79
|
+
"SVG2DrawIOLib add": [
|
|
80
|
+
{
|
|
81
|
+
"name": "Duplicate Handling",
|
|
82
|
+
"options": ["--replace", "--add-dupes"],
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
"name": "Sizing Options",
|
|
86
|
+
"options": ["--max-size", "--width", "--height"],
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
"name": "Styling Options",
|
|
90
|
+
"options": ["--css"],
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
"name": "General Options",
|
|
94
|
+
"options": ["--recursive", "--verbose", "--quiet", "--help"],
|
|
95
|
+
},
|
|
96
|
+
],
|
|
97
|
+
"SVG2DrawIOLib remove": [
|
|
98
|
+
{
|
|
99
|
+
"name": "Options",
|
|
100
|
+
"options": ["--verbose", "--quiet", "--help"],
|
|
101
|
+
},
|
|
102
|
+
],
|
|
103
|
+
"SVG2DrawIOLib list": [
|
|
104
|
+
{
|
|
105
|
+
"name": "Options",
|
|
106
|
+
"options": ["--verbose", "--quiet", "--help"],
|
|
107
|
+
},
|
|
108
|
+
],
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
logger = logging.getLogger(__name__)
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
@rc.group()
|
|
115
|
+
@rc.version_option()
|
|
116
|
+
def cli() -> None:
|
|
117
|
+
"""SVG2DrawIOLib - Manage DrawIO/diagrams.net shape libraries.
|
|
118
|
+
|
|
119
|
+
Convert SVG files into DrawIO libraries and manage existing libraries.
|
|
120
|
+
Supports color-editable icons and proportional scaling.
|
|
121
|
+
|
|
122
|
+
\b
|
|
123
|
+
Examples:
|
|
124
|
+
Create a new library from SVG files:
|
|
125
|
+
$ SVG2DrawIOLib create icons/*.svg -o my-library.xml
|
|
126
|
+
|
|
127
|
+
Add icons to an existing library:
|
|
128
|
+
$ SVG2DrawIOLib add my-library.xml new-icon.svg
|
|
129
|
+
|
|
130
|
+
List icons in a library:
|
|
131
|
+
$ SVG2DrawIOLib list my-library.xml
|
|
132
|
+
"""
|
|
133
|
+
pass
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
# Dynamic discovery: auto-register all CLI commands from cli/ directory
|
|
137
|
+
COMMAND_DIR = Path(__file__).parent
|
|
138
|
+
if COMMAND_DIR.exists():
|
|
139
|
+
for filepath in COMMAND_DIR.iterdir():
|
|
140
|
+
# Only import .py files that are not __init__.py or helpers.py
|
|
141
|
+
if filepath.suffix == ".py" and filepath.stem not in ("__init__", "helpers"):
|
|
142
|
+
command_name = filepath.stem
|
|
143
|
+
module_name = f"SVG2DrawIOLib.cli.{command_name}"
|
|
144
|
+
try:
|
|
145
|
+
module = importlib.import_module(module_name)
|
|
146
|
+
# Look for a function with the same name as the module
|
|
147
|
+
cli_function = getattr(module, command_name, None)
|
|
148
|
+
if cli_function and callable(cli_function):
|
|
149
|
+
cli.add_command(cli_function)
|
|
150
|
+
logger.debug(f"Registered command: {command_name}")
|
|
151
|
+
else:
|
|
152
|
+
logger.debug(f"No command function found in {module_name}")
|
|
153
|
+
except Exception as e:
|
|
154
|
+
logger.debug(f"Failed to import {module_name}: {e}")
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
if __name__ == "__main__":
|
|
158
|
+
cli()
|
SVG2DrawIOLib/cli/add.py
ADDED
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
"""Add command - Add SVG icons to an existing DrawIO library."""
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
import sys
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
import rich_click as rc
|
|
8
|
+
|
|
9
|
+
from SVG2DrawIOLib.cli.helpers import console, setup_logging
|
|
10
|
+
from SVG2DrawIOLib.library_manager import LibraryManager
|
|
11
|
+
from SVG2DrawIOLib.models import SVGProcessingOptions
|
|
12
|
+
from SVG2DrawIOLib.svg_processor import SVGProcessor
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@rc.command()
|
|
16
|
+
@rc.argument(
|
|
17
|
+
"library_file",
|
|
18
|
+
type=rc.Path(exists=True, dir_okay=False, path_type=Path),
|
|
19
|
+
)
|
|
20
|
+
@rc.argument(
|
|
21
|
+
"svg_paths",
|
|
22
|
+
nargs=-1,
|
|
23
|
+
required=True,
|
|
24
|
+
type=rc.Path(exists=True, path_type=Path),
|
|
25
|
+
metavar="PATHS...",
|
|
26
|
+
)
|
|
27
|
+
@rc.option(
|
|
28
|
+
"--replace",
|
|
29
|
+
"-r",
|
|
30
|
+
is_flag=True,
|
|
31
|
+
help="Replace icons with duplicate names (default: skip duplicates).",
|
|
32
|
+
)
|
|
33
|
+
@rc.option(
|
|
34
|
+
"--add-dupes",
|
|
35
|
+
"-d",
|
|
36
|
+
is_flag=True,
|
|
37
|
+
help="Add duplicate icons with modified names (e.g., icon_2, icon_3).",
|
|
38
|
+
)
|
|
39
|
+
@rc.option(
|
|
40
|
+
"--max-size",
|
|
41
|
+
"-s",
|
|
42
|
+
type=float,
|
|
43
|
+
help="Maximum dimension for new icons (scaled proportionally).",
|
|
44
|
+
)
|
|
45
|
+
@rc.option(
|
|
46
|
+
"--width",
|
|
47
|
+
"-w",
|
|
48
|
+
type=float,
|
|
49
|
+
help="Fixed width in pixels (overrides --max-size).",
|
|
50
|
+
)
|
|
51
|
+
@rc.option(
|
|
52
|
+
"--height",
|
|
53
|
+
"-h",
|
|
54
|
+
type=float,
|
|
55
|
+
help="Fixed height in pixels (overrides --max-size).",
|
|
56
|
+
)
|
|
57
|
+
@rc.option(
|
|
58
|
+
"--css/--no-css",
|
|
59
|
+
"-c/-C",
|
|
60
|
+
default=False,
|
|
61
|
+
help="Add CSS classes to new icons for color editing.",
|
|
62
|
+
)
|
|
63
|
+
@rc.option(
|
|
64
|
+
"--verbose",
|
|
65
|
+
"-v",
|
|
66
|
+
is_flag=True,
|
|
67
|
+
help="Enable verbose debug logging.",
|
|
68
|
+
)
|
|
69
|
+
@rc.option(
|
|
70
|
+
"--quiet",
|
|
71
|
+
"-q",
|
|
72
|
+
is_flag=True,
|
|
73
|
+
help="Suppress all output except errors.",
|
|
74
|
+
)
|
|
75
|
+
@rc.option(
|
|
76
|
+
"--recursive",
|
|
77
|
+
"-R",
|
|
78
|
+
is_flag=True,
|
|
79
|
+
help="Recursively search directories for SVG files.",
|
|
80
|
+
)
|
|
81
|
+
def add(
|
|
82
|
+
library_file: Path,
|
|
83
|
+
svg_paths: tuple[Path, ...],
|
|
84
|
+
replace: bool,
|
|
85
|
+
add_dupes: bool,
|
|
86
|
+
max_size: float | None,
|
|
87
|
+
width: float | None,
|
|
88
|
+
height: float | None,
|
|
89
|
+
css: bool,
|
|
90
|
+
verbose: bool,
|
|
91
|
+
quiet: bool,
|
|
92
|
+
recursive: bool,
|
|
93
|
+
) -> None:
|
|
94
|
+
"""Add SVG icons to an existing DrawIO library.
|
|
95
|
+
|
|
96
|
+
Processes SVG files and adds them to an existing library.
|
|
97
|
+
By default, skips icons with duplicate names.
|
|
98
|
+
|
|
99
|
+
Accepts individual SVG files, directories, or a mix of both.
|
|
100
|
+
Use --recursive to search subdirectories.
|
|
101
|
+
|
|
102
|
+
\b
|
|
103
|
+
Duplicate Handling:
|
|
104
|
+
Default: Skip icons with duplicate names
|
|
105
|
+
--replace: Replace existing icons with same names
|
|
106
|
+
--add-dupes: Add duplicates with modified names (e.g., icon_2, icon_3)
|
|
107
|
+
|
|
108
|
+
\b
|
|
109
|
+
Scaling Options:
|
|
110
|
+
--max-size: Scale icons proportionally (longest side = max-size)
|
|
111
|
+
--width/--height: Set fixed dimensions (ignores aspect ratio)
|
|
112
|
+
Neither: Use original dimensions or library defaults
|
|
113
|
+
|
|
114
|
+
\b
|
|
115
|
+
Examples:
|
|
116
|
+
Add individual files:
|
|
117
|
+
$ SVG2DrawIOLib add my-library.xml new-icon1.svg new-icon2.svg
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
Add from directory:
|
|
121
|
+
$ SVG2DrawIOLib add my-library.xml icons/
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
Add from directory recursively:
|
|
125
|
+
$ SVG2DrawIOLib add my-library.xml icons/ --recursive
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
Replace existing icons with same names:
|
|
129
|
+
$ SVG2DrawIOLib add my-library.xml icons/ --replace
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
Add duplicates with modified names:
|
|
133
|
+
$ SVG2DrawIOLib add my-library.xml icons/ --add-dupes
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
Add with proportional scaling:
|
|
137
|
+
$ SVG2DrawIOLib add my-library.xml icons/ --max-size 64 -R
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
Add with fixed dimensions:
|
|
141
|
+
$ SVG2DrawIOLib add my-library.xml icons/ -w 50 -h 50
|
|
142
|
+
"""
|
|
143
|
+
setup_logging(verbose, quiet)
|
|
144
|
+
logger = logging.getLogger(__name__)
|
|
145
|
+
|
|
146
|
+
try:
|
|
147
|
+
# Validate conflicting options
|
|
148
|
+
if replace and add_dupes:
|
|
149
|
+
console.print(
|
|
150
|
+
"[red]Error:[/red] Cannot use both --replace and --add-dupes", style="bold"
|
|
151
|
+
)
|
|
152
|
+
sys.exit(1)
|
|
153
|
+
|
|
154
|
+
# Collect all SVG files from paths (files and/or directories)
|
|
155
|
+
svg_files = []
|
|
156
|
+
for path in svg_paths:
|
|
157
|
+
if path.is_file():
|
|
158
|
+
if path.suffix.lower() == ".svg":
|
|
159
|
+
svg_files.append(path)
|
|
160
|
+
else:
|
|
161
|
+
logger.warning(f"Skipping non-SVG file: {path}")
|
|
162
|
+
elif path.is_dir():
|
|
163
|
+
if recursive:
|
|
164
|
+
# Recursively find all .svg files
|
|
165
|
+
found = list(path.rglob("*.svg"))
|
|
166
|
+
svg_files.extend(found)
|
|
167
|
+
logger.debug(f"Found {len(found)} SVG file(s) in {path} (recursive)")
|
|
168
|
+
else:
|
|
169
|
+
# Only direct children
|
|
170
|
+
found = list(path.glob("*.svg"))
|
|
171
|
+
svg_files.extend(found)
|
|
172
|
+
logger.debug(f"Found {len(found)} SVG file(s) in {path}")
|
|
173
|
+
else:
|
|
174
|
+
logger.warning(f"Path does not exist or is not accessible: {path}")
|
|
175
|
+
|
|
176
|
+
# Validate we found files
|
|
177
|
+
if not svg_files:
|
|
178
|
+
console.print("[red]Error:[/red] No SVG files found in specified paths", style="bold")
|
|
179
|
+
sys.exit(1)
|
|
180
|
+
|
|
181
|
+
# Determine sizing strategy
|
|
182
|
+
if width is not None and height is not None:
|
|
183
|
+
# Fixed dimensions - will be handled per-icon
|
|
184
|
+
max_dimension = None
|
|
185
|
+
logger.info(f"Using fixed dimensions: {width}x{height}")
|
|
186
|
+
elif width is not None or height is not None:
|
|
187
|
+
# Only one dimension specified - warn user
|
|
188
|
+
console.print(
|
|
189
|
+
"[yellow]Warning:[/yellow] Both --width and --height must be specified for fixed dimensions. "
|
|
190
|
+
"Using original dimensions instead.",
|
|
191
|
+
style="bold",
|
|
192
|
+
)
|
|
193
|
+
max_dimension = None
|
|
194
|
+
logger.warning("Only one dimension specified, using original dimensions")
|
|
195
|
+
elif max_size is not None:
|
|
196
|
+
max_dimension = max_size
|
|
197
|
+
logger.info(f"Using proportional scaling with max dimension: {max_size}")
|
|
198
|
+
else:
|
|
199
|
+
max_dimension = None
|
|
200
|
+
logger.info("Using original dimensions")
|
|
201
|
+
|
|
202
|
+
# Create processing options
|
|
203
|
+
options = SVGProcessingOptions(add_css=css)
|
|
204
|
+
|
|
205
|
+
# Process new SVG files
|
|
206
|
+
processor = SVGProcessor(options)
|
|
207
|
+
new_icons = []
|
|
208
|
+
|
|
209
|
+
logger.info(f"Processing {len(svg_files)} SVG file(s)")
|
|
210
|
+
|
|
211
|
+
for svg_path in svg_files:
|
|
212
|
+
try:
|
|
213
|
+
# If fixed dimensions specified, pass them to processor
|
|
214
|
+
if width is not None and height is not None:
|
|
215
|
+
icon = processor.process_svg_file(svg_path, fixed_dimensions=(width, height))
|
|
216
|
+
else:
|
|
217
|
+
icon = processor.process_svg_file(svg_path, max_dimension=max_dimension)
|
|
218
|
+
|
|
219
|
+
new_icons.append(icon)
|
|
220
|
+
except Exception as e:
|
|
221
|
+
logger.error(f"Failed to process {svg_path}: {e}")
|
|
222
|
+
if verbose:
|
|
223
|
+
raise
|
|
224
|
+
sys.exit(1)
|
|
225
|
+
|
|
226
|
+
# Add to library
|
|
227
|
+
manager = LibraryManager()
|
|
228
|
+
metadata = manager.add_icons_to_library(
|
|
229
|
+
library_file, new_icons, replace_duplicates=replace, add_duplicates=add_dupes
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
console.print(
|
|
233
|
+
f"[green]✓[/green] Updated library with {metadata.icon_count} total icon(s): "
|
|
234
|
+
f"[cyan]{library_file}[/cyan]"
|
|
235
|
+
)
|
|
236
|
+
|
|
237
|
+
except Exception as e:
|
|
238
|
+
logger.error(f"Error: {e}")
|
|
239
|
+
if verbose:
|
|
240
|
+
raise
|
|
241
|
+
sys.exit(1)
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
"""Create command - Create a new DrawIO library from SVG files."""
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
import sys
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
import rich_click as rc
|
|
8
|
+
|
|
9
|
+
from SVG2DrawIOLib.cli.helpers import console, setup_logging
|
|
10
|
+
from SVG2DrawIOLib.library_manager import LibraryManager
|
|
11
|
+
from SVG2DrawIOLib.models import SVGProcessingOptions
|
|
12
|
+
from SVG2DrawIOLib.svg_processor import SVGProcessor
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@rc.command()
|
|
16
|
+
@rc.argument(
|
|
17
|
+
"svg_paths",
|
|
18
|
+
nargs=-1,
|
|
19
|
+
required=True,
|
|
20
|
+
type=rc.Path(exists=True, path_type=Path),
|
|
21
|
+
metavar="PATHS...",
|
|
22
|
+
)
|
|
23
|
+
@rc.option(
|
|
24
|
+
"--output",
|
|
25
|
+
"-o",
|
|
26
|
+
type=rc.Path(path_type=Path),
|
|
27
|
+
required=True,
|
|
28
|
+
help="Output library file path (e.g., my-library.xml).",
|
|
29
|
+
)
|
|
30
|
+
@rc.option(
|
|
31
|
+
"--max-size",
|
|
32
|
+
"-s",
|
|
33
|
+
type=float,
|
|
34
|
+
help="Maximum dimension (width or height) in pixels. Icons are scaled proportionally.",
|
|
35
|
+
)
|
|
36
|
+
@rc.option(
|
|
37
|
+
"--width",
|
|
38
|
+
"-w",
|
|
39
|
+
type=float,
|
|
40
|
+
help="Fixed width in pixels (overrides --max-size).",
|
|
41
|
+
)
|
|
42
|
+
@rc.option(
|
|
43
|
+
"--height",
|
|
44
|
+
"-h",
|
|
45
|
+
type=float,
|
|
46
|
+
help="Fixed height in pixels (overrides --max-size).",
|
|
47
|
+
)
|
|
48
|
+
@rc.option(
|
|
49
|
+
"--css/--no-css",
|
|
50
|
+
"-c/-C",
|
|
51
|
+
default=False,
|
|
52
|
+
help="Add CSS classes to enable color editing in DrawIO.",
|
|
53
|
+
)
|
|
54
|
+
@rc.option(
|
|
55
|
+
"--css-color",
|
|
56
|
+
default="#000000",
|
|
57
|
+
show_default=True,
|
|
58
|
+
help="Default CSS fill color (requires --css).",
|
|
59
|
+
)
|
|
60
|
+
@rc.option(
|
|
61
|
+
"--namespace",
|
|
62
|
+
"-n",
|
|
63
|
+
default="http://www.w3.org/2000/svg",
|
|
64
|
+
show_default=True,
|
|
65
|
+
help="XML namespace for SVG elements.",
|
|
66
|
+
)
|
|
67
|
+
@rc.option(
|
|
68
|
+
"--tag",
|
|
69
|
+
"-t",
|
|
70
|
+
default="path",
|
|
71
|
+
show_default=True,
|
|
72
|
+
help="XML tag to add CSS classes to (requires --css).",
|
|
73
|
+
)
|
|
74
|
+
@rc.option(
|
|
75
|
+
"--verbose",
|
|
76
|
+
"-v",
|
|
77
|
+
is_flag=True,
|
|
78
|
+
help="Enable verbose debug logging.",
|
|
79
|
+
)
|
|
80
|
+
@rc.option(
|
|
81
|
+
"--quiet",
|
|
82
|
+
"-q",
|
|
83
|
+
is_flag=True,
|
|
84
|
+
help="Suppress all output except errors.",
|
|
85
|
+
)
|
|
86
|
+
@rc.option(
|
|
87
|
+
"--recursive",
|
|
88
|
+
"-R",
|
|
89
|
+
is_flag=True,
|
|
90
|
+
help="Recursively search directories for SVG files.",
|
|
91
|
+
)
|
|
92
|
+
def create(
|
|
93
|
+
svg_paths: tuple[Path, ...],
|
|
94
|
+
output: Path,
|
|
95
|
+
max_size: float | None,
|
|
96
|
+
width: float | None,
|
|
97
|
+
height: float | None,
|
|
98
|
+
css: bool,
|
|
99
|
+
css_color: str,
|
|
100
|
+
namespace: str,
|
|
101
|
+
tag: str,
|
|
102
|
+
verbose: bool,
|
|
103
|
+
quiet: bool,
|
|
104
|
+
recursive: bool,
|
|
105
|
+
) -> None:
|
|
106
|
+
"""Create a new DrawIO library from SVG files.
|
|
107
|
+
|
|
108
|
+
Converts one or more SVG files into a DrawIO library XML file.
|
|
109
|
+
Icons can be scaled proportionally or set to fixed dimensions.
|
|
110
|
+
|
|
111
|
+
Accepts individual SVG files, directories, or a mix of both.
|
|
112
|
+
Use --recursive to search subdirectories.
|
|
113
|
+
|
|
114
|
+
\b
|
|
115
|
+
Scaling Options:
|
|
116
|
+
--max-size: Scale icons proportionally (longest side = max-size)
|
|
117
|
+
--width/--height: Set fixed dimensions (ignores aspect ratio)
|
|
118
|
+
Neither: Use default 40x40 pixels
|
|
119
|
+
|
|
120
|
+
Examples:
|
|
121
|
+
Create from individual files:
|
|
122
|
+
$ SVG2DrawIOLib create icon1.svg icon2.svg -o lib.xml
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
Create from directory:
|
|
126
|
+
$ SVG2DrawIOLib create icons/ -o lib.xml
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
Create from directory recursively:
|
|
130
|
+
$ SVG2DrawIOLib create icons/ -o lib.xml --recursive
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
Create with proportional scaling (max 64px):
|
|
134
|
+
$ SVG2DrawIOLib create icons/ -o lib.xml --max-size 64 -R
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
Create with fixed dimensions:
|
|
138
|
+
$ SVG2DrawIOLib create icons/*.svg -o lib.xml -w 50 -h 50
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
Enable color editing:
|
|
142
|
+
$ SVG2DrawIOLib create icons/ -o lib.xml --css
|
|
143
|
+
"""
|
|
144
|
+
setup_logging(verbose, quiet)
|
|
145
|
+
logger = logging.getLogger(__name__)
|
|
146
|
+
|
|
147
|
+
try:
|
|
148
|
+
# Collect all SVG files from paths (files and/or directories)
|
|
149
|
+
svg_files = []
|
|
150
|
+
for path in svg_paths:
|
|
151
|
+
if path.is_file():
|
|
152
|
+
if path.suffix.lower() == ".svg":
|
|
153
|
+
svg_files.append(path)
|
|
154
|
+
else:
|
|
155
|
+
logger.warning(f"Skipping non-SVG file: {path}")
|
|
156
|
+
elif path.is_dir():
|
|
157
|
+
if recursive:
|
|
158
|
+
# Recursively find all .svg files
|
|
159
|
+
found = list(path.rglob("*.svg"))
|
|
160
|
+
svg_files.extend(found)
|
|
161
|
+
logger.debug(f"Found {len(found)} SVG file(s) in {path} (recursive)")
|
|
162
|
+
else:
|
|
163
|
+
# Only direct children
|
|
164
|
+
found = list(path.glob("*.svg"))
|
|
165
|
+
svg_files.extend(found)
|
|
166
|
+
logger.debug(f"Found {len(found)} SVG file(s) in {path}")
|
|
167
|
+
else:
|
|
168
|
+
logger.warning(f"Path does not exist or is not accessible: {path}")
|
|
169
|
+
|
|
170
|
+
# Validate we found files
|
|
171
|
+
if not svg_files:
|
|
172
|
+
console.print("[red]Error:[/red] No SVG files found in specified paths", style="bold")
|
|
173
|
+
sys.exit(1)
|
|
174
|
+
|
|
175
|
+
# Determine sizing strategy
|
|
176
|
+
if width is not None and height is not None:
|
|
177
|
+
# Fixed dimensions - will be handled per-icon
|
|
178
|
+
max_dimension = None
|
|
179
|
+
logger.info(f"Using fixed dimensions: {width}x{height}")
|
|
180
|
+
elif width is not None or height is not None:
|
|
181
|
+
# Only one dimension specified - warn user
|
|
182
|
+
console.print(
|
|
183
|
+
"[yellow]Warning:[/yellow] Both --width and --height must be specified for fixed dimensions. "
|
|
184
|
+
"Using default sizing instead.",
|
|
185
|
+
style="bold",
|
|
186
|
+
)
|
|
187
|
+
max_dimension = None
|
|
188
|
+
logger.warning("Only one dimension specified, using default sizing")
|
|
189
|
+
elif max_size is not None:
|
|
190
|
+
max_dimension = max_size
|
|
191
|
+
logger.info(f"Using proportional scaling with max dimension: {max_size}")
|
|
192
|
+
else:
|
|
193
|
+
max_dimension = None
|
|
194
|
+
logger.info("Using default dimensions: 40x40")
|
|
195
|
+
|
|
196
|
+
# Create processing options
|
|
197
|
+
options = SVGProcessingOptions(
|
|
198
|
+
add_css=css,
|
|
199
|
+
css_color=css_color,
|
|
200
|
+
xml_namespace=namespace,
|
|
201
|
+
css_tag=tag,
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
# Process SVG files
|
|
205
|
+
processor = SVGProcessor(options)
|
|
206
|
+
icons = []
|
|
207
|
+
|
|
208
|
+
logger.info(f"Processing {len(svg_files)} SVG file(s)")
|
|
209
|
+
|
|
210
|
+
for svg_path in svg_files:
|
|
211
|
+
try:
|
|
212
|
+
# If fixed dimensions specified, pass them to processor
|
|
213
|
+
if width is not None and height is not None:
|
|
214
|
+
icon = processor.process_svg_file(svg_path, fixed_dimensions=(width, height))
|
|
215
|
+
else:
|
|
216
|
+
icon = processor.process_svg_file(svg_path, max_dimension=max_dimension)
|
|
217
|
+
|
|
218
|
+
icons.append(icon)
|
|
219
|
+
|
|
220
|
+
except Exception as e:
|
|
221
|
+
logger.error(f"Failed to process {svg_path}: {e}")
|
|
222
|
+
if verbose:
|
|
223
|
+
raise
|
|
224
|
+
sys.exit(1)
|
|
225
|
+
|
|
226
|
+
# Create library
|
|
227
|
+
manager = LibraryManager()
|
|
228
|
+
metadata = manager.create_library(icons, output)
|
|
229
|
+
|
|
230
|
+
console.print(
|
|
231
|
+
f"[green]✓[/green] Created library with {metadata.icon_count} icon(s): "
|
|
232
|
+
f"[cyan]{output}[/cyan]"
|
|
233
|
+
)
|
|
234
|
+
|
|
235
|
+
except KeyboardInterrupt:
|
|
236
|
+
console.print("\n[yellow]Interrupted by user[/yellow]")
|
|
237
|
+
sys.exit(130)
|
|
238
|
+
except Exception as e:
|
|
239
|
+
logger.error(f"Unexpected error: {e}")
|
|
240
|
+
if verbose:
|
|
241
|
+
raise
|
|
242
|
+
sys.exit(1)
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"""Helper functions and utilities for CLI commands."""
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
|
|
5
|
+
from rich.console import Console
|
|
6
|
+
from rich.logging import RichHandler
|
|
7
|
+
|
|
8
|
+
console = Console()
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def setup_logging(verbose: bool, quiet: bool) -> None:
|
|
12
|
+
"""Configure logging with rich output.
|
|
13
|
+
|
|
14
|
+
Args:
|
|
15
|
+
verbose: Enable debug-level logging.
|
|
16
|
+
quiet: Suppress all but error-level logging.
|
|
17
|
+
"""
|
|
18
|
+
if quiet:
|
|
19
|
+
level = logging.ERROR
|
|
20
|
+
elif verbose:
|
|
21
|
+
level = logging.DEBUG
|
|
22
|
+
else:
|
|
23
|
+
level = logging.INFO
|
|
24
|
+
|
|
25
|
+
logging.basicConfig(
|
|
26
|
+
level=level,
|
|
27
|
+
format="%(message)s",
|
|
28
|
+
handlers=[RichHandler(console=console, rich_tracebacks=True, show_time=False)],
|
|
29
|
+
)
|