tailwhip 0.9__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.
tailwhip-0.9/LICENSE ADDED
@@ -0,0 +1,25 @@
1
+ Copyright (c) 2025, Martin Mahner <https://elephant.house/>
2
+ All rights reserved.
3
+
4
+ Redistribution and use in source and binary forms, with or without modification,
5
+ are permitted provided that the following conditions are met:
6
+
7
+ * Redistributions of source code must retain the above copyright notice,
8
+ this list of conditions and the following disclaimer.
9
+ * Redistributions in binary form must reproduce the above copyright notice,
10
+ this list of conditions and the following disclaimer in the documentation
11
+ and/or other materials provided with the distribution.
12
+ * Neither the name django-markup nor the names of its contributors
13
+ may be used to endorse or promote products derived from this software without
14
+ specific prior written permission.
15
+
16
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
17
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
20
+ ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
21
+ (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22
+ LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
23
+ ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
tailwhip-0.9/PKG-INFO ADDED
@@ -0,0 +1,179 @@
1
+ Metadata-Version: 2.4
2
+ Name: tailwhip
3
+ Version: 0.9
4
+ Summary: A pure Python Tailwind CSS class sorter that works with any HTML or CSS file, including Django templates and other template languages.
5
+ Author-email: Martin Mahner <martin@mahner.org>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/bartTC/tailwhip/
8
+ Project-URL: Repository, https://github.com/bartTC/tailwhip/
9
+ Classifier: Development Status :: 3 - Alpha
10
+ Classifier: Programming Language :: Python :: 3 :: Only
11
+ Classifier: Environment :: Web Environment
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Operating System :: OS Independent
14
+ Classifier: Programming Language :: Python
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Programming Language :: Python :: 3.13
18
+ Classifier: Programming Language :: Python :: 3.14
19
+ Classifier: Topic :: Internet :: WWW/HTTP
20
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
21
+ Requires-Python: >=3.11
22
+ Description-Content-Type: text/markdown
23
+ License-File: LICENSE
24
+ Requires-Dist: dynaconf
25
+ Requires-Dist: rich
26
+ Requires-Dist: typer
27
+ Requires-Dist: wcmatch
28
+ Dynamic: license-file
29
+
30
+ # Tailwhip — Tailwind CSS class sorter
31
+
32
+ [![Test](https://github.com/bartTC/tailwhip/actions/workflows/test.yml/badge.svg)](https://github.com/bartTC/tailwhip/actions/workflows/test.yml)
33
+ [![Python Version](https://img.shields.io/badge/python-3.11%20%7C%203.12%20%7C%203.13%20%7C%203.14-blue)](https://www.python.org/downloads/)
34
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
35
+
36
+ Tailwhip is a pure Python Tailwind CSS class sorter that works with any HTML or CSS
37
+ file — including Django templates and other templating languages.
38
+
39
+ ![Screenshot of Tailwhip](https://github.com/bartTC/tailwhip/blob/main/screenshot.png?raw=true)
40
+
41
+ ## Why Tailwhip?
42
+
43
+ The [official Prettier plugin][1] for sorting Tailwind classes doesn’t play well
44
+ with many template languages, such as Django. While there are Prettier plugins that
45
+ add limited support for [Jinja templates][2], they often require configuration
46
+ workarounds or restrict what you can do with Prettier.
47
+
48
+ Tailwhip takes a more pragmatic approach. Instead of trying to parse and understand
49
+ every possible template syntax, it focuses on sorting Tailwind classes reliably, and
50
+ ignores class attributes that contain template syntax.
51
+
52
+ [1]: https://github.com/tailwindlabs/prettier-plugin-tailwindcss
53
+ [2]: https://github.com/davidodenwald/prettier-plugin-jinja-template
54
+
55
+ How it works:
56
+
57
+ 1. It finds all `class=""` attributes and `@apply` directives in the given files.
58
+ 2. It sorts the contained classes according to the official Tailwind CSS class order.
59
+ 3. If a class attribute contains template syntax (e.g., `{{ ... }}` or `{% ... %}`),
60
+ Tailwhip leaves it untouched.
61
+
62
+ This approach ensures Tailwhip works across diverse environments — Django, Flask,
63
+ Jinja2, or even custom templating engines — without breaking your templates or
64
+ requiring complicated setup.
65
+
66
+ ## Usage
67
+
68
+ Tailwhip requires Python 3.11 or later.
69
+
70
+ ```bash
71
+ $ uvx tailwhip [options] [filepath...]
72
+
73
+ # Find all .html and .css files in the templates directory
74
+ $ uvx tailwhip templates/
75
+
76
+ # Preview changes
77
+ $ uvx tailwhip templates/ -vv
78
+
79
+ # Actually apply changes
80
+ $ uvx tailwhip templates/ --write
81
+
82
+ # Sort classes in .scss files
83
+ $ uvx tailwhip "templates/**/*.scss"
84
+
85
+ # Standard glob patterns are supported
86
+ $ uvx tailwhip "static/**/*.{css,scss}" "templates/**/*.htm[l]"
87
+ ```
88
+
89
+ You can also install it with pip and use it as a Python library:
90
+
91
+ ```bash
92
+ $ pip install tailwhip
93
+
94
+ $ tailwhip templates/
95
+ $ python -m tailwhip templates/
96
+ ```
97
+
98
+ See `--help` for all options and features.
99
+
100
+ ## Configuration
101
+
102
+ Tailwhip works great out of the box with sensible defaults, but you can customize its behavior to match your project's needs. There are two ways to configure Tailwhip:
103
+
104
+ ### Option 1: `pyproject.toml`
105
+
106
+ Add a `[tool.tailwhip]` section to your project's `pyproject.toml`:
107
+
108
+ ```toml
109
+ [tool.tailwhip]
110
+ # Increase verbosity to see detailed changes
111
+ verbosity = 2
112
+
113
+ # Customize file patterns to include JSX/TSX files
114
+ default_globs = [
115
+ "**/*.html",
116
+ "**/*.css",
117
+ "**/*.jsx",
118
+ "**/*.tsx",
119
+ ]
120
+
121
+ # Add your custom Tailwind colors
122
+ custom_colors = ["brand", "accent", "company"]
123
+
124
+ # Add template syntax for your templating engine
125
+ skip_expressions = ["{{", "{%", "<%", "[[", "]]"]
126
+ ```
127
+
128
+ ### Option 2: Custom Configuration File
129
+
130
+ Create a `tailwhip.toml` file anywhere in your project and pass it via the `--configuration` flag:
131
+
132
+ ```toml
133
+ # tailwhip.toml
134
+ verbosity = 3
135
+
136
+ default_globs = [
137
+ "**/*.html",
138
+ "**/*.css",
139
+ "**/*.liquid", # Shopify Liquid templates
140
+ ]
141
+
142
+ custom_colors = ["primary", "secondary", "accent"]
143
+
144
+ skip_expressions = ["{{", "{%", "<%", "{-"] # Add Nunjucks syntax
145
+ ```
146
+
147
+ Then use it with:
148
+
149
+ ```bash
150
+ $ tailwhip templates/ --configuration=tailwhip.toml
151
+ ```
152
+
153
+ ### Configuration Precedence
154
+
155
+ Settings are loaded in this order (later sources override earlier ones):
156
+
157
+ 1. **Default values** (built into Tailwhip)
158
+ 2. **`pyproject.toml`** (`[tool.tailwhip]` section)
159
+ 3. **Custom config file** (via `--configuration` flag)
160
+ 4. **CLI arguments** (e.g., `--write`, `-v`, `--quiet`)
161
+
162
+ CLI arguments always take precedence, so you can override any config value on the command line.
163
+
164
+ ### Available Configuration Options
165
+
166
+ For a complete list of all configuration options with detailed explanations, see the [default configuration file](https://github.com/bartTC/tailwhip/blob/main/tailwhip/configuration.toml). It includes:
167
+
168
+ - **Output settings**: `verbosity`, `write_mode`
169
+ - **File discovery**: `default_globs`
170
+ - **Template handling**: `skip_expressions`
171
+ - **Sorting behavior**: `utility_groups`, `variant_groups`
172
+ - **Color recognition**: `tailwind_colors`, `custom_colors`
173
+ - **Pattern matching**: `class_regex`, `apply_regex` (advanced)
174
+
175
+ Most users only need to customize `custom_colors` and occasionally `default_globs` or `skip_expressions`. The sorting algorithm is based on Tailwind best practices and rarely needs modification.
176
+
177
+ ## Changelog
178
+
179
+ See [CHANGELOG.md](CHANGELOG.md) for a complete list of changes and version history.
tailwhip-0.9/README.md ADDED
@@ -0,0 +1,150 @@
1
+ # Tailwhip — Tailwind CSS class sorter
2
+
3
+ [![Test](https://github.com/bartTC/tailwhip/actions/workflows/test.yml/badge.svg)](https://github.com/bartTC/tailwhip/actions/workflows/test.yml)
4
+ [![Python Version](https://img.shields.io/badge/python-3.11%20%7C%203.12%20%7C%203.13%20%7C%203.14-blue)](https://www.python.org/downloads/)
5
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
6
+
7
+ Tailwhip is a pure Python Tailwind CSS class sorter that works with any HTML or CSS
8
+ file — including Django templates and other templating languages.
9
+
10
+ ![Screenshot of Tailwhip](https://github.com/bartTC/tailwhip/blob/main/screenshot.png?raw=true)
11
+
12
+ ## Why Tailwhip?
13
+
14
+ The [official Prettier plugin][1] for sorting Tailwind classes doesn’t play well
15
+ with many template languages, such as Django. While there are Prettier plugins that
16
+ add limited support for [Jinja templates][2], they often require configuration
17
+ workarounds or restrict what you can do with Prettier.
18
+
19
+ Tailwhip takes a more pragmatic approach. Instead of trying to parse and understand
20
+ every possible template syntax, it focuses on sorting Tailwind classes reliably, and
21
+ ignores class attributes that contain template syntax.
22
+
23
+ [1]: https://github.com/tailwindlabs/prettier-plugin-tailwindcss
24
+ [2]: https://github.com/davidodenwald/prettier-plugin-jinja-template
25
+
26
+ How it works:
27
+
28
+ 1. It finds all `class=""` attributes and `@apply` directives in the given files.
29
+ 2. It sorts the contained classes according to the official Tailwind CSS class order.
30
+ 3. If a class attribute contains template syntax (e.g., `{{ ... }}` or `{% ... %}`),
31
+ Tailwhip leaves it untouched.
32
+
33
+ This approach ensures Tailwhip works across diverse environments — Django, Flask,
34
+ Jinja2, or even custom templating engines — without breaking your templates or
35
+ requiring complicated setup.
36
+
37
+ ## Usage
38
+
39
+ Tailwhip requires Python 3.11 or later.
40
+
41
+ ```bash
42
+ $ uvx tailwhip [options] [filepath...]
43
+
44
+ # Find all .html and .css files in the templates directory
45
+ $ uvx tailwhip templates/
46
+
47
+ # Preview changes
48
+ $ uvx tailwhip templates/ -vv
49
+
50
+ # Actually apply changes
51
+ $ uvx tailwhip templates/ --write
52
+
53
+ # Sort classes in .scss files
54
+ $ uvx tailwhip "templates/**/*.scss"
55
+
56
+ # Standard glob patterns are supported
57
+ $ uvx tailwhip "static/**/*.{css,scss}" "templates/**/*.htm[l]"
58
+ ```
59
+
60
+ You can also install it with pip and use it as a Python library:
61
+
62
+ ```bash
63
+ $ pip install tailwhip
64
+
65
+ $ tailwhip templates/
66
+ $ python -m tailwhip templates/
67
+ ```
68
+
69
+ See `--help` for all options and features.
70
+
71
+ ## Configuration
72
+
73
+ Tailwhip works great out of the box with sensible defaults, but you can customize its behavior to match your project's needs. There are two ways to configure Tailwhip:
74
+
75
+ ### Option 1: `pyproject.toml`
76
+
77
+ Add a `[tool.tailwhip]` section to your project's `pyproject.toml`:
78
+
79
+ ```toml
80
+ [tool.tailwhip]
81
+ # Increase verbosity to see detailed changes
82
+ verbosity = 2
83
+
84
+ # Customize file patterns to include JSX/TSX files
85
+ default_globs = [
86
+ "**/*.html",
87
+ "**/*.css",
88
+ "**/*.jsx",
89
+ "**/*.tsx",
90
+ ]
91
+
92
+ # Add your custom Tailwind colors
93
+ custom_colors = ["brand", "accent", "company"]
94
+
95
+ # Add template syntax for your templating engine
96
+ skip_expressions = ["{{", "{%", "<%", "[[", "]]"]
97
+ ```
98
+
99
+ ### Option 2: Custom Configuration File
100
+
101
+ Create a `tailwhip.toml` file anywhere in your project and pass it via the `--configuration` flag:
102
+
103
+ ```toml
104
+ # tailwhip.toml
105
+ verbosity = 3
106
+
107
+ default_globs = [
108
+ "**/*.html",
109
+ "**/*.css",
110
+ "**/*.liquid", # Shopify Liquid templates
111
+ ]
112
+
113
+ custom_colors = ["primary", "secondary", "accent"]
114
+
115
+ skip_expressions = ["{{", "{%", "<%", "{-"] # Add Nunjucks syntax
116
+ ```
117
+
118
+ Then use it with:
119
+
120
+ ```bash
121
+ $ tailwhip templates/ --configuration=tailwhip.toml
122
+ ```
123
+
124
+ ### Configuration Precedence
125
+
126
+ Settings are loaded in this order (later sources override earlier ones):
127
+
128
+ 1. **Default values** (built into Tailwhip)
129
+ 2. **`pyproject.toml`** (`[tool.tailwhip]` section)
130
+ 3. **Custom config file** (via `--configuration` flag)
131
+ 4. **CLI arguments** (e.g., `--write`, `-v`, `--quiet`)
132
+
133
+ CLI arguments always take precedence, so you can override any config value on the command line.
134
+
135
+ ### Available Configuration Options
136
+
137
+ For a complete list of all configuration options with detailed explanations, see the [default configuration file](https://github.com/bartTC/tailwhip/blob/main/tailwhip/configuration.toml). It includes:
138
+
139
+ - **Output settings**: `verbosity`, `write_mode`
140
+ - **File discovery**: `default_globs`
141
+ - **Template handling**: `skip_expressions`
142
+ - **Sorting behavior**: `utility_groups`, `variant_groups`
143
+ - **Color recognition**: `tailwind_colors`, `custom_colors`
144
+ - **Pattern matching**: `class_regex`, `apply_regex` (advanced)
145
+
146
+ Most users only need to customize `custom_colors` and occasionally `default_globs` or `skip_expressions`. The sorting algorithm is based on Tailwind best practices and rarely needs modification.
147
+
148
+ ## Changelog
149
+
150
+ See [CHANGELOG.md](CHANGELOG.md) for a complete list of changes and version history.
@@ -0,0 +1,82 @@
1
+ [project]
2
+ name = "tailwhip"
3
+ version = "0.9"
4
+ description = "A pure Python Tailwind CSS class sorter that works with any HTML or CSS file, including Django templates and other template languages."
5
+ readme = "README.md"
6
+ authors = [
7
+ { name = "Martin Mahner", email = "martin@mahner.org" }
8
+ ]
9
+ license = "MIT"
10
+ classifiers = [
11
+ "Development Status :: 3 - Alpha",
12
+ "Programming Language :: Python :: 3 :: Only",
13
+ "Environment :: Web Environment",
14
+ "Intended Audience :: Developers",
15
+ "Operating System :: OS Independent",
16
+ "Programming Language :: Python",
17
+ "Programming Language :: Python :: 3.11",
18
+ "Programming Language :: Python :: 3.12",
19
+ "Programming Language :: Python :: 3.13",
20
+ "Programming Language :: Python :: 3.14",
21
+ "Topic :: Internet :: WWW/HTTP",
22
+ "Topic :: Software Development :: Libraries :: Python Modules",
23
+ ]
24
+ requires-python = ">=3.11"
25
+ dependencies = [
26
+ "dynaconf",
27
+ "rich",
28
+ "typer",
29
+ "wcmatch",
30
+ ]
31
+
32
+ [project.urls]
33
+ Homepage = "https://github.com/bartTC/tailwhip/"
34
+ Repository = "https://github.com/bartTC/tailwhip/"
35
+
36
+ [dependency-groups]
37
+ dev = [
38
+ "pytest",
39
+ "pytest-cov",
40
+ ]
41
+
42
+ [project.scripts]
43
+ tailwhip = "tailwhip:main"
44
+
45
+ [tool.setuptools.package-data]
46
+ tailwhip = ["configuration.toml"]
47
+
48
+ [tool.uv]
49
+ package = true
50
+
51
+ [tool.ruff]
52
+ target-version = "py39"
53
+ lint.select = ["ALL"]
54
+ lint.ignore = [
55
+ # Valid exclusions.
56
+ "COM812", # Conflicts with ruff format
57
+ "E501", # Line too long (>88)
58
+ "D203", # incorrect-blank-line-before-class (conflicts with D211)
59
+ "D212", # Don't require summary on first line
60
+ "ERA001", # Found commented-out code
61
+ "FBT", # Flake Boolean Trap (don't use arg=True in functions)
62
+ "FIX", # Line contains TODO/FIXME. Consider resolving.
63
+
64
+ ]
65
+
66
+ [tool.ruff.lint.extend-per-file-ignores]
67
+ "test*.py" = [
68
+ "S101", # Use of `assert` detected
69
+ "PLR2004", # Magic value used in comparison, consider replacing <int> with a constant variable
70
+ ]
71
+
72
+ [tool.pytest.ini_options]
73
+ python_files = ["*.py"]
74
+
75
+ [tool.coverage]
76
+ run.omit = [
77
+ "tailwhip/tests/*",
78
+ ]
79
+ report.exclude_lines = [
80
+ "pragma: no cover",
81
+ "if TYPE_CHECKING:",
82
+ ]
tailwhip-0.9/setup.cfg ADDED
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,11 @@
1
+ """Tailwhip - Sort Tailwind CSS classes in HTML and CSS files."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from importlib import metadata
6
+
7
+ from tailwhip.cli import main
8
+
9
+ __version__ = metadata.version("tailwhip")
10
+ __author__ = "Martin Mahner"
11
+ __all__ = ["__author__", "__version__", "main"]
@@ -0,0 +1,8 @@
1
+ """Entry point for python -m tailwhip."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from tailwhip.cli import main
6
+
7
+ if __name__ == "__main__":
8
+ main()
@@ -0,0 +1,158 @@
1
+ """CLI entrypoint."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import sys
6
+ import time
7
+ from importlib import metadata
8
+ from pathlib import Path
9
+ from typing import Annotated
10
+
11
+ import typer
12
+ from rich.console import Console
13
+
14
+ from tailwhip.configuration import (
15
+ CONSOLE_THEME,
16
+ VerbosityLevel,
17
+ config,
18
+ get_pyproject_toml_data,
19
+ update_configuration,
20
+ )
21
+ from tailwhip.files import apply_changes, find_files
22
+
23
+
24
+ def version_callback(value: bool) -> None:
25
+ """Print version and exit."""
26
+ if value:
27
+ version = metadata.version("tailwhip")
28
+ typer.echo(f"tailwhip {version}")
29
+ raise typer.Exit
30
+
31
+
32
+ app = typer.Typer(
33
+ help="Sort Tailwind CSS classes in HTML and CSS files.",
34
+ add_completion=False,
35
+ rich_markup_mode="rich",
36
+ )
37
+
38
+
39
+ def main() -> None:
40
+ """Entrypoint for the CLI."""
41
+ app()
42
+
43
+
44
+ @app.command(context_settings={"help_option_names": ["-h", "--help"]})
45
+ def run( # noqa: PLR0913
46
+ paths: Annotated[
47
+ list[Path],
48
+ typer.Argument(
49
+ help="Files or directories to process.",
50
+ metavar="PATH",
51
+ ),
52
+ ],
53
+ version: Annotated[ # noqa: ARG001
54
+ bool,
55
+ typer.Option(
56
+ "--version",
57
+ "-V",
58
+ callback=version_callback,
59
+ is_eager=True,
60
+ help="Show version and exit.",
61
+ ),
62
+ ] = False,
63
+ write_mode: Annotated[
64
+ bool,
65
+ typer.Option(
66
+ "--write",
67
+ "-w",
68
+ help="Write changes to files (default: dry-run mode).",
69
+ ),
70
+ ] = False,
71
+ quiet: Annotated[
72
+ bool,
73
+ typer.Option(
74
+ "--quiet",
75
+ "-q",
76
+ help="Suppress output except errors and warnings.",
77
+ ),
78
+ ] = False,
79
+ verbosity: Annotated[
80
+ int,
81
+ typer.Option(
82
+ "--verbose",
83
+ "-v",
84
+ count=True,
85
+ help="Increase output verbosity (-v: changes, -vv: diff, -vvv: debug).",
86
+ ),
87
+ ] = 0,
88
+ custom_configuration_file: Annotated[
89
+ Path | None,
90
+ typer.Option(
91
+ "--configuration",
92
+ "-c",
93
+ help="Load custom configuration file (overrides pyproject.toml settings).",
94
+ metavar="FILE",
95
+ ),
96
+ ] = None,
97
+ ) -> None:
98
+ """
99
+ Sort Tailwind CSS classes in HTML and CSS files.
100
+
101
+ Automatically discovers and sorts Tailwind classes according to a consistent
102
+ ordering. Supports HTML, CSS, and template files with Tailwind @apply directives.
103
+ """
104
+ # Check if the given configuration file exists
105
+ if custom_configuration_file and not custom_configuration_file.exists():
106
+ typer.echo(
107
+ f"Custom configuration file {custom_configuration_file} not found.",
108
+ err=True,
109
+ )
110
+ raise typer.Exit(1)
111
+
112
+ # Setup configuration values -------------------------------------------------------
113
+
114
+ # 1. Overwrite config defaults with pyproject.toml settings if they exist
115
+ if pyproject_data := get_pyproject_toml_data(Path.cwd()):
116
+ update_configuration(pyproject_data)
117
+
118
+ # 2. Overwrite config defaults with custom settings if they exist
119
+ if custom_configuration_file:
120
+ update_configuration(custom_configuration_file)
121
+
122
+ # 3. Overwrite config defaults with CLI options
123
+ update_configuration(
124
+ {
125
+ "write_mode": write_mode,
126
+ "verbosity": 0 if quiet else verbosity + 1,
127
+ }
128
+ )
129
+
130
+ # Setup helper tools ---------------------------------------------------------------
131
+
132
+ config.console = Console(quiet=quiet, theme=CONSOLE_THEME)
133
+
134
+ # Start Tailwhipping ---------------------------------------------------------------
135
+
136
+ start_time = time.time()
137
+ found_any, skipped, changed = apply_changes(targets=find_files(paths=paths))
138
+ duration = time.time() - start_time
139
+
140
+ if not found_any:
141
+ config.console.print("[red]Error: No files found[/red]")
142
+ sys.exit(1)
143
+
144
+ if config.verbosity == VerbosityLevel.NORMAL:
145
+ config.console.print(
146
+ "\nUse [important] -v [/important] (show unchanged files) or [important] -vv [/important] (show diff preview) for more detail."
147
+ )
148
+
149
+ if not config.write_mode:
150
+ config.console.print(
151
+ "\n:warning: Dry Run. No files were actually written. "
152
+ "Use [important] --write [/important] to write changes."
153
+ )
154
+
155
+ config.console.print(
156
+ f"⏱ Completed in [bold]{duration:.3f}s[/bold] for {changed} files. [dim]({skipped} skipped)[/dim]",
157
+ highlight=False,
158
+ )