SVG2DrawIOLib 1.1.2__tar.gz → 1.2.0__tar.gz
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-1.1.2 → svg2drawiolib-1.2.0}/.github/workflows/ci.yml +1 -1
- {svg2drawiolib-1.1.2 → svg2drawiolib-1.2.0}/.pre-commit-config.yaml +2 -2
- {svg2drawiolib-1.1.2 → svg2drawiolib-1.2.0}/CHANGELOG.md +37 -0
- {svg2drawiolib-1.1.2 → svg2drawiolib-1.2.0}/PKG-INFO +1 -1
- {svg2drawiolib-1.1.2 → svg2drawiolib-1.2.0}/pyproject.toml +4 -0
- {svg2drawiolib-1.1.2 → svg2drawiolib-1.2.0}/src/SVG2DrawIOLib/__about__.py +1 -1
- {svg2drawiolib-1.1.2 → svg2drawiolib-1.2.0}/src/SVG2DrawIOLib/cli/__init__.py +38 -4
- {svg2drawiolib-1.1.2 → svg2drawiolib-1.2.0}/src/SVG2DrawIOLib/cli/add.py +36 -2
- svg2drawiolib-1.2.0/src/SVG2DrawIOLib/cli/create.py +314 -0
- svg2drawiolib-1.2.0/src/SVG2DrawIOLib/cli/create_helpers.py +193 -0
- svg2drawiolib-1.2.0/src/SVG2DrawIOLib/cli/extract.py +168 -0
- svg2drawiolib-1.2.0/src/SVG2DrawIOLib/cli/helpers.py +121 -0
- svg2drawiolib-1.2.0/src/SVG2DrawIOLib/cli/inspect.py +259 -0
- {svg2drawiolib-1.1.2 → svg2drawiolib-1.2.0}/src/SVG2DrawIOLib/cli/list.py +9 -2
- svg2drawiolib-1.2.0/src/SVG2DrawIOLib/cli/rename.py +129 -0
- svg2drawiolib-1.2.0/src/SVG2DrawIOLib/cli/validate.py +146 -0
- svg2drawiolib-1.2.0/src/SVG2DrawIOLib/icon_analyzer.py +210 -0
- {svg2drawiolib-1.1.2 → svg2drawiolib-1.2.0}/src/SVG2DrawIOLib/library_manager.py +98 -0
- {svg2drawiolib-1.1.2 → svg2drawiolib-1.2.0}/src/SVG2DrawIOLib/models.py +14 -0
- {svg2drawiolib-1.1.2 → svg2drawiolib-1.2.0}/src/SVG2DrawIOLib/svg_processor.py +162 -38
- svg2drawiolib-1.2.0/src/SVG2DrawIOLib/validator.py +380 -0
- {svg2drawiolib-1.1.2 → svg2drawiolib-1.2.0}/tests/test_cli.py +126 -34
- svg2drawiolib-1.2.0/tests/test_cli_create_helpers.py +212 -0
- svg2drawiolib-1.2.0/tests/test_cli_create_split.py +333 -0
- svg2drawiolib-1.2.0/tests/test_cli_extract.py +253 -0
- svg2drawiolib-1.2.0/tests/test_cli_helpers.py +110 -0
- svg2drawiolib-1.2.0/tests/test_cli_inspect.py +315 -0
- svg2drawiolib-1.2.0/tests/test_cli_rename.py +287 -0
- svg2drawiolib-1.2.0/tests/test_cli_validate.py +510 -0
- svg2drawiolib-1.2.0/tests/test_css_modes.py +433 -0
- {svg2drawiolib-1.1.2 → svg2drawiolib-1.2.0}/tests/test_library_manager.py +5 -1
- {svg2drawiolib-1.1.2 → svg2drawiolib-1.2.0}/tests/test_path_splitter.py +44 -31
- {svg2drawiolib-1.1.2 → svg2drawiolib-1.2.0}/tests/test_svg_processor.py +204 -76
- svg2drawiolib-1.2.0/tests/test_svg_processor_coverage.py +497 -0
- svg2drawiolib-1.2.0/tests/test_viewbox_improvements.py +252 -0
- svg2drawiolib-1.1.2/src/SVG2DrawIOLib/cli/create.py +0 -248
- svg2drawiolib-1.1.2/src/SVG2DrawIOLib/cli/helpers.py +0 -29
- {svg2drawiolib-1.1.2 → svg2drawiolib-1.2.0}/.github/workflows/publish.yml +0 -0
- {svg2drawiolib-1.1.2 → svg2drawiolib-1.2.0}/.github/workflows/release.yml +0 -0
- {svg2drawiolib-1.1.2 → svg2drawiolib-1.2.0}/.gitignore +0 -0
- {svg2drawiolib-1.1.2 → svg2drawiolib-1.2.0}/ARCHITECTURE.md +0 -0
- {svg2drawiolib-1.1.2 → svg2drawiolib-1.2.0}/CONTRIBUTING.md +0 -0
- {svg2drawiolib-1.1.2 → svg2drawiolib-1.2.0}/LICENSE.txt +0 -0
- {svg2drawiolib-1.1.2 → svg2drawiolib-1.2.0}/MANIFEST.in +0 -0
- {svg2drawiolib-1.1.2 → svg2drawiolib-1.2.0}/Makefile +0 -0
- {svg2drawiolib-1.1.2 → svg2drawiolib-1.2.0}/QUICKSTART.md +0 -0
- {svg2drawiolib-1.1.2 → svg2drawiolib-1.2.0}/README.md +0 -0
- {svg2drawiolib-1.1.2 → svg2drawiolib-1.2.0}/src/SVG2DrawIOLib/__init__.py +0 -0
- {svg2drawiolib-1.1.2 → svg2drawiolib-1.2.0}/src/SVG2DrawIOLib/cli/remove.py +0 -0
- {svg2drawiolib-1.1.2 → svg2drawiolib-1.2.0}/src/SVG2DrawIOLib/cli/split_paths.py +0 -0
- {svg2drawiolib-1.1.2 → svg2drawiolib-1.2.0}/src/SVG2DrawIOLib/path_splitter.py +0 -0
- {svg2drawiolib-1.1.2 → svg2drawiolib-1.2.0}/tests/__init__.py +0 -0
- {svg2drawiolib-1.1.2 → svg2drawiolib-1.2.0}/tests/conftest.py +0 -0
- {svg2drawiolib-1.1.2 → svg2drawiolib-1.2.0}/tests/test_models.py +0 -0
- {svg2drawiolib-1.1.2 → svg2drawiolib-1.2.0}/uv.lock +0 -0
|
@@ -5,6 +5,43 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [1.2.0] - 2026-02-08
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- **Improved ViewBox Adjustment with Transforms**: Significantly improved viewBox adjustment accuracy for SVGs with transforms. The svgelements-based bbox calculation now correctly handles transformed elements (translate, scale, rotate, skew, matrix) while still excluding non-rendering containers (defs, clipPath, mask, symbol, pattern, marker). This reduces unnecessary padding in many real-world icons that use transforms, which are common in exported SVGs. The manual fallback still conservatively skips transforms for compatibility.
|
|
13
|
+
- **Enhanced CSS Injection**: Significantly improved CSS class generation for color editing in DrawIO with support for real-world SVG authoring patterns:
|
|
14
|
+
- **CSS Modes**: New `--css-mode` option with three modes:
|
|
15
|
+
- `fill` (default): Generate CSS rules for fill colors only
|
|
16
|
+
- `stroke`: Generate CSS rules for stroke colors only
|
|
17
|
+
- `both`: Generate CSS rules for both fill and stroke colors
|
|
18
|
+
- **Style Attribute Parsing**: Now parses `style="fill:#fff;stroke:#000"` attributes in addition to direct `fill` and `stroke` attributes
|
|
19
|
+
- **fill="none" Handling**: Properly respects `fill="none"` and `stroke="none"` - no longer forces colors on stroke-only or fill-only paths
|
|
20
|
+
- **currentColor Support**: New `--preserve-current-color` flag (default: true) to preserve `currentColor` values for theme-aware icons
|
|
21
|
+
- **Default Stroke Color**: New `--css-stroke-color` option to set default stroke color (used with `--css-mode stroke` or `both`)
|
|
22
|
+
- Available in both `create` and `add` commands
|
|
23
|
+
- Makes split-paths workflow more consistently colorable in DrawIO
|
|
24
|
+
- **Extract Command**: New `extract` CLI command for extracting icons from DrawIO libraries back to individual SVG files. This is the inverse operation of `create`, allowing users to recover SVG files from library files. Supports extracting all icons or specific icons by name, with optional overwrite functionality.
|
|
25
|
+
- **Rename Command**: New `rename` CLI command for renaming icons within a DrawIO library. Allows renaming a single icon while preserving its content. Supports optional `--overwrite` flag to replace an existing icon with the new name.
|
|
26
|
+
- **Inspect Command**: New `inspect` CLI command for displaying detailed information about icons in a DrawIO library. Shows dimensions, shape type, CSS classes, and inline styles for each icon. Supports inspecting all icons or specific icons by name, with optional `--show-svg` flag to display the decoded SVG content. Includes `--json` flag for machine-readable JSON output.
|
|
27
|
+
- **Validate Command**: New `validate` CLI command for comprehensive validation of DrawIO library files. Validates XML structure, JSON format, icon integrity (required fields, dimensions, base64 encoding, compression), mxGraphModel structure, and SVG content (namespace, viewBox/dimensions, empty SVG detection). Provides detailed error and warning messages with color-coded status output. Includes `LibraryValidator` service class following SOLID principles for separation of concerns.
|
|
28
|
+
- **CLI Command Groups**: Reorganized CLI commands into three logical groups for better discoverability: Library Management Commands (create, add, remove, extract, rename), Library Inspection Commands (list, inspect, validate), and SVG Processing Commands (split-paths).
|
|
29
|
+
- **Split by Folder**: New `--split-by-folder` flag for `create` command that creates separate library files for each subdirectory when used with `--recursive`. Output filename is modified with subdirectory name (e.g., `FontAwesome.xml` becomes `FontAwesome-Regular.xml`, `FontAwesome-Solid.xml`, `FontAwesome-Brands.xml`). This is useful for organizing icon sets that are already grouped by category in the filesystem.
|
|
30
|
+
|
|
31
|
+
### Changed
|
|
32
|
+
|
|
33
|
+
- **Refactored CLI Commands**: Improved separation of concerns following SOLID principles. Business logic has been extracted from CLI commands into reusable service classes:
|
|
34
|
+
- Created `IconAnalyzer` service for extracting and analyzing icon content (used by `extract` and `inspect` commands)
|
|
35
|
+
- Added `LibraryManager.rename_icon()` method for icon renaming logic (used by `rename` command)
|
|
36
|
+
- CLI commands are now thin orchestration layers that delegate to service classes
|
|
37
|
+
- All business logic is now testable independently of the CLI layer
|
|
38
|
+
- **Test Organization**: Improved test file organization for consistency. Each CLI command now has its own dedicated test file (`test_cli_extract.py`, `test_cli_rename.py`, etc.) following the same pattern as other command tests.
|
|
39
|
+
|
|
40
|
+
### Fixed
|
|
41
|
+
|
|
42
|
+
- **List Command Table Width**: Fixed table width calculation in `list` command to prevent title text wrapping. The table now properly sizes to accommodate whichever is wider: the title text, column header, or icon names. This ensures clean, readable output regardless of filename or icon name length.
|
|
43
|
+
- **Type Checking Configuration**: Fixed mypy configuration inconsistency between local pre-commit and GitHub CI. Removed CLI directory exclusion from local pre-commit config and added tests directory to both local and CI mypy checks. All 107 type errors across test files have been resolved with proper type annotations and assertions. Both local and CI environments now run `mypy --strict src tests` consistently.
|
|
44
|
+
|
|
8
45
|
## [1.1.2] - 2026-02-08
|
|
9
46
|
|
|
10
47
|
### Fixed
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: SVG2DrawIOLib
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.2.0
|
|
4
4
|
Summary: Generate DrawIO shape libraries from SVGs
|
|
5
5
|
Project-URL: Homepage, https://github.com/jamesbconner/SVG2DrawIOLib
|
|
6
6
|
Project-URL: Documentation, https://github.com/jamesbconner/SVG2DrawIOLib#readme
|
|
@@ -47,7 +47,13 @@ rc.rich_click.COMMAND_GROUPS = {
|
|
|
47
47
|
"SVG2DrawIOLib": [
|
|
48
48
|
{
|
|
49
49
|
"name": "Library Management Commands",
|
|
50
|
-
"commands": [
|
|
50
|
+
"commands": [
|
|
51
|
+
"create",
|
|
52
|
+
"add",
|
|
53
|
+
"remove",
|
|
54
|
+
"extract",
|
|
55
|
+
"rename",
|
|
56
|
+
],
|
|
51
57
|
"table_styles": {
|
|
52
58
|
"show_lines": True,
|
|
53
59
|
"row_styles": ["none"],
|
|
@@ -55,6 +61,20 @@ rc.rich_click.COMMAND_GROUPS = {
|
|
|
55
61
|
"box": "ROUNDED",
|
|
56
62
|
},
|
|
57
63
|
},
|
|
64
|
+
{
|
|
65
|
+
"name": "Library Inspection Commands",
|
|
66
|
+
"commands": [
|
|
67
|
+
"list",
|
|
68
|
+
"inspect",
|
|
69
|
+
"validate",
|
|
70
|
+
],
|
|
71
|
+
"table_styles": {
|
|
72
|
+
"show_lines": True,
|
|
73
|
+
"row_styles": ["none"],
|
|
74
|
+
"border_style": "cyan",
|
|
75
|
+
"box": "ROUNDED",
|
|
76
|
+
},
|
|
77
|
+
},
|
|
58
78
|
{
|
|
59
79
|
"name": "SVG Processing Commands",
|
|
60
80
|
"commands": ["split-paths"],
|
|
@@ -81,11 +101,19 @@ rc.rich_click.OPTION_GROUPS = {
|
|
|
81
101
|
},
|
|
82
102
|
{
|
|
83
103
|
"name": "Styling Options",
|
|
84
|
-
"options": [
|
|
104
|
+
"options": [
|
|
105
|
+
"--css",
|
|
106
|
+
"--css-color",
|
|
107
|
+
"--css-mode",
|
|
108
|
+
"--css-stroke-color",
|
|
109
|
+
"--preserve-current-color",
|
|
110
|
+
"--namespace",
|
|
111
|
+
"--tag",
|
|
112
|
+
],
|
|
85
113
|
},
|
|
86
114
|
{
|
|
87
115
|
"name": "General Options",
|
|
88
|
-
"options": ["--recursive", "--verbose", "--quiet", "--help"],
|
|
116
|
+
"options": ["--recursive", "--split-by-folder", "--verbose", "--quiet", "--help"],
|
|
89
117
|
},
|
|
90
118
|
],
|
|
91
119
|
"SVG2DrawIOLib add": [
|
|
@@ -99,7 +127,13 @@ rc.rich_click.OPTION_GROUPS = {
|
|
|
99
127
|
},
|
|
100
128
|
{
|
|
101
129
|
"name": "Styling Options",
|
|
102
|
-
"options": [
|
|
130
|
+
"options": [
|
|
131
|
+
"--css",
|
|
132
|
+
"--css-color",
|
|
133
|
+
"--css-mode",
|
|
134
|
+
"--css-stroke-color",
|
|
135
|
+
"--preserve-current-color",
|
|
136
|
+
],
|
|
103
137
|
},
|
|
104
138
|
{
|
|
105
139
|
"name": "General Options",
|
|
@@ -59,6 +59,31 @@ from SVG2DrawIOLib.svg_processor import SVGProcessor
|
|
|
59
59
|
default=False,
|
|
60
60
|
help="Add CSS classes to new icons for color editing.",
|
|
61
61
|
)
|
|
62
|
+
@rc.option(
|
|
63
|
+
"--css-mode",
|
|
64
|
+
type=rc.Choice(["fill", "stroke", "both"], case_sensitive=False),
|
|
65
|
+
default="fill",
|
|
66
|
+
show_default=True,
|
|
67
|
+
help="CSS mode: 'fill' for fill colors, 'stroke' for stroke colors, 'both' for both (requires --css).",
|
|
68
|
+
)
|
|
69
|
+
@rc.option(
|
|
70
|
+
"--css-color",
|
|
71
|
+
default="#000000",
|
|
72
|
+
show_default=True,
|
|
73
|
+
help="Default CSS fill color (requires --css).",
|
|
74
|
+
)
|
|
75
|
+
@rc.option(
|
|
76
|
+
"--css-stroke-color",
|
|
77
|
+
default="#000000",
|
|
78
|
+
show_default=True,
|
|
79
|
+
help="Default CSS stroke color (requires --css with --css-mode stroke or both).",
|
|
80
|
+
)
|
|
81
|
+
@rc.option(
|
|
82
|
+
"--preserve-current-color/--no-preserve-current-color",
|
|
83
|
+
default=True,
|
|
84
|
+
show_default=True,
|
|
85
|
+
help="Preserve 'currentColor' values in CSS (requires --css).",
|
|
86
|
+
)
|
|
62
87
|
@rc.option(
|
|
63
88
|
"--verbose",
|
|
64
89
|
"-v",
|
|
@@ -86,6 +111,10 @@ def add(
|
|
|
86
111
|
width: float | None,
|
|
87
112
|
height: float | None,
|
|
88
113
|
css: bool,
|
|
114
|
+
css_mode: str,
|
|
115
|
+
css_color: str,
|
|
116
|
+
css_stroke_color: str,
|
|
117
|
+
preserve_current_color: bool,
|
|
89
118
|
verbose: bool,
|
|
90
119
|
quiet: bool,
|
|
91
120
|
recursive: bool,
|
|
@@ -200,7 +229,13 @@ def add(
|
|
|
200
229
|
logger.info("Using default sizing: max dimension 40 (aspect ratio preserved)")
|
|
201
230
|
|
|
202
231
|
# Create processing options
|
|
203
|
-
options = SVGProcessingOptions(
|
|
232
|
+
options = SVGProcessingOptions(
|
|
233
|
+
add_css=css,
|
|
234
|
+
css_mode=css_mode,
|
|
235
|
+
css_color=css_color,
|
|
236
|
+
css_stroke_color=css_stroke_color,
|
|
237
|
+
preserve_current_color=preserve_current_color,
|
|
238
|
+
)
|
|
204
239
|
|
|
205
240
|
# Process new SVG files
|
|
206
241
|
processor = SVGProcessor(options)
|
|
@@ -233,7 +268,6 @@ def add(
|
|
|
233
268
|
source_files=svg_files,
|
|
234
269
|
)
|
|
235
270
|
|
|
236
|
-
logger.info(f"Successfully updated library: {library_file}")
|
|
237
271
|
console.print(
|
|
238
272
|
f"[green]✓[/green] Updated library with {metadata.icon_count} total icon(s): "
|
|
239
273
|
f"[cyan]{library_file}[/cyan]"
|
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
"""Create command - Create a new DrawIO library from SVG files."""
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
import rich_click as rc
|
|
7
|
+
|
|
8
|
+
from SVG2DrawIOLib.cli.create_helpers import (
|
|
9
|
+
collect_svg_files,
|
|
10
|
+
determine_sizing_strategy,
|
|
11
|
+
generate_output_path,
|
|
12
|
+
group_files_by_folder,
|
|
13
|
+
process_svg_files,
|
|
14
|
+
)
|
|
15
|
+
from SVG2DrawIOLib.cli.helpers import console, setup_logging
|
|
16
|
+
from SVG2DrawIOLib.library_manager import LibraryManager
|
|
17
|
+
from SVG2DrawIOLib.models import SVGProcessingOptions
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@rc.command()
|
|
21
|
+
@rc.argument(
|
|
22
|
+
"svg_paths",
|
|
23
|
+
nargs=-1,
|
|
24
|
+
required=True,
|
|
25
|
+
type=rc.Path(exists=True, path_type=Path),
|
|
26
|
+
metavar="PATHS...",
|
|
27
|
+
)
|
|
28
|
+
@rc.option(
|
|
29
|
+
"--output",
|
|
30
|
+
"-o",
|
|
31
|
+
type=rc.Path(path_type=Path),
|
|
32
|
+
required=True,
|
|
33
|
+
help="Output library file path (e.g., my-library.xml).",
|
|
34
|
+
)
|
|
35
|
+
@rc.option(
|
|
36
|
+
"--max-size",
|
|
37
|
+
"-s",
|
|
38
|
+
type=float,
|
|
39
|
+
help="Maximum dimension (width or height) in pixels. Icons are scaled proportionally.",
|
|
40
|
+
)
|
|
41
|
+
@rc.option(
|
|
42
|
+
"--width",
|
|
43
|
+
"-w",
|
|
44
|
+
type=float,
|
|
45
|
+
help="Fixed width in pixels (overrides --max-size).",
|
|
46
|
+
)
|
|
47
|
+
@rc.option(
|
|
48
|
+
"--height",
|
|
49
|
+
"-h",
|
|
50
|
+
type=float,
|
|
51
|
+
help="Fixed height in pixels (overrides --max-size).",
|
|
52
|
+
)
|
|
53
|
+
@rc.option(
|
|
54
|
+
"--css/--no-css",
|
|
55
|
+
"-c/-C",
|
|
56
|
+
default=False,
|
|
57
|
+
help="Add CSS classes to enable color editing in DrawIO.",
|
|
58
|
+
)
|
|
59
|
+
@rc.option(
|
|
60
|
+
"--css-mode",
|
|
61
|
+
type=rc.Choice(["fill", "stroke", "both"], case_sensitive=False),
|
|
62
|
+
default="fill",
|
|
63
|
+
show_default=True,
|
|
64
|
+
help="CSS mode: 'fill' for fill colors, 'stroke' for stroke colors, 'both' for both (requires --css).",
|
|
65
|
+
)
|
|
66
|
+
@rc.option(
|
|
67
|
+
"--css-color",
|
|
68
|
+
default="#000000",
|
|
69
|
+
show_default=True,
|
|
70
|
+
help="Default CSS fill color (requires --css).",
|
|
71
|
+
)
|
|
72
|
+
@rc.option(
|
|
73
|
+
"--css-stroke-color",
|
|
74
|
+
default="#000000",
|
|
75
|
+
show_default=True,
|
|
76
|
+
help="Default CSS stroke color (requires --css with --css-mode stroke or both).",
|
|
77
|
+
)
|
|
78
|
+
@rc.option(
|
|
79
|
+
"--preserve-current-color/--no-preserve-current-color",
|
|
80
|
+
default=True,
|
|
81
|
+
show_default=True,
|
|
82
|
+
help="Preserve 'currentColor' values in CSS (requires --css).",
|
|
83
|
+
)
|
|
84
|
+
@rc.option(
|
|
85
|
+
"--namespace",
|
|
86
|
+
"-n",
|
|
87
|
+
default="http://www.w3.org/2000/svg",
|
|
88
|
+
show_default=True,
|
|
89
|
+
help="XML namespace for SVG elements.",
|
|
90
|
+
)
|
|
91
|
+
@rc.option(
|
|
92
|
+
"--tag",
|
|
93
|
+
"-t",
|
|
94
|
+
default="path",
|
|
95
|
+
show_default=True,
|
|
96
|
+
help="XML tag to add CSS classes to (requires --css).",
|
|
97
|
+
)
|
|
98
|
+
@rc.option(
|
|
99
|
+
"--verbose",
|
|
100
|
+
"-v",
|
|
101
|
+
is_flag=True,
|
|
102
|
+
help="Enable verbose debug logging.",
|
|
103
|
+
)
|
|
104
|
+
@rc.option(
|
|
105
|
+
"--quiet",
|
|
106
|
+
"-q",
|
|
107
|
+
is_flag=True,
|
|
108
|
+
help="Suppress all output except errors.",
|
|
109
|
+
)
|
|
110
|
+
@rc.option(
|
|
111
|
+
"--recursive",
|
|
112
|
+
"-R",
|
|
113
|
+
is_flag=True,
|
|
114
|
+
help="Recursively search directories for SVG files.",
|
|
115
|
+
)
|
|
116
|
+
@rc.option(
|
|
117
|
+
"--split-by-folder",
|
|
118
|
+
"-S",
|
|
119
|
+
is_flag=True,
|
|
120
|
+
help="Create separate libraries for each subdirectory (requires --recursive). "
|
|
121
|
+
"Output filename will be modified with subdirectory name (e.g., lib.xml -> lib-FolderName.xml).",
|
|
122
|
+
)
|
|
123
|
+
def create(
|
|
124
|
+
svg_paths: tuple[Path, ...],
|
|
125
|
+
output: Path,
|
|
126
|
+
max_size: float | None,
|
|
127
|
+
width: float | None,
|
|
128
|
+
height: float | None,
|
|
129
|
+
css: bool,
|
|
130
|
+
css_mode: str,
|
|
131
|
+
css_color: str,
|
|
132
|
+
css_stroke_color: str,
|
|
133
|
+
preserve_current_color: bool,
|
|
134
|
+
namespace: str,
|
|
135
|
+
tag: str,
|
|
136
|
+
verbose: bool,
|
|
137
|
+
quiet: bool,
|
|
138
|
+
recursive: bool,
|
|
139
|
+
split_by_folder: bool,
|
|
140
|
+
) -> None:
|
|
141
|
+
"""[bold cyan]Create a new DrawIO library from SVG files.[/]
|
|
142
|
+
|
|
143
|
+
\b
|
|
144
|
+
\nConverts one or more SVG files into a DrawIO library XML file.
|
|
145
|
+
Icons can be scaled proportionally or set to fixed dimensions.
|
|
146
|
+
|
|
147
|
+
\b
|
|
148
|
+
Accepts individual SVG files, directories, or a mix of both.
|
|
149
|
+
Use --recursive to search subdirectories.
|
|
150
|
+
|
|
151
|
+
\b
|
|
152
|
+
Scaling Options:
|
|
153
|
+
--max-size: Scale icons proportionally (longest side = max-size)
|
|
154
|
+
--width/--height: Set fixed dimensions (ignores aspect ratio)
|
|
155
|
+
Neither: Use default 40x40 pixels
|
|
156
|
+
|
|
157
|
+
\b
|
|
158
|
+
Examples:
|
|
159
|
+
Create from individual files:
|
|
160
|
+
$ SVG2DrawIOLib create icon1.svg icon2.svg -o lib.xml
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
Create from directory:
|
|
164
|
+
$ SVG2DrawIOLib create icons/ -o lib.xml
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
Create from directory recursively:
|
|
168
|
+
$ SVG2DrawIOLib create icons/ -o lib.xml --recursive
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
Create separate libraries per subdirectory:
|
|
172
|
+
$ SVG2DrawIOLib create icons/ -o FontAwesome.xml -R --split-by-folder
|
|
173
|
+
# Creates: FontAwesome-Regular.xml, FontAwesome-Solid.xml, etc.
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
Create with proportional scaling (max 64px):
|
|
177
|
+
$ SVG2DrawIOLib create icons/ -o lib.xml --max-size 64 -R
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
Create with fixed dimensions:
|
|
181
|
+
$ SVG2DrawIOLib create icons/*.svg -o lib.xml -w 50 -h 50
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
Enable color editing:
|
|
185
|
+
$ SVG2DrawIOLib create icons/ -o lib.xml --css
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
Enable stroke color editing:
|
|
189
|
+
$ SVG2DrawIOLib create icons/ -o lib.xml --css --css-mode stroke
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
Enable both fill and stroke editing:
|
|
193
|
+
$ SVG2DrawIOLib create icons/ -o lib.xml --css --css-mode both
|
|
194
|
+
"""
|
|
195
|
+
setup_logging(verbose, quiet)
|
|
196
|
+
logger = logging.getLogger(__name__)
|
|
197
|
+
|
|
198
|
+
try:
|
|
199
|
+
# Validate conflicting options
|
|
200
|
+
if split_by_folder and not recursive:
|
|
201
|
+
console.print("[red]Error:[/red] --split-by-folder requires --recursive", style="bold")
|
|
202
|
+
raise rc.ClickException("--split-by-folder requires --recursive")
|
|
203
|
+
|
|
204
|
+
# Collect all SVG files from paths
|
|
205
|
+
svg_files = collect_svg_files(svg_paths, recursive)
|
|
206
|
+
base_dirs = [p for p in svg_paths if p.is_dir()]
|
|
207
|
+
|
|
208
|
+
# Validate we found files
|
|
209
|
+
if not svg_files:
|
|
210
|
+
console.print("[red]Error:[/red] No SVG files found in specified paths", style="bold")
|
|
211
|
+
raise rc.ClickException("No SVG files found in specified paths")
|
|
212
|
+
|
|
213
|
+
# If split-by-folder, group files by their immediate subdirectory
|
|
214
|
+
folder_groups: dict[str, list[Path]] = {}
|
|
215
|
+
if split_by_folder:
|
|
216
|
+
if not base_dirs:
|
|
217
|
+
console.print(
|
|
218
|
+
"[red]Error:[/red] --split-by-folder requires at least one directory path",
|
|
219
|
+
style="bold",
|
|
220
|
+
)
|
|
221
|
+
raise rc.ClickException("--split-by-folder requires at least one directory path")
|
|
222
|
+
|
|
223
|
+
folder_groups = group_files_by_folder(svg_files, base_dirs)
|
|
224
|
+
|
|
225
|
+
if not folder_groups:
|
|
226
|
+
console.print(
|
|
227
|
+
"[yellow]Warning:[/yellow] No subdirectories found, creating single library",
|
|
228
|
+
style="bold",
|
|
229
|
+
)
|
|
230
|
+
split_by_folder = False
|
|
231
|
+
else:
|
|
232
|
+
logger.info(
|
|
233
|
+
f"Split by folder: found {len(folder_groups)} subdirectories with SVG files"
|
|
234
|
+
)
|
|
235
|
+
|
|
236
|
+
# Determine sizing strategy
|
|
237
|
+
max_dimension, has_sizing_warning = determine_sizing_strategy(width, height, max_size)
|
|
238
|
+
if has_sizing_warning:
|
|
239
|
+
console.print(
|
|
240
|
+
"[yellow]Warning:[/yellow] Both --width and --height must be specified for fixed dimensions. "
|
|
241
|
+
"Using default sizing instead.",
|
|
242
|
+
style="bold",
|
|
243
|
+
)
|
|
244
|
+
|
|
245
|
+
# Create processing options
|
|
246
|
+
options = SVGProcessingOptions(
|
|
247
|
+
add_css=css,
|
|
248
|
+
css_mode=css_mode,
|
|
249
|
+
css_color=css_color,
|
|
250
|
+
css_stroke_color=css_stroke_color,
|
|
251
|
+
preserve_current_color=preserve_current_color,
|
|
252
|
+
xml_namespace=namespace,
|
|
253
|
+
css_tag=tag,
|
|
254
|
+
)
|
|
255
|
+
|
|
256
|
+
# Initialize library manager
|
|
257
|
+
manager = LibraryManager()
|
|
258
|
+
|
|
259
|
+
if split_by_folder:
|
|
260
|
+
# Create separate libraries for each folder
|
|
261
|
+
created_libraries = []
|
|
262
|
+
|
|
263
|
+
for folder_name, folder_files in sorted(folder_groups.items()):
|
|
264
|
+
logger.info(f"Processing folder '{folder_name}' with {len(folder_files)} file(s)")
|
|
265
|
+
|
|
266
|
+
try:
|
|
267
|
+
icons = process_svg_files(folder_files, options, width, height, max_dimension)
|
|
268
|
+
except Exception as e:
|
|
269
|
+
logger.error(f"Failed to process folder '{folder_name}': {e}")
|
|
270
|
+
if verbose:
|
|
271
|
+
raise
|
|
272
|
+
raise rc.ClickException(f"Failed to process folder '{folder_name}': {e}") from e
|
|
273
|
+
|
|
274
|
+
# Generate output filename with folder name
|
|
275
|
+
folder_output = generate_output_path(output, folder_name)
|
|
276
|
+
|
|
277
|
+
# Create library for this folder
|
|
278
|
+
metadata = manager.create_library(icons, folder_output, source_files=folder_files)
|
|
279
|
+
created_libraries.append((folder_output, metadata.icon_count))
|
|
280
|
+
|
|
281
|
+
# Summary
|
|
282
|
+
console.print(f"[green]✓[/green] Created {len(created_libraries)} libraries by folder:")
|
|
283
|
+
for lib_path, icon_count in created_libraries:
|
|
284
|
+
console.print(f" [cyan]{lib_path.name}[/cyan]: {icon_count} icon(s)")
|
|
285
|
+
|
|
286
|
+
else:
|
|
287
|
+
# Create single library with all files
|
|
288
|
+
try:
|
|
289
|
+
icons = process_svg_files(svg_files, options, width, height, max_dimension)
|
|
290
|
+
except Exception as e:
|
|
291
|
+
logger.error(f"Failed to process SVG files: {e}")
|
|
292
|
+
if verbose:
|
|
293
|
+
raise
|
|
294
|
+
raise rc.ClickException(f"Failed to process SVG files: {e}") from e
|
|
295
|
+
|
|
296
|
+
# Create library
|
|
297
|
+
metadata = manager.create_library(icons, output, source_files=svg_files)
|
|
298
|
+
|
|
299
|
+
console.print(
|
|
300
|
+
f"[green]✓[/green] Created library with {metadata.icon_count} icon(s): "
|
|
301
|
+
f"[cyan]{output}[/cyan]"
|
|
302
|
+
)
|
|
303
|
+
|
|
304
|
+
except KeyboardInterrupt:
|
|
305
|
+
console.print("\n[yellow]Interrupted by user[/yellow]")
|
|
306
|
+
raise rc.Abort() from None
|
|
307
|
+
except rc.ClickException:
|
|
308
|
+
# Re-raise ClickException from inner handlers without wrapping
|
|
309
|
+
raise
|
|
310
|
+
except Exception as e:
|
|
311
|
+
logger.error(f"Failed to create library: {e}")
|
|
312
|
+
if verbose:
|
|
313
|
+
raise
|
|
314
|
+
raise rc.ClickException(f"Failed to create library: {e}") from e
|