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.
Files changed (55) hide show
  1. {svg2drawiolib-1.1.2 → svg2drawiolib-1.2.0}/.github/workflows/ci.yml +1 -1
  2. {svg2drawiolib-1.1.2 → svg2drawiolib-1.2.0}/.pre-commit-config.yaml +2 -2
  3. {svg2drawiolib-1.1.2 → svg2drawiolib-1.2.0}/CHANGELOG.md +37 -0
  4. {svg2drawiolib-1.1.2 → svg2drawiolib-1.2.0}/PKG-INFO +1 -1
  5. {svg2drawiolib-1.1.2 → svg2drawiolib-1.2.0}/pyproject.toml +4 -0
  6. {svg2drawiolib-1.1.2 → svg2drawiolib-1.2.0}/src/SVG2DrawIOLib/__about__.py +1 -1
  7. {svg2drawiolib-1.1.2 → svg2drawiolib-1.2.0}/src/SVG2DrawIOLib/cli/__init__.py +38 -4
  8. {svg2drawiolib-1.1.2 → svg2drawiolib-1.2.0}/src/SVG2DrawIOLib/cli/add.py +36 -2
  9. svg2drawiolib-1.2.0/src/SVG2DrawIOLib/cli/create.py +314 -0
  10. svg2drawiolib-1.2.0/src/SVG2DrawIOLib/cli/create_helpers.py +193 -0
  11. svg2drawiolib-1.2.0/src/SVG2DrawIOLib/cli/extract.py +168 -0
  12. svg2drawiolib-1.2.0/src/SVG2DrawIOLib/cli/helpers.py +121 -0
  13. svg2drawiolib-1.2.0/src/SVG2DrawIOLib/cli/inspect.py +259 -0
  14. {svg2drawiolib-1.1.2 → svg2drawiolib-1.2.0}/src/SVG2DrawIOLib/cli/list.py +9 -2
  15. svg2drawiolib-1.2.0/src/SVG2DrawIOLib/cli/rename.py +129 -0
  16. svg2drawiolib-1.2.0/src/SVG2DrawIOLib/cli/validate.py +146 -0
  17. svg2drawiolib-1.2.0/src/SVG2DrawIOLib/icon_analyzer.py +210 -0
  18. {svg2drawiolib-1.1.2 → svg2drawiolib-1.2.0}/src/SVG2DrawIOLib/library_manager.py +98 -0
  19. {svg2drawiolib-1.1.2 → svg2drawiolib-1.2.0}/src/SVG2DrawIOLib/models.py +14 -0
  20. {svg2drawiolib-1.1.2 → svg2drawiolib-1.2.0}/src/SVG2DrawIOLib/svg_processor.py +162 -38
  21. svg2drawiolib-1.2.0/src/SVG2DrawIOLib/validator.py +380 -0
  22. {svg2drawiolib-1.1.2 → svg2drawiolib-1.2.0}/tests/test_cli.py +126 -34
  23. svg2drawiolib-1.2.0/tests/test_cli_create_helpers.py +212 -0
  24. svg2drawiolib-1.2.0/tests/test_cli_create_split.py +333 -0
  25. svg2drawiolib-1.2.0/tests/test_cli_extract.py +253 -0
  26. svg2drawiolib-1.2.0/tests/test_cli_helpers.py +110 -0
  27. svg2drawiolib-1.2.0/tests/test_cli_inspect.py +315 -0
  28. svg2drawiolib-1.2.0/tests/test_cli_rename.py +287 -0
  29. svg2drawiolib-1.2.0/tests/test_cli_validate.py +510 -0
  30. svg2drawiolib-1.2.0/tests/test_css_modes.py +433 -0
  31. {svg2drawiolib-1.1.2 → svg2drawiolib-1.2.0}/tests/test_library_manager.py +5 -1
  32. {svg2drawiolib-1.1.2 → svg2drawiolib-1.2.0}/tests/test_path_splitter.py +44 -31
  33. {svg2drawiolib-1.1.2 → svg2drawiolib-1.2.0}/tests/test_svg_processor.py +204 -76
  34. svg2drawiolib-1.2.0/tests/test_svg_processor_coverage.py +497 -0
  35. svg2drawiolib-1.2.0/tests/test_viewbox_improvements.py +252 -0
  36. svg2drawiolib-1.1.2/src/SVG2DrawIOLib/cli/create.py +0 -248
  37. svg2drawiolib-1.1.2/src/SVG2DrawIOLib/cli/helpers.py +0 -29
  38. {svg2drawiolib-1.1.2 → svg2drawiolib-1.2.0}/.github/workflows/publish.yml +0 -0
  39. {svg2drawiolib-1.1.2 → svg2drawiolib-1.2.0}/.github/workflows/release.yml +0 -0
  40. {svg2drawiolib-1.1.2 → svg2drawiolib-1.2.0}/.gitignore +0 -0
  41. {svg2drawiolib-1.1.2 → svg2drawiolib-1.2.0}/ARCHITECTURE.md +0 -0
  42. {svg2drawiolib-1.1.2 → svg2drawiolib-1.2.0}/CONTRIBUTING.md +0 -0
  43. {svg2drawiolib-1.1.2 → svg2drawiolib-1.2.0}/LICENSE.txt +0 -0
  44. {svg2drawiolib-1.1.2 → svg2drawiolib-1.2.0}/MANIFEST.in +0 -0
  45. {svg2drawiolib-1.1.2 → svg2drawiolib-1.2.0}/Makefile +0 -0
  46. {svg2drawiolib-1.1.2 → svg2drawiolib-1.2.0}/QUICKSTART.md +0 -0
  47. {svg2drawiolib-1.1.2 → svg2drawiolib-1.2.0}/README.md +0 -0
  48. {svg2drawiolib-1.1.2 → svg2drawiolib-1.2.0}/src/SVG2DrawIOLib/__init__.py +0 -0
  49. {svg2drawiolib-1.1.2 → svg2drawiolib-1.2.0}/src/SVG2DrawIOLib/cli/remove.py +0 -0
  50. {svg2drawiolib-1.1.2 → svg2drawiolib-1.2.0}/src/SVG2DrawIOLib/cli/split_paths.py +0 -0
  51. {svg2drawiolib-1.1.2 → svg2drawiolib-1.2.0}/src/SVG2DrawIOLib/path_splitter.py +0 -0
  52. {svg2drawiolib-1.1.2 → svg2drawiolib-1.2.0}/tests/__init__.py +0 -0
  53. {svg2drawiolib-1.1.2 → svg2drawiolib-1.2.0}/tests/conftest.py +0 -0
  54. {svg2drawiolib-1.1.2 → svg2drawiolib-1.2.0}/tests/test_models.py +0 -0
  55. {svg2drawiolib-1.1.2 → svg2drawiolib-1.2.0}/uv.lock +0 -0
