Skip to content

Commit

Permalink
nice...
Browse files Browse the repository at this point in the history
  • Loading branch information
dickermoshe committed Dec 26, 2024
1 parent 1ce3ccb commit edb3542
Show file tree
Hide file tree
Showing 14 changed files with 352 additions and 119 deletions.
6 changes: 3 additions & 3 deletions .github/workflows/widget_wrapper.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: widget_wrapper
name: rabbit

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
Expand All @@ -7,15 +7,15 @@ concurrency:
on:
pull_request:
paths:
- ".github/workflows/widget_wrapper.yaml"
- ".github/workflows/rabbit.yaml"
- "lib/**"
- "test/**"
- "pubspec.yaml"
push:
branches:
- master
paths:
- ".github/workflows/widget_wrapper.yaml"
- ".github/workflows/rabbit.yaml"
- "lib/**"
- "test/**"
- "pubspec.yaml"
Expand Down
317 changes: 275 additions & 42 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,71 +1,304 @@
## widget_wrapper

![coverage][coverage_badge]
[![style: very good analysis][very_good_analysis_badge]][very_good_analysis_link]
[![License: MIT][license_badge]][license_link]
## Table of Contents

Generated by the [Very Good CLI][very_good_cli_link] 🤖
- [Rabbit 🐰](#rabbit-)
- [Features](#features)
- [Requirements](#requirements)
- [Installation](#installation)
- [Getting Started](#getting-started)
- [Configuration Options](#configuration-options)
- [Example Configuration](#example-configuration)
- [`widgets`](#widgets)
- [`output_dir`](#output_dir)
- [`add_imports`](#add_imports)
- [`prefix`](#prefix)
- [`docs`](#docs)
- [Multiple Constructors](#multiple-constructors)
- [Maintaining Generated Code](#maintaining-generated-code)
- [Common Use Cases](#common-use-cases)
- [Theming Widgets](#theming-widgets)
- [Adding Default Behaviors](#adding-default-behaviors)
- [Best Practices](#best-practices)
- [When to Use Wrappers](#when-to-use-wrappers)
- [When Not to Use Wrappers](#when-not-to-use-wrappers)
- [Naming Conventions](#naming-conventions)

Customize you favorite widgets by wrapping them..

---
# Rabbit 🐰

## Getting Started 🚀
A Dart CLI tool that generates wrapper widgets to help reduce boilerplate code and create reusable widget variations. Instead of manually writing wrapper widgets for customized versions of existing widgets, Rabbit generates the boilerplate code for you, allowing you to focus on the customization.

If the CLI application is available on [pub](https://pub.dev), activate globally via:
For example, if you need a red container in multiple places in your app, instead of writing:
```dart
Container(
color: Colors.red,
child: child,
)
```
You can generate a `RedContainer` widget that encapsulates this styling:
```dart
RedContainer(
child: child,
)
```

Rabbit helps you create these wrapper widgets quickly and consistently, maintaining all the original widget's parameters while allowing you to customize what you need.

## Features

- **Easy Widget Wrapping**: Generate wrapper widgets from any existing widget with a single command
- **Type Safety**: Preserves generic types and constructor parameters from the original widget
- **Customizable Output**:
- Choose your output directory
- Customize widget name prefixes
- Control import statement generation
- **Documentation Support**: Option to include original widget's documentation comments
- **Bulk Generation**: Generate multiple wrappers at once, even for entire packages
- **Non-Destructive**: Never overwrites existing files, allowing safe regeneration
- **Flutter Compatible**: Works with any Flutter widget, including third-party packages

## Requirements

- Flutter SDK: >=3.19.0
- Dart SDK: >=3.19.0

## Installation

Add Rabbit to your project's dev dependencies:

```sh
dart pub global activate widget_wrapper
flutter pub add dev:rabbit
```

Or locally via:
Or manually add it to your `pubspec.yaml`:

```yaml
dev_dependencies:
rabbit: ^latest_version
```
Then run:
```sh
dart pub global activate --source=path <path to this package>
flutter pub get
```

## Usage
## Getting Started

```sh
# Sample command
$ widget_wrapper sample
In this example we will generate a `RedContainer` widget that is identical to a `Container` widget but with a red background color.

# Sample command option
$ widget_wrapper sample --cyan
1. Configure your widget wrappers in `pubspec.yaml`:

# Show CLI version
$ widget_wrapper --version
```yaml
widget_wrapper:
widgets:
package:flutter/material.dart:
- Container
```
For a complete list of configuration options, see the [Configuration](#configuration) section.
# Show usage help
$ widget_wrapper --help
2. Run the generation command:
```bash
dart run rabbit generate
```
This will generate a `$Container` widget in the default output directory.
3. Locate the generated widget and customize it as needed.
```dart
class RedContainer extends StatelessWidget {
final AlignmentGeometry alignment;
final EdgeInsetsGeometry padding;
// ... and the rest of the Container properties
const RedContainer({
super.key,
this.alignment,
this.padding,
// ... and the rest of the Container properties
});
@override
Widget build(BuildContext context) {
return Container(
color: Colors.red,
alignment: alignment,
padding: padding,
// ... and the rest of the Container properties
);
}
}
```
You can now use the `RedContainer` widget in your app as you would a `Container` widget.
4. (Optional) Rename the generated file:
Rename the generated file to `red_container.dart` for better readability and import statements.

Now you can use the `RedContainer` widget in your app as you would a `Container` widget.
```dart
RedContainer(
child: Text('Hello, World!'),
)
```

## Running Tests with coverage 🧪
## Configuration Options

To run all unit tests use the following command:
The following options can be configured in your `pubspec.yaml` under the `widget_wrapper` key:

```sh
$ dart pub global activate coverage 1.2.0
$ dart test --coverage=coverage
$ dart pub global run coverage:format_coverage --lcov --in=coverage --out=coverage/lcov.info
| Option | Default | Description |
| ------------- | ----------------- | ---------------------------------------------------- |
| `widgets` | Required | Map of package imports and widget names |
| `output_dir` | `lib/src/wrapped` | Directory where generated files will be placed |
| `add_imports` | `false` | Create the import statements for the generated files |
| `prefix` | `$` | Prefix for generated widget names |
| `docs` | `false` | Include documentation comments from original widget |

### Example Configuration

```yaml
widget_wrapper:
output_dir: lib/src/widgets
add_imports: true
prefix: My
docs: true
widgets:
package:flutter/material.dart:
- Container
- ElevatedButton
package:shadcn_ui/shadcn_ui.dart:

Check warning on line 166 in README.md

View workflow job for this annotation

GitHub Actions / spell-check / build

Unknown word (shadcn)

Check warning on line 166 in README.md

View workflow job for this annotation

GitHub Actions / spell-check / build

Unknown word (shadcn)
- ShadButton
```

### `widgets`

To view the generated coverage report you can use [lcov](https://github.com/linux-test-project/lcov)
.
The `widgets` option is a map where:
- Keys are package import statements
- Values are lists of widget names to generate wrappers for

```sh
# Generate Coverage Report
$ genhtml coverage/lcov.info -o coverage/
You can use the special value `all` to generate wrappers for all widgets in a package:
```yaml
widget_wrapper:
widgets:
package:flutter/material.dart:
- all # Will generate wrappers for all Material widgets
```
⚠️ **Warning**: Using `all` will generate a large number of files and is not recommended for most use cases.

### `output_dir`
The directory where generated files will be placed.
```yaml
widget_wrapper:
output_dir: lib/src/widgets # Your custom path
```
- Default: `lib/src/wrapped`
- The directory will be created if it doesn't exist
- Relative paths are resolved from your project root
- The generate widgets will match the package structure of the original widgets

# Open Coverage Report
$ open coverage/index.html
### `add_imports`
`rabbit` *can* generate import statements for the generated files, however they look very ugly so it's disabled by default.

If you want to use this feature, you can enable it like this:

```yaml
widget_wrapper:
add_imports: true
```
- Default: `false`
- When `true`, adds imports with package prefixes to avoid naming conflicts

### `prefix`
The prefix added to generated widget names to avoid naming conflicts.
```yaml
widget_wrapper:
prefix: My # Will generate MyContainer, MyButton, etc.
```
- Default: `$`
- Can be set to an empty string (`''`) if you want no prefix
- If using no prefix, consider setting `add_imports: true` to avoid naming conflicts

### `docs`
Controls whether documentation comments from the original widget are included.
```yaml
widget_wrapper:
docs: true
```
- Default: `false`
- Includes parameter descriptions, examples, and other documentation
- Can significantly increase the size of generated files
- Useful when creating public packages or maintaining API documentation
-
## Multiple Constructors

When a widget has multiple constructors, Rabbit generates a separate wrapper for each constructor. For example, with `ListView`:

```dart
// Original ListView has multiple constructors:
// ListView()
// ListView.builder()
// ListView.separated()
// ListView.custom()
// Rabbit will generate:
class $ListView extends StatelessWidget { ... }
class $ListViewBuilder extends StatelessWidget { ... }
class $ListViewSeparated extends StatelessWidget { ... }
class $ListViewCustom extends StatelessWidget { ... }
```

Each generated wrapper maintains the exact signature and functionality of its corresponding constructor.

## Maintaining Generated Code

If a Flutter update changes the API of wrapped widgets:

1. Backup your customized wrappers
2. Regenerate the wrappers with the latest Flutter version:
```bash
dart run rabbit generate
```
3. Copy your customizations from the backup to the newly generated files

This ensures your wrappers stay in sync with Flutter's API changes while preserving your modifications.


## Common Use Cases

### Theming Widgets
Create consistent themed versions of widgets across your app:
```dart
class PrimaryButton extends StatelessWidget {
// Generated from ElevatedButton
// Customized with your theme's primary color
}
### Platform-Specific Variants
```dart
class AdaptiveContainer extends StatelessWidget {
// Generated from Container
// Customized with platform-specific styling
}
```

### Adding Default Behaviors
```dart
class LoadingButton extends StatelessWidget {
// Generated from ElevatedButton
// Adds loading state handling
}
```

## Best Practices

### When to Use Wrappers
- For consistent styling across your app
- When you need multiple variants of a widget
- To encapsulate complex behavior

---
### When Not to Use Wrappers
- For one-off customizations
- When composition would be clearer
- For very simple modifications

[coverage_badge]: coverage_badge.svg
[license_badge]: https://img.shields.io/badge/license-MIT-blue.svg
[license_link]: https://opensource.org/licenses/MIT
[very_good_analysis_badge]: https://img.shields.io/badge/style-very_good_analysis-B22C89.svg
[very_good_analysis_link]: https://pub.dev/packages/very_good_analysis
[very_good_cli_link]: https://github.com/VeryGoodOpenSource/very_good_cli
### Naming Conventions
- Use descriptive names that indicate the purpose
- Follow Flutter's widget naming conventions
- Consider grouping related wrappers
```
2 changes: 1 addition & 1 deletion bin/widget_wrapper.dart → bin/rabbit.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import 'dart:io';

import 'package:widget_wrapper/src/command_runner.dart';
import 'package:rabbit/src/command_runner.dart';

Future<void> main(List<String> args) async {
await _flushThenExit(await WidgetWrapperCommandRunner().run(args));
Expand Down
Loading

0 comments on commit edb3542

Please sign in to comment.