diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..a0bb1a5 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,11 @@ +## 2.1.0 + +- Feat: 支持配置 `font_package` 以支持从其他包加载字体。 +- Refactor: 优化生成的代码结构,使用私有工厂方法减少代码体积。 +- Refactor: 统一 `fontFamily` 常量定义。 + +## 2.0.0 + +- Feat: 重构为 Dart CLI 工具。 +- Feat: 支持 YAML 配置文件。 +- Feat: 优化代码生成逻辑。 diff --git a/README.md b/README.md index 24a64aa..9a1deaf 100644 --- a/README.md +++ b/README.md @@ -1,339 +1,202 @@ # YX Icon Fonts -学习官OA系统图标字体库,基于iconfont.ttf和iconfont.json生成的Flutter图标库。 +从 iconfont.json 生成 Flutter IconData 常量的 Dart CLI 工具。 ## 特性 -- 🎨 包含50+个精心设计的图标 -- 📱 支持Flutter应用 -- 🎯 类型安全的图标使用 -- 📦 轻量级,无额外依赖 -- 🔧 易于集成和使用 -- ⚡ 自动化生成脚本 +- 🎨 解析 iconfont.cn 导出的 JSON 文件 +- 📝 生成类型安全的 Flutter IconData 常量 +- ⚙️ YAML 配置文件支持 +- 🏗️ 自定义类名和输出路径 +- 📚 自动生成文档注释 ## 安装 -在您的`pubspec.yaml`文件中添加依赖: +在您的 Flutter 项目的 `pubspec.yaml` 中添加为开发依赖: ```yaml -dependencies: - yx_icon_fonts: ^1.0.0 +dev_dependencies: + yx_icon_fonts: + git: + url: https://gitea.23544.com/wangyang/yx_icon_fonts_flutter + ref: v2.0.0 ``` 然后运行: ```bash -flutter pub get +dart pub get ``` -## 使用方法 +## 快速开始 -### 基本用法 +### 1. 初始化配置文件 + +```bash +dart run yx_icon_fonts init +``` + +这将在项目根目录创建 `icon_generator_config.yaml` 配置模板。 + +### 2. 准备资源文件 + +从 [iconfont.cn](https://www.iconfont.cn/) 下载您的图标项目,将以下文件放到项目中: + +- `iconfont.ttf` - 字体文件 +- `iconfont.json` - 图标配置文件 + +### 3. 配置 pubspec.yaml + +在 `pubspec.yaml` 中注册字体: + +```yaml +flutter: + fonts: + - family: iconfont + fonts: + - asset: assets/fonts/iconfont.ttf +``` + +### 4. 编辑配置文件 + +修改 `icon_generator_config.yaml`: + +```yaml +input: + font_file: "assets/fonts/iconfont.ttf" + json_file: "assets/fonts/iconfont.json" + font_family: "iconfont" + +output: + file_path: "lib/generated/icons.dart" + class_name: "AppIcons" +``` + +### 5. 生成代码 + +```bash +dart run yx_icon_fonts generate +``` + +### 6. 使用生成的图标 ```dart -import 'package:flutter/material.dart'; -import 'package:yx_icon_fonts/yx_icon_fonts.dart'; +import 'package:your_app/generated/icons.dart'; -class MyWidget extends StatelessWidget { - @override - Widget build(BuildContext context) { - return Icon( - YXIconFonts.iconMsgContacts, - size: 24, - color: Colors.blue, - ); - } +// 在 Widget 中使用 +Icon(AppIcons.iconHome, size: 24, color: Colors.blue) + +// 在 IconButton 中使用 +IconButton( + icon: Icon(AppIcons.iconSettings), + onPressed: () {}, +) +``` + +## 命令参考 + +### help - 显示帮助 + +```bash +dart run yx_icon_fonts help +``` + +### init - 初始化配置 + +```bash +# 创建配置文件 +dart run yx_icon_fonts init + +# 强制覆盖已存在的配置文件 +dart run yx_icon_fonts init --force +``` + +### generate - 生成代码 + +```bash +# 使用默认配置文件 +dart run yx_icon_fonts generate + +# 指定配置文件 +dart run yx_icon_fonts generate --config my_config.yaml + +# 详细输出 +dart run yx_icon_fonts generate --verbose +``` + +## 配置选项 + +完整的配置选项请参考 [icon_generator_config.template.yaml](./icon_generator_config.template.yaml)。 + +### generator 配置 + +| 选项 | 类型 | 默认值 | 说明 | +|------|------|--------|------| +| name | string | "Icon Generator" | 生成器名称,用于代码注释 | +| version | string | "1.0" | 版本号 | +| author | string | "" | 作者信息 | + +### input 配置 + +| 选项 | 类型 | 默认值 | 说明 | +|------|------|--------|------| +| font_file | string | "assets/fonts/iconfont.ttf" | 字体文件路径 | +| json_file | string | "assets/fonts/iconfont.json" | JSON 配置文件路径 | +| font_family | string | "iconfont" | 字体家族名称 | + +### output 配置 + +| 选项 | 类型 | 默认值 | 说明 | +|------|------|--------|------| +| file_path | string | "lib/generated/icons.dart" | 输出文件路径 | +| class_name | string | "AppIcons" | 生成的类名 | +| generate_docs | bool | true | 是否生成文档注释 | +| use_const_constructor | bool | true | 是否使用 const 构造函数 | + +## 生成的代码示例 + +```dart +// ignore_for_file: constant_identifier_names + +// ============================================ +// 此文件由 yx_icon_fonts 自动生成,请勿手动修改 +// 生成时间: 2025-01-01 12:00 +// 图标数量: 50 +// ============================================ + +import 'package:flutter/widgets.dart'; + +/// MyApp Icons 图标常量 +class AppIcons { + AppIcons._(); + + // 统一的 IconData 构造方法 + static const _icon = _IconDataFactory._(); + + /// icon_home (U+E600) + static final IconData iconHome = _icon(0xe600); + + // ... 更多图标 +} + +/// 内部 IconData 工厂类 +class _IconDataFactory { + const _IconDataFactory._(); + + static const String _fontFamily = 'iconfont'; + + IconData call(int codePoint) => IconData(codePoint, fontFamily: _fontFamily); } ``` -### 使用YXIcon组件 +## 从 v1.x 迁移 -```dart -import 'package:flutter/material.dart'; -import 'package:yx_icon_fonts/yx_icon_fonts.dart'; +v2.0 版本将 yx_icon_fonts 从 Flutter 库转换为了 CLI 工具。主要变化: -class MyWidget extends StatelessWidget { - @override - Widget build(BuildContext context) { - return YXIcon( - YXIconFonts.iconMsgContacts, - size: 24, - color: Colors.blue, - ); - } -} -``` +1. **安装方式变化**:从 `dependencies` 改为 `dev_dependencies` +2. **资源文件位置**:字体和 JSON 文件现在放在使用项目中,而非库中 +3. **配置方式**:使用 YAML 配置文件,支持自定义类名和路径 -### 在AppBar中使用 +## License -```dart -AppBar( - title: Text('我的应用'), - actions: [ - IconButton( - icon: Icon(YXIconFonts.iconMsgSearch), - onPressed: () { - // 搜索操作 - }, - ), - ], -) -``` - -## 可用图标 - -### 消息相关图标 -- `YXIconFonts.iconMsgContacts` - 联系人 -- `YXIconFonts.iconMsgViedo` - 视频 -- `YXIconFonts.iconMsgSearch` - 搜索 -- `YXIconFonts.iconMsgCall` - 通话 -- `YXIconFonts.iconMsgPhoto` - 照片 -- `YXIconFonts.iconMsgImg` - 图片 -- `YXIconFonts.iconMsgFile` - 文件 - -### 箭头相关图标 -- `YXIconFonts.icon32Arrowright` - 右箭头32 -- `YXIconFonts.icon44Arrowleft` - 左箭头44 -- `YXIconFonts.icon44Arrowright` - 右箭头44 -- `YXIconFonts.icon24Arrowleft` - 左箭头24 -- `YXIconFonts.icon24Arrowdown` - 下箭头24 -- `YXIconFonts.icon44Arrowright1` - 右箭头44(1) - -### 编辑相关图标 -- `YXIconFonts.icon44Edit` - 编辑44 -- `YXIconFonts.icon24Edit` - 编辑24 -- `YXIconFonts.icon36Editline` - 编辑线36 - -### 个人中心相关图标 -- `YXIconFonts.icon44MePassword` - 密码 -- `YXIconFonts.icon44MeHelp` - 帮助 -- `YXIconFonts.icon44MeVersion` - 版本 -- `YXIconFonts.icon44MePrivacy` - 隐私 -- `YXIconFonts.icon44MeUser` - 用户 -- `YXIconFonts.icon44MeSafe` - 安全 -- `YXIconFonts.icon44MePhone` - 手机 - -### 功能图标 -- `YXIconFonts.icon32Filter` - 筛选 -- `YXIconFonts.icon36Question` - 问题 -- `YXIconFonts.icon36Onlysee` - 仅查看 -- `YXIconFonts.icon36Hint` - 提示 -- `YXIconFonts.icon24Switch` - 切换 - -### 操作图标 -- `YXIconFonts.icon44Delete` - 删除44 -- `YXIconFonts.icon36Delete` - 删除36 -- `YXIconFonts.icon24Delete` - 删除24 -- `YXIconFonts.icon44More` - 更多 -- `YXIconFonts.icon44More2` - 更多2 -- `YXIconFonts.icon24Copy` - 复制 -- `YXIconFonts.icon44Share` - 分享 -- `YXIconFonts.icon44Calendar` - 日历 - -### 添加相关图标 -- `YXIconFonts.icon24Add` - 添加24 -- `YXIconFonts.icon24Plus` - 加号 -- `YXIconFonts.icon44Add` - 添加44 -- `YXIconFonts.icon32Add` - 添加32 - -### 消息功能图标 -- `YXIconFonts.icon56Msgadd` - 消息添加 -- `YXIconFonts.icon56Msgface` - 消息表情 -- `YXIconFonts.icon56Msgvoice` - 消息语音 - -### 其他图标 -- `YXIconFonts.icon36Team` - 团队 -- `YXIconFonts.icon32Student` - 学生 -- `YXIconFonts.icon44Keyboard` - 键盘 -- `YXIconFonts.icon24Minus` - 减号 -- `YXIconFonts.icon44Quitlite` - 退出轻量 -- `YXIconFonts.icon32Quit` - 退出32 -- `YXIconFonts.icon44Quit` - 退出44 - -## 示例 - -运行示例应用查看所有图标: - -```bash -cd example -flutter run -``` - -## 自定义 - -### 修改图标大小 - -```dart -Icon( - YXIconFonts.iconMsgContacts, - size: 48, // 自定义大小 -) -``` - -### 修改图标颜色 - -```dart -Icon( - YXIconFonts.iconMsgContacts, - color: Colors.red, // 自定义颜色 -) -``` - -### 在主题中使用 - -```dart -Theme( - data: Theme.of(context).copyWith( - iconTheme: IconThemeData( - color: Colors.blue, - size: 24, - ), - ), - child: Icon(YXIconFonts.iconMsgContacts), -) -``` - -## 开发 - -### 自动化生成脚本 - -项目提供了 `generate.sh` 脚本来自动化图标数据的生成过程。 - -#### 脚本功能 - -- 🔄 自动生成图标数据文件 (`lib/src/yx_icon_fonts_data.dart`) -- 📝 自动生成示例文件 (`example/lib/icons.dart`) -- ✅ 错误检查和验证 -- 🎨 彩色输出和进度提示 - -#### 使用方法 - -```bash -# 生成所有文件(默认) -./generate.sh - -# 只生成图标数据 -./generate.sh icons - -# 只生成示例文件 -./generate.sh example - -# 显示帮助信息 -./generate.sh help -``` - -#### 脚本选项 - -| 选项 | 功能 | 生成文件 | -|------|------|----------| -| `all` (默认) | 生成所有文件 | `lib/src/yx_icon_fonts_data.dart` + `example/lib/icons.dart` | -| `icons` | 只生成图标数据 | `lib/src/yx_icon_fonts_data.dart` | -| `example` | 只生成示例文件 | `example/lib/icons.dart` | -| `help` | 显示帮助信息 | - | - -#### 使用示例 - -```bash -# 首次使用,确保脚本有执行权限 -chmod +x generate.sh - -# 生成所有文件 -./generate.sh - -# 只更新图标数据(当修改了 iconfont.json 时) -./generate.sh icons - -# 只更新示例文件(当修改了图标数据时) -./generate.sh example -``` - -#### 脚本输出示例 - -``` -ℹ️ 开始生成所有文件... - -ℹ️ 正在生成图标数据... -✅ 图标数据生成完成 - -ℹ️ 正在生成示例文件... -✅ 示例文件生成完成 - -✅ 所有文件生成完成! -``` - -### 项目结构 - -``` -yx_icon_fonts/ -├── lib/ -│ ├── yx_icon_fonts.dart # 主库文件 -│ ├── main.dart # 库入口文件 -│ └── src/ -│ ├── yx_icon_data.dart # 图标数据类 -│ ├── yx_icon.dart # 图标组件 -│ └── yx_icon_fonts_data.dart # 图标常量定义(自动生成) -├── fonts/ -│ └── iconfont.ttf # 字体文件 -├── scripts/ -│ ├── generate_icons.dart # 图标数据生成脚本 -│ ├── generate_example.dart # 示例文件生成脚本 -│ └── utils.dart # 共享工具函数 -├── example/ -│ └── lib/ -│ ├── main.dart # 示例应用 -│ └── icons.dart # 示例图标文件(自动生成) -├── generate.sh # 自动化生成脚本 -├── iconfont.json # 图标配置数据 -├── pubspec.yaml # 包配置 -└── README.md # 项目文档 -``` - -### 更新图标 - -1. 替换 `fonts/iconfont.ttf` 文件 -2. 更新 `iconfont.json` 文件 -3. 运行生成脚本: - ```bash - ./generate.sh icons - ``` -4. 更新版本号 - -### 开发工作流 - -1. **修改图标源文件**: - - 更新 `fonts/iconfont.ttf` - - 更新 `iconfont.json` - -2. **生成代码**: - ```bash - ./generate.sh - ``` - -3. **测试**: - ```bash - flutter analyze - flutter test - cd example && flutter run - ``` - -4. **发布**: - ```bash - flutter pub publish - ``` - -## 许可证 - -MIT License - -## 贡献 - -欢迎提交Issue和Pull Request! - -## 更新日志 - -### 1.0.0 -- 初始版本 -- 包含50+个图标 -- 支持Flutter 3.0+ -- 添加自动化生成脚本 -- 优化项目结构和文档 \ No newline at end of file +MIT License \ No newline at end of file diff --git a/analysis_options.yaml b/analysis_options.yaml index 33a5fb4..6fb7e84 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -1,6 +1,17 @@ -include: package:lints/recommended.yaml +include: package:very_good_analysis/analysis_options.yaml analyzer: exclude: - temp_font_awesome/** - temp_font_awesome/**/* + - generate/** + - example/** + +linter: + rules: + # 允许在生成的代码中使用常量标识符命名 + constant_identifier_names: false + # 允许单行过长(生成的代码可能较长) + lines_longer_than_80_chars: false + # 允许公共成员不写文档 + public_member_api_docs: false diff --git a/bin/main.dart b/bin/main.dart new file mode 100644 index 0000000..9536282 --- /dev/null +++ b/bin/main.dart @@ -0,0 +1,71 @@ +#!/usr/bin/env dart + +import 'dart:io'; + +import 'package:logging/logging.dart'; +import 'package:yx_icon_fonts/yx_icon_fonts.dart'; + +/// YX Icon Fonts CLI 工具主入口 +/// +/// 这是一个图标字体代码生成工具,可以: +/// - 解析 iconfont.json 文件 +/// - 生成 Flutter IconData 常量 +/// - 支持自定义类名和输出路径 +/// +/// 使用方法: +/// `dart run yx_icon_fonts [options]` +/// +/// 可用命令: +/// - generate: 生成代码文件 +/// - init: 初始化配置文件 +/// - help: 显示帮助信息 +/// - version: 显示版本信息 +Future main(List arguments) async { + setupLogging(level: Level.ALL); + + // 检查是否有参数 + var resolvedArgs = arguments; + if (resolvedArgs.isEmpty) { + _showWelcome(); + resolvedArgs = ['help']; + } + + // 检查特殊命令 + if (resolvedArgs.contains('--version') || resolvedArgs.contains('-v')) { + _showVersion(); + return; + } + + // 使用 CLI 运行器 + final cli = IconFontsCLI(); + final exitCode = await cli.run(resolvedArgs); + + // 设置退出代码 + exit(exitCode); +} + +/// 显示欢迎信息 +void _showWelcome() { + appLogger + ..info('🎨 欢迎使用 YX Icon Fonts CLI 工具!') + ..info('这是一个强大的图标字体代码生成工具,可以帮助您:') + ..info(' 📋 解析 iconfont.json 文件') + ..info(' 🛠️ 生成 Flutter IconData 常量') + ..info(' 📁 自定义输出路径和类名') + ..info('使用 --help 查看详细帮助信息'); +} + +/// 显示版本信息 +void _showVersion() { + appLogger + ..info('🎨 YX Icon Fonts CLI v2.0.0') + ..info('构建信息:') + ..info(' - Dart SDK: ${Platform.version}') + ..info(' - 平台: ${Platform.operatingSystem}') + ..info('特性:') + ..info(' ✨ YAML 配置文件支持') + ..info(' 🏗️ 模块化架构设计') + ..info(' 🚀 快速代码生成') + ..info(' 📝 自定义类名和路径') + ..info('更多信息请访问: https://gitea.23544.com/wangyang/yx_icon_fonts_flutter'); +} diff --git a/bin/yx_icon_fonts.dart b/bin/yx_icon_fonts.dart new file mode 100644 index 0000000..058366a --- /dev/null +++ b/bin/yx_icon_fonts.dart @@ -0,0 +1,8 @@ +#!/usr/bin/env dart +// YX Icon Fonts CLI 主入口 +// 这是包的默认可执行文件入口点 +// 当用户运行 `dart run yx_icon_fonts` 时会执行此文件 + +import 'main.dart' as entry; + +void main(List arguments) => entry.main(arguments); diff --git a/example/assets/fonts/iconfont.json b/example/assets/fonts/iconfont.json new file mode 100644 index 0000000..c1121af --- /dev/null +++ b/example/assets/fonts/iconfont.json @@ -0,0 +1,541 @@ +{ + "id": "4944890", + "name": "劝学APP-学习官OA系统", + "font_family": "iconfont", + "css_prefix_text": "", + "description": "劝学APP-学习官OA系统图标", + "glyphs": [ + { + "icon_id": "46349152", + "name": "icon_32_clean", + "font_class": "icon_32_clean", + "unicode": "e660", + "unicode_decimal": 58976 + }, + { + "icon_id": "46348938", + "name": "icon_36_viewhistory", + "font_class": "icon_36_viewhistory", + "unicode": "e65f", + "unicode_decimal": 58975 + }, + { + "icon_id": "46205256", + "name": "icon_36_revoke", + "font_class": "icon_36_revoke", + "unicode": "e65e", + "unicode_decimal": 58974 + }, + { + "icon_id": "46077069", + "name": "icon_30_edit", + "font_class": "icon_30_edit", + "unicode": "e65b", + "unicode_decimal": 58971 + }, + { + "icon_id": "46077068", + "name": "icon_24_copy2", + "font_class": "icon_24_copy2", + "unicode": "e65c", + "unicode_decimal": 58972 + }, + { + "icon_id": "46077067", + "name": "icon_30_delete", + "font_class": "icon_30_delete", + "unicode": "e65d", + "unicode_decimal": 58973 + }, + { + "icon_id": "46077053", + "name": "icon_36_add", + "font_class": "icon_36_add", + "unicode": "e65a", + "unicode_decimal": 58970 + }, + { + "icon_id": "46076927", + "name": "icon_32_location", + "font_class": "icon_32_location", + "unicode": "e659", + "unicode_decimal": 58969 + }, + { + "icon_id": "46076827", + "name": "icon_24_mypoints", + "font_class": "icon_24_mypoints", + "unicode": "e657", + "unicode_decimal": 58967 + }, + { + "icon_id": "45986664", + "name": "icon_44_me_about", + "font_class": "icon_44_me_about", + "unicode": "e656", + "unicode_decimal": 58966 + }, + { + "icon_id": "45986657", + "name": "icon_44_me_pointsmall", + "font_class": "icon_44_me_pointsmall", + "unicode": "e658", + "unicode_decimal": 58968 + }, + { + "icon_id": "45876267", + "name": "icon_44_search", + "font_class": "icon_44_search", + "unicode": "e655", + "unicode_decimal": 58965 + }, + { + "icon_id": "45855059", + "name": "icon_36_clear_people", + "font_class": "icon_36_clear_people", + "unicode": "e654", + "unicode_decimal": 58964 + }, + { + "icon_id": "45530506", + "name": "icon_44_me_log_out", + "font_class": "icon_44_me_log_out", + "unicode": "e652", + "unicode_decimal": 58962 + }, + { + "icon_id": "45530507", + "name": "icon_44_me_switch", + "font_class": "icon_44_me_switch", + "unicode": "e653", + "unicode_decimal": 58963 + }, + { + "icon_id": "45513436", + "name": "icon_44_wechat_share_white", + "font_class": "icon_44_wechat_share_white", + "unicode": "e651", + "unicode_decimal": 58961 + }, + { + "icon_id": "45513173", + "name": "icon_32_publish", + "font_class": "icon_32_publish", + "unicode": "e64e", + "unicode_decimal": 58958 + }, + { + "icon_id": "45513174", + "name": "icon_32_close", + "font_class": "icon_32_close", + "unicode": "e64f", + "unicode_decimal": 58959 + }, + { + "icon_id": "45513172", + "name": "icon_32_output", + "font_class": "icon_32_output", + "unicode": "e650", + "unicode_decimal": 58960 + }, + { + "icon_id": "45500127", + "name": "icon_24_download", + "font_class": "icon_24_download", + "unicode": "e64d", + "unicode_decimal": 58957 + }, + { + "icon_id": "45492400", + "name": "icon_voice_pause", + "font_class": "icon_voice_pause", + "unicode": "e64b", + "unicode_decimal": 58955 + }, + { + "icon_id": "45492399", + "name": "icon_voice", + "font_class": "icon_voice", + "unicode": "e64c", + "unicode_decimal": 58956 + }, + { + "icon_id": "45263171", + "name": "icon_subtract", + "font_class": "icon_subtract", + "unicode": "e64a", + "unicode_decimal": 58954 + }, + { + "icon_id": "45214203", + "name": "icon_44_edit", + "font_class": "icon_44_edit", + "unicode": "e648", + "unicode_decimal": 58952 + }, + { + "icon_id": "45214202", + "name": "icon_44_delete", + "font_class": "icon_44_delete", + "unicode": "e649", + "unicode_decimal": 58953 + }, + { + "icon_id": "45160662", + "name": "icon_44_cancel_account", + "font_class": "icon_44_cancel_account", + "unicode": "e647", + "unicode_decimal": 58951 + }, + { + "icon_id": "45103313", + "name": "icon_24_search", + "font_class": "icon_24_search", + "unicode": "e646", + "unicode_decimal": 58950 + }, + { + "icon_id": "45037865", + "name": "icon_24_up", + "font_class": "icon_24_up", + "unicode": "e645", + "unicode_decimal": 58949 + }, + { + "icon_id": "44661359", + "name": "icon_msg_contacts", + "font_class": "icon_msg_contacts", + "unicode": "e644", + "unicode_decimal": 58948 + }, + { + "icon_id": "44659960", + "name": "icon_msg_video", + "font_class": "icon_msg_video", + "unicode": "e63e", + "unicode_decimal": 58942 + }, + { + "icon_id": "44659959", + "name": "icon_msg_search", + "font_class": "icon_msg_search", + "unicode": "e63f", + "unicode_decimal": 58943 + }, + { + "icon_id": "44659958", + "name": "icon_msg_call", + "font_class": "icon_msg_call", + "unicode": "e640", + "unicode_decimal": 58944 + }, + { + "icon_id": "44659956", + "name": "icon_msg_photo", + "font_class": "icon_msg_photo", + "unicode": "e641", + "unicode_decimal": 58945 + }, + { + "icon_id": "44659957", + "name": "icon_msg_img", + "font_class": "icon_msg_img", + "unicode": "e642", + "unicode_decimal": 58946 + }, + { + "icon_id": "44659955", + "name": "icon_msg_file", + "font_class": "icon_msg_file", + "unicode": "e643", + "unicode_decimal": 58947 + }, + { + "icon_id": "44625643", + "name": "icon_32_arrow_right", + "font_class": "icon_32_arrow_right", + "unicode": "e61d", + "unicode_decimal": 58909 + }, + { + "icon_id": "44625452", + "name": "icon_44_me_password", + "font_class": "icon_44_me_password", + "unicode": "e639", + "unicode_decimal": 58937 + }, + { + "icon_id": "44625448", + "name": "icon_44_me_help", + "font_class": "icon_44_me_help", + "unicode": "e63a", + "unicode_decimal": 58938 + }, + { + "icon_id": "44625447", + "name": "icon_24_minus", + "font_class": "icon_24_minus", + "unicode": "e63b", + "unicode_decimal": 58939 + }, + { + "icon_id": "44625450", + "name": "icon_36_team", + "font_class": "icon_36_team", + "unicode": "e63c", + "unicode_decimal": 58940 + }, + { + "icon_id": "44625446", + "name": "icon_32_filter", + "font_class": "icon_32_filter", + "unicode": "e63d", + "unicode_decimal": 58941 + }, + { + "icon_id": "44625458", + "name": "icon_44_quit_lite", + "font_class": "icon_44_quit_lite", + "unicode": "e631", + "unicode_decimal": 58929 + }, + { + "icon_id": "44625459", + "name": "icon_44_me_version", + "font_class": "icon_44_me_version", + "unicode": "e632", + "unicode_decimal": 58930 + }, + { + "icon_id": "44625456", + "name": "icon_36_question", + "font_class": "icon_36_question", + "unicode": "e633", + "unicode_decimal": 58931 + }, + { + "icon_id": "44625457", + "name": "icon_44_me_privacy", + "font_class": "icon_44_me_privacy", + "unicode": "e634", + "unicode_decimal": 58932 + }, + { + "icon_id": "44625455", + "name": "icon_44_more2", + "font_class": "icon_44_more2", + "unicode": "e635", + "unicode_decimal": 58933 + }, + { + "icon_id": "44625453", + "name": "icon_36_only_see", + "font_class": "icon_36_only_see", + "unicode": "e636", + "unicode_decimal": 58934 + }, + { + "icon_id": "44625454", + "name": "icon_44_me_user", + "font_class": "icon_44_me_user", + "unicode": "e637", + "unicode_decimal": 58935 + }, + { + "icon_id": "44625461", + "name": "icon_44_me_safe", + "font_class": "icon_44_me_safe", + "unicode": "e62c", + "unicode_decimal": 58924 + }, + { + "icon_id": "44625460", + "name": "icon_44_me_phone", + "font_class": "icon_44_me_phone", + "unicode": "e62d", + "unicode_decimal": 58925 + }, + { + "icon_id": "44625445", + "name": "icon_24_copy", + "font_class": "icon_24_copy", + "unicode": "e62e", + "unicode_decimal": 58926 + }, + { + "icon_id": "44625444", + "name": "icon_32_quit", + "font_class": "icon_32_quit", + "unicode": "e630", + "unicode_decimal": 58928 + }, + { + "icon_id": "44572509", + "name": "icon_44_calendar", + "font_class": "icon_44_calendar", + "unicode": "e626", + "unicode_decimal": 58918 + }, + { + "icon_id": "44572496", + "name": "icon_32_student", + "font_class": "icon_32_student", + "unicode": "e627", + "unicode_decimal": 58919 + }, + { + "icon_id": "44572501", + "name": "icon_36_delete", + "font_class": "icon_36_delete", + "unicode": "e628", + "unicode_decimal": 58920 + }, + { + "icon_id": "44572505", + "name": "icon_44_more", + "font_class": "icon_44_more", + "unicode": "e629", + "unicode_decimal": 58921 + }, + { + "icon_id": "44572502", + "name": "icon_44_arrow_left", + "font_class": "icon_44_arrow_left", + "unicode": "e62a", + "unicode_decimal": 58922 + }, + { + "icon_id": "44572499", + "name": "icon_36_edit_line", + "font_class": "icon_36_edit_line", + "unicode": "e62b", + "unicode_decimal": 58923 + }, + { + "icon_id": "44572515", + "name": "icon_44_arrow_right", + "font_class": "icon_44_arrow_right", + "unicode": "e622", + "unicode_decimal": 58914 + }, + { + "icon_id": "44572507", + "name": "icon_24_delete", + "font_class": "icon_24_delete", + "unicode": "e623", + "unicode_decimal": 58915 + }, + { + "icon_id": "44572513", + "name": "icon_56_msg_add", + "font_class": "icon_56_msg_add", + "unicode": "e624", + "unicode_decimal": 58916 + }, + { + "icon_id": "44572511", + "name": "icon_56_msg_face", + "font_class": "icon_56_msg_face", + "unicode": "e625", + "unicode_decimal": 58917 + }, + { + "icon_id": "44572516", + "name": "icon_56_msg_voice", + "font_class": "icon_56_msg_voice", + "unicode": "e616", + "unicode_decimal": 58902 + }, + { + "icon_id": "44572514", + "name": "icon_44_share", + "font_class": "icon_44_share", + "unicode": "e615", + "unicode_decimal": 58901 + }, + { + "icon_id": "44572500", + "name": "icon_36_hint", + "font_class": "icon_36_hint", + "unicode": "e613", + "unicode_decimal": 58899 + }, + { + "icon_id": "44572510", + "name": "icon_24_add", + "font_class": "icon_24_add", + "unicode": "e617", + "unicode_decimal": 58903 + }, + { + "icon_id": "44572512", + "name": "icon_24_plus", + "font_class": "icon_24_plus", + "unicode": "e618", + "unicode_decimal": 58904 + }, + { + "icon_id": "44572508", + "name": "icon_44_keyboard", + "font_class": "icon_44_keyboard", + "unicode": "e614", + "unicode_decimal": 58900 + }, + { + "icon_id": "44572506", + "name": "icon_44_quit", + "font_class": "icon_44_quit", + "unicode": "e619", + "unicode_decimal": 58905 + }, + { + "icon_id": "44572504", + "name": "icon_44_add", + "font_class": "icon_44_add", + "unicode": "e61a", + "unicode_decimal": 58906 + }, + { + "icon_id": "44572495", + "name": "icon_24_switch", + "font_class": "icon_24_switch", + "unicode": "e61b", + "unicode_decimal": 58907 + }, + { + "icon_id": "44572503", + "name": "icon_44_arrow_down_line", + "font_class": "icon_44_arrow_down_line", + "unicode": "e61c", + "unicode_decimal": 58908 + }, + { + "icon_id": "44572493", + "name": "icon_24_arrow_right", + "font_class": "icon_24_arrow_right", + "unicode": "e61e", + "unicode_decimal": 58910 + }, + { + "icon_id": "44572498", + "name": "icon_32_add", + "font_class": "icon_32_add", + "unicode": "e61f", + "unicode_decimal": 58911 + }, + { + "icon_id": "44572494", + "name": "icon_24_edit", + "font_class": "icon_24_edit", + "unicode": "e620", + "unicode_decimal": 58912 + }, + { + "icon_id": "44572492", + "name": "icon_24_arrow_down", + "font_class": "icon_24_arrow_down", + "unicode": "e621", + "unicode_decimal": 58913 + } + ] +} diff --git a/example/assets/fonts/iconfont.ttf b/example/assets/fonts/iconfont.ttf new file mode 100644 index 0000000..df26bea Binary files /dev/null and b/example/assets/fonts/iconfont.ttf differ diff --git a/example/icon_generator_config.yaml b/example/icon_generator_config.yaml new file mode 100644 index 0000000..a2a01a3 --- /dev/null +++ b/example/icon_generator_config.yaml @@ -0,0 +1,23 @@ +# 图标字体生成器配置文件 +# 用于生成 Flutter IconData 常量 + +generator: + name: "Example App Icons" + version: "1.0" + author: "Example" + +input: + # 字体文件路径 + font_file: "assets/fonts/iconfont.ttf" + # JSON 配置文件路径 + json_file: "assets/fonts/iconfont.json" + # 字体家族名称 + font_family: "iconfont" + +output: + # 生成的 Dart 文件路径 + file_path: "lib/generated/icons.dart" + # 生成的类名 + class_name: "AppIcons" + # 是否生成文档注释 + generate_docs: true diff --git a/example/lib/example_icon.dart b/example/lib/example_icon.dart index 02b5322..1d8b24f 100644 --- a/example/lib/example_icon.dart +++ b/example/lib/example_icon.dart @@ -1,24 +1,9 @@ import 'package:flutter/widgets.dart'; -/// 示例图标类 -/// -/// 用于在示例应用中展示图标 -class ExampleIcon implements Comparable { +/// 示例图标模型 +class ExampleIcon { + const ExampleIcon(this.iconData, this.title); + final IconData iconData; final String title; - - ExampleIcon(this.iconData, this.title); - - @override - String toString() => 'ExampleIcon{iconData: $iconData, title: $title}'; - - @override - bool operator ==(Object other) => - identical(this, other) || other is ExampleIcon && runtimeType == other.runtimeType && iconData == other.iconData && title == other.title; - - @override - int get hashCode => iconData.hashCode ^ title.hashCode; - - @override - int compareTo(other) => title.compareTo(other.title); } diff --git a/example/lib/generated/icons.dart b/example/lib/generated/icons.dart new file mode 100644 index 0000000..d5c9aa9 --- /dev/null +++ b/example/lib/generated/icons.dart @@ -0,0 +1,261 @@ +// ignore_for_file: constant_identifier_names + +// ============================================ +// 此文件由 yx_icon_fonts 自动生成,请勿手动修改 +// 生成时间: 2025-12-17 11:07 +// 图标数量: 76 +// ============================================ + +import 'package:flutter/widgets.dart'; + +/// Example App Icons 图标常量 +/// +/// 包含所有图标的常量定义,可以直接在 Flutter 的 Icon 组件中使用 +/// +/// 示例: +/// ```dart +/// Icon(AppIcons.iconName, size: 24) +/// ``` +class AppIcons { + AppIcons._(); + + // 统一的 IconData 构造方法 + static const _icon = _IconDataFactory._(); + + /// icon_32_clean (U+E660) + static final IconData icon32Clean = _icon(0xe660); + + /// icon_36_viewhistory (U+E65F) + static final IconData icon36Viewhistory = _icon(0xe65f); + + /// icon_36_revoke (U+E65E) + static final IconData icon36Revoke = _icon(0xe65e); + + /// icon_30_edit (U+E65B) + static final IconData icon30Edit = _icon(0xe65b); + + /// icon_24_copy2 (U+E65C) + static final IconData icon24Copy2 = _icon(0xe65c); + + /// icon_30_delete (U+E65D) + static final IconData icon30Delete = _icon(0xe65d); + + /// icon_36_add (U+E65A) + static final IconData icon36Add = _icon(0xe65a); + + /// icon_32_location (U+E659) + static final IconData icon32Location = _icon(0xe659); + + /// icon_24_mypoints (U+E657) + static final IconData icon24Mypoints = _icon(0xe657); + + /// icon_44_me_about (U+E656) + static final IconData icon44MeAbout = _icon(0xe656); + + /// icon_44_me_pointsmall (U+E658) + static final IconData icon44MePointsmall = _icon(0xe658); + + /// icon_44_search (U+E655) + static final IconData icon44Search = _icon(0xe655); + + /// icon_36_clear_people (U+E654) + static final IconData icon36ClearPeople = _icon(0xe654); + + /// icon_44_me_log_out (U+E652) + static final IconData icon44MeLogOut = _icon(0xe652); + + /// icon_44_me_switch (U+E653) + static final IconData icon44MeSwitch = _icon(0xe653); + + /// icon_44_wechat_share_white (U+E651) + static final IconData icon44WechatShareWhite = _icon(0xe651); + + /// icon_32_publish (U+E64E) + static final IconData icon32Publish = _icon(0xe64e); + + /// icon_32_close (U+E64F) + static final IconData icon32Close = _icon(0xe64f); + + /// icon_32_output (U+E650) + static final IconData icon32Output = _icon(0xe650); + + /// icon_24_download (U+E64D) + static final IconData icon24Download = _icon(0xe64d); + + /// icon_voice_pause (U+E64B) + static final IconData iconVoicePause = _icon(0xe64b); + + /// icon_voice (U+E64C) + static final IconData iconVoice = _icon(0xe64c); + + /// icon_subtract (U+E64A) + static final IconData iconSubtract = _icon(0xe64a); + + /// icon_44_edit (U+E648) + static final IconData icon44Edit = _icon(0xe648); + + /// icon_44_delete (U+E649) + static final IconData icon44Delete = _icon(0xe649); + + /// icon_44_cancel_account (U+E647) + static final IconData icon44CancelAccount = _icon(0xe647); + + /// icon_24_search (U+E646) + static final IconData icon24Search = _icon(0xe646); + + /// icon_24_up (U+E645) + static final IconData icon24Up = _icon(0xe645); + + /// icon_msg_contacts (U+E644) + static final IconData iconMsgContacts = _icon(0xe644); + + /// icon_msg_video (U+E63E) + static final IconData iconMsgVideo = _icon(0xe63e); + + /// icon_msg_search (U+E63F) + static final IconData iconMsgSearch = _icon(0xe63f); + + /// icon_msg_call (U+E640) + static final IconData iconMsgCall = _icon(0xe640); + + /// icon_msg_photo (U+E641) + static final IconData iconMsgPhoto = _icon(0xe641); + + /// icon_msg_img (U+E642) + static final IconData iconMsgImg = _icon(0xe642); + + /// icon_msg_file (U+E643) + static final IconData iconMsgFile = _icon(0xe643); + + /// icon_32_arrow_right (U+E61D) + static final IconData icon32ArrowRight = _icon(0xe61d); + + /// icon_44_me_password (U+E639) + static final IconData icon44MePassword = _icon(0xe639); + + /// icon_44_me_help (U+E63A) + static final IconData icon44MeHelp = _icon(0xe63a); + + /// icon_24_minus (U+E63B) + static final IconData icon24Minus = _icon(0xe63b); + + /// icon_36_team (U+E63C) + static final IconData icon36Team = _icon(0xe63c); + + /// icon_32_filter (U+E63D) + static final IconData icon32Filter = _icon(0xe63d); + + /// icon_44_quit_lite (U+E631) + static final IconData icon44QuitLite = _icon(0xe631); + + /// icon_44_me_version (U+E632) + static final IconData icon44MeVersion = _icon(0xe632); + + /// icon_36_question (U+E633) + static final IconData icon36Question = _icon(0xe633); + + /// icon_44_me_privacy (U+E634) + static final IconData icon44MePrivacy = _icon(0xe634); + + /// icon_44_more2 (U+E635) + static final IconData icon44More2 = _icon(0xe635); + + /// icon_36_only_see (U+E636) + static final IconData icon36OnlySee = _icon(0xe636); + + /// icon_44_me_user (U+E637) + static final IconData icon44MeUser = _icon(0xe637); + + /// icon_44_me_safe (U+E62C) + static final IconData icon44MeSafe = _icon(0xe62c); + + /// icon_44_me_phone (U+E62D) + static final IconData icon44MePhone = _icon(0xe62d); + + /// icon_24_copy (U+E62E) + static final IconData icon24Copy = _icon(0xe62e); + + /// icon_32_quit (U+E630) + static final IconData icon32Quit = _icon(0xe630); + + /// icon_44_calendar (U+E626) + static final IconData icon44Calendar = _icon(0xe626); + + /// icon_32_student (U+E627) + static final IconData icon32Student = _icon(0xe627); + + /// icon_36_delete (U+E628) + static final IconData icon36Delete = _icon(0xe628); + + /// icon_44_more (U+E629) + static final IconData icon44More = _icon(0xe629); + + /// icon_44_arrow_left (U+E62A) + static final IconData icon44ArrowLeft = _icon(0xe62a); + + /// icon_36_edit_line (U+E62B) + static final IconData icon36EditLine = _icon(0xe62b); + + /// icon_44_arrow_right (U+E622) + static final IconData icon44ArrowRight = _icon(0xe622); + + /// icon_24_delete (U+E623) + static final IconData icon24Delete = _icon(0xe623); + + /// icon_56_msg_add (U+E624) + static final IconData icon56MsgAdd = _icon(0xe624); + + /// icon_56_msg_face (U+E625) + static final IconData icon56MsgFace = _icon(0xe625); + + /// icon_56_msg_voice (U+E616) + static final IconData icon56MsgVoice = _icon(0xe616); + + /// icon_44_share (U+E615) + static final IconData icon44Share = _icon(0xe615); + + /// icon_36_hint (U+E613) + static final IconData icon36Hint = _icon(0xe613); + + /// icon_24_add (U+E617) + static final IconData icon24Add = _icon(0xe617); + + /// icon_24_plus (U+E618) + static final IconData icon24Plus = _icon(0xe618); + + /// icon_44_keyboard (U+E614) + static final IconData icon44Keyboard = _icon(0xe614); + + /// icon_44_quit (U+E619) + static final IconData icon44Quit = _icon(0xe619); + + /// icon_44_add (U+E61A) + static final IconData icon44Add = _icon(0xe61a); + + /// icon_24_switch (U+E61B) + static final IconData icon24Switch = _icon(0xe61b); + + /// icon_44_arrow_down_line (U+E61C) + static final IconData icon44ArrowDownLine = _icon(0xe61c); + + /// icon_24_arrow_right (U+E61E) + static final IconData icon24ArrowRight = _icon(0xe61e); + + /// icon_32_add (U+E61F) + static final IconData icon32Add = _icon(0xe61f); + + /// icon_24_edit (U+E620) + static final IconData icon24Edit = _icon(0xe620); + + /// icon_24_arrow_down (U+E621) + static final IconData icon24ArrowDown = _icon(0xe621); +} + +/// 内部 IconData 工厂类 +class _IconDataFactory { + const _IconDataFactory._(); + + static const String _fontFamily = 'iconfont'; + + IconData call(int codePoint) => IconData(codePoint, fontFamily: _fontFamily); +} diff --git a/example/lib/icons.dart b/example/lib/icons.dart index faea0ac..94e083f 100644 --- a/example/lib/icons.dart +++ b/example/lib/icons.dart @@ -1,113 +1,112 @@ -import 'package:yx_icon_fonts/yx_icon_fonts.dart'; import 'package:yx_icon_fonts_example/example_icon.dart'; +import 'package:yx_icon_fonts_example/generated/icons.dart'; -// 此文件由脚本自动生成! +// 此文件用于示例展示,使用生成的 AppIcons 类 final icons = [ // 其他图标 - ExampleIcon(YXIconFonts.icon32Clean, 'icon 32 clean'), - ExampleIcon(YXIconFonts.icon36Viewhistory, 'icon 36 viewhistory'), - ExampleIcon(YXIconFonts.icon36Revoke, 'icon 36 revoke'), - ExampleIcon(YXIconFonts.icon32Location, 'icon 32 location'), - ExampleIcon(YXIconFonts.icon24Mypoints, 'icon 24 mypoints'), - ExampleIcon(YXIconFonts.icon44Search, 'icon 44 search'), - ExampleIcon(YXIconFonts.icon36ClearPeople, 'icon 36 clear people'), - ExampleIcon(YXIconFonts.icon32Publish, 'icon 32 publish'), - ExampleIcon(YXIconFonts.icon32Close, 'icon 32 close'), - ExampleIcon(YXIconFonts.icon32Output, 'icon 32 output'), - ExampleIcon(YXIconFonts.icon24Download, 'icon 24 download'), - ExampleIcon(YXIconFonts.iconVoicePause, 'icon voice pause'), - ExampleIcon(YXIconFonts.iconVoice, 'icon voice'), - ExampleIcon(YXIconFonts.iconSubtract, 'icon subtract'), - ExampleIcon(YXIconFonts.icon44CancelAccount, 'icon 44 cancel account'), - ExampleIcon(YXIconFonts.icon24Search, 'icon 24 search'), - ExampleIcon(YXIconFonts.icon24Up, 'icon 24 up'), - ExampleIcon(YXIconFonts.icon24Minus, 'icon 24 minus'), - ExampleIcon(YXIconFonts.icon36OnlySee, 'icon 36 only see'), - ExampleIcon(YXIconFonts.icon24Plus, 'icon 24 plus'), + ExampleIcon(AppIcons.icon32Clean, 'icon 32 clean'), + ExampleIcon(AppIcons.icon36Viewhistory, 'icon 36 viewhistory'), + ExampleIcon(AppIcons.icon36Revoke, 'icon 36 revoke'), + ExampleIcon(AppIcons.icon32Location, 'icon 32 location'), + ExampleIcon(AppIcons.icon24Mypoints, 'icon 24 mypoints'), + ExampleIcon(AppIcons.icon44Search, 'icon 44 search'), + ExampleIcon(AppIcons.icon36ClearPeople, 'icon 36 clear people'), + ExampleIcon(AppIcons.icon32Publish, 'icon 32 publish'), + ExampleIcon(AppIcons.icon32Close, 'icon 32 close'), + ExampleIcon(AppIcons.icon32Output, 'icon 32 output'), + ExampleIcon(AppIcons.icon24Download, 'icon 24 download'), + ExampleIcon(AppIcons.iconVoicePause, 'icon voice pause'), + ExampleIcon(AppIcons.iconVoice, 'icon voice'), + ExampleIcon(AppIcons.iconSubtract, 'icon subtract'), + ExampleIcon(AppIcons.icon44CancelAccount, 'icon 44 cancel account'), + ExampleIcon(AppIcons.icon24Search, 'icon 24 search'), + ExampleIcon(AppIcons.icon24Up, 'icon 24 up'), + ExampleIcon(AppIcons.icon24Minus, 'icon 24 minus'), + ExampleIcon(AppIcons.icon36OnlySee, 'icon 36 only see'), + ExampleIcon(AppIcons.icon24Plus, 'icon 24 plus'), // 编辑相关图标 - ExampleIcon(YXIconFonts.icon30Edit, 'icon 30 edit'), - ExampleIcon(YXIconFonts.icon44Edit, 'icon 44 edit'), - ExampleIcon(YXIconFonts.icon36EditLine, 'icon 36 edit line'), - ExampleIcon(YXIconFonts.icon24Edit, 'icon 24 edit'), + ExampleIcon(AppIcons.icon30Edit, 'icon 30 edit'), + ExampleIcon(AppIcons.icon44Edit, 'icon 44 edit'), + ExampleIcon(AppIcons.icon36EditLine, 'icon 36 edit line'), + ExampleIcon(AppIcons.icon24Edit, 'icon 24 edit'), // 复制相关图标 - ExampleIcon(YXIconFonts.icon24Copy2, 'icon 24 copy2'), - ExampleIcon(YXIconFonts.icon24Copy, 'icon 24 copy'), + ExampleIcon(AppIcons.icon24Copy2, 'icon 24 copy2'), + ExampleIcon(AppIcons.icon24Copy, 'icon 24 copy'), // 删除相关图标 - ExampleIcon(YXIconFonts.icon30Delete, 'icon 30 delete'), - ExampleIcon(YXIconFonts.icon44Delete, 'icon 44 delete'), - ExampleIcon(YXIconFonts.icon36Delete, 'icon 36 delete'), - ExampleIcon(YXIconFonts.icon24Delete, 'icon 24 delete'), + ExampleIcon(AppIcons.icon30Delete, 'icon 30 delete'), + ExampleIcon(AppIcons.icon44Delete, 'icon 44 delete'), + ExampleIcon(AppIcons.icon36Delete, 'icon 36 delete'), + ExampleIcon(AppIcons.icon24Delete, 'icon 24 delete'), // 添加相关图标 - ExampleIcon(YXIconFonts.icon36Add, 'icon 36 add'), - ExampleIcon(YXIconFonts.icon24Add, 'icon 24 add'), - ExampleIcon(YXIconFonts.icon44Add, 'icon 44 add'), - ExampleIcon(YXIconFonts.icon32Add, 'icon 32 add'), + ExampleIcon(AppIcons.icon36Add, 'icon 36 add'), + ExampleIcon(AppIcons.icon24Add, 'icon 24 add'), + ExampleIcon(AppIcons.icon44Add, 'icon 44 add'), + ExampleIcon(AppIcons.icon32Add, 'icon 32 add'), // 个人中心相关图标 - ExampleIcon(YXIconFonts.icon44MeAbout, 'icon 44 me about'), - ExampleIcon(YXIconFonts.icon44MePointsmall, 'icon 44 me pointsmall'), - ExampleIcon(YXIconFonts.icon44MeLogOut, 'icon 44 me log out'), - ExampleIcon(YXIconFonts.icon44MeSwitch, 'icon 44 me switch'), - ExampleIcon(YXIconFonts.icon44MePassword, 'icon 44 me password'), - ExampleIcon(YXIconFonts.icon44MeHelp, 'icon 44 me help'), - ExampleIcon(YXIconFonts.icon44MeVersion, 'icon 44 me version'), - ExampleIcon(YXIconFonts.icon44MePrivacy, 'icon 44 me privacy'), - ExampleIcon(YXIconFonts.icon44MeUser, 'icon 44 me user'), - ExampleIcon(YXIconFonts.icon44MeSafe, 'icon 44 me safe'), - ExampleIcon(YXIconFonts.icon44MePhone, 'icon 44 me phone'), + ExampleIcon(AppIcons.icon44MeAbout, 'icon 44 me about'), + ExampleIcon(AppIcons.icon44MePointsmall, 'icon 44 me pointsmall'), + ExampleIcon(AppIcons.icon44MeLogOut, 'icon 44 me log out'), + ExampleIcon(AppIcons.icon44MeSwitch, 'icon 44 me switch'), + ExampleIcon(AppIcons.icon44MePassword, 'icon 44 me password'), + ExampleIcon(AppIcons.icon44MeHelp, 'icon 44 me help'), + ExampleIcon(AppIcons.icon44MeVersion, 'icon 44 me version'), + ExampleIcon(AppIcons.icon44MePrivacy, 'icon 44 me privacy'), + ExampleIcon(AppIcons.icon44MeUser, 'icon 44 me user'), + ExampleIcon(AppIcons.icon44MeSafe, 'icon 44 me safe'), + ExampleIcon(AppIcons.icon44MePhone, 'icon 44 me phone'), // 分享相关图标 - ExampleIcon(YXIconFonts.icon44WechatShareWhite, 'icon 44 wechat share white'), - ExampleIcon(YXIconFonts.icon44Share, 'icon 44 share'), + ExampleIcon(AppIcons.icon44WechatShareWhite, 'icon 44 wechat share white'), + ExampleIcon(AppIcons.icon44Share, 'icon 44 share'), // 消息相关图标 - ExampleIcon(YXIconFonts.iconMsgContacts, 'icon msg contacts'), - ExampleIcon(YXIconFonts.iconMsgVideo, 'icon msg video'), - ExampleIcon(YXIconFonts.iconMsgSearch, 'icon msg search'), - ExampleIcon(YXIconFonts.iconMsgCall, 'icon msg call'), - ExampleIcon(YXIconFonts.iconMsgPhoto, 'icon msg photo'), - ExampleIcon(YXIconFonts.iconMsgImg, 'icon msg img'), - ExampleIcon(YXIconFonts.iconMsgFile, 'icon msg file'), - ExampleIcon(YXIconFonts.icon56MsgAdd, 'icon 56 msg add'), - ExampleIcon(YXIconFonts.icon56MsgFace, 'icon 56 msg face'), - ExampleIcon(YXIconFonts.icon56MsgVoice, 'icon 56 msg voice'), + ExampleIcon(AppIcons.iconMsgContacts, 'icon msg contacts'), + ExampleIcon(AppIcons.iconMsgVideo, 'icon msg video'), + ExampleIcon(AppIcons.iconMsgSearch, 'icon msg search'), + ExampleIcon(AppIcons.iconMsgCall, 'icon msg call'), + ExampleIcon(AppIcons.iconMsgPhoto, 'icon msg photo'), + ExampleIcon(AppIcons.iconMsgImg, 'icon msg img'), + ExampleIcon(AppIcons.iconMsgFile, 'icon msg file'), + ExampleIcon(AppIcons.icon56MsgAdd, 'icon 56 msg add'), + ExampleIcon(AppIcons.icon56MsgFace, 'icon 56 msg face'), + ExampleIcon(AppIcons.icon56MsgVoice, 'icon 56 msg voice'), // 箭头相关图标 - ExampleIcon(YXIconFonts.icon32ArrowRight, 'icon 32 arrow right'), - ExampleIcon(YXIconFonts.icon44ArrowLeft, 'icon 44 arrow left'), - ExampleIcon(YXIconFonts.icon44ArrowRight, 'icon 44 arrow right'), - ExampleIcon(YXIconFonts.icon44ArrowDownLine, 'icon 44 arrow down line'), - ExampleIcon(YXIconFonts.icon24ArrowRight, 'icon 24 arrow right'), - ExampleIcon(YXIconFonts.icon24ArrowDown, 'icon 24 arrow down'), + ExampleIcon(AppIcons.icon32ArrowRight, 'icon 32 arrow right'), + ExampleIcon(AppIcons.icon44ArrowLeft, 'icon 44 arrow left'), + ExampleIcon(AppIcons.icon44ArrowRight, 'icon 44 arrow right'), + ExampleIcon(AppIcons.icon44ArrowDownLine, 'icon 44 arrow down line'), + ExampleIcon(AppIcons.icon24ArrowRight, 'icon 24 arrow right'), + ExampleIcon(AppIcons.icon24ArrowDown, 'icon 24 arrow down'), // 团队和用户相关图标 - ExampleIcon(YXIconFonts.icon36Team, 'icon 36 team'), - ExampleIcon(YXIconFonts.icon32Student, 'icon 32 student'), + ExampleIcon(AppIcons.icon36Team, 'icon 36 team'), + ExampleIcon(AppIcons.icon32Student, 'icon 32 student'), // 功能图标 - ExampleIcon(YXIconFonts.icon32Filter, 'icon 32 filter'), - ExampleIcon(YXIconFonts.icon36Question, 'icon 36 question'), - ExampleIcon(YXIconFonts.icon36Hint, 'icon 36 hint'), - ExampleIcon(YXIconFonts.icon24Switch, 'icon 24 switch'), + ExampleIcon(AppIcons.icon32Filter, 'icon 32 filter'), + ExampleIcon(AppIcons.icon36Question, 'icon 36 question'), + ExampleIcon(AppIcons.icon36Hint, 'icon 36 hint'), + ExampleIcon(AppIcons.icon24Switch, 'icon 24 switch'), // 退出相关图标 - ExampleIcon(YXIconFonts.icon44QuitLite, 'icon 44 quit lite'), - ExampleIcon(YXIconFonts.icon32Quit, 'icon 32 quit'), - ExampleIcon(YXIconFonts.icon44Quit, 'icon 44 quit'), + ExampleIcon(AppIcons.icon44QuitLite, 'icon 44 quit lite'), + ExampleIcon(AppIcons.icon32Quit, 'icon 32 quit'), + ExampleIcon(AppIcons.icon44Quit, 'icon 44 quit'), // 更多和菜单相关图标 - ExampleIcon(YXIconFonts.icon44More2, 'icon 44 more2'), - ExampleIcon(YXIconFonts.icon44More, 'icon 44 more'), + ExampleIcon(AppIcons.icon44More2, 'icon 44 more2'), + ExampleIcon(AppIcons.icon44More, 'icon 44 more'), // 日历相关图标 - ExampleIcon(YXIconFonts.icon44Calendar, 'icon 44 calendar'), + ExampleIcon(AppIcons.icon44Calendar, 'icon 44 calendar'), // 键盘相关图标 - ExampleIcon(YXIconFonts.icon44Keyboard, 'icon 44 keyboard'), - + ExampleIcon(AppIcons.icon44Keyboard, 'icon 44 keyboard'), ]; diff --git a/example/lib/main.dart b/example/lib/main.dart index 834ed4e..ae43f77 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -1,6 +1,5 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; -import 'package:yx_icon_fonts/yx_icon_fonts.dart'; import 'package:yx_icon_fonts_example/icons.dart'; void main() { @@ -35,12 +34,18 @@ class YXIconFontsGalleryHome extends StatefulWidget { } class _YXIconFontsGalleryHomeState extends State { - var _searchTerm = ""; + var _searchTerm = ''; var _isSearching = false; @override Widget build(BuildContext context) { - final filteredIcons = icons.where((icon) => _searchTerm.isEmpty || icon.title.toLowerCase().contains(_searchTerm.toLowerCase())).toList(); + final filteredIcons = icons + .where( + (icon) => + _searchTerm.isEmpty || + icon.title.toLowerCase().contains(_searchTerm.toLowerCase()), + ) + .toList(); return Scaffold( appBar: _isSearching ? _searchBar(context) : _titleBar(), @@ -69,7 +74,7 @@ class _YXIconFontsGalleryHomeState extends State { alignment: Alignment.center, child: Hero( tag: icon, - child: YXIcon( + child: Icon( icon.iconData, size: 100, ), @@ -83,7 +88,7 @@ class _YXIconFontsGalleryHomeState extends State { child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ - Hero(tag: icon, child: YXIcon(icon.iconData)), + Hero(tag: icon, child: Icon(icon.iconData)), Container( padding: const EdgeInsets.only(top: 16.0), child: Text( @@ -92,7 +97,7 @@ class _YXIconFontsGalleryHomeState extends State { color: Colors.black, ), ), - ) + ), ], ), ); @@ -103,7 +108,7 @@ class _YXIconFontsGalleryHomeState extends State { onPressed: () { setState(() { _isSearching = !_isSearching; - if (!_isSearching) _searchTerm = ""; + if (!_isSearching) _searchTerm = ''; }); }, child: Icon(_isSearching ? Icons.close : Icons.search), @@ -133,7 +138,7 @@ class _YXIconFontsGalleryHomeState extends State { onPressed: () { setState(() { _isSearching = false; - _searchTerm = ""; + _searchTerm = ''; }); }, ), diff --git a/example/pubspec.lock b/example/pubspec.lock index bd0c5ee..42f5149 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -99,6 +99,14 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "3.0.0" + logging: + dependency: transitive + description: + name: logging + sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61 + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.3.0" matcher: dependency: transitive description: @@ -200,13 +208,21 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "15.0.2" + yaml: + dependency: transitive + description: + name: yaml + sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce + url: "https://pub.flutter-io.cn" + source: hosted + version: "3.1.3" yx_icon_fonts: - dependency: "direct main" + dependency: "direct dev" description: path: ".." relative: true source: path - version: "1.0.5" + version: "2.0.0" sdks: dart: ">=3.8.0-0 <4.0.0" flutter: ">=3.18.0-18.0.pre.54" diff --git a/example/pubspec.yaml b/example/pubspec.yaml index 6ded487..b6c9024 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -10,13 +10,20 @@ environment: dependencies: flutter: sdk: flutter + +dev_dependencies: + flutter_lints: ^3.0.0 + flutter_test: + sdk: flutter + # CLI 代码生成工具 yx_icon_fonts: path: ../ -dev_dependencies: - flutter_test: - sdk: flutter - flutter_lints: ^3.0.0 - flutter: - uses-material-design: true \ No newline at end of file + uses-material-design: true + + # 字体配置 + fonts: + - family: iconfont + fonts: + - asset: assets/fonts/iconfont.ttf \ No newline at end of file diff --git a/generate.sh b/generate.sh deleted file mode 100755 index 10e9a03..0000000 --- a/generate.sh +++ /dev/null @@ -1,158 +0,0 @@ -#!/bin/bash - -# YX Icon Fonts 生成脚本 -# 用于生成图标数据和示例文件 - -set -e # 遇到错误时退出 - -# 颜色定义 -RED='\033[0;31m' -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -BLUE='\033[0;34m' -NC='\033[0m' # No Color - -# 打印带颜色的消息 -print_info() { - echo -e "${BLUE}ℹ️ $1${NC}" -} - -print_success() { - echo -e "${GREEN}✅ $1${NC}" -} - -print_warning() { - echo -e "${YELLOW}⚠️ $1${NC}" -} - -print_error() { - echo -e "${RED}❌ $1${NC}" -} - -# 检查 Dart 是否安装 -check_dart() { - if ! command -v dart &> /dev/null; then - print_error "Dart 未安装或不在 PATH 中" - exit 1 - fi -} - -# 检查必要文件是否存在 -check_files() { - if [ ! -f "iconfont.json" ]; then - print_error "找不到 iconfont.json 文件" - exit 1 - fi - - if [ ! -f "scripts/generate_icons.dart" ]; then - print_error "找不到 scripts/generate_icons.dart 文件" - exit 1 - fi - - if [ ! -f "scripts/generate_example.dart" ]; then - print_error "找不到 scripts/generate_example.dart 文件" - exit 1 - fi - - if [ ! -f "scripts/generate_wrapper.dart" ]; then - print_error "找不到 scripts/generate_wrapper.dart 文件" - exit 1 - fi -} - -# 生成图标数据 -generate_icons() { - print_info "正在生成图标数据..." - dart scripts/generate_icons.dart - print_success "图标数据生成完成" -} - -# 生成示例文件 -generate_example() { - print_info "正在生成示例文件..." - dart scripts/generate_example.dart - print_success "示例文件生成完成" -} - -# 生成外部封装文件 -generate_wrapper() { - print_info "正在生成外部封装文件..." - dart scripts/generate_wrapper.dart - print_success "外部封装文件生成完成" -} - -# 生成所有文件 -generate_all() { - print_info "开始生成所有文件..." - echo - - generate_icons - echo - generate_wrapper - echo - generate_example - echo - - print_success "所有文件生成完成!" -} - -# 显示帮助信息 -show_help() { - echo "YX Icon Fonts 生成脚本" - echo - echo "用法: $0 [选项]" - echo - echo "选项:" - echo " icons 只生成图标数据 (lib/src/yx_icon_fonts_data.dart)" - echo " wrapper 只生成外部封装文件 (generate/icons.dart)" - echo " example 只生成示例文件 (example/lib/icons.dart)" - echo " all 生成所有文件 (默认)" - echo " help 显示此帮助信息" - echo - echo "示例:" - echo " $0 # 生成所有文件" - echo " $0 icons # 只生成图标数据" - echo " $0 wrapper # 只生成外部封装文件" - echo " $0 example # 只生成示例文件" - echo -} - -# 主函数 -main() { - local action=${1:-all} - - case $action in - "icons") - check_dart - check_files - generate_icons - ;; - "wrapper") - check_dart - check_files - generate_wrapper - ;; - "example") - check_dart - check_files - generate_example - ;; - "all") - check_dart - check_files - generate_all - ;; - "help"|"-h"|"--help") - show_help - ;; - *) - print_error "未知选项: $action" - echo - show_help - exit 1 - ;; - esac -} - -# 运行主函数 -main "$@" \ No newline at end of file diff --git a/icon_generator_config.template.yaml b/icon_generator_config.template.yaml new file mode 100644 index 0000000..218641e --- /dev/null +++ b/icon_generator_config.template.yaml @@ -0,0 +1,57 @@ +# 图标字体生成器配置文件模板 +# 复制此文件到您的项目根目录,重命名为 icon_generator_config.yaml +# 并根据需要修改配置 + +# 生成器信息 +generator: + # 项目名称(用于生成的代码注释) + name: "MyApp Icons" + # 版本 + version: "1.0" + # 作者 + author: "Your Name" + +# 输入配置 +input: + # 字体文件路径(相对于项目根目录) + # 这个文件从 iconfont.cn 下载 + font_file: "assets/fonts/iconfont.ttf" + + # JSON 配置文件路径(从 iconfont.cn 下载) + # 包含所有图标的 unicode 编码和名称 + json_file: "assets/fonts/iconfont.json" + + # 字体家族名称 + # 需要与 pubspec.yaml 中 flutter.fonts 配置的 family 一致 + # 例如: + # flutter: + # fonts: + # - family: iconfont + # fonts: + # - asset: assets/fonts/iconfont.ttf + font_family: "iconfont" + + # 字体包名(可选) + # 如果字体文件位于另一个 package 中,请指定包名 + # font_package: "my_icons_package" + +# 输出配置 +output: + # 生成的 Dart 文件路径 + # 建议放在 lib/generated/ 目录下,并添加到 .gitignore + file_path: "lib/generated/icons.dart" + + # 生成的类名 + # 使用时: Icon(AppIcons.iconName, size: 24) + class_name: "AppIcons" + + # 是否生成文档注释 + # 设为 true 会为每个图标生成 Unicode 说明 + generate_docs: true + + # 是否使用 const 构造函数 + use_const_constructor: true + + # 包导入路径(可选) + # 如果需要在生成的文件中添加特定的 package 导入 + # package_import: "package:my_app/common/icons.dart" diff --git a/lib/main.dart b/lib/main.dart deleted file mode 100644 index a725658..0000000 --- a/lib/main.dart +++ /dev/null @@ -1,20 +0,0 @@ -import 'package:flutter/material.dart'; - -void main() { - runApp(const MainApp()); -} - -class MainApp extends StatelessWidget { - const MainApp({super.key}); - - @override - Widget build(BuildContext context) { - return const MaterialApp( - home: Scaffold( - body: Center( - child: Text('Hello World!'), - ), - ), - ); - } -} diff --git a/lib/src/cli/icon_fonts_cli.dart b/lib/src/cli/icon_fonts_cli.dart new file mode 100644 index 0000000..200db74 --- /dev/null +++ b/lib/src/cli/icon_fonts_cli.dart @@ -0,0 +1,172 @@ +import 'dart:io'; + +import 'package:logging/logging.dart'; +import 'package:yx_icon_fonts/src/commands/base_command.dart'; +import 'package:yx_icon_fonts/src/commands/generate_command.dart'; +import 'package:yx_icon_fonts/src/commands/init_command.dart'; +import 'package:yx_icon_fonts/src/utils/string_helper.dart'; + +/// YX Icon Fonts CLI 应用程序 +/// +/// 使用命令模式架构的 CLI 工具 +class IconFontsCLI { + IconFontsCLI() { + _registerCommands(); + } + + static final Logger _logger = Logger('IconFontsCLI'); + final Map _commands = {}; + + /// 注册所有命令 + void _registerCommands() { + _registerCommand(GenerateCommand()); + _registerCommand(InitCommand()); + } + + /// 注册单个命令 + void _registerCommand(BaseCommand command) { + _commands[command.name] = command; + } + + /// 运行 CLI 应用程序 + Future run(List arguments) async { + try { + _showBanner(); + + if (arguments.isEmpty || + arguments.first == 'help' || + arguments.first == '--help') { + _showHelp(); + return 0; + } + + final commandName = arguments.first; + final commandArgs = + arguments.length > 1 ? arguments.sublist(1) : []; + + // 检查特殊命令 + if (commandName == 'version' || commandName == '--version') { + _showVersion(); + return 0; + } + + // 查找并执行命令 + final command = _commands[commandName]; + if (command == null) { + _logger + ..severe('❌ 未知命令: $commandName') + ..info(''); + _showAvailableCommands(); + return 1; + } + + // 检查命令帮助 + if (commandArgs.contains('--help') || commandArgs.contains('-h')) { + command.showHelp(); + return 0; + } + + // 执行命令 + final stopwatch = Stopwatch()..start(); + final exitCode = await command.execute(commandArgs); + stopwatch.stop(); + + // 显示执行时间 + if (exitCode == 0) { + _logger + ..info('') + ..info( + '⏱️ 总耗时: ${StringHelper.formatDuration(stopwatch.elapsed)}', + ); + } + + return exitCode; + } on Exception catch (error, stackTrace) { + _logger + ..severe('❌ 应用程序错误: $error') + ..severe('堆栈跟踪: $stackTrace'); + return 1; + } + } + + /// 显示应用程序横幅 + void _showBanner() { + _logger + ..info('') + ..info('🎨 YX Icon Fonts 代码生成器 v2.0') + ..info('=====================================') + ..info('从 iconfont.json 生成 Flutter IconData 常量') + ..info(''); + } + + /// 显示帮助信息 + void _showHelp() { + _logger + ..info('用法: dart run yx_icon_fonts <命令> [选项]') + ..info('') + ..info('支持 YAML 配置文件,可自定义输出路径和类名。') + ..info(''); + _showAvailableCommands(); + _showGlobalOptions(); + _showExamples(); + } + + /// 显示可用命令 + void _showAvailableCommands() { + _logger + ..info('📋 可用命令:') + ..info(''); + + for (final command in _commands.values) { + _logger.info(' ${command.name.padRight(12)} ${command.description}'); + } + + _logger + ..info(' help 显示帮助信息') + ..info(' version 显示版本信息') + ..info(''); + } + + /// 显示全局选项 + void _showGlobalOptions() { + _logger + ..info('🔧 全局选项:') + ..info(' -h, --help 显示帮助信息') + ..info(' --version 显示版本信息') + ..info(''); + } + + /// 显示使用示例 + void _showExamples() { + _logger + ..info('💡 使用示例:') + ..info('') + ..info(' # 初始化配置文件') + ..info(' dart run yx_icon_fonts init') + ..info('') + ..info(' # 生成图标代码') + ..info(' dart run yx_icon_fonts generate') + ..info('') + ..info(' # 使用指定配置文件') + ..info(' dart run yx_icon_fonts generate --config my_config.yaml') + ..info('') + ..info(' # 查看命令帮助') + ..info(' dart run yx_icon_fonts generate --help') + ..info(''); + } + + /// 显示版本信息 + void _showVersion() { + _logger + ..info('YX Icon Fonts CLI v2.0.0') + ..info('构建于: ${DateTime.now().toIso8601String()}') + ..info('Dart SDK: ${Platform.version}') + ..info('') + ..info('功能特性:') + ..info('- 🎨 从 iconfont.json 生成代码') + ..info('- 📝 YAML 配置文件支持') + ..info('- 🏗️ 自定义类名和输出路径') + ..info('- 📚 自动生成文档注释') + ..info(''); + } +} diff --git a/lib/src/commands/base_command.dart b/lib/src/commands/base_command.dart new file mode 100644 index 0000000..640f070 --- /dev/null +++ b/lib/src/commands/base_command.dart @@ -0,0 +1,63 @@ +import 'package:logging/logging.dart'; + +/// 命令基类 +/// +/// 所有 CLI 命令都应继承此类 +abstract class BaseCommand { + /// 命令名称 + String get name; + + /// 命令描述 + String get description; + + /// 日志器 + Logger get logger => Logger(name); + + /// 执行命令 + /// + /// 返回退出码:0 表示成功,非 0 表示失败 + Future execute(List arguments); + + /// 显示帮助信息 + void showHelp() { + logger + ..info('命令: $name') + ..info('描述: $description') + ..info('') + ..info('用法: dart run yx_icon_fonts $name [选项]') + ..info('') + ..info('选项:'); + showOptions(); + } + + /// 显示命令选项 + void showOptions() { + logger.info(' -h, --help 显示帮助信息'); + } + + /// 解析参数中的选项值 + String? getOption(List arguments, String name, {String? shortName}) { + for (var i = 0; i < arguments.length; i++) { + final arg = arguments[i]; + if (arg == '--$name' || (shortName != null && arg == '-$shortName')) { + if (i + 1 < arguments.length) { + return arguments[i + 1]; + } + } + if (arg.startsWith('--$name=')) { + return arg.substring('--$name='.length); + } + } + return null; + } + + /// 检查参数中是否有指定标志 + bool hasFlag(List arguments, String name, {String? shortName}) { + for (final arg in arguments) { + if (arg == '--$name' || (shortName != null && arg == '-$shortName')) { + return true; + } + } + return false; + } +} diff --git a/lib/src/commands/generate_command.dart b/lib/src/commands/generate_command.dart new file mode 100644 index 0000000..514a42f --- /dev/null +++ b/lib/src/commands/generate_command.dart @@ -0,0 +1,83 @@ +import 'package:yx_icon_fonts/src/commands/base_command.dart'; +import 'package:yx_icon_fonts/src/config/config_loader.dart'; +import 'package:yx_icon_fonts/src/generator/icon_generator.dart'; +import 'package:yx_icon_fonts/src/utils/logger.dart'; +import 'package:yx_icon_fonts/src/utils/string_helper.dart'; + +/// 生成命令 +/// +/// 从配置文件读取配置,生成图标常量代码 +class GenerateCommand extends BaseCommand { + @override + String get name => 'generate'; + + @override + String get description => '从 iconfont.json 生成 Flutter IconData 常量'; + + @override + void showOptions() { + super.showOptions(); + logger + ..info(' -c, --config 指定配置文件路径 ' + '(默认: icon_generator_config.yaml)') + ..info(' -v, --verbose 显示详细输出'); + } + + @override + Future execute(List arguments) async { + try { + // 解析参数 + final configPath = getOption(arguments, 'config', shortName: 'c'); + final verbose = hasFlag(arguments, 'verbose', shortName: 'v'); + + if (verbose) { + appLogger.info('🔍 详细模式已启用'); + } + + // 加载配置 + appLogger.progress('正在加载配置...'); + final config = ConfigLoader.load(configPath: configPath); + + if (verbose) { + appLogger + ..info('📋 配置信息:') + ..info(' - 输入文件: ${config.input.jsonFile}') + ..info(' - 字体家族: ${config.input.fontFamily}') + ..info(' - 输出文件: ${config.output.filePath}') + ..info(' - 类名: ${config.output.className}'); + } + + // 执行生成 + final generator = IconGenerator(config: config); + final result = await generator.generate(); + + if (result.success) { + appLogger + ..success('代码生成成功!') + ..info('') + ..info('📁 输出文件: ${result.outputPath}') + ..stats('共生成 ${result.iconCount} 个图标常量'); + + if (result.duration != null) { + appLogger.info( + '⏱️ 耗时: ${StringHelper.formatDuration(result.duration!)}', + ); + } + + return 0; + } else { + appLogger.severe('❌ 生成失败: ${result.error}'); + return 1; + } + } on ConfigNotFoundException catch (e) { + appLogger + ..severe('❌ ${e.message}') + ..info('') + ..info('💡 提示: 运行 "dart run yx_icon_fonts init" 创建配置文件'); + return 1; + } on Exception catch (e) { + appLogger.severe('❌ 发生错误: $e'); + return 1; + } + } +} diff --git a/lib/src/commands/init_command.dart b/lib/src/commands/init_command.dart new file mode 100644 index 0000000..8c614d4 --- /dev/null +++ b/lib/src/commands/init_command.dart @@ -0,0 +1,107 @@ +import 'dart:io'; + +import 'package:path/path.dart' as path; +import 'package:yx_icon_fonts/src/commands/base_command.dart'; +import 'package:yx_icon_fonts/src/config/config_loader.dart'; +import 'package:yx_icon_fonts/src/utils/logger.dart'; + +/// 初始化命令 +/// +/// 在当前目录创建配置文件模板 +class InitCommand extends BaseCommand { + @override + String get name => 'init'; + + @override + String get description => '创建配置文件模板'; + + @override + void showOptions() { + super.showOptions(); + logger + ..info(' -f, --force 强制覆盖已存在的配置文件') + ..info(' -o, --output 指定配置文件输出路径'); + } + + @override + Future execute(List arguments) async { + try { + final force = hasFlag(arguments, 'force', shortName: 'f'); + final outputPath = getOption(arguments, 'output', shortName: 'o'); + + final configPath = outputPath ?? + path.join( + Directory.current.path, + ConfigLoader.defaultConfigFileName, + ); + + // 检查文件是否已存在 + if (File(configPath).existsSync() && !force) { + appLogger + ..warning('⚠️ 配置文件已存在: $configPath') + ..info('') + ..info('💡 使用 --force 选项覆盖现有文件'); + return 1; + } + + // 写入配置模板 + appLogger.progress('正在创建配置文件...'); + File(configPath).writeAsStringSync(_configTemplate); + + appLogger + ..success('配置文件创建成功!') + ..info('') + ..info('📁 配置文件: $configPath') + ..info('') + ..info('📝 下一步:') + ..info(' 1. 编辑配置文件,设置输入输出路径') + ..info(' 2. 运行 "dart run yx_icon_fonts generate" 生成代码'); + + return 0; + } on Exception catch (e) { + appLogger.severe('❌ 发生错误: $e'); + return 1; + } + } + + /// 配置文件模板 + static const String _configTemplate = ''' +# 图标字体生成器配置文件 +# 由 yx_icon_fonts init 命令生成 + +# 生成器信息 +generator: + name: "MyApp Icons" + version: "1.0" + author: "Your Name" + +# 输入配置 +input: + # 字体文件路径(相对于项目根目录) + font_file: "assets/fonts/iconfont.ttf" + + # JSON 配置文件路径(从 iconfont.cn 下载) + json_file: "assets/fonts/iconfont.json" + + # 字体家族名称(与 pubspec.yaml 中配置的一致) + font_family: "iconfont" + + # 字体包名(可选) + # 如果字体文件位于另一个 package 中,请指定包名 + # font_package: "my_icons_package" + +# 输出配置 +output: + # 生成的 Dart 文件路径 + file_path: "lib/generated/icons.dart" + + # 生成的类名 + class_name: "AppIcons" + + # 是否生成文档注释 + generate_docs: true + + # 是否使用 const 构造函数 + use_const_constructor: true +'''; +} diff --git a/lib/src/config/config_loader.dart b/lib/src/config/config_loader.dart new file mode 100644 index 0000000..9e1eab6 --- /dev/null +++ b/lib/src/config/config_loader.dart @@ -0,0 +1,103 @@ +import 'dart:io'; + +import 'package:path/path.dart' as path; +import 'package:yaml/yaml.dart'; +import 'package:yx_icon_fonts/src/config/generator_config.dart'; + +/// 配置加载器 +/// +/// 负责从 YAML 文件加载配置 +class ConfigLoader { + /// 默认配置文件名 + static const String defaultConfigFileName = 'icon_generator_config.yaml'; + + /// 加载配置文件 + /// + /// [configPath] 配置文件路径,如果为 null 则使用默认路径 + /// [workingDir] 工作目录,默认为当前目录 + static GeneratorConfig load({ + String? configPath, + String? workingDir, + }) { + final directory = workingDir ?? Directory.current.path; + final filePath = configPath ?? path.join(directory, defaultConfigFileName); + final file = File(filePath); + + if (!file.existsSync()) { + throw ConfigNotFoundException( + '配置文件不存在: $filePath\n' + '请运行 "dart run yx_icon_fonts init" 创建配置文件', + ); + } + + try { + final content = file.readAsStringSync(); + final yamlMap = loadYaml(content) as YamlMap?; + + if (yamlMap == null) { + throw ConfigParseException('配置文件为空或格式错误'); + } + + // 将 YamlMap 转换为 Map + final map = _convertYamlMap(yamlMap); + return GeneratorConfig.fromMap(map); + } on YamlException catch (e) { + throw ConfigParseException('YAML 解析错误: ${e.message}'); + } + } + + /// 检查配置文件是否存在 + static bool exists({String? configPath, String? workingDir}) { + final directory = workingDir ?? Directory.current.path; + final filePath = configPath ?? path.join(directory, defaultConfigFileName); + return File(filePath).existsSync(); + } + + /// 将 YamlMap 递归转换为 Map + static Map _convertYamlMap(YamlMap yamlMap) { + final result = {}; + for (final key in yamlMap.keys) { + final value = yamlMap[key]; + if (value is YamlMap) { + result[key.toString()] = _convertYamlMap(value); + } else if (value is YamlList) { + result[key.toString()] = _convertYamlList(value); + } else { + result[key.toString()] = value; + } + } + return result; + } + + /// 将 YamlList 递归转换为 List + static List _convertYamlList(YamlList yamlList) { + return yamlList.map((item) { + if (item is YamlMap) { + return _convertYamlMap(item); + } else if (item is YamlList) { + return _convertYamlList(item); + } + return item; + }).toList(); + } +} + +/// 配置文件未找到异常 +class ConfigNotFoundException implements Exception { + ConfigNotFoundException(this.message); + + final String message; + + @override + String toString() => 'ConfigNotFoundException: $message'; +} + +/// 配置解析异常 +class ConfigParseException implements Exception { + ConfigParseException(this.message); + + final String message; + + @override + String toString() => 'ConfigParseException: $message'; +} diff --git a/lib/src/config/generator_config.dart b/lib/src/config/generator_config.dart new file mode 100644 index 0000000..cc99a3a --- /dev/null +++ b/lib/src/config/generator_config.dart @@ -0,0 +1,178 @@ +// 配置模型类 +// +// 定义图标生成器的配置结构 + +/// 总配置类 +class GeneratorConfig { + const GeneratorConfig({ + required this.generator, + required this.input, + required this.output, + }); + + /// 从 Map 创建配置 + factory GeneratorConfig.fromMap(Map map) { + return GeneratorConfig( + generator: GeneratorInfo.fromMap( + map['generator'] as Map? ?? {}, + ), + input: InputConfig.fromMap( + map['input'] as Map? ?? {}, + ), + output: OutputConfig.fromMap( + map['output'] as Map? ?? {}, + ), + ); + } + + /// 创建默认配置 + factory GeneratorConfig.defaults() { + return GeneratorConfig( + generator: GeneratorInfo.defaults(), + input: InputConfig.defaults(), + output: OutputConfig.defaults(), + ); + } + + /// 生成器信息 + final GeneratorInfo generator; + + /// 输入配置 + final InputConfig input; + + /// 输出配置 + final OutputConfig output; + + @override + String toString() { + return 'GeneratorConfig(generator: $generator, input: $input, ' + 'output: $output)'; + } +} + +/// 生成器信息 +class GeneratorInfo { + const GeneratorInfo({ + required this.name, + this.version = '1.0', + this.author = '', + }); + + factory GeneratorInfo.fromMap(Map map) { + return GeneratorInfo( + name: map['name'] as String? ?? 'Icon Generator', + version: map['version'] as String? ?? '1.0', + author: map['author'] as String? ?? '', + ); + } + + factory GeneratorInfo.defaults() { + return const GeneratorInfo(name: 'Icon Generator'); + } + + /// 生成器名称 + final String name; + + /// 版本 + final String version; + + /// 作者 + final String author; + + @override + String toString() => 'GeneratorInfo(name: $name, version: $version)'; +} + +/// 输入配置 +class InputConfig { + const InputConfig({ + required this.fontFile, + required this.jsonFile, + required this.fontFamily, + this.fontPackage, + }); + + factory InputConfig.fromMap(Map map) { + return InputConfig( + fontFile: map['font_file'] as String? ?? 'assets/fonts/iconfont.ttf', + jsonFile: map['json_file'] as String? ?? 'assets/fonts/iconfont.json', + fontFamily: map['font_family'] as String? ?? 'iconfont', + fontPackage: map['font_package'] as String?, + ); + } + + factory InputConfig.defaults() { + return const InputConfig( + fontFile: 'assets/fonts/iconfont.ttf', + jsonFile: 'assets/fonts/iconfont.json', + fontFamily: 'iconfont', + ); + } + + /// 字体文件路径(相对于项目根目录) + final String fontFile; + + /// JSON 配置文件路径 + final String jsonFile; + + /// 字体家族名称 + final String fontFamily; + + /// 字体包名 + final String? fontPackage; + + @override + String toString() { + return 'InputConfig(fontFile: $fontFile, jsonFile: $jsonFile, ' + 'fontFamily: $fontFamily, fontPackage: $fontPackage)'; + } +} + +/// 输出配置 +class OutputConfig { + const OutputConfig({ + required this.filePath, + required this.className, + this.generateDocs = true, + this.useConstConstructor = true, + this.packageImport, + }); + + factory OutputConfig.fromMap(Map map) { + return OutputConfig( + filePath: map['file_path'] as String? ?? 'lib/generated/icons.dart', + className: map['class_name'] as String? ?? 'AppIcons', + generateDocs: map['generate_docs'] as bool? ?? true, + useConstConstructor: map['use_const_constructor'] as bool? ?? true, + packageImport: map['package_import'] as String?, + ); + } + + factory OutputConfig.defaults() { + return const OutputConfig( + filePath: 'lib/generated/icons.dart', + className: 'AppIcons', + ); + } + + /// 生成的 Dart 文件路径 + final String filePath; + + /// 生成的类名 + final String className; + + /// 是否生成文档注释 + final bool generateDocs; + + /// 是否使用 const 构造函数 + final bool useConstConstructor; + + /// 包导入路径(可选,用于生成 part of 指令) + final String? packageImport; + + @override + String toString() { + return 'OutputConfig(filePath: $filePath, className: $className, ' + 'generateDocs: $generateDocs)'; + } +} diff --git a/lib/src/generator/dart_code_builder.dart b/lib/src/generator/dart_code_builder.dart new file mode 100644 index 0000000..5c7a5c9 --- /dev/null +++ b/lib/src/generator/dart_code_builder.dart @@ -0,0 +1,159 @@ +import 'package:yx_icon_fonts/src/config/generator_config.dart'; +import 'package:yx_icon_fonts/src/generator/icon_parser.dart'; +import 'package:yx_icon_fonts/src/utils/string_helper.dart'; + +/// Dart 代码构建器 +/// +/// 负责生成 Flutter IconData 常量代码 +class DartCodeBuilder { + const DartCodeBuilder({ + required this.config, + required this.meta, + }); + + /// 配置 + final GeneratorConfig config; + + /// 图标元数据 + final IconFontMeta meta; + + /// 构建完整的 Dart 文件内容 + String build() { + final buffer = StringBuffer() + // 文件头注释 + .._writeHeader(meta) + // 导入语句 + .._writeImports() + // 类定义 + .._writeClass(config, meta); + + return buffer.toString(); + } +} + +extension _StringBufferExt on StringBuffer { + /// 写入文件头注释 + void _writeHeader(IconFontMeta meta) { + final now = DateTime.now(); + final timestamp = '${now.year}-${_pad(now.month)}-${_pad(now.day)} ' + '${_pad(now.hour)}:${_pad(now.minute)}'; + + this + ..writeln('// ignore_for_file: constant_identifier_names') + ..writeln() + ..writeln('// ============================================') + ..writeln('// 此文件由 yx_icon_fonts 自动生成,请勿手动修改') + ..writeln('// 生成时间: $timestamp') + ..writeln('// 图标数量: ${meta.icons.length}') + ..writeln('// ============================================') + ..writeln(); + } + + String _pad(int n) => n.toString().padLeft(2, '0'); + + /// 写入导入语句 + void _writeImports() { + this + ..writeln("import 'package:flutter/widgets.dart';") + ..writeln(); + } + + /// 写入类定义 + void _writeClass(GeneratorConfig config, IconFontMeta meta) { + final className = config.output.className; + final fontFamily = config.input.fontFamily; + final fontPackage = config.input.fontPackage; + final generateDocs = config.output.generateDocs; + + if (generateDocs) { + this + ..writeln('/// ${config.generator.name} 图标常量') + ..writeln('///') + ..writeln( + '/// 包含所有图标的常量定义,可以直接在 Flutter 的 Icon 组件中使用', + ) + ..writeln('///') + ..writeln('/// 示例:') + ..writeln('/// ```dart') + ..writeln('/// Icon($className.iconName, size: 24)') + ..writeln('/// ```'); + } + + this + ..writeln('class $className {') + ..writeln(' $className._();') + ..writeln(); + + if (!config.output.useConstConstructor) { + // 添加私有 _icon 工厂方法 + this + ..writeln(' // 统一的 IconData 构造方法') + ..writeln(' static const _icon = _IconDataFactory._();') + ..writeln(); + } + + // 生成图标常量 + for (final icon in meta.icons) { + _writeIconConstant( + icon, + generateDocs: generateDocs, + useConst: config.output.useConstConstructor, + fontFamily: fontFamily, + fontPackage: fontPackage, + ); + } + + this + ..writeln('}') + ..writeln(); + + if (!config.output.useConstConstructor) { + final hasPackage = fontPackage != null; + // 添加私有 IconData 工厂类 + this + ..writeln('/// 内部 IconData 工厂类') + ..writeln('class _IconDataFactory {') + ..writeln(' const _IconDataFactory._();') + ..writeln() + ..writeln(" static const String _fontFamily = '$fontFamily';") + ..writeln( + hasPackage + ? " static const String _fontPackage = '$fontPackage';" + : '', + ) + ..writeln() + ..writeln( + ' IconData call(int codePoint) => ' + "IconData(codePoint, fontFamily: _fontFamily${hasPackage ? ', fontPackage: _fontPackage' : ''});", + ) + ..writeln('}'); + } + } + + /// 写入单个图标常量 + void _writeIconConstant( + IconInfo icon, { + required bool generateDocs, + required bool useConst, + required String fontFamily, + String? fontPackage, + }) { + final constantName = StringHelper.toLegalDartName(icon.fontClass); + + if (generateDocs) { + writeln(' /// ${icon.name} (U+${icon.unicode.toUpperCase()})'); + } + + if (useConst) { + final packageParam = + fontPackage != null ? ", fontPackage: '$fontPackage'" : ''; + writeln( + " static const IconData $constantName = IconData(0x${icon.unicode}, fontFamily: '$fontFamily'$packageParam);", + ); + } else { + writeln( + ' static final IconData $constantName = _icon(0x${icon.unicode});', + ); + } + } +} diff --git a/lib/src/generator/icon_generator.dart b/lib/src/generator/icon_generator.dart new file mode 100644 index 0000000..c33f009 --- /dev/null +++ b/lib/src/generator/icon_generator.dart @@ -0,0 +1,119 @@ +import 'package:yx_icon_fonts/src/config/generator_config.dart'; +import 'package:yx_icon_fonts/src/generator/dart_code_builder.dart'; +import 'package:yx_icon_fonts/src/generator/icon_parser.dart'; +import 'package:yx_icon_fonts/src/utils/file_helper.dart'; +import 'package:yx_icon_fonts/src/utils/logger.dart'; + +/// 图标生成器 +/// +/// 协调整个代码生成流程 +class IconGenerator { + IconGenerator({ + required this.config, + this.workingDir, + }); + + /// 配置 + final GeneratorConfig config; + + /// 工作目录 + final String? workingDir; + + /// 生成的图标元数据 + IconFontMeta? _meta; + + /// 获取图标元数据 + IconFontMeta? get meta => _meta; + + /// 执行生成 + Future generate() async { + final stopwatch = Stopwatch()..start(); + + try { + // 1. 解析 JSON 文件 + appLogger.progress('正在解析图标配置文件...'); + final jsonPath = FileHelper.resolvePropertyPath( + config.input.jsonFile, + workingDir: workingDir, + ); + + if (!FileHelper.fileExists(jsonPath)) { + return GenerationResult( + success: false, + error: '图标配置文件不存在: $jsonPath', + ); + } + + _meta = IconParser.parseFile(jsonPath); + appLogger + ..stats('解析完成: 找到 ${_meta!.icons.length} 个图标') + // 2. 生成代码 + ..progress('正在生成 Dart 代码...'); + final builder = DartCodeBuilder( + config: config, + meta: _meta!, + ); + final code = builder.build(); + + // 3. 写入文件 + final outputPath = FileHelper.resolvePropertyPath( + config.output.filePath, + workingDir: workingDir, + ); + + appLogger.progress('正在写入文件: $outputPath'); + FileHelper.writeFile(outputPath, code); + + stopwatch.stop(); + + return GenerationResult( + success: true, + outputPath: outputPath, + iconCount: _meta!.icons.length, + duration: stopwatch.elapsed, + ); + } on Exception catch (e) { + stopwatch.stop(); + return GenerationResult( + success: false, + error: e.toString(), + duration: stopwatch.elapsed, + ); + } + } +} + +/// 生成结果 +class GenerationResult { + const GenerationResult({ + required this.success, + this.outputPath, + this.iconCount = 0, + this.error, + this.duration, + }); + + /// 是否成功 + final bool success; + + /// 输出文件路径 + final String? outputPath; + + /// 生成的图标数量 + final int iconCount; + + /// 错误信息 + final String? error; + + /// 执行时间 + final Duration? duration; + + @override + String toString() { + if (success) { + return 'GenerationResult(success: true, icons: $iconCount, ' + 'path: $outputPath)'; + } + return 'GenerationResult(success: false, error: $error)'; + } +} diff --git a/lib/src/generator/icon_parser.dart b/lib/src/generator/icon_parser.dart new file mode 100644 index 0000000..1cec88f --- /dev/null +++ b/lib/src/generator/icon_parser.dart @@ -0,0 +1,109 @@ +import 'package:yx_icon_fonts/src/utils/file_helper.dart'; + +/// 图标数据模型 +class IconInfo { + const IconInfo({ + required this.iconId, + required this.name, + required this.fontClass, + required this.unicode, + required this.unicodeDecimal, + }); + + /// 从 JSON Map 创建 + factory IconInfo.fromJson(Map json) { + return IconInfo( + iconId: json['icon_id']?.toString() ?? '', + name: json['name'] as String? ?? '', + fontClass: json['font_class'] as String? ?? '', + unicode: json['unicode'] as String? ?? '', + unicodeDecimal: json['unicode_decimal'] as int? ?? 0, + ); + } + + /// 图标 ID + final String iconId; + + /// 图标名称 + final String name; + + /// CSS 类名 + final String fontClass; + + /// Unicode 值(十六进制字符串) + final String unicode; + + /// Unicode 值(十进制) + final int unicodeDecimal; + + @override + String toString() => 'IconInfo(name: $name, unicode: $unicode)'; +} + +/// 图标字体元数据 +class IconFontMeta { + const IconFontMeta({ + required this.id, + required this.name, + required this.fontFamily, + required this.cssPrefix, + required this.description, + required this.icons, + }); + + /// 从 JSON Map 创建 + factory IconFontMeta.fromJson(Map json) { + final glyphs = json['glyphs'] as List? ?? []; + return IconFontMeta( + id: json['id']?.toString() ?? '', + name: json['name'] as String? ?? '', + fontFamily: json['font_family'] as String? ?? 'iconfont', + cssPrefix: json['css_prefix_text'] as String? ?? '', + description: json['description'] as String? ?? '', + icons: glyphs + .map((g) => IconInfo.fromJson(g as Map)) + .toList(), + ); + } + + /// 字体 ID + final String id; + + /// 字体名称 + final String name; + + /// 字体家族 + final String fontFamily; + + /// CSS 前缀 + final String cssPrefix; + + /// 描述 + final String description; + + /// 图标列表 + final List icons; + + @override + String toString() => 'IconFontMeta(name: $name, fontFamily: $fontFamily, ' + 'icons: ${icons.length})'; +} + +/// 图标解析器 +/// +/// 负责解析 iconfont.json 文件 +class IconParser { + /// 私有构造函数 + IconParser._(); + + /// 从文件解析 + static IconFontMeta parseFile(String jsonFilePath) { + final jsonData = FileHelper.readJsonFile(jsonFilePath); + return IconFontMeta.fromJson(jsonData); + } + + /// 从 Map 解析 + static IconFontMeta parseMap(Map jsonData) { + return IconFontMeta.fromJson(jsonData); + } +} diff --git a/lib/src/utils/file_helper.dart b/lib/src/utils/file_helper.dart new file mode 100644 index 0000000..8144a1e --- /dev/null +++ b/lib/src/utils/file_helper.dart @@ -0,0 +1,90 @@ +import 'dart:convert'; +import 'dart:io'; + +import 'package:path/path.dart' as path; + +/// 文件操作工具类 +class FileHelper { + /// 私有构造函数,防止实例化 + FileHelper._(); + + /// 读取 JSON 文件并解析 + static Map readJsonFile(String filePath) { + final file = File(filePath); + + if (!file.existsSync()) { + throw FileNotFoundException('文件不存在: $filePath'); + } + + try { + final content = file.readAsStringSync(); + return json.decode(content) as Map; + } on FormatException catch (e) { + throw FileParseException('JSON 解析错误: ${e.message}'); + } + } + + /// 写入文件内容 + /// + /// 如果目录不存在,会自动创建 + static void writeFile(String filePath, String content) { + final file = File(filePath); + final directory = file.parent; + + // 确保目录存在 + if (!directory.existsSync()) { + directory.createSync(recursive: true); + } + + file.writeAsStringSync(content); + } + + /// 检查文件是否存在 + static bool fileExists(String filePath) { + return File(filePath).existsSync(); + } + + /// 获取相对于工作目录的绝对路径 + static String resolvePropertyPath(String relativePath, {String? workingDir}) { + final directory = workingDir ?? Directory.current.path; + if (path.isAbsolute(relativePath)) { + return relativePath; + } + return path.join(directory, relativePath); + } + + /// 获取文件的目录路径 + static String getDirectoryPath(String filePath) { + return path.dirname(filePath); + } + + /// 获取文件名(不含扩展名) + static String getFileNameWithoutExtension(String filePath) { + return path.basenameWithoutExtension(filePath); + } + + /// 获取文件名(含扩展名) + static String getFileName(String filePath) { + return path.basename(filePath); + } +} + +/// 文件未找到异常 +class FileNotFoundException implements Exception { + FileNotFoundException(this.message); + + final String message; + + @override + String toString() => 'FileNotFoundException: $message'; +} + +/// 文件解析异常 +class FileParseException implements Exception { + FileParseException(this.message); + + final String message; + + @override + String toString() => 'FileParseException: $message'; +} diff --git a/lib/src/utils/logger.dart b/lib/src/utils/logger.dart new file mode 100644 index 0000000..5c3763e --- /dev/null +++ b/lib/src/utils/logger.dart @@ -0,0 +1,41 @@ +import 'dart:io'; + +import 'package:logging/logging.dart'; + +/// 应用程序日志器 +final Logger appLogger = Logger('YXIconFonts'); + +/// 设置日志 +void setupLogging({Level level = Level.INFO}) { + Logger.root.level = level; + Logger.root.onRecord.listen((record) { + if (record.level >= Level.SEVERE) { + stderr.writeln(record.message); + } else { + stdout.writeln(record.message); + } + }); +} + +/// 日志工具扩展 +extension LoggerExtension on Logger { + /// 成功消息(绿色) + void success(String message) { + info('✅ $message'); + } + + /// 进度消息 + void progress(String message) { + info('⏳ $message'); + } + + /// 完成消息 + void done(String message) { + info('✨ $message'); + } + + /// 统计消息 + void stats(String message) { + info('📊 $message'); + } +} diff --git a/lib/src/utils/string_helper.dart b/lib/src/utils/string_helper.dart new file mode 100644 index 0000000..b037b87 --- /dev/null +++ b/lib/src/utils/string_helper.dart @@ -0,0 +1,66 @@ +/// 字符串工具类 +/// +/// 提供字符串转换和格式化功能 +class StringHelper { + /// 私有构造函数,防止实例化 + StringHelper._(); + + /// 下划线转驼峰命名 + /// + /// 例如:icon_32_clean -> icon32Clean + static String toCamelCase(String input) { + // 预处理:将常见特殊字符转换 + final processedInput = input + .replaceAll('-', '_') + .replaceAll('+', 'plus') + .replaceAll('(', '') + .replaceAll(')', '') + .replaceAll(' ', ''); + + final parts = processedInput.split('_'); + if (parts.isEmpty) return input; + + final firstPart = parts[0]; + final remainingParts = parts.skip(1).map((part) { + if (part.isEmpty) return ''; + return part[0].toUpperCase() + part.substring(1); + }).join(); + + return firstPart + remainingParts; + } + + /// 转换为合法的 Dart 标识符 + /// + /// 确保首字符不是数字,只包含字母、数字和下划线 + static String toLegalDartName(String input) { + var name = toCamelCase(input); + + // 首字符为数字,前面加下划线 + if (name.isNotEmpty && RegExp('^[0-9]').hasMatch(name[0])) { + name = '_$name'; + } + + // 只保留字母、数字和下划线 + return name.replaceAll(RegExp('[^a-zA-Z0-9_]'), ''); + } + + /// 转换为 PascalCase(首字母大写的驼峰命名) + /// + /// 例如:icon_class -> IconClass + static String toPascalCase(String input) { + final camelCase = toCamelCase(input); + if (camelCase.isEmpty) return camelCase; + return camelCase[0].toUpperCase() + camelCase.substring(1); + } + + /// 格式化时间长度 + static String formatDuration(Duration duration) { + if (duration.inMinutes > 0) { + return '${duration.inMinutes}m ${duration.inSeconds % 60}s'; + } else if (duration.inSeconds > 0) { + return '${duration.inSeconds}.${(duration.inMilliseconds % 1000) ~/ 100}s'; + } else { + return '${duration.inMilliseconds}ms'; + } + } +} diff --git a/lib/src/yx_icon.dart b/lib/src/yx_icon.dart deleted file mode 100644 index dac3271..0000000 --- a/lib/src/yx_icon.dart +++ /dev/null @@ -1,118 +0,0 @@ -import 'package:flutter/foundation.dart'; -import 'package:flutter/rendering.dart'; -import 'package:flutter/widgets.dart'; - -/// 学习官OA系统图标组件 -/// -/// 专门为学习官OA系统图标设计的组件,处理图标渲染和布局 -/// 可以直接使用 Flutter 的 Icon 组件,也可以使用此专门的 YXIcon 组件 -class YXIcon extends StatelessWidget { - /// 创建图标组件 - /// - /// [icon] 图标数据,可以是 YXIconData 或任何 IconData - /// [size] 图标大小 - /// [color] 图标颜色 - /// [semanticLabel] 语义标签 - /// [textDirection] 文本方向 - /// [shadows] 阴影效果 - const YXIcon( - this.icon, { - super.key, - this.size, - this.color, - this.semanticLabel, - this.textDirection, - this.shadows, - }); - - /// 图标数据 - /// - /// 支持 YXIconData 和任何 IconData 类型 - final IconData? icon; - - /// 图标大小 - final double? size; - - /// 图标颜色 - final Color? color; - - /// 图标语义标签 - final String? semanticLabel; - - /// 文本方向 - final TextDirection? textDirection; - - /// 阴影效果 - final List? shadows; - - @override - Widget build(BuildContext context) { - assert(this.textDirection != null || debugCheckHasDirectionality(context)); - final TextDirection textDirection = this.textDirection ?? Directionality.of(context); - - final IconThemeData iconTheme = IconTheme.of(context); - - final double? iconSize = size ?? iconTheme.size; - final List? iconShadows = shadows ?? iconTheme.shadows; - - if (icon == null) { - return Semantics( - label: semanticLabel, - child: SizedBox(width: iconSize, height: iconSize), - ); - } - - final double iconOpacity = iconTheme.opacity ?? 1.0; - Color iconColor = color ?? iconTheme.color!; - if (iconOpacity != 1.0) { - iconColor = iconColor.withValues(alpha: iconColor.a * iconOpacity); - } - - Widget iconWidget = RichText( - overflow: TextOverflow.visible, - textDirection: textDirection, - text: TextSpan( - text: String.fromCharCode(icon!.codePoint), - style: TextStyle( - inherit: false, - color: iconColor, - fontSize: iconSize, - fontFamily: icon!.fontFamily, - package: icon!.fontPackage, - shadows: iconShadows, - ), - ), - ); - - if (icon!.matchTextDirection) { - switch (textDirection) { - case TextDirection.rtl: - iconWidget = Transform( - transform: Matrix4.identity()..scale(-1.0, 1.0, 1.0), - alignment: Alignment.center, - transformHitTests: false, - child: iconWidget, - ); - break; - case TextDirection.ltr: - break; - } - } - - return Semantics( - label: semanticLabel, - child: ExcludeSemantics( - child: iconWidget, - ), - ); - } - - @override - void debugFillProperties(DiagnosticPropertiesBuilder properties) { - super.debugFillProperties(properties); - properties.add(IconDataProperty('icon', icon, ifNull: '', showName: false)); - properties.add(DoubleProperty('size', size, defaultValue: null)); - properties.add(ColorProperty('color', color, defaultValue: null)); - properties.add(IterableProperty('shadows', shadows, defaultValue: null)); - } -} diff --git a/lib/src/yx_icon_data.dart b/lib/src/yx_icon_data.dart deleted file mode 100644 index 9ea9930..0000000 --- a/lib/src/yx_icon_data.dart +++ /dev/null @@ -1,15 +0,0 @@ -import 'package:flutter/widgets.dart'; - -/// 学习官OA系统图标数据类 -/// -/// 继承自 IconData,提供统一的图标数据接口 -class YXIconData extends IconData { - /// 创建图标数据实例 - /// - /// [codePoint] Unicode码点 - const YXIconData(super.codePoint) - : super( - fontFamily: 'iconfont', - fontPackage: 'yx_icon_fonts', - ); -} diff --git a/lib/src/yx_icon_fonts_data.dart b/lib/src/yx_icon_fonts_data.dart deleted file mode 100644 index 239717b..0000000 --- a/lib/src/yx_icon_fonts_data.dart +++ /dev/null @@ -1,392 +0,0 @@ -import 'package:flutter/widgets.dart'; -import 'yx_icon_data.dart'; - -/// 学习官OA系统图标字体数据 -/// -/// 包含所有图标的常量定义,可以直接在 Flutter 的 Icon 组件中使用 -/// 也可以使用专门的 YXIcon 组件 -class YXIconFonts { - // 私有构造函数,防止实例化 - YXIconFonts._(); - - /// icon_32_clean 图标 - /// - /// Unicode: e660 - static const IconData icon32Clean = YXIconData(0xe660); - - /// icon_36_viewhistory 图标 - /// - /// Unicode: e65f - static const IconData icon36Viewhistory = YXIconData(0xe65f); - - /// icon_36_revoke 图标 - /// - /// Unicode: e65e - static const IconData icon36Revoke = YXIconData(0xe65e); - - /// icon_30_edit 图标 - /// - /// Unicode: e65b - static const IconData icon30Edit = YXIconData(0xe65b); - - /// icon_24_copy2 图标 - /// - /// Unicode: e65c - static const IconData icon24Copy2 = YXIconData(0xe65c); - - /// icon_30_delete 图标 - /// - /// Unicode: e65d - static const IconData icon30Delete = YXIconData(0xe65d); - - /// icon_36_add 图标 - /// - /// Unicode: e65a - static const IconData icon36Add = YXIconData(0xe65a); - - /// icon_32_location 图标 - /// - /// Unicode: e659 - static const IconData icon32Location = YXIconData(0xe659); - - /// icon_24_mypoints 图标 - /// - /// Unicode: e657 - static const IconData icon24Mypoints = YXIconData(0xe657); - - /// icon_44_me_about 图标 - /// - /// Unicode: e656 - static const IconData icon44MeAbout = YXIconData(0xe656); - - /// icon_44_me_pointsmall 图标 - /// - /// Unicode: e658 - static const IconData icon44MePointsmall = YXIconData(0xe658); - - /// icon_44_search 图标 - /// - /// Unicode: e655 - static const IconData icon44Search = YXIconData(0xe655); - - /// icon_36_clear_people 图标 - /// - /// Unicode: e654 - static const IconData icon36ClearPeople = YXIconData(0xe654); - - /// icon_44_me_log_out 图标 - /// - /// Unicode: e652 - static const IconData icon44MeLogOut = YXIconData(0xe652); - - /// icon_44_me_switch 图标 - /// - /// Unicode: e653 - static const IconData icon44MeSwitch = YXIconData(0xe653); - - /// icon_44_wechat_share_white 图标 - /// - /// Unicode: e651 - static const IconData icon44WechatShareWhite = YXIconData(0xe651); - - /// icon_32_publish 图标 - /// - /// Unicode: e64e - static const IconData icon32Publish = YXIconData(0xe64e); - - /// icon_32_close 图标 - /// - /// Unicode: e64f - static const IconData icon32Close = YXIconData(0xe64f); - - /// icon_32_output 图标 - /// - /// Unicode: e650 - static const IconData icon32Output = YXIconData(0xe650); - - /// icon_24_download 图标 - /// - /// Unicode: e64d - static const IconData icon24Download = YXIconData(0xe64d); - - /// icon_voice_pause 图标 - /// - /// Unicode: e64b - static const IconData iconVoicePause = YXIconData(0xe64b); - - /// icon_voice 图标 - /// - /// Unicode: e64c - static const IconData iconVoice = YXIconData(0xe64c); - - /// icon_subtract 图标 - /// - /// Unicode: e64a - static const IconData iconSubtract = YXIconData(0xe64a); - - /// icon_44_edit 图标 - /// - /// Unicode: e648 - static const IconData icon44Edit = YXIconData(0xe648); - - /// icon_44_delete 图标 - /// - /// Unicode: e649 - static const IconData icon44Delete = YXIconData(0xe649); - - /// icon_44_cancel_account 图标 - /// - /// Unicode: e647 - static const IconData icon44CancelAccount = YXIconData(0xe647); - - /// icon_24_search 图标 - /// - /// Unicode: e646 - static const IconData icon24Search = YXIconData(0xe646); - - /// icon_24_up 图标 - /// - /// Unicode: e645 - static const IconData icon24Up = YXIconData(0xe645); - - /// icon_msg_contacts 图标 - /// - /// Unicode: e644 - static const IconData iconMsgContacts = YXIconData(0xe644); - - /// icon_msg_video 图标 - /// - /// Unicode: e63e - static const IconData iconMsgVideo = YXIconData(0xe63e); - - /// icon_msg_search 图标 - /// - /// Unicode: e63f - static const IconData iconMsgSearch = YXIconData(0xe63f); - - /// icon_msg_call 图标 - /// - /// Unicode: e640 - static const IconData iconMsgCall = YXIconData(0xe640); - - /// icon_msg_photo 图标 - /// - /// Unicode: e641 - static const IconData iconMsgPhoto = YXIconData(0xe641); - - /// icon_msg_img 图标 - /// - /// Unicode: e642 - static const IconData iconMsgImg = YXIconData(0xe642); - - /// icon_msg_file 图标 - /// - /// Unicode: e643 - static const IconData iconMsgFile = YXIconData(0xe643); - - /// icon_32_arrow_right 图标 - /// - /// Unicode: e61d - static const IconData icon32ArrowRight = YXIconData(0xe61d); - - /// icon_44_me_password 图标 - /// - /// Unicode: e639 - static const IconData icon44MePassword = YXIconData(0xe639); - - /// icon_44_me_help 图标 - /// - /// Unicode: e63a - static const IconData icon44MeHelp = YXIconData(0xe63a); - - /// icon_24_minus 图标 - /// - /// Unicode: e63b - static const IconData icon24Minus = YXIconData(0xe63b); - - /// icon_36_team 图标 - /// - /// Unicode: e63c - static const IconData icon36Team = YXIconData(0xe63c); - - /// icon_32_filter 图标 - /// - /// Unicode: e63d - static const IconData icon32Filter = YXIconData(0xe63d); - - /// icon_44_quit_lite 图标 - /// - /// Unicode: e631 - static const IconData icon44QuitLite = YXIconData(0xe631); - - /// icon_44_me_version 图标 - /// - /// Unicode: e632 - static const IconData icon44MeVersion = YXIconData(0xe632); - - /// icon_36_question 图标 - /// - /// Unicode: e633 - static const IconData icon36Question = YXIconData(0xe633); - - /// icon_44_me_privacy 图标 - /// - /// Unicode: e634 - static const IconData icon44MePrivacy = YXIconData(0xe634); - - /// icon_44_more2 图标 - /// - /// Unicode: e635 - static const IconData icon44More2 = YXIconData(0xe635); - - /// icon_36_only_see 图标 - /// - /// Unicode: e636 - static const IconData icon36OnlySee = YXIconData(0xe636); - - /// icon_44_me_user 图标 - /// - /// Unicode: e637 - static const IconData icon44MeUser = YXIconData(0xe637); - - /// icon_44_me_safe 图标 - /// - /// Unicode: e62c - static const IconData icon44MeSafe = YXIconData(0xe62c); - - /// icon_44_me_phone 图标 - /// - /// Unicode: e62d - static const IconData icon44MePhone = YXIconData(0xe62d); - - /// icon_24_copy 图标 - /// - /// Unicode: e62e - static const IconData icon24Copy = YXIconData(0xe62e); - - /// icon_32_quit 图标 - /// - /// Unicode: e630 - static const IconData icon32Quit = YXIconData(0xe630); - - /// icon_44_calendar 图标 - /// - /// Unicode: e626 - static const IconData icon44Calendar = YXIconData(0xe626); - - /// icon_32_student 图标 - /// - /// Unicode: e627 - static const IconData icon32Student = YXIconData(0xe627); - - /// icon_36_delete 图标 - /// - /// Unicode: e628 - static const IconData icon36Delete = YXIconData(0xe628); - - /// icon_44_more 图标 - /// - /// Unicode: e629 - static const IconData icon44More = YXIconData(0xe629); - - /// icon_44_arrow_left 图标 - /// - /// Unicode: e62a - static const IconData icon44ArrowLeft = YXIconData(0xe62a); - - /// icon_36_edit_line 图标 - /// - /// Unicode: e62b - static const IconData icon36EditLine = YXIconData(0xe62b); - - /// icon_44_arrow_right 图标 - /// - /// Unicode: e622 - static const IconData icon44ArrowRight = YXIconData(0xe622); - - /// icon_24_delete 图标 - /// - /// Unicode: e623 - static const IconData icon24Delete = YXIconData(0xe623); - - /// icon_56_msg_add 图标 - /// - /// Unicode: e624 - static const IconData icon56MsgAdd = YXIconData(0xe624); - - /// icon_56_msg_face 图标 - /// - /// Unicode: e625 - static const IconData icon56MsgFace = YXIconData(0xe625); - - /// icon_56_msg_voice 图标 - /// - /// Unicode: e616 - static const IconData icon56MsgVoice = YXIconData(0xe616); - - /// icon_44_share 图标 - /// - /// Unicode: e615 - static const IconData icon44Share = YXIconData(0xe615); - - /// icon_36_hint 图标 - /// - /// Unicode: e613 - static const IconData icon36Hint = YXIconData(0xe613); - - /// icon_24_add 图标 - /// - /// Unicode: e617 - static const IconData icon24Add = YXIconData(0xe617); - - /// icon_24_plus 图标 - /// - /// Unicode: e618 - static const IconData icon24Plus = YXIconData(0xe618); - - /// icon_44_keyboard 图标 - /// - /// Unicode: e614 - static const IconData icon44Keyboard = YXIconData(0xe614); - - /// icon_44_quit 图标 - /// - /// Unicode: e619 - static const IconData icon44Quit = YXIconData(0xe619); - - /// icon_44_add 图标 - /// - /// Unicode: e61a - static const IconData icon44Add = YXIconData(0xe61a); - - /// icon_24_switch 图标 - /// - /// Unicode: e61b - static const IconData icon24Switch = YXIconData(0xe61b); - - /// icon_44_arrow_down_line 图标 - /// - /// Unicode: e61c - static const IconData icon44ArrowDownLine = YXIconData(0xe61c); - - /// icon_24_arrow_right 图标 - /// - /// Unicode: e61e - static const IconData icon24ArrowRight = YXIconData(0xe61e); - - /// icon_32_add 图标 - /// - /// Unicode: e61f - static const IconData icon32Add = YXIconData(0xe61f); - - /// icon_24_edit 图标 - /// - /// Unicode: e620 - static const IconData icon24Edit = YXIconData(0xe620); - - /// icon_24_arrow_down 图标 - /// - /// Unicode: e621 - static const IconData icon24ArrowDown = YXIconData(0xe621); - -} diff --git a/lib/yx_icon_fonts.dart b/lib/yx_icon_fonts.dart index 9e7ef30..e4286ea 100644 --- a/lib/yx_icon_fonts.dart +++ b/lib/yx_icon_fonts.dart @@ -1,26 +1,48 @@ +/// YX Icon Fonts - 图标字体代码生成器 +/// +/// 从 iconfont.json 生成 Flutter IconData 常量 +/// +/// ## 使用方法 +/// +/// 1. 添加到 dev_dependencies: +/// ```yaml +/// dev_dependencies: +/// yx_icon_fonts: +/// git: +/// url: https://gitea.23544.com/wangyang/yx_icon_fonts_flutter +/// ``` +/// +/// 2. 初始化配置文件: +/// ```bash +/// dart run yx_icon_fonts init +/// ``` +/// +/// 3. 编辑配置文件 `icon_generator_config.yaml` +/// +/// 4. 生成代码: +/// ```bash +/// dart run yx_icon_fonts generate +/// ``` library yx_icon_fonts; -// 导出所有公共组件和类 -export 'src/yx_icon.dart'; -export 'src/yx_icon_data.dart'; -export 'src/yx_icon_fonts_data.dart'; +// CLI +export 'src/cli/icon_fonts_cli.dart'; -/// 学习官OA系统图标字体库 -/// -/// 提供学习官OA系统的图标字体支持,包含所有图标常量和组件 -/// -/// ## 使用方法 -/// -/// ```dart -/// // 使用 YXIcon 组件 -/// YXIcon(YXIconFonts.iconMsgContacts, size: 24, color: Colors.blue) -/// -/// // 使用 Flutter 原生 Icon 组件 -/// Icon(YXIconFonts.iconMsgContacts, size: 24, color: Colors.blue) -/// -/// // 在 IconButton 中使用 -/// IconButton( -/// icon: YXIcon(YXIconFonts.iconMsgContacts), -/// onPressed: () {}, -/// ) -/// ``` +// 命令 +export 'src/commands/base_command.dart'; +export 'src/commands/generate_command.dart'; +export 'src/commands/init_command.dart'; + +// 配置 +export 'src/config/config_loader.dart'; +export 'src/config/generator_config.dart'; + +// 生成器 +export 'src/generator/dart_code_builder.dart'; +export 'src/generator/icon_generator.dart'; +export 'src/generator/icon_parser.dart'; + +// 工具 +export 'src/utils/file_helper.dart'; +export 'src/utils/logger.dart'; +export 'src/utils/string_helper.dart'; diff --git a/pubspec.lock b/pubspec.lock index 36aae32..6d7f5ce 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1,6 +1,30 @@ # Generated by pub # See https://dart.dev/tools/pub/glossary#lockfile packages: + _fe_analyzer_shared: + dependency: transitive + description: + name: _fe_analyzer_shared + sha256: "5b7468c326d2f8a4f630056404ca0d291ade42918f4a3c6233618e724f39da8e" + url: "https://pub.flutter-io.cn" + source: hosted + version: "92.0.0" + analyzer: + dependency: transitive + description: + name: analyzer + sha256: "70e4b1ef8003c64793a9e268a551a82869a8a96f39deb73dea28084b0e8bf75e" + url: "https://pub.flutter-io.cn" + source: hosted + version: "9.0.0" + args: + dependency: transitive + description: + name: args + sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04 + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.7.0" async: dependency: transitive description: @@ -17,22 +41,14 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "2.1.2" - characters: + cli_config: dependency: transitive description: - name: characters - sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803 + name: cli_config + sha256: ac20a183a07002b700f0c25e61b7ee46b23c309d76ab7b7640a028f18e4d99ec url: "https://pub.flutter-io.cn" source: hosted - version: "1.4.0" - clock: - dependency: transitive - description: - name: clock - sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b - url: "https://pub.flutter-io.cn" - source: hosted - version: "1.1.2" + version: "0.2.0" collection: dependency: transitive description: @@ -41,64 +57,86 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "1.19.1" - fake_async: + convert: dependency: transitive description: - name: fake_async - sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44" + name: convert + sha256: b30acd5944035672bc15c6b7a8b47d773e41e2f17de064350988c5d02adb1c68 url: "https://pub.flutter-io.cn" source: hosted - version: "1.3.3" - flutter: + version: "3.1.2" + coverage: + dependency: transitive + description: + name: coverage + sha256: "5da775aa218eaf2151c721b16c01c7676fbfdd99cebba2bf64e8b807a28ff94d" + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.15.0" + crypto: + dependency: transitive + description: + name: crypto + sha256: c8ea0233063ba03258fbcf2ca4d6dadfefe14f02fab57702265467a19f27fadf + url: "https://pub.flutter-io.cn" + source: hosted + version: "3.0.7" + file: + dependency: transitive + description: + name: file + sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4 + url: "https://pub.flutter-io.cn" + source: hosted + version: "7.0.1" + frontend_server_client: + dependency: transitive + description: + name: frontend_server_client + sha256: f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694 + url: "https://pub.flutter-io.cn" + source: hosted + version: "4.0.0" + glob: + dependency: transitive + description: + name: glob + sha256: c3f1ee72c96f8f78935e18aa8cecced9ab132419e8625dc187e1c2408efc20de + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.1.3" + http_multi_server: + dependency: transitive + description: + name: http_multi_server + sha256: aa6199f908078bb1c5efb8d8638d4ae191aac11b311132c3ef48ce352fb52ef8 + url: "https://pub.flutter-io.cn" + source: hosted + version: "3.2.2" + http_parser: + dependency: transitive + description: + name: http_parser + sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571" + url: "https://pub.flutter-io.cn" + source: hosted + version: "4.1.2" + io: + dependency: transitive + description: + name: io + sha256: dfd5a80599cf0165756e3181807ed3e77daf6dd4137caaad72d0b7931597650b + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.0.5" + logging: dependency: "direct main" - description: flutter - source: sdk - version: "0.0.0" - flutter_lints: - dependency: "direct dev" description: - name: flutter_lints - sha256: "9e8c3858111da373efc5aa341de011d9bd23e2c5c5e0c62bccf32438e192d7b1" + name: logging + sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61 url: "https://pub.flutter-io.cn" source: hosted - version: "3.0.2" - flutter_test: - dependency: "direct dev" - description: flutter - source: sdk - version: "0.0.0" - leak_tracker: - dependency: transitive - description: - name: leak_tracker - sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de" - url: "https://pub.flutter-io.cn" - source: hosted - version: "11.0.2" - leak_tracker_flutter_testing: - dependency: transitive - description: - name: leak_tracker_flutter_testing - sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1" - url: "https://pub.flutter-io.cn" - source: hosted - version: "3.0.10" - leak_tracker_testing: - dependency: transitive - description: - name: leak_tracker_testing - sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1" - url: "https://pub.flutter-io.cn" - source: hosted - version: "3.0.2" - lints: - dependency: transitive - description: - name: lints - sha256: cbf8d4b858bb0134ef3ef87841abdf8d63bfc255c266b7bf6b39daa1085c4290 - url: "https://pub.flutter-io.cn" - source: hosted - version: "3.0.0" + version: "1.3.0" matcher: dependency: transitive description: @@ -107,14 +145,6 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "0.12.17" - material_color_utilities: - dependency: transitive - description: - name: material_color_utilities - sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec - url: "https://pub.flutter-io.cn" - source: hosted - version: "0.11.1" meta: dependency: transitive description: @@ -123,19 +153,102 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "1.16.0" - path: + mime: dependency: transitive + description: + name: mime + sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.0.0" + node_preamble: + dependency: transitive + description: + name: node_preamble + sha256: "6e7eac89047ab8a8d26cf16127b5ed26de65209847630400f9aefd7cd5c730db" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.0.2" + package_config: + dependency: transitive + description: + name: package_config + sha256: f096c55ebb7deb7e384101542bfba8c52696c1b56fca2eb62827989ef2353bbc + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.2.0" + path: + dependency: "direct main" description: name: path sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" url: "https://pub.flutter-io.cn" source: hosted version: "1.9.1" - sky_engine: + pool: dependency: transitive - description: flutter - source: sdk - version: "0.0.0" + description: + name: pool + sha256: "978783255c543aa3586a1b3c21f6e9d720eb315376a915872c61ef8b5c20177d" + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.5.2" + pub_semver: + dependency: transitive + description: + name: pub_semver + sha256: "5bfcf68ca79ef689f8990d1160781b4bad40a3bd5e5218ad4076ddb7f4081585" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.2.0" + shelf: + dependency: transitive + description: + name: shelf + sha256: e7dd780a7ffb623c57850b33f43309312fc863fb6aa3d276a754bb299839ef12 + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.4.2" + shelf_packages_handler: + dependency: transitive + description: + name: shelf_packages_handler + sha256: "89f967eca29607c933ba9571d838be31d67f53f6e4ee15147d5dc2934fee1b1e" + url: "https://pub.flutter-io.cn" + source: hosted + version: "3.0.2" + shelf_static: + dependency: transitive + description: + name: shelf_static + sha256: c87c3875f91262785dade62d135760c2c69cb217ac759485334c5857ad89f6e3 + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.1.3" + shelf_web_socket: + dependency: transitive + description: + name: shelf_web_socket + sha256: "3632775c8e90d6c9712f883e633716432a27758216dfb61bd86a8321c0580925" + url: "https://pub.flutter-io.cn" + source: hosted + version: "3.0.0" + source_map_stack_trace: + dependency: transitive + description: + name: source_map_stack_trace + sha256: c0713a43e323c3302c2abe2a1cc89aa057a387101ebd280371d6a6c9fa68516b + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.1.2" + source_maps: + dependency: transitive + description: + name: source_maps + sha256: "190222579a448b03896e0ca6eca5998fa810fda630c1d65e2f78b3f638f54812" + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.10.13" source_span: dependency: transitive description: @@ -176,22 +289,46 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "1.2.2" + test: + dependency: "direct dev" + description: + name: test + sha256: "77cc98ea27006c84e71a7356cf3daf9ddbde2d91d84f77dbfe64cf0e4d9611ae" + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.28.0" test_api: dependency: transitive description: name: test_api - sha256: "522f00f556e73044315fa4585ec3270f1808a4b186c936e612cab0b565ff1e00" + sha256: "19a78f63e83d3a61f00826d09bc2f60e191bf3504183c001262be6ac75589fb8" url: "https://pub.flutter-io.cn" source: hosted - version: "0.7.6" - vector_math: + version: "0.7.8" + test_core: dependency: transitive description: - name: vector_math - sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b + name: test_core + sha256: f1072617a6657e5fc09662e721307f7fb009b4ed89b19f47175d11d5254a62d4 url: "https://pub.flutter-io.cn" source: hosted - version: "2.2.0" + version: "0.6.14" + typed_data: + dependency: transitive + description: + name: typed_data + sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.4.0" + very_good_analysis: + dependency: "direct dev" + description: + name: very_good_analysis + sha256: "1fb637c0022034b1f19ea2acb42a3603cbd8314a470646a59a2fb01f5f3a8629" + url: "https://pub.flutter-io.cn" + source: hosted + version: "6.0.0" vm_service: dependency: transitive description: @@ -200,6 +337,53 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "15.0.2" + watcher: + dependency: transitive + description: + name: watcher + sha256: f52385d4f73589977c80797e60fe51014f7f2b957b5e9a62c3f6ada439889249 + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.2.0" + web: + dependency: transitive + description: + name: web + sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a" + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.1.1" + web_socket: + dependency: transitive + description: + name: web_socket + sha256: "34d64019aa8e36bf9842ac014bb5d2f5586ca73df5e4d9bf5c936975cae6982c" + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.0.1" + web_socket_channel: + dependency: transitive + description: + name: web_socket_channel + sha256: d645757fb0f4773d602444000a8131ff5d48c9e47adfe9772652dd1a4f2d45c8 + url: "https://pub.flutter-io.cn" + source: hosted + version: "3.0.3" + webkit_inspection_protocol: + dependency: transitive + description: + name: webkit_inspection_protocol + sha256: "87d3f2333bb240704cd3f1c6b5b7acd8a10e7f0bc28c28dcf14e782014f4a572" + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.2.1" + yaml: + dependency: "direct main" + description: + name: yaml + sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce + url: "https://pub.flutter-io.cn" + source: hosted + version: "3.1.3" sdks: - dart: ">=3.8.0-0 <4.0.0" - flutter: ">=3.18.0-18.0.pre.54" + dart: ">=3.9.0 <4.0.0" diff --git a/pubspec.yaml b/pubspec.yaml index 245654e..4bb2692 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,23 +1,25 @@ name: yx_icon_fonts -description: 学习官OA系统图标字体库,基于iconfont.ttf和iconfont.json生成 -version: 1.0.7 +description: 图标字体代码生成器 - 从 iconfont.json 生成 Flutter IconData 常量 +version: 2.1.0 homepage: https://gitea.23544.com/wangyang/yx_icon_fonts_flutter environment: sdk: '>=3.0.0 <4.0.0' - flutter: ">=3.0.0" + +# 可执行命令配置 +# 使用方式:dart run yx_icon_fonts generate +# 或者使用别名:dart run yx_icon_fonts:yx_icon_fonts +executables: + yx_icon_fonts: main dependencies: - flutter: - sdk: flutter + # 日志 + logging: ^1.3.0 + # 路径处理 + path: ^1.9.1 + # YAML 解析 + yaml: ^3.1.3 dev_dependencies: - flutter_test: - sdk: flutter - flutter_lints: ^3.0.0 - -flutter: - fonts: - - family: iconfont - fonts: - - asset: fonts/iconfont.ttf \ No newline at end of file + test: ^1.24.0 + very_good_analysis: ^6.0.0 \ No newline at end of file diff --git a/scripts/generate_example.dart b/scripts/generate_example.dart deleted file mode 100644 index bb13ccc..0000000 --- a/scripts/generate_example.dart +++ /dev/null @@ -1,96 +0,0 @@ -import 'dart:io'; -import 'utils.dart'; - -/// 自动生成示例内容的脚本 -/// -/// 使用方法: -/// dart scripts/generate_example.dart -void main() { - final iconData = readIconFontJson(); - final glyphs = getGlyphs(iconData); - // 读取已定义的 YXIconFonts 静态常量 - final definedIcons = getDefinedIconNames(); - - // 生成 icons.dart - _generateIconsFile(glyphs, definedIcons); - - print('✅ 成功生成示例应用文件'); - print('📁 生成的文件:'); - print(' - example/lib/icons.dart'); -} - -/// 生成 icons.dart 文件 -void _generateIconsFile(List glyphs, Set definedIcons) { - final buffer = StringBuffer(); - buffer.writeln("import 'package:yx_icon_fonts/yx_icon_fonts.dart';"); - buffer.writeln("import 'package:yx_icon_fonts_example/example_icon.dart';"); - buffer.writeln(); - buffer.writeln("// 此文件由脚本自动生成!"); - buffer.writeln(); - buffer.writeln("final icons = ["); - - // 按类别分组图标 - final categories = >>{}; - for (final glyph in glyphs) { - final name = glyph['font_class'] as String; - final unicodeDecimal = glyph['unicode_decimal'] as int; - String category = '其他图标'; - if (name.contains('msg')) { - category = '消息相关图标'; - } else if (name.contains('arrow')) { - category = '箭头相关图标'; - } else if (name.contains('edit')) { - category = '编辑相关图标'; - } else if (name.contains('me_')) { - category = '个人中心相关图标'; - } else if (name.contains('team') || name.contains('student')) { - category = '团队和用户相关图标'; - } else if (name.contains('filter') || name.contains('question') || name.contains('onlysee') || name.contains('hint') || name.contains('switch')) { - category = '功能图标'; - } else if (name.contains('delete')) { - category = '删除相关图标'; - } else if (name.contains('more')) { - category = '更多和菜单相关图标'; - } else if (name.contains('quit')) { - category = '退出相关图标'; - } else if (name.contains('copy')) { - category = '复制相关图标'; - } else if (name.contains('calendar')) { - category = '日历相关图标'; - } else if (name.contains('share')) { - category = '分享相关图标'; - } else if (name.contains('add') || name.contains('+')) { - category = '添加相关图标'; - } else if (name.contains('keyboard')) { - category = '键盘相关图标'; - } else if (name.contains('-')) { - category = '特殊字符图标'; - } - categories.putIfAbsent(category, () => []); - categories[category]!.add({ - 'name': name, - 'unicodeDecimal': unicodeDecimal, - }); - } - - // 生成图标列表 - for (final category in categories.keys) { - buffer.writeln(" // $category"); - for (final icon in categories[category]!) { - final name = icon['name'] as String; - final camelCaseName = toLegalDartName(name); - if (!definedIcons.contains(camelCaseName)) continue; // 跳过未定义的 - final displayName = _getDisplayName(name); - buffer.writeln(" ExampleIcon(YXIconFonts.$camelCaseName, '$displayName'),"); - } - buffer.writeln(); - } - buffer.writeln("];"); - final file = File('example/lib/icons.dart'); - file.writeAsStringSync(buffer.toString()); -} - -/// 获取展示用名称 -String _getDisplayName(String name) { - return name.replaceAll('_', ' '); -} diff --git a/scripts/generate_icons.dart b/scripts/generate_icons.dart deleted file mode 100644 index 9a2f500..0000000 --- a/scripts/generate_icons.dart +++ /dev/null @@ -1,48 +0,0 @@ -import 'dart:io'; -import 'utils.dart'; - -/// 自动生成图标数据文件 -/// -/// 从 iconfont.json 文件读取图标信息,生成 Dart 代码 -void main() { - final iconData = readIconFontJson(); - final icons = getGlyphs(iconData); - - final StringBuffer buffer = StringBuffer(); - - // 添加文件头部 - buffer.writeln("import 'package:flutter/widgets.dart';"); - buffer.writeln("import 'yx_icon_data.dart';"); - buffer.writeln(); - buffer.writeln("/// 学习官OA系统图标字体数据"); - buffer.writeln("/// "); - buffer.writeln("/// 包含所有图标的常量定义,可以直接在 Flutter 的 Icon 组件中使用"); - buffer.writeln("/// 也可以使用专门的 YXIcon 组件"); - buffer.writeln("class YXIconFonts {"); - buffer.writeln(" // 私有构造函数,防止实例化"); - buffer.writeln(" YXIconFonts._();"); - buffer.writeln(); - - // 生成图标常量 - for (final Map icon in icons) { - final String name = icon['name'] as String; - final String unicode = icon['unicode'] as String; - // 转换为驼峰命名并处理特殊字符 - final String camelCaseName = toCamelCase(name); - // 添加注释 - buffer.writeln(" /// $name 图标"); - buffer.writeln(" /// "); - buffer.writeln(" /// Unicode: $unicode"); - buffer.writeln(" static const IconData $camelCaseName = YXIconData(0x$unicode);"); - buffer.writeln(); - } - - buffer.writeln("}"); - - // 写入文件 - final File outputFile = File('lib/src/yx_icon_fonts_data.dart'); - outputFile.writeAsStringSync(buffer.toString()); - - print('✅ 图标数据文件生成成功: [32m${outputFile.path} [0m'); - print('📊 共生成 ${icons.length} 个图标常量'); -} diff --git a/scripts/generate_wrapper.dart b/scripts/generate_wrapper.dart deleted file mode 100644 index 5b87fba..0000000 --- a/scripts/generate_wrapper.dart +++ /dev/null @@ -1,144 +0,0 @@ -import 'dart:io'; -import 'utils.dart'; - -/// 自动生成外部使用的图标封装文件 -/// -/// 从 iconfont.json 文件读取图标信息,生成 FontIcons 封装类 -/// 生成位置:项目根目录的 icons.dart -void main() { - final iconData = readIconFontJson(); - final glyphs = getGlyphs(iconData); - final definedIcons = getDefinedIconNames(); - - _generateWrapperFile(glyphs, definedIcons); - - print('✅ 外部图标封装文件生成成功: \x1B[32mgenerate/icons.dart\x1B[0m'); - print('📊 共生成 ${definedIcons.length} 个图标常量'); -} - -/// 生成 icons.dart 文件 -void _generateWrapperFile(List glyphs, Set definedIcons) { - final buffer = StringBuffer(); - - // 文件头部 - buffer.writeln("// ignore_for_file: constant_identifier_names"); - buffer.writeln(); - buffer.writeln("import 'package:flutter/material.dart';"); - buffer.writeln("import 'package:yx_icon_fonts/yx_icon_fonts.dart';"); - buffer.writeln(); - buffer.writeln("class FontIcons {"); - buffer.writeln(" // 私有构造函数,防止实例化"); - buffer.writeln(" FontIcons._();"); - buffer.writeln(); - - // 按类别分组图标 - final categories = _categorizeIcons(glyphs, definedIcons); - - // 定义类别显示顺序 - final categoryOrder = [ - '其他图标', - '编辑相关图标', - '删除相关图标', - '个人中心相关图标', - '消息相关图标', - '箭头相关图标', - '特殊字符图标', - '团队和用户相关图标', - '功能图标', - '退出相关图标', - '更多和菜单相关图标', - '复制相关图标', - '日历相关图标', - '分享相关图标', - '添加相关图标', - '键盘相关图标', - ]; - - // 按顺序生成图标 - for (final category in categoryOrder) { - if (categories.containsKey(category) && categories[category]!.isNotEmpty) { - buffer.writeln(" // $category"); - for (final iconName in categories[category]!) { - buffer.writeln(" static const IconData $iconName = YXIconFonts.$iconName;"); - } - buffer.writeln(); - } - } - - buffer.writeln("}"); - - // 确保 generate 目录存在 - final dir = Directory('generate'); - if (!dir.existsSync()) { - dir.createSync(recursive: true); - } - - // 写入文件 - final file = File('generate/icons.dart'); - file.writeAsStringSync(buffer.toString()); -} - -/// 将图标按类别分组 -Map> _categorizeIcons(List glyphs, Set definedIcons) { - final categories = >{}; - - for (final glyph in glyphs) { - final name = glyph['font_class'] as String; - final camelCaseName = toLegalDartName(name); - - // 跳过未定义的图标 - if (!definedIcons.contains(camelCaseName)) continue; - - // 确定类别 - String category = _getCategory(name); - - categories.putIfAbsent(category, () => []); - categories[category]!.add(camelCaseName); - } - - return categories; -} - -/// 根据图标名称确定类别 -String _getCategory(String name) { - final lowerName = name.toLowerCase(); - - if (lowerName.contains('edit')) { - return '编辑相关图标'; - } else if (lowerName.contains('delete')) { - return '删除相关图标'; - } else if (lowerName.contains('me_')) { - return '个人中心相关图标'; - } else if (lowerName.contains('msg')) { - return '消息相关图标'; - } else if (lowerName.contains('arrow')) { - return '箭头相关图标'; - } else if (lowerName == '24_-' || lowerName.contains('subtract')) { - return '特殊字符图标'; - } else if (lowerName.contains('team') || lowerName.contains('student')) { - return '团队和用户相关图标'; - } else if (lowerName.contains('filter') || - lowerName.contains('question') || - lowerName.contains('onlysee') || - lowerName.contains('hint') || - lowerName.contains('switch')) { - return '功能图标'; - } else if (lowerName.contains('quit')) { - return '退出相关图标'; - } else if (lowerName.contains('more')) { - return '更多和菜单相关图标'; - } else if (lowerName.contains('copy')) { - return '复制相关图标'; - } else if (lowerName.contains('calendar')) { - return '日历相关图标'; - } else if (lowerName.contains('share')) { - return '分享相关图标'; - } else if (lowerName.contains('add') || lowerName.contains('+') || lowerName.contains('plus')) { - return '添加相关图标'; - } else if (lowerName.contains('keyboard')) { - return '键盘相关图标'; - } else { - return '其他图标'; - } -} - diff --git a/scripts/utils.dart b/scripts/utils.dart deleted file mode 100644 index 96d1abc..0000000 --- a/scripts/utils.dart +++ /dev/null @@ -1,48 +0,0 @@ -import 'dart:convert'; -import 'dart:io'; - -/// 读取 iconfont.json 文件,返回解析后的 Map -Map readIconFontJson([String path = 'iconfont.json']) { - final file = File(path); - if (!file.existsSync()) { - throw Exception('未找到 $path 文件'); - } - final content = file.readAsStringSync(); - return json.decode(content) as Map; -} - -/// 获取 glyphs 列表 -List getGlyphs(Map iconData) { - return iconData['glyphs'] as List; -} - -/// 下划线转驼峰命名,并处理常见特殊字符,保证 Dart 标识符合法 -String toCamelCase(String input) { - String processedInput = input.replaceAll('-', '_').replaceAll('+', 'plus').replaceAll('(', '').replaceAll(')', '').replaceAll(' ', ''); - final List parts = processedInput.split('_'); - if (parts.isEmpty) return input; - final String firstPart = parts[0]; - final String remainingParts = parts.skip(1).map((part) => part.isNotEmpty ? part[0].toUpperCase() + part.substring(1) : '').join(); - return firstPart + remainingParts; -} - -/// Dart 标识符合法化(首字符不能为数字,不能有特殊符号) -String toLegalDartName(String input) { - String name = toCamelCase(input); - // 首字符为数字,前面加下划线 - if (name.isNotEmpty && RegExp(r'^[0-9]').hasMatch(name[0])) { - name = '_$name'; - } - // 只保留字母、数字和下划线 - name = name.replaceAll(RegExp(r'[^a-zA-Z0-9_]'), ''); - return name; -} - -/// 读取 Dart 文件中已定义的静态常量名(如 static const IconData xxx = ...) -Set getDefinedIconNames([String path = 'lib/src/yx_icon_fonts_data.dart']) { - final file = File(path); - if (!file.existsSync()) return {}; - final content = file.readAsStringSync(); - final reg = RegExp(r'static const IconData (\w+) ='); - return reg.allMatches(content).map((m) => m.group(1)!).toSet(); -} diff --git a/test/yx_icon_fonts_test.dart b/test/yx_icon_fonts_test.dart index 318dc02..8b36e83 100644 --- a/test/yx_icon_fonts_test.dart +++ b/test/yx_icon_fonts_test.dart @@ -1,29 +1,82 @@ -import 'package:flutter_test/flutter_test.dart'; +import 'package:test/test.dart'; import 'package:yx_icon_fonts/yx_icon_fonts.dart'; void main() { - group('YXIconFonts', () { - test('should have valid icon data', () { - // 测试图标常量是否存在且有效 - expect(YXIconFonts.iconMsgContacts, isNotNull); - expect(YXIconFonts.iconMsgContacts.codePoint, isA()); - expect(YXIconFonts.iconMsgContacts.fontFamily, equals('iconfont')); + group('StringHelper', () { + test('toCamelCase converts underscore to camelCase', () { + expect(StringHelper.toCamelCase('icon_32_clean'), equals('icon32Clean')); + expect( + StringHelper.toCamelCase('icon_msg_contacts'), + equals('iconMsgContacts'), + ); }); - test('should create YXIcon widget', () { - // 测试 YXIcon 组件创建 - final icon = YXIcon(YXIconFonts.iconMsgContacts); - expect(icon, isNotNull); - expect(icon.icon, equals(YXIconFonts.iconMsgContacts)); + test('toLegalDartName handles numeric prefix', () { + expect(StringHelper.toLegalDartName('32_icon'), equals('_32Icon')); }); - test('should have multiple icons defined', () { - // 测试多个图标常量 - expect(YXIconFonts.iconMsgContacts, isNotNull); - expect(YXIconFonts.iconMsgSearch, isNotNull); - expect(YXIconFonts.icon44Edit, isNotNull); - expect(YXIconFonts.icon44Delete, isNotNull); - expect(YXIconFonts.icon44Add, isNotNull); + test('toPascalCase converts to PascalCase', () { + expect(StringHelper.toPascalCase('icon_class'), equals('IconClass')); + }); + }); + + group('GeneratorConfig', () { + test('fromMap creates config from map', () { + final map = { + 'generator': {'name': 'Test'}, + 'input': { + 'font_file': 'test.ttf', + 'json_file': 'test.json', + 'font_family': 'test', + }, + 'output': { + 'file_path': 'lib/icons.dart', + 'class_name': 'TestIcons', + }, + }; + + final config = GeneratorConfig.fromMap(map); + + expect(config.generator.name, equals('Test')); + expect(config.input.fontFile, equals('test.ttf')); + expect(config.input.jsonFile, equals('test.json')); + expect(config.output.filePath, equals('lib/icons.dart')); + expect(config.output.className, equals('TestIcons')); + }); + + test('defaults creates default config', () { + final config = GeneratorConfig.defaults(); + + expect(config.input.fontFamily, equals('iconfont')); + expect(config.output.className, equals('AppIcons')); + expect(config.output.generateDocs, isTrue); + }); + }); + + group('IconParser', () { + test('parseMap parses icon font metadata', () { + final jsonData = { + 'id': '123', + 'name': 'Test Font', + 'font_family': 'testfont', + 'glyphs': [ + { + 'icon_id': '1', + 'name': 'icon_test', + 'font_class': 'icon_test', + 'unicode': 'e600', + 'unicode_decimal': 58880, + }, + ], + }; + + final meta = IconParser.parseMap(jsonData); + + expect(meta.name, equals('Test Font')); + expect(meta.fontFamily, equals('testfont')); + expect(meta.icons.length, equals(1)); + expect(meta.icons.first.name, equals('icon_test')); + expect(meta.icons.first.unicode, equals('e600')); }); }); }