@@ -38,7 +38,7 @@ jobs:
38
38
  run: ruff check src tests
39
39
 
40
40
  - name: Run mypy type checking
41
- run: mypy src
41
+ run: mypy --strict src tests
42
42
 
43
43
  - name: Run bandit security checks
44
44
  run: bandit -r src -ll
@@ -24,9 +24,9 @@ repos:
24
24
  additional_dependencies:
25
25
  - types-click
26
26
  - types-setuptools
27
+ - pytest
27
28
  args: [--strict]
28
- files: ^src/
29
- exclude: ^src/SVG2DrawIOLib/cli/
29
+ files: ^(src|tests)/
30
30
 
31
31
  - repo: https://github.com/PyCQA/bandit
32
32
  rev: 1.9.3
@@ -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.1.2
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
@@ -143,3 +143,7 @@ ignore_missing_imports = true
143
143
  [[tool.mypy.overrides]]
144
144
  module = "rich_click.*"
145
145
  ignore_missing_imports = true
146
+
147
+ [[tool.mypy.overrides]]
148
+ module = "SVG2DrawIOLib.cli.*"
149
+ disable_error_code = ["untyped-decorator"]
@@ -1,3 +1,3 @@
1
1
  """Package version information."""
2
2
 
3
- __version__ = "1.1.2"
3
+ __version__ = "1.2.0"
@@ -47,7 +47,13 @@ rc.rich_click.COMMAND_GROUPS = {
47
47
  "SVG2DrawIOLib": [
48
48
  {
49
49
  "name": "Library Management Commands",
50
- "commands": ["create", "add", "remove", "list"],
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": ["--css", "--css-color", "--namespace", "--tag"],
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": ["--css"],
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(add_css=css)
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