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.
@@ -0,0 +1,3 @@
1
+ """Package version information."""
2
+
3
+ __version__ = "1.0.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()
@@ -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
+ )