├── .gitignore ├── .rspec ├── .travis.yml ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── Docs ├── flr_cli_deployment_check_list.md └── flr_usage_example_gif_record_scripts.txt ├── Gemfile ├── Gemfile.lock ├── LICENSE.txt ├── README.md ├── README.zh-cn.md ├── README_Assets └── flr-usage-example.gif ├── Rakefile ├── bin └── flr ├── flr.gemspec └── lib ├── flr.rb └── flr ├── checker.rb ├── command.rb ├── constant.rb ├── string_extensions.rb ├── util ├── asset_util.rb ├── code_util.rb └── file_util.rb └── version.rb /.gitignore: -------------------------------------------------------------------------------- 1 | # General 2 | *.swp 3 | *.swo 4 | *.rbo 5 | *.gem 6 | .DS_Store 7 | .rbenv-version 8 | .rbx/ 9 | /concatenated.* 10 | build/ 11 | 12 | # Yardoc 13 | /.yardoc 14 | /_yardoc/ 15 | /coverage/ 16 | /doc/ 17 | /pkg/ 18 | 19 | # Specs 20 | /spec/reports/ 21 | /tmp/ 22 | 23 | # RVM files 24 | /.rvmrc 25 | /.ruby-version 26 | /.ruby-gemset 27 | 28 | # Bundler files 29 | /.bundle 30 | 31 | # rspec failure tracking 32 | .rspec_status 33 | 34 | # IDEs 35 | .idea/ 36 | 37 | # Local Pubspec Tests 38 | pubspec.yaml 39 | pubspec.lock 40 | .packages 41 | .dart_tool/ -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --format documentation 2 | --color 3 | --require spec_helper 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | --- 2 | sudo: false 3 | language: ruby 4 | cache: bundler 5 | rvm: 6 | - 2.6.3 7 | before_install: gem install bundler -v 1.17.2 8 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 3.2.0 2 | 3 | - Support for nullsafety-feature of Dart 2.12 4 | 5 | ## 3.1.0 6 | 7 | - Support for processing (init/generate/monitor) multi projects (the main project and its sub projects in one workspace) 8 | 9 | - Support for auto merging old asset specifications when specifying new assets 10 | 11 | > This is can help you to auto keep the manually added asset specifications. 12 | 13 | ## 3.0.0 14 | 15 | - Support for processing non-implied resource file 16 | 17 | > - non-implied resource file: the resource file which is outside of `lib/` directory, for example: 18 | > - `~/path/to/flutter_r_demo/assets/images/test.png` 19 | > - `~/path/to/flutter_r_demo/assets/images/3.0x/test.png` 20 | > - implied resource file: the resource file which is inside of `lib/` directory, for example: 21 | > - `~/path/to/flutter_r_demo/lib/assets/images/hot_foot_N.png` 22 | > - `~/path/to/flutter_r_demo/lib/assets/images/3.0x/hot_foot_N.png` 23 | 24 | - New recommended flutter resource structure 25 | 26 | ## 2.0.0 27 | 28 | - New asset generation algorithm to support all kinds of standard or nonstandard image/text resource structure 29 | - New asset-id generation algorithm to support assets with the same filename but different path 30 | - New recommended flutter resource structure 31 | 32 | ## 1.1.0 33 | 34 | - Improve generate-capability to support nonstandard image resource structure 35 | - Add recommend-capability to display the recommended flutter resource structure 36 | 37 | ## 1.0.0 38 | 39 | - Support for processing font assets ( `.ttf`, `.otf`, `.ttc`) 40 | - Improve robustness 41 | 42 | ## 0.2.2 43 | 44 | - improve robustness 45 | - fix bug 46 | 47 | ## 0.2.1 48 | 49 | - optimize flr help command 50 | - fix bad info in flr.gemspec 51 | 52 | ## 0.2.0 - BREAKING CHANGES 53 | 54 | - modify the way the Flr configuration is stored: discard the flrfile.yaml file and write the configuration to the pubspec.yaml file 55 | - `flr generate` and `flr monitor` are combined into `flr run [--auto]` 56 | - generate `r.g.dart` instead of `R.dart` 57 | - new `R` class: 58 | - discards `R_X` code struct, and uses `R.x` code struct 59 | - unifies the access way of all types asset resources: using the asset resource ID function, such as `R.image.test()`, `R.svg.test(width: 100, height: 100)`, `R.text.test_json()` 60 | - provides `AssetResource` class to acces the asset metadata, such as `assetName`, `packageName`, `fileBasename` 61 | - increase the range of legal character sets : `0-9`, `A-Z`, `a-z`, `_`, `+`, `-`, `.`, `·`, `!`, `@`, `&`, `$`, `¥` 62 | - colored terminal output 63 | 64 | ## 0.1.13 65 | 66 | - `flr generate` supports checking and outputtting assets with bad filename 67 | - fix bug 68 | 69 | ## 0.1.12 70 | 71 | - support auto service that automatically specify assets in `pubspec.yaml` and generate `R.dart` file, which can be triggered manually or by monitoring asset changes 72 | - support for processing image assets ( `.png`, `.jpg`, `.jpeg`, `.gif`, `.webp`, `.icon`, `.bmp`, `.wbmp`, `.svg` ) 73 | - support for processing text assets ( `.txt`, `.json`, `.yaml`, `.xml` ) 74 | - support for processing [image asset variants](https://flutter.dev/docs/development/ui/assets-and-images#asset-variants) 75 | - support for processing asset which’s filename is bad: 76 | - filename has illegal character (such as `blank`, `~`, `@`, `#` ) which is outside the range of valid characters (`0-9`, `A-Z`, `a-z`, `_`, `$`) 77 | - filename begins with a number or character `_` or character`$` -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, gender identity and expression, level of experience, 9 | nationality, personal appearance, race, religion, or sexual identity and 10 | orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at yorkzhang520@gmail.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at [http://contributor-covenant.org/version/1/4][version] 72 | 73 | [homepage]: http://contributor-covenant.org 74 | [version]: http://contributor-covenant.org/version/1/4/ 75 | -------------------------------------------------------------------------------- /Docs/flr_cli_deployment_check_list.md: -------------------------------------------------------------------------------- 1 | ## Flr Cli Deployment Check List 2 | 3 | 1. 确定Deployment的版本号:X.Y.Z 4 | 1. 编辑`lib/flr/version.rb`,更新`VERSION`: 5 | 6 | ```ruby 7 | module Flr 8 | VERSION = "X.Y.Z" 9 | ... 10 | end 11 | ``` 12 | 1. 更新CHANGELOG.md 13 | 1. 在项目根目录下运行脚本更新Gemfile:`bundle exec ./bin/flr` 14 | 1. 提交当前变更到git 15 | 1. 在项目根目录下运行脚本打包Gem:`gem build flr.gemspec` 16 | 1. 本地安装Flr进行测试:`sudo gem install flr-x.y.z.gem` 17 | 1. 若无问题,则发布Flr到RubyGems市场 18 | 19 | ## Publish Flr Cli 20 | 21 | 1. 在项目根目录下运行脚本发布Flr:`gem push flr-x.y.z.gem` 22 | 23 | ## Other 24 | 25 | 1. 从RubyGems市场下架指定版本的Flr:`gem yank flr -v x.y.z` 26 | -------------------------------------------------------------------------------- /Docs/flr_usage_example_gif_record_scripts.txt: -------------------------------------------------------------------------------- 1 | 2 | 注意:录制前,请先还原pubspec.yaml 3 | --------------------------------------------- 4 | 5 | Flr Usage Example 6 | 7 | --------------------------------------------- 8 | 9 | 1. Run `flr init` 10 | 11 | --------------------------------------------- 12 | 13 | 2. Edit pubspec.yaml 14 | to configure the resource directory paths 15 | 16 | --------------------------------------------- 17 | 18 | 3. Run `flr run` 19 | 20 | --------------------------------------------- 21 | 22 | One More Thing: 23 | 24 | If you want Flr to auto generate when you change Flutter asssets, 25 | 26 | you can Run `flr run --auto` to launch a monitoring service. 27 | 28 | For example: 29 | 30 | --------------------------------------------- 31 | 32 | 1. Run `flr run --auto` 33 | 34 | --------------------------------------------- 35 | 36 | 2. Add a new asset: 37 | 163.com.yaml 38 | 39 | --------------------------------------------- 40 | 41 | 3. Press `Ctrl-C` 42 | to terminate the monitoring service if you want 43 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | git_source(:github) {|repo_name| "https://github.com/#{repo_name}" } 4 | 5 | # Specify your gem's dependencies in flr.gemspec 6 | gemspec 7 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | PATH 2 | remote: . 3 | specs: 4 | flr (3.2.0) 5 | bundler (~> 2.0, >= 2.0.2) 6 | listen (~> 3.0, >= 3.2.1) 7 | thor (~> 1.0, >= 1.0.1) 8 | 9 | GEM 10 | remote: https://rubygems.org/ 11 | specs: 12 | ffi (1.12.1) 13 | listen (3.2.1) 14 | rb-fsevent (~> 0.10, >= 0.10.3) 15 | rb-inotify (~> 0.9, >= 0.9.10) 16 | rb-fsevent (0.10.3) 17 | rb-inotify (0.10.1) 18 | ffi (~> 1.0) 19 | thor (1.0.1) 20 | 21 | PLATFORMS 22 | ruby 23 | 24 | DEPENDENCIES 25 | flr! 26 | 27 | BUNDLED WITH 28 | 2.0.2 29 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2019 York 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Flr CLI 2 | 3 | ![Ruby](https://img.shields.io/badge/language-ruby-orange.svg) [![Gem Name](https://badgen.net/rubygems/n/flr) ![Gem Downloads](https://img.shields.io/gem/dt/flr) ![Gem Version](https://img.shields.io/gem/v/flr)](https://rubygems.org/gems/flr) 4 | 5 | Flr (Flutter-R) CLI: A Flutter Resource Manager CLI TooL, which can help flutter developer to auto specify assets in `pubspec.yaml` and generate `r.g.dart` file after he changes the flutter project assets. With `r.g.dart`, flutter developer can apply the asset in code by referencing it's asset ID function. 6 | 7 | ![Flr Usage Example](README_Assets/flr-usage-example.gif) 8 | 9 | 10 | 📖 *Read this in other languages: [English](README.md), [简体中文](README.zh-cn.md)* 11 | 12 | ## Feature 13 | 14 | - Support auto service that automatically specify assets in `pubspec.yaml` and generate `r.g.dart` file, which can be triggered manually or by monitoring asset changes 15 | - Support `R.x` (such as `R.image.test()`, `R.svg.test(width: 100, height: 100)`, `R.txt.test_json()`) code struct 16 | - Support for processing image assets ( `.png`, `.jpg`, `.jpeg`, `.gif`, `.webp`, `.icon`, `.bmp`, `.wbmp`, `.svg` ) 17 | - Support for processing text assets ( `.txt`, `.json`, `.yaml`, `.xml` ) 18 | - Support for processing font assets ( `.ttf`, `.otf`, `.ttc`) 19 | - Support for processing [image asset variants](https://flutter.dev/docs/development/ui/assets-and-images#asset-variants) 20 | - Support for processing asset which’s filename is bad: 21 | - filename has illegal character (such as `blank`, `~`, `@`, `#` ) which is outside the range of valid characters (`0-9`, `A-Z`, `a-z`, `_`, `+`, `-`, `.`, `·`, `!`, `@`, `&`, `$`, `¥`) 22 | - filename begins with a number or character `_` or character`$` 23 | - Support for processing assets with the same filename but different path 24 | - Support for processing multi projects (the main project and its sub projects in one workspace) 25 | - Support for auto merging old asset specifications when specifying new assets 26 | 27 | ## Install & Update Flr CLI 28 | 29 | To install or update Flr, run `sudo gem install flr` 30 | 31 | > If you want to use Flr tool on the Windows system, you are strongly recommended to run it on [WSL(Windows Subsystem for Linux)](https://docs.microsoft.com/en-us/windows/wsl/install-win10) environment !!! 32 | 33 | ## Uninstall Flr CLI 34 | 35 | To uninstall Flr run `sudo gem uninstall flr` 36 | 37 | ## Usage 38 | 39 | 1. Init your flutter project: 40 | 41 | ``` 42 | cd flutter_project_dir 43 | flr init 44 | ``` 45 | 46 | > The `flr init` command will check to see if the current project is a legal flutter project, add flr configuration and dependency [r_dart_library](https://github.com/YK-Unit/r_dart_library) into `pubspec.yaml`. 47 | > 48 | > **Attention:** 49 | > 50 | > The Flutter SDK is currently in an unstable state, so if you get a build error of `r_dart_library` , you can fix it by modify the dependent version of `r_dart_library`. 51 | > 52 | > You can select the correct version of `r_dart_library` based on this [dependency relationship table](https://github.com/YK-Unit/r_dart_library#dependency-relationship-table). 53 | 54 | 2. Open `pubspec.yaml` file, find the configuration item for `Flr`, and then configure the resource directories that needs to be scanned by `Flr` and configure the line length that is used to format `r.g.dart`, such as: 55 | 56 | ```yaml 57 | flr: 58 | core_version: 1.0.0 59 | # config the line length that is used to format r.g.dart 60 | dartfmt_line_length: 80 61 | # config the image and text resource directories that need to be scanned 62 | assets: 63 | - lib/assets/images 64 | - lib/assets/texts 65 | # config the font resource directories that need to be scanned 66 | fonts: 67 | - lib/assets/fonts 68 | ``` 69 | 70 | 3. Scan assets, specify assets, and generate `r.g.dart`: 71 | 72 | ```shell 73 | flr run 74 | ``` 75 | 76 | > After run `flr run` command, `Flr` will scan the resource directories configured in `pubspec.yaml`, then specify scanned assets in `pubspec.yaml`, and generate `r.g.dart` file. 77 | > 78 | > **If you want `Flr` to do the above operations automatically every time a asset changes, you can run the command `Flr run --auto`.** 79 | > Then `Flr` will launch a monitoring service that continuously monitors resource directories configured in `pubspec.yaml`. If the service detects any asset changes, `Flr` will automatically scan the resource directories, then specify scanned assets in pubspec.yaml, and generate "r.g.dart" file. 80 | > 81 | > **You can terminate this monitoring service by manually pressing `Ctrl-C`.** 82 | 83 | 84 | **Attention:** all commands MUST be runned in your flutter project root directory. 85 | 86 | ## Recommended Flutter Resource Structure 87 | 88 | `Flr` the following flutter resource structure schemes: 89 | 90 | - scheme 1: 91 | 92 | ``` 93 | flutter_project_root_dir 94 | ├── build 95 | │ ├── .. 96 | ├── lib 97 | │ ├── assets 98 | │ │ ├── images // image resource directory of all modules 99 | │ │ │ ├── #{module} // image resource directory of a module 100 | │ │ │ │ ├── #{main_image_asset} 101 | │ │ │ │ ├── #{variant-dir} // image resource directory of a variant 102 | │ │ │ │ │ ├── #{image_asset_variant} 103 | │ │ │ │ 104 | │ │ │ ├── home // image resource directory of home module 105 | │ │ │ │ ├── home_badge.svg 106 | │ │ │ │ ├── home_icon.png 107 | │ │ │ │ ├── 3.0x // image resource directory of a 3.0x-ratio-variant 108 | │ │ │ │ │ ├── home_icon.png 109 | │ │ │ │ 110 | │ │ ├── texts // text resource directory 111 | │ │ │ │ // (you can also break it down further by module) 112 | │ │ │ └── test.json 113 | │ │ │ └── test.yaml 114 | │ │ │ │ 115 | │ │ ├── fonts // font resource directory of all font-families 116 | │ │ │ ├── #{font-family} // font resource directory of a font-family 117 | │ │ │ │ ├── #{font-family}-#{font_weight_or_style}.ttf 118 | │ │ │ │ 119 | │ │ │ ├── Amiri // font resource directory of Amiri font-family 120 | │ │ │ │ ├── Amiri-Regular.ttf 121 | │ │ │ │ ├── Amiri-Bold.ttf 122 | │ │ │ │ ├── Amiri-Italic.ttf 123 | │ │ │ │ ├── Amiri-BoldItalic.ttf 124 | │ ├── .. 125 | 126 | ``` 127 | - scheme 2: 128 | ``` 129 | flutter_project_root_dir 130 | ├── build 131 | │ ├── .. 132 | ├── lib 133 | │ ├── .. 134 | ├── assets 135 | │ ├── images // image resource directory of all modules 136 | │ │ ├── #{module} // image resource directory of a module 137 | │ │ │ ├── #{main_image_asset} 138 | │ │ │ ├── #{variant-dir} // image resource directory of a variant 139 | │ │ │ │ ├── #{image_asset_variant} 140 | │ │ │ 141 | │ │ ├── home // image resource directory of home module 142 | │ │ │ ├── home_badge.svg 143 | │ │ │ ├── home_icon.png 144 | │ │ │ ├── 3.0x // image resource directory of a 3.0x-ratio-variant 145 | │ │ │ │ ├── home_icon.png 146 | │ │ │ 147 | │ ├── texts // text resource directory 148 | │ │ │ // (you can also break it down further by module) 149 | │ │ └── test.json 150 | │ │ └── test.yaml 151 | │ │ │ 152 | │ ├── fonts // font resource directory of all font-families 153 | │ │ ├── #{font-family} // font resource directory of a font-family 154 | │ │ │ ├── #{font-family}-#{font_weight_or_style}.ttf 155 | │ │ │ 156 | │ │ ├── Amiri // font resource directory of Amiri font-family 157 | │ │ │ ├── Amiri-Regular.ttf 158 | │ │ │ ├── Amiri-Bold.ttf 159 | │ │ │ ├── Amiri-Italic.ttf 160 | │ │ │ ├── Amiri-BoldItalic.ttf 161 | │ ├── .. 162 | 163 | ``` 164 | 165 | 166 | **Big Attention, the resource structure in the root directory of the font resource MUST follow the structure described above:** name the subdirectory with a font family name, and place the font resources of the font family in the subdirectory. Otherwise, `Flr` may not scan the font resource correctly. 167 | 168 | ## r.g.dart 169 | 170 | After you run `flr run [--auto]`, Flr will scan the asset directories configured in `pubspec.yaml`, then specify scanned assets in `pubspec.yaml`, and generate `r.g.dart` file. 171 | 172 | `r.g.dart` defines a asset access interface class: `R`, which allows flutter developer to apply the asset in code by referencing it's asset ID function, such as: 173 | 174 | ```dart 175 | import 'package:flutter_r_demo/r.g.dart'; 176 | 177 | // test_sameName.png 178 | var normalImageWidget = Image( 179 | width: 200, 180 | height: 120, 181 | image: R.image.test_sameName(), 182 | ); 183 | 184 | // test_sameName.gif 185 | var gifImageWidget = Image( 186 | image: R.mage.test_sameName_gif(), 187 | ); 188 | 189 | // test.svg 190 | var svgImageWidget = Image( 191 | width: 100, 192 | height: 100, 193 | image: R.svg.test(width: 100, height: 100), 194 | ); 195 | 196 | // test.json 197 | var jsonString = await R.text.test_json(); 198 | 199 | // test.yaml 200 | var yamlString = await R.text.test_yaml(); 201 | 202 | // Amiri Font Style 203 | var amiriTextStyle = TextStyle(fontFamily: R.fontFamily.amiri); 204 | ``` 205 | 206 | ### `_R_X` class 207 | 208 | `r.g.dart` defines several private `_R_X` asset management classes: `_R_Image`, `_R_Svg`, `_R_Text`, `_R_FontFamily`. These private asset management classes are used to manage the asset IDs of the respective asset types: 209 | 210 | - `_R_Image`: manage the asset IDs of non-svg type image assets ( `.png`, `.jpg`, `.jpeg`, `.gif`, `.webp`, `.icon`, `.bmp`, `.wbmp` ) 211 | - `_R_Svg`: manage the asset IDs of svg type image assets 212 | - `_R_Text`: manage the asset IDs of text assets ( `.txt`, `.json`, `.yaml`, `.xml` ) 213 | - `_R_FontFamily`: manage the asset IDs of font assets ( `.ttf`, `.otf`, `.ttc`) 214 | 215 | 216 | ### `R` class and `R.x` struct 217 | 218 | `r.g.dart` defines a asset access interface class: `R`, which is used to manage common information, aggregate the `_R_X` asset management classes, and implement `R.x` code struct: 219 | 220 | ```dart 221 | /// This `R` class is generated and contains references to static asset resources. 222 | class R { 223 | /// package name: flutter_r_demo 224 | static const package = "flutter_r_demo"; 225 | 226 | /// This `R.image` struct is generated, and contains static references to static non-svg type image asset resources. 227 | static const image = _R_Image(); 228 | 229 | /// This `R.svg` struct is generated, and contains static references to static svg type image asset resources. 230 | static const svg = _R_Svg(); 231 | 232 | /// This `R.text` struct is generated, and contains static references to static text asset resources. 233 | static const text = _R_Text(); 234 | 235 | /// This `R.fontFamily` struct is generated, and contains static references to static font resources. 236 | static const fontFamily = _R_FontFamily(); 237 | } 238 | ``` 239 | 240 | ## Example 241 | 242 | Here are some example demos to show how to use Flr tool in flutter project and show how to use `R` class in your code: 243 | 244 | - [Flutter-R Demo](https://github.com/Fly-Mix/flutter_r_demo) 245 | 246 | - [flutter_hello_app](https://github.com/Fly-Mix/flutter_hello_app) 247 | 248 | - [flutter_hello_module](https://github.com/Fly-Mix/flutter_hello_module) 249 | 250 | - [flutter_hello_package](https://github.com/Fly-Mix/flutter_hello_package) 251 | 252 | - [flutter_hello_plugin](https://github.com/Fly-Mix/flutter_hello_plugin) 253 | 254 | ## License 255 | 256 | The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT). 257 | -------------------------------------------------------------------------------- /README.zh-cn.md: -------------------------------------------------------------------------------- 1 | # Flr CLI 2 | 3 | ![Ruby](https://img.shields.io/badge/language-ruby-orange.svg) [![Gem Name](https://badgen.net/rubygems/n/flr) ![Gem Downloads](https://img.shields.io/gem/dt/flr) ![Gem Version](https://img.shields.io/gem/v/flr)](https://rubygems.org/gems/flr) 4 | 5 | `Flr`(Flutter-R)CLI:一个Flutter资源管理的`CLI`工具,用于帮助Flutter开发者在修改项目资源后,可以自动为资源添加声明到 `pubspec.yaml` 以及生成`r.g.dart`文件。借助`r.g.dart`,Flutter开发者可以在代码中通过资源ID函数的方式应用资源。 6 | 7 | ![Flr Usage Example](README_Assets/flr-usage-example.gif) 8 | 9 | 10 | 📖 *其他语言版本:[English](README.md)、 [简体中文](README.zh-cn.md)* 11 | 12 | ## Feature 13 | - 支持“自动添加资源声明到 `pubspec.yaml` 和自动生成`r.g.dart`文件”的自动化服务,该服务可以通过手动触发,也可以通过监控资源变化触发 14 | - 支持`R.x`(如 `R.image.test()`,`R.svg.test(width: 100, height: 100)`,`R.txt.test_json()`)的代码结构 15 | - 支持处理图片资源( `.png`、 `.jpg`、 `.jpeg`、`.gif`、 `.webp`、`.icon`、`.bmp`、`.wbmp`、`.svg` ) 16 | - 支持处理文本资源(`.txt`、`.json`、`.yaml`、`.xml`) 17 | - 支持处理字体资源(`.ttf`、`.otf`、`.ttc`) 18 | - 支持处理[图片资源变体](https://flutter.dev/docs/development/ui/assets-and-images#asset-variants) 19 | - 支持处理带有坏味道的文件名的资源: 20 | - 文件名带有非法字符,如空格、`~`、`#` 等(非法字符是指不在合法字符集合内的字符;合法字符集合的字符有:`0-9`、`A-Z`、 `a-z`、 `_`、`+`、`-`、`.`、`·`、 `!`、 `@`、 `&`、`$`、`¥`) 21 | - 文件名以数字或者`_`或者`$`字符开头 22 | - 支持处理文件名相同但路径不同的资源 23 | - 支持处理多工程(在同一工作空间的flutter主工程和其所有子工程) 24 | - 支持自动合并旧的资产声明 25 | 26 | ## Install & Update Flr CLI 27 | 28 | 安装或者更新`Flr`,只需要在终端运行一句命令即可: `sudo gem install flr`。 29 | > 若你希望在Windows系统下使用Flr,强烈建议你在[WSL(Windows Subsystem for Linux)](https://docs.microsoft.com/en-us/windows/wsl/install-win10) 环境下安装和运行。 30 | 31 | ## Uninstall Flr CLI 32 | 33 | 卸载`Flr`,只需要在终端运行一句命令即可: `sudo gem uninstall flr`。 34 | 35 | ## Usage 36 | 37 | 1. 初始化你的Flutter项目: 38 | 39 | ``` 40 | cd flutter_project_dir 41 | flr init 42 | ``` 43 | 44 | >`flr init`命令将会检测当前项目是否是一个合法的Flutter项目,并在`pubspec.yaml`中添加`Flr`的配置和[r_dart_library](https://github.com/YK-Unit/r_dart_library) 依赖库的声明。 45 | > 46 | >**注意:** 47 | > 48 | >Flutter SDK目前处于不稳定的状态,因此若你遇到`r_dart_library`的编译错误,你可以尝试通过修改`r_dart_library`的依赖版本来修复它。 49 | > 50 | >你可以根据这个[依赖版本关系表](https://github.com/YK-Unit/r_dart_library#dependency-relationship-table)来选择`r_dart_library`的正确版本。 51 | 52 | 2. 打开`pubspec.yaml`文件,找到`Flr`的配置项,然后配置需要`Flr`扫描的资源目录路径以及配置用于格式化`r.g.dart`的行长,如: 53 | 54 | ```yaml 55 | flr: 56 | core_version: 1.0.0 57 | # config the line length for formatting r.g.dart 58 | dartfmt_line_length: 80 59 | # config the image and text resource directories that need to be scanned 60 | assets: 61 | - lib/assets/images 62 | - lib/assets/texts 63 | # config the font resource directories that need to be scanned 64 | fonts: 65 | - lib/assets/fonts 66 | ``` 67 | 68 | 3. 扫描资源,声明资源以及生成`r.g.dart`: 69 | 70 | ```shell 71 | flr run 72 | ``` 73 | 74 | > 运行`flr run`命令后,`Flr`会扫描配置在`pubspec.yaml`中资源目录,然后为扫描到的资源添加声明到`pubspec.yaml`,并生成`r.g.dart`文件。 75 | > 76 | > **若你希望每次资源有变化时,`Flr`就能自动执行上述操作,你可以运行命令`flr run --auto`。** 77 | > 78 | > 这时,`Flr`会启动一个对配置在`pubspec.yaml`中资源目录进行持续监控的服务。若该监控服务检测有资源变化,`Flr`将会自动扫描这些资源目录,然后为扫描到的资源添加声明到`pubspec.yaml`,并生成`r.g.dart`文件。 79 | > 80 | > **你可以通过手动输入`Ctrl-C`来终止这个监控服务。** 81 | 82 | **注意:** 以上所有命令都必须在你的Flutter项目的根目录下执行。 83 | 84 | ## 推荐的flutter资源目录组织结构 85 | 86 | `Flr`推荐如下的flutter资源目录组织结构方案: 87 | 88 | - 方案一: 89 | 90 | ``` 91 | flutter_project_root_dir 92 | ├── build 93 | │ ├── .. 94 | ├── lib 95 | │ ├── assets 96 | │ │ ├── images // 所有模块的图片资源总目录 97 | │ │ │ ├── #{module} // 某个模块的图片资源总目录 98 | │ │ │ │ ├── #{main_image_asset} 99 | │ │ │ │ ├── #{variant-dir} // 某个变体版本的图片资源总目录 100 | │ │ │ │ │ ├── #{image_asset_variant} 101 | │ │ │ │ 102 | │ │ │ ├── home // home模块的图片资源总目录 103 | │ │ │ │ ├── home_badge.svg 104 | │ │ │ │ ├── home_icon.png 105 | │ │ │ │ ├── 3.0x // 3.0倍分辨率版本的图片资源总目录 106 | │ │ │ │ │ ├── home_icon.png 107 | │ │ │ │ 108 | │ │ ├── texts // 文本资源总目录 109 | │ │ │ │ // (你也可以根据模块进一步细分) 110 | │ │ │ └── test.json 111 | │ │ │ └── test.yaml 112 | │ │ │ │ 113 | │ │ ├── fonts // 所有字体家族的字体资源总目录 114 | │ │ │ ├── #{font-family} // 某个字体家族的字体资源总目录 115 | │ │ │ │ ├── #{font-family}-#{font_weight_or_style}.ttf 116 | │ │ │ │ 117 | │ │ │ ├── Amiri // Amiri字体家族的字体资源总目录 118 | │ │ │ │ ├── Amiri-Regular.ttf 119 | │ │ │ │ ├── Amiri-Bold.ttf 120 | │ │ │ │ ├── Amiri-Italic.ttf 121 | │ │ │ │ ├── Amiri-BoldItalic.ttf 122 | │ ├── .. 123 | ``` 124 | - 方案二: 125 | 126 | ``` 127 | flutter_project_root_dir 128 | ├── build 129 | │ ├── .. 130 | ├── lib 131 | │ ├── .. 132 | ├── assets 133 | │ ├── images // 所有模块的图片资源总目录 134 | │ │ ├── #{module} // 某个模块的图片资源总目录 135 | │ │ │ ├── #{main_image_asset} 136 | │ │ │ ├── #{variant-dir} // 某个变体版本的图片资源总目录 137 | │ │ │ │ ├── #{image_asset_variant} 138 | │ │ │ 139 | │ │ ├── home // home模块的图片资源总目录 140 | │ │ │ ├── home_badge.svg 141 | │ │ │ ├── home_icon.png 142 | │ │ │ ├── 3.0x // 3.0倍分辨率版本的图片资源总目录 143 | │ │ │ │ ├── home_icon.png 144 | │ │ │ 145 | │ ├── texts // 文本资源总目录 146 | │ │ │ // (你也可以根据模块进一步细分) 147 | │ │ └── test.json 148 | │ │ └── test.yaml 149 | │ │ │ 150 | │ ├── fonts // 所有字体家族的字体资源总目录 151 | │ │ ├── #{font-family} // 某个字体家族的字体资源总目录 152 | │ │ │ ├── #{font-family}-#{font_weight_or_style}.ttf 153 | │ │ │ 154 | │ │ ├── Amiri // Amiri字体家族的字体资源总目录 155 | │ │ │ ├── Amiri-Regular.ttf 156 | │ │ │ ├── Amiri-Bold.ttf 157 | │ │ │ ├── Amiri-Italic.ttf 158 | │ │ │ ├── Amiri-BoldItalic.ttf 159 | │ ├── .. 160 | ``` 161 | 162 | 163 | **需要注意的是,字体资源根目录下的组织结构必须(MUST)采用上述的组织结构:** 以字体家族名称命名子目录,然后字体家族的字体资源放在子目录下。否则,`Flr`可能无法正确扫描字体资源。 164 | 165 | ## r.g.dart 166 | 167 | 在你运行`flr run [--auto]`命令后,`Flr`会扫描`pubspec.yaml`中配置的资源目录,并为扫描到的资源添加声明到`pubspec.yaml`,以及生成`r.g.dart`。 168 | 169 | `r.g.dart`中定义了一个资源访问接口类:`R`,让Flutter开发者在代码中可通过资源ID函数的方式应用资源,如: 170 | 171 | ```dart 172 | import 'package:flutter_r_demo/r.g.dart'; 173 | 174 | // test_sameName.png 175 | var normalImageWidget = Image( 176 | width: 200, 177 | height: 120, 178 | image: R.image.test_sameName(), 179 | ); 180 | 181 | // test_sameName.gif 182 | var gifImageWidget = Image( 183 | image: R.mage.test_sameName_gif(), 184 | ); 185 | 186 | // test.svg 187 | var svgImageWidget = Image( 188 | width: 100, 189 | height: 100, 190 | image: R.svg.test(width: 100, height: 100), 191 | ); 192 | 193 | // test.json 194 | var jsonString = await R.text.test_json(); 195 | 196 | // test.yaml 197 | var yamlString = await R.text.test_yaml(); 198 | 199 | // Amiri Font Style 200 | var amiriTextStyle = TextStyle(fontFamily: R.fontFamily.amiri); 201 | ``` 202 | 203 | ### `_R_X` class 204 | 205 | `r.g.dart`中定义了几个私有的`_R_X`资源管理类:`_R_Image`、`_R_svg`、`_R_Text`、`_R_FontFamily`。这些私有的资源管理类用于管理各自资源类型的资源ID: 206 | 207 | - `_R_Image`:管理非SVG类的图片资源( `.png`、 `.jpg`、 `.jpeg`、`.gif`、 `.webp`、`.icon`、`.bmp`、`.wbmp`)的资源ID 208 | - `_R_Svg`:管理SVG类图片资源的资源ID 209 | - `_R_Text`:管理文本资源(`.txt`、`.json`、`.yaml`、`.xml`)的资源ID 210 | - `_R_FontFamily`:管理字体资源(`.ttf`、`.otf`、`.ttc`)的资源ID 211 | 212 | 213 | ### `R` class and `R.x` struct 214 | 215 | `r.g.dart`中定义了一个资源访问接口类:`R`,用来管理公共信息,聚合`_R_X`资源管理类,和实现`R.x`的代码结构方式: 216 | 217 | ```dart 218 | /// This `R` class is generated and contains references to static asset resources. 219 | class R { 220 | /// package name: flutter_r_demo 221 | static const package = "flutter_r_demo"; 222 | 223 | /// This `R.image` struct is generated, and contains static references to static non-svg type image asset resources. 224 | static const image = _R_Image(); 225 | 226 | /// This `R.svg` struct is generated, and contains static references to static svg type image asset resources. 227 | static const svg = _R_Svg(); 228 | 229 | /// This `R.text` struct is generated, and contains static references to static text asset resources. 230 | static const text = _R_Text(); 231 | } 232 | 233 | /// This `R.fontFamily` struct is generated, and contains static references to static font resources. 234 | static const fontFamily = _R_FontFamily(); 235 | ``` 236 | 237 | ## Example 238 | 239 | 这里提供了一些示例Demo来展示如何在Flutter项目中使用`Flr`工具和在代码中如何使用`R`类: 240 | 241 | - [Flutter-R Demo](https://github.com/Fly-Mix/flutter_r_demo) 242 | 243 | - [flutter_hello_app](https://github.com/Fly-Mix/flutter_hello_app) 244 | 245 | - [flutter_hello_module](https://github.com/Fly-Mix/flutter_hello_module) 246 | 247 | - [flutter_hello_package](https://github.com/Fly-Mix/flutter_hello_package) 248 | 249 | - [flutter_hello_plugin](https://github.com/Fly-Mix/flutter_hello_plugin) 250 | 251 | 252 | ## License 253 | 254 | The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT). 255 | -------------------------------------------------------------------------------- /README_Assets/flr-usage-example.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Fly-Mix/flr-cli/69925b9e5e4fe83ef8f2ab44ad8d6da0a0aea610/README_Assets/flr-usage-example.gif -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require "bundler/gem_tasks" 2 | require "rspec/core/rake_task" 3 | 4 | RSpec::Core::RakeTask.new(:spec) 5 | 6 | task :default => :spec 7 | -------------------------------------------------------------------------------- /bin/flr: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require "bundler/setup" 4 | require "flr" 5 | 6 | Flr::CLI.start(ARGV) 7 | -------------------------------------------------------------------------------- /flr.gemspec: -------------------------------------------------------------------------------- 1 | 2 | lib = File.expand_path("../lib", __FILE__) 3 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 4 | require "flr/version" 5 | 6 | Gem::Specification.new do |spec| 7 | spec.name = "flr" 8 | spec.version = Flr::VERSION 9 | spec.authors = ["York"] 10 | spec.email = ["yorkzhang520@gmail.com"] 11 | 12 | spec.summary = "Flr(Flutter-R): A Flutter Resource Manager CLI TooL." 13 | spec.description = "Flr(Flutter-R): A Flutter Resource Manager CLI TooL, which can help flutter developer to auto specify assets in pubspec.yaml and generate r.g.dart file after he changes the flutter project assets." 14 | spec.homepage = "https://github.com/Fly-Mix/flr-cli" 15 | spec.license = "MIT" 16 | 17 | # Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host' 18 | # to allow pushing to a single host or delete this section to allow pushing to any host. 19 | if spec.respond_to?(:metadata) 20 | # spec.metadata["allowed_push_host"] = "https://rubygems.org" 21 | 22 | spec.metadata["homepage_uri"] = spec.homepage 23 | spec.metadata["source_code_uri"] = spec.homepage 24 | spec.metadata["changelog_uri"] = "#{spec.homepage}/blob/master/CHANGELOG.md" 25 | else 26 | raise "RubyGems 2.0 or newer is required to protect against " \ 27 | "public gem pushes." 28 | end 29 | 30 | # Specify which files should be added to the gem when it is released. 31 | # The `git ls-files -z` loads the files in the RubyGem that have been added into git. 32 | spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do 33 | `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features|README)}) } 34 | end 35 | 36 | spec.bindir = "bin" 37 | spec.executables = ["flr"] 38 | spec.require_paths = ["lib"] 39 | 40 | # spec.add_development_dependency "rake", "~> 10.0" 41 | # spec.add_development_dependency "rspec", "~> 3.0" 42 | 43 | spec.add_runtime_dependency "bundler", "~> 2.0", '>= 2.0.2' 44 | spec.add_runtime_dependency "thor", "~> 1.0", '>= 1.0.1' 45 | spec.add_runtime_dependency "listen", "~> 3.0", '>= 3.2.1' 46 | 47 | end 48 | -------------------------------------------------------------------------------- /lib/flr.rb: -------------------------------------------------------------------------------- 1 | require 'thor' 2 | require 'flr/version' 3 | require 'flr/command' 4 | require 'flr/string_extensions' 5 | 6 | module Flr 7 | 8 | class CLI < Thor 9 | 10 | def self.help(shell, subcommand = false, display_introduction = true) 11 | introduction = <<-MESSAGE 12 | A Flutter Resource Manager CLI TooL, which can help flutter developer to auto specify assets in pubspec.yaml and generate r.g.dart file after he changes the flutter project assets. 13 | More details see https://github.com/Fly-Mix/flr-cli 14 | 15 | MESSAGE 16 | if display_introduction 17 | puts(introduction) 18 | end 19 | super(shell,subcommand) 20 | end 21 | 22 | def self.exit_on_failure? 23 | puts("") 24 | help(CLI::Base.shell.new, false, false) 25 | true 26 | end 27 | 28 | desc "version", "Display version" 29 | long_desc <<-LONGDESC 30 | Display the version of Flr. 31 | 32 | LONGDESC 33 | def version 34 | Command.version 35 | end 36 | map %w[-v --version] => :version 37 | 38 | desc "init", "Add flr configuration and dependency \"r_dart_library\" into pubspec.yaml" 39 | long_desc <<-LONGDESC 40 | Add flr configuration 41 | and dependency \"r_dart_library\"(https://github.com/YK-Unit/r_dart_library) into pubspec.yaml. 42 | 43 | LONGDESC 44 | def init 45 | Command.init_all 46 | end 47 | 48 | desc "run [--auto]", "Scan assets, specify scanned assets in pubspec.yaml, generate \"r.g.dart\"" 49 | long_desc <<-LONGDESC 50 | 51 | #{"With no option".bold}, #{"Flr".bold} will scan the asset directories configured in `pubspec.yaml`, 52 | then specify scanned assets in pubspec.yaml, 53 | and generate "r.g.dart" file. 54 | 55 | #{"With".bold} #{"--auto".red.bold} #{"option".bold}, #{"Flr".bold} will launch a monitoring service that continuously monitors asset directories configured in pubspec.yaml. 56 | If the service detects any asset changes, #{"Flr".bold} will automatically scan the asset directories, 57 | then specify scanned assets in pubspec.yaml, 58 | and generate "r.g.dart" file. 59 | 60 | You can terminate the monitoring service by manually pressing #{"\"Ctrl-C\"".bold} if it exists. 61 | 62 | LONGDESC 63 | option :auto, :type => :boolean 64 | def run_command 65 | options[:auto] ? Command.start_monitor : Command.generate_all 66 | end 67 | map 'run' => :run_command 68 | 69 | desc "recommend", "Display the recommended flutter resource structure" 70 | long_desc <<-LONGDESC 71 | Display the good flutter resource structure which is recommended by Flr. 72 | 73 | LONGDESC 74 | def recommend 75 | Command.display_recommended_flutter_resource_structure 76 | end 77 | 78 | end 79 | end 80 | -------------------------------------------------------------------------------- /lib/flr/checker.rb: -------------------------------------------------------------------------------- 1 | require 'flr/string_extensions' 2 | 3 | module Flr 4 | # 条件检测器,提供检测各种条件是否合法的方法 5 | class Checker 6 | 7 | # check_pubspec_file_is_existed(flutter_project_dir) -> true 8 | # 9 | # 检测当前flutter工程目录是否存在pubspec.yaml文件 10 | # 若存在,返回true 11 | # 若不存在,则抛出异常 12 | # 13 | # === Examples 14 | # 15 | # flutter_project_dir = "~path/to/flutter_r_demo" 16 | # Checker.check_pubspec_file_is_existed(flutter_project_dir) 17 | # 18 | def self.check_pubspec_file_is_existed(flutter_project_dir) 19 | pubspec_file_path = flutter_project_dir + "/pubspec.yaml" 20 | 21 | if File.exist?(pubspec_file_path) == false 22 | message = <<-MESSAGE 23 | #{"[x]: #{pubspec_file_path} not found".error_style} 24 | #{"[*]: please make sure pubspec.yaml is existed in #{flutter_project_dir}".tips_style} 25 | MESSAGE 26 | 27 | raise(message) 28 | end 29 | 30 | end 31 | 32 | # check_flr_config_is_existed(pubspec_config) -> true 33 | # 34 | # 检测pubspec.yaml中是否存在flr的配置信息`flr_config`: 35 | # 36 | # ``` yaml 37 | # flr: 38 | # core_version: 1.0.0 39 | # dartfmt_line_length: 80 40 | # assets: 41 | # fonts: 42 | # ``` 43 | # 若存在,返回true 44 | # 若不存在,则抛出异常 45 | # 46 | # === Examples 47 | # 48 | # pubspec_config = YAML.load(pubspec_file) 49 | # Checker.check_flr_config_is_existed(pubspec_config) 50 | # 51 | def self.check_flr_config_is_existed(pubspec_config) 52 | flr_config = pubspec_config["flr"] 53 | 54 | if flr_config.is_a?(Hash) == false 55 | message = <<-MESSAGE 56 | #{"[x]: have no flr configuration in pubspec.yaml".error_style} 57 | #{"[*]: please run \"flr init\" to fix it".tips_style} 58 | MESSAGE 59 | 60 | raise(message) 61 | end 62 | 63 | return true 64 | end 65 | 66 | # check_flr_assets_is_legal(flutter_project_dir, flr_config) -> resource_dir_result_tuple 67 | # 68 | # 检测当前flr配置信息中的assets配置是否合法 69 | # 若合法,返回资源目录结果三元组 resource_dir_result_triplet 70 | # 若不合法,抛出异常 71 | # 72 | # resource_dir_result_tuple = [assets_legal_resource_dir_array, illegal_resource_dir_array, fonts_legal_resource_dir_array] 73 | # 74 | # 75 | # flr的assets配置是用于配置需要flr进行扫描的资源目录,如: 76 | # 77 | # ``` yaml 78 | # flr: 79 | # core_version: 1.0.0 80 | # dartfmt_line_length: 80 81 | # assets: 82 | # - lib/assets/images 83 | # - lib/assets/texts 84 | # fonts: 85 | # - lib/assets/fonts 86 | # ``` 87 | # 88 | # 判断flr的assets配置合法的标准是:assets配置的resource_dir数组中的legal_resource_dir数量大于0。 89 | # 90 | # === Examples 91 | # flutter_project_dir = "~/path/to/flutter_r_demo" 92 | # assets_legal_resource_dir_array = ["~/path/to/flutter_r_demo/lib/assets/images", "~/path/to/flutter_r_demo/lib/assets/texts"] 93 | # fonts_legal_resource_dir_array = ["~/path/to/flutter_r_demo/lib/assets/fonts"] 94 | # illegal_resource_dir_array = ["~/path/to/flutter_r_demo/to/non-existed_folder"] 95 | # 96 | def self.check_flr_assets_is_legal(flutter_project_dir, flr_config) 97 | core_version = flr_config["core_version"] 98 | dartfmt_line_length = flr_config["dartfmt_line_length"] 99 | assets_resource_dir_array = flr_config["assets"] 100 | fonts_resource_dir_array = flr_config["fonts"] 101 | 102 | if assets_resource_dir_array.is_a?(Array) == false 103 | assets_resource_dir_array = [] 104 | end 105 | 106 | if fonts_resource_dir_array.is_a?(Array) == false 107 | fonts_resource_dir_array = [] 108 | end 109 | 110 | # 移除非法的 resource_dir(nil,空字符串,空格字符串) 111 | assets_resource_dir_array = assets_resource_dir_array - [nil, "", " "] 112 | fonts_resource_dir_array = fonts_resource_dir_array - [nil, "", " "] 113 | # 过滤重复的 resource_dir 114 | assets_resource_dir_array = assets_resource_dir_array.uniq 115 | fonts_resource_dir_array = fonts_resource_dir_array.uniq 116 | 117 | 118 | # 筛选合法的和非法的resource_dir 119 | assets_legal_resource_dir_array = [] 120 | fonts_legal_resource_dir_array = [] 121 | illegal_resource_dir_array = [] 122 | 123 | assets_resource_dir_array.each do |relative_resource_dir| 124 | resource_dir = flutter_project_dir + "/" + relative_resource_dir 125 | if File.exist?(resource_dir) == true 126 | assets_legal_resource_dir_array.push(resource_dir) 127 | else 128 | illegal_resource_dir_array.push(resource_dir) 129 | end 130 | end 131 | 132 | fonts_resource_dir_array.each do |relative_resource_dir| 133 | resource_dir = flutter_project_dir + "/" + relative_resource_dir 134 | if File.exist?(resource_dir) == true 135 | fonts_legal_resource_dir_array.push(resource_dir) 136 | else 137 | illegal_resource_dir_array.push(resource_dir) 138 | end 139 | end 140 | 141 | legal_resource_dir_array = assets_legal_resource_dir_array + fonts_legal_resource_dir_array 142 | if legal_resource_dir_array.length <= 0 143 | 144 | if illegal_resource_dir_array.length > 0 145 | message = "[!]: warning, found the following resource directory which is not existed: ".warning_style 146 | illegal_resource_dir_array.each do |resource_dir| 147 | message = message + "\n" + " - #{resource_dir}".warning_style 148 | end 149 | puts(message) 150 | puts("") 151 | end 152 | 153 | message = <<-MESSAGE 154 | #{"[x]: have no valid resource directories configuration in pubspec.yaml".error_style} 155 | #{"[*]: please manually configure the resource directories to fix it, for example: ".tips_style} 156 | 157 | #{"flr:".tips_style} 158 | #{"core_version: #{core_version}".tips_style} 159 | #{"dartfmt_line_length: #{dartfmt_line_length}".tips_style} 160 | #{"# config the image and text resource directories that need to be scanned".tips_style} 161 | #{"assets:".tips_style} 162 | #{"- lib/assets/images".tips_style} 163 | #{"- lib/assets/texts".tips_style} 164 | #{"# config the font resource directories that need to be scanned".tips_style} 165 | #{"fonts:".tips_style} 166 | #{"- lib/assets/fonts".tips_style} 167 | MESSAGE 168 | 169 | raise(message) 170 | end 171 | 172 | resource_dir_result_tuple = [assets_legal_resource_dir_array, fonts_legal_resource_dir_array, illegal_resource_dir_array] 173 | return resource_dir_result_tuple 174 | end 175 | end 176 | end 177 | 178 | 179 | -------------------------------------------------------------------------------- /lib/flr/command.rb: -------------------------------------------------------------------------------- 1 | require 'yaml' 2 | require 'find' 3 | require 'listen' 4 | require 'flr/version' 5 | require 'flr/string_extensions' 6 | require 'flr/checker' 7 | require 'flr/util/file_util' 8 | require 'flr/util/asset_util' 9 | require 'flr/util/code_util' 10 | 11 | # 专有名词简单解释和示例: 12 | # (详细定义请看 flr-core 项目的文档描述) 13 | # 14 | # package_name:flutter工程的package产物的名称,例如“flutter_demo” 15 | # resource_file:flutter工程的资源文件,例如“lib/assets/images/hot_foot_N.png”、“lib/assets/images/3.0x/hot_foot_N.png” 16 | # asset:flutter工程的package产物中资源,可当作是工程中的资源文件的映射和声明,例如上述2个资源对于的asset都是“packages/flutter_demo/assets/images/hot_foot_N.png” 17 | # file_basename:资源的文件名,其定义是“#{file_basename_no_extension}#{file_extname}”,例如“hot_foot_N.png” 18 | # file_basename_no_extension:资源的不带扩展名的文件名,例如“hot_foot_N” 19 | # file_extname:资源的扩展名,例如“.png” 20 | # 21 | # asset_name:main asset的名称,例如“assets/images/hot_foot_N.png” 22 | # asset_id:资源ID,其值一般为 file_basename_no_extension 23 | 24 | module Flr 25 | class Command 26 | 27 | # Listen Class Instance 28 | @@listener = nil 29 | 30 | # display the version of flr 31 | def self.version 32 | version_desc = "Flr version #{Flr::VERSION}\nCoreLogic version #{Flr::CORE_VERSION}" 33 | puts(version_desc) 34 | end 35 | 36 | # display recommended flutter resource structure 37 | def self.display_recommended_flutter_resource_structure 38 | message = <<-MESSAGE 39 | Flr recommends the following flutter resource structure schemes: 40 | 41 | #{"------------------------------ scheme 1 ------------------------------".bold.bg_red} 42 | 43 | flutter_project_root_dir 44 | ├── build 45 | │ ├── .. 46 | ├── lib 47 | │ ├── assets 48 | │ │ ├── images #{"// image resource directory of all modules".red} 49 | │ │ │ ├── \#{module} #{"// image resource directory of a module".red} 50 | │ │ │ │ ├── \#{main_image_asset} 51 | │ │ │ │ ├── \#{variant-dir} #{"// image resource directory of a variant".red} 52 | │ │ │ │ │ ├── \#{image_asset_variant} 53 | │ │ │ │ 54 | │ │ │ ├── home #{"// image resource directory of home module".red} 55 | │ │ │ │ ├── home_badge.svg 56 | │ │ │ │ ├── home_icon.png 57 | │ │ │ │ ├── 3.0x #{"// image resource directory of a 3.0x-ratio-variant".red} 58 | │ │ │ │ │ ├── home_icon.png 59 | │ │ │ │ 60 | │ │ ├── texts #{"// text resource directory".red} 61 | │ │ │ │ #{"// (you can also break it down further by module)".red} 62 | │ │ │ └── test.json 63 | │ │ │ └── test.yaml 64 | │ │ │ │ 65 | │ │ ├── fonts #{"// font resource directory of all font-families".red} 66 | │ │ │ ├── \#{font-family} #{"// font resource directory of a font-family".red} 67 | │ │ │ │ ├── \#{font-family}-\#{font_weight_or_style}.ttf 68 | │ │ │ │ 69 | │ │ │ ├── Amiri #{"// font resource directory of Amiri font-family".red} 70 | │ │ │ │ ├── Amiri-Regular.ttf 71 | │ │ │ │ ├── Amiri-Bold.ttf 72 | │ │ │ │ ├── Amiri-Italic.ttf 73 | │ │ │ │ ├── Amiri-BoldItalic.ttf 74 | │ ├── .. 75 | 76 | #{"[*]: Then config the resource directories that need to be scanned as follows:".tips_style} 77 | 78 | #{"flr:".tips_style} 79 | #{"core_version: #{Flr::CORE_VERSION}".tips_style} 80 | #{"dartfmt_line_length: #{Flr::DARTFMT_LINE_LENGTH}".tips_style} 81 | #{"# config the image and text resource directories that need to be scanned".tips_style} 82 | #{"assets:".tips_style} 83 | #{"- lib/assets/images".tips_style} 84 | #{"- lib/assets/texts".tips_style} 85 | #{"# config the font resource directories that need to be scanned".tips_style} 86 | #{"fonts:".tips_style} 87 | #{"- lib/assets/fonts".tips_style} 88 | 89 | #{"------------------------------ scheme 2 ------------------------------".bold.bg_red} 90 | 91 | flutter_project_root_dir 92 | ├── build 93 | │ ├── .. 94 | ├── lib 95 | │ ├── .. 96 | ├── assets 97 | │ ├── images #{"// image resource directory of all modules".red} 98 | │ │ ├── \#{module} #{"// image resource directory of a module".red} 99 | │ │ │ ├── \#{main_image_asset} 100 | │ │ │ ├── \#{variant-dir} #{"// image resource directory of a variant".red} 101 | │ │ │ │ ├── \#{image_asset_variant} 102 | │ │ │ 103 | │ │ ├── home #{"// image resource directory of home module".red} 104 | │ │ │ ├── home_badge.svg 105 | │ │ │ ├── home_icon.png 106 | │ │ │ ├── 3.0x #{"// image resource directory of a 3.0x-ratio-variant".red} 107 | │ │ │ │ ├── home_icon.png 108 | │ │ │ 109 | │ ├── texts #{"// text resource directory".red} 110 | │ │ │ #{"// (you can also break it down further by module)".red} 111 | │ │ └── test.json 112 | │ │ └── test.yaml 113 | │ │ │ 114 | │ ├── fonts #{"// font resource directory of all font-families".red} 115 | │ │ ├── \#{font-family} #{"// font resource directory of a font-family".red} 116 | │ │ │ ├── \#{font-family}-\#{font_weight_or_style}.ttf 117 | │ │ │ 118 | │ │ ├── Amiri #{"// font resource directory of Amiri font-family".red} 119 | │ │ │ ├── Amiri-Regular.ttf 120 | │ │ │ ├── Amiri-Bold.ttf 121 | │ │ │ ├── Amiri-Italic.ttf 122 | │ │ │ ├── Amiri-BoldItalic.ttf 123 | │ ├── .. 124 | 125 | #{"[*]: Then config the resource directories that need to be scanned as follows:".tips_style} 126 | 127 | #{"flr:".tips_style} 128 | #{"core_version: #{Flr::CORE_VERSION}".tips_style} 129 | #{"dartfmt_line_length: #{Flr::DARTFMT_LINE_LENGTH}".tips_style} 130 | #{"# config the image and text resource directories that need to be scanned".tips_style} 131 | #{"assets:".tips_style} 132 | #{"- assets/images".tips_style} 133 | #{"- assets/texts".tips_style} 134 | #{"# config the font resource directories that need to be scanned".tips_style} 135 | #{"fonts:".tips_style} 136 | #{"- assets/fonts".tips_style} 137 | MESSAGE 138 | 139 | puts(message) 140 | end 141 | 142 | # get the right version of r_dart_library package based on flutter's version 143 | # to get more detail, see https://github.com/YK-Unit/r_dart_library#dependency-relationship-table 144 | def self.get_r_dart_library_version 145 | r_dart_library_version = "0.1.1" 146 | 147 | # $ flutter --version 148 | # Flutter 1.12.13+hotfix.5 • channel stable • https://github.com/flutter/flutter.git 149 | # Framework • revision 27321ebbad (5 weeks ago) • 2019-12-10 18:15:01 -0800 150 | # Engine • revision 2994f7e1e6 151 | # Tools • Dart 2.7.0 152 | flutter_version_result = `flutter --version` 153 | if (flutter_version_result.nil? == true || flutter_version_result.empty? == true) 154 | return r_dart_library_version 155 | end 156 | 157 | flutter_version_with_hotfix_str = flutter_version_result.split(" ")[1] 158 | flutter_version_without_hotfix_str = flutter_version_with_hotfix_str.split("+")[0] 159 | 160 | dart_version_str = flutter_version_result.split("Dart")[1] 161 | dart_version_str.strip! 162 | 163 | puts("the flutter development environment is:") 164 | puts(" - Flutter SDK Version: #{flutter_version_with_hotfix_str}") 165 | puts(" - Dart SDK Version: #{dart_version_str}") 166 | 167 | if Version.new(flutter_version_with_hotfix_str) >= Version.new("1.10.15") 168 | r_dart_library_version = "0.2.1" 169 | if Version.new(dart_version_str) >= Version.new("2.12.0") 170 | r_dart_library_version = "0.4.0-nullsafety.0" 171 | end 172 | end 173 | 174 | return r_dart_library_version 175 | end 176 | 177 | def self.init_all 178 | flutter_main_project_root_dir = FileUtil.get_flutter_main_project_root_dir 179 | 180 | # ----- Step-1 Begin ----- 181 | # 进行环境检测;若发现不合法的环境,则抛出异常,终止当前进程 182 | # - 检测当前flutter主工程根目录是否存在 pubspec.yaml 183 | # 184 | 185 | begin 186 | Checker.check_pubspec_file_is_existed(flutter_main_project_root_dir) 187 | rescue Exception => e 188 | puts(e.message) 189 | return 190 | end 191 | 192 | # ----- Step-1 End ----- 193 | 194 | puts("init all flutter projects now...") 195 | 196 | # ----- Step-2 Begin ----- 197 | # 获取主工程和其所有子工程,对它们进行init_one操作 198 | # - 获取flutter主工程根目录下所有的子工程目录 199 | # - 初始化主工程 200 | # - 初始化所有子工程 201 | # 202 | flutter_sub_project_root_dir_array = FileUtil.get_flutter_sub_project_root_dirs(flutter_main_project_root_dir) 203 | 204 | puts("") 205 | init_one(flutter_main_project_root_dir) 206 | 207 | flutter_sub_project_root_dir_array.each do |flutter_project_root_dir| 208 | puts("") 209 | init_one(flutter_project_root_dir) 210 | end 211 | 212 | # ----- Step-2 End ----- 213 | 214 | # ----- Step-3 Begin ----- 215 | # 调用 flutter 工具,为主工程和所有子工程获取依赖 216 | # 217 | puts("") 218 | puts("get dependencies for all flutter projects via execute \"flutter pub get\" now ...") 219 | 220 | get_flutter_pub_cmd = "flutter pub get" 221 | system(get_flutter_pub_cmd) 222 | 223 | puts("[√]: get dependencies for all flutter projects done !!!") 224 | 225 | # ----- Step-3 End ----- 226 | puts("") 227 | puts("[√]: init all flutter projects done !!!") 228 | 229 | puts("") 230 | puts("[*]: if you want to know how to make a good resource structure for your flutter project, please run \"flr recommend\" ".tips_style) 231 | end 232 | 233 | # 对指定 flutter 工程进行初始化 234 | def self.init_one(flutter_project_root_dir) 235 | puts("------------------------------- init specified project -------------------------------") 236 | 237 | puts("init #{flutter_project_root_dir} now...") 238 | 239 | # ----- Step-1 Begin ----- 240 | # 进行环境检测;若发现不合法的环境,则抛出异常,终止当前进程 241 | # - 检测 pubspec.yaml 是否存在 242 | # - 检测 pubspec.yaml 是否合法 243 | # 244 | begin 245 | Checker.check_pubspec_file_is_existed(flutter_project_root_dir) 246 | pubspec_file_path = FileUtil.get_pubspec_file_path(flutter_project_root_dir) 247 | pubspec_config = FileUtil.load_pubspec_config_from_file(pubspec_file_path) 248 | rescue Exception => e 249 | puts(e.message) 250 | puts("[x]: init #{flutter_project_root_dir} failed".error_style) 251 | puts("--------------------------------------------------------------------------------------") 252 | return 253 | end 254 | 255 | # ----- Step-1 End ----- 256 | 257 | # ----- Step-2 Begin ----- 258 | # 添加 flr_config 和 r_dart_library 的依赖声明到 pubspec.yaml 259 | # 260 | 261 | dependencies_config = pubspec_config["dependencies"] 262 | 263 | # 添加flr_config到pubspec.yaml:检测当前是否存在flr_config;若不存在,则添加flr_config;若存在,则按照以下步骤处理: 264 | # - 读取dartfmt_line_length选项、assets选项和fonts选项的值(这些选项值若存在,则应用于新建的flr_config;需要注意,使用前需要判断选项值是否合法:dartfmt_line_length选项值 >=80;assets选项和fonts选项的值为数组) 265 | # - 新建flr_config,然后使用旧值或者默认值设置各个选项 266 | # 267 | # flr_config: Flr的配置信息 268 | # ```yaml 269 | # flr: 270 | # core_version: 1.0.0 271 | # dartfmt_line_length: 80 272 | # assets: [] 273 | # fonts: [] 274 | # ``` 275 | 276 | dartfmt_line_length = Flr::DARTFMT_LINE_LENGTH 277 | asset_resource_dir_array = [] 278 | font_resource_dir_array = [] 279 | 280 | old_flr_config = pubspec_config["flr"] 281 | if old_flr_config.is_a?(Hash) 282 | dartfmt_line_length = old_flr_config["dartfmt_line_length"] 283 | if dartfmt_line_length.nil? or dartfmt_line_length.is_a?(Integer) == false 284 | dartfmt_line_length = Flr::DARTFMT_LINE_LENGTH 285 | end 286 | if dartfmt_line_length < Flr::DARTFMT_LINE_LENGTH 287 | dartfmt_line_length = Flr::DARTFMT_LINE_LENGTH 288 | end 289 | 290 | asset_resource_dir_array = old_flr_config["assets"] 291 | if asset_resource_dir_array.nil? or asset_resource_dir_array.is_a?(Array) == false 292 | asset_resource_dir_array = [] 293 | end 294 | 295 | font_resource_dir_array = old_flr_config["fonts"] 296 | if font_resource_dir_array.nil? or font_resource_dir_array.is_a?(Array) == false 297 | font_resource_dir_array = [] 298 | end 299 | end 300 | 301 | flr_config = Hash["core_version" => "#{Flr::CORE_VERSION}", "dartfmt_line_length" => dartfmt_line_length, "assets" => asset_resource_dir_array, "fonts" => font_resource_dir_array] 302 | pubspec_config["flr"] = flr_config 303 | 304 | # 添加 r_dart_library(https://github.com/YK-Unit/r_dart_library)的依赖声明 305 | # - 获取正确的库版本 306 | # - 添加依赖声明 307 | # 308 | # r_dart_library的依赖声明: 309 | # ```yaml 310 | # r_dart_library: 0.1.1 311 | # ``` 312 | r_dart_library_version = get_r_dart_library_version 313 | dependencies_config["r_dart_library"] = r_dart_library_version 314 | pubspec_config["dependencies"] = dependencies_config 315 | 316 | puts("add flr configuration into pubspec.yaml done!") 317 | puts("add dependency \"r_dart_library\"(https://github.com/YK-Unit/r_dart_library) into pubspec.yaml done!") 318 | 319 | # ----- Step-2 End ----- 320 | 321 | # ----- Step-3 Begin ----- 322 | # 对Flutter配置进行修正,以避免执行获取依赖操作时会失败: 323 | # - 检测Flutter配置中的assets选项是否是一个非空数组;若不是,则删除assets选项; 324 | # - 检测Flutter配置中的fonts选项是否是一个非空数组;若不是,则删除fonts选项。 325 | # 326 | 327 | flutter_config = pubspec_config["flutter"] 328 | if flutter_config.nil? 329 | flutter_config = {} 330 | end 331 | 332 | flutter_assets = flutter_config["assets"] 333 | should_rm_flutter_assets_Key = true 334 | if flutter_assets.is_a?(Array) == true and flutter_assets.empty? == false 335 | should_rm_flutter_assets_Key = false 336 | end 337 | if should_rm_flutter_assets_Key 338 | flutter_config.delete("assets") 339 | end 340 | 341 | flutter_fonts = flutter_config["fonts"] 342 | should_rm_flutter_fonts_Key = true 343 | if flutter_fonts.is_a?(Array) == true and flutter_fonts.empty? == false 344 | should_rm_flutter_fonts_Key = false 345 | end 346 | if should_rm_flutter_fonts_Key 347 | flutter_config.delete("fonts") 348 | end 349 | 350 | pubspec_config["flutter"] = flutter_config 351 | 352 | # ----- Step-3 End ----- 353 | 354 | # 保存 pubspec.yaml 355 | FileUtil.dump_pubspec_config_to_file(pubspec_config, pubspec_file_path) 356 | 357 | puts("[√]: init #{flutter_project_root_dir} done !!!") 358 | puts("--------------------------------------------------------------------------------------") 359 | 360 | end 361 | 362 | def self.generate_all 363 | flutter_main_project_root_dir = FileUtil.get_flutter_main_project_root_dir 364 | 365 | # ----- Step-1 Begin ----- 366 | # 进行环境检测;若发现不合法的环境,则抛出异常,终止当前进程。 367 | # - 检测当前flutter主工程根目录是否存在 pubspec.yaml 368 | # 369 | begin 370 | Checker.check_pubspec_file_is_existed(flutter_main_project_root_dir) 371 | rescue Exception => e 372 | puts(e.message) 373 | return 374 | end 375 | 376 | # ----- Step-1 End ----- 377 | 378 | puts("generate for all flutter projects now...") 379 | 380 | # ----- Step-2 Begin ----- 381 | # 获取主工程和其所有子工程,对它们进行generate_one操作 382 | # - 获取flutter主工程根目录下所有的子工程目录 383 | # - 对主工程执行generate_one操作 384 | # - 对所有子工程执行generate_one操作 385 | # 386 | flutter_sub_project_root_dir_array = FileUtil.get_flutter_sub_project_root_dirs(flutter_main_project_root_dir) 387 | 388 | puts("") 389 | generate_one(flutter_main_project_root_dir) 390 | 391 | flutter_sub_project_root_dir_array.each do |flutter_project_root_dir| 392 | puts("") 393 | generate_one(flutter_project_root_dir) 394 | end 395 | 396 | # ----- Step-2 End ----- 397 | 398 | # ----- Step-3 Begin ----- 399 | # 调用 flutter 工具,为主工程和所有子工程获取依赖 400 | # 401 | puts("") 402 | puts("get dependencies for all flutter projects via execute \"flutter pub get\" now ...") 403 | 404 | get_flutter_pub_cmd = "flutter pub get" 405 | system(get_flutter_pub_cmd) 406 | 407 | puts("[√]: get dependencies for all flutter projects done !!!") 408 | 409 | # ----- Step-3 End ----- 410 | puts("") 411 | puts("[√]: generate for all flutter projects done !!!") 412 | end 413 | 414 | # 为指定 flutter 工程扫描资源目录,自动为资源添加声明到 pubspec.yaml 和生成 r.g.dart 415 | def self.generate_one(flutter_project_root_dir) 416 | puts("--------------------------- generate for specified project ---------------------------") 417 | puts("generate for #{flutter_project_root_dir} now...") 418 | 419 | # 警告日志数组 420 | warning_messages = [] 421 | 422 | # ----- Step-1 Begin ----- 423 | # 进行环境检测;若发现不合法的环境,则抛出异常,终止当前进程: 424 | # - 检测当前flutter工程根目录是否存在pubspec.yaml 425 | # - 检测当前pubspec.yaml中是否存在Flr的配置 426 | # - 检测当前flr_config中的resource_dir配置是否合法: 427 | # 判断合法的标准是:assets配置或者fonts配置了至少1个legal_resource_dir 428 | # 429 | 430 | begin 431 | Checker.check_pubspec_file_is_existed(flutter_project_root_dir) 432 | 433 | pubspec_file_path = FileUtil.get_pubspec_file_path(flutter_project_root_dir) 434 | 435 | pubspec_config = FileUtil.load_pubspec_config_from_file(pubspec_file_path) 436 | 437 | Checker.check_flr_config_is_existed(pubspec_config) 438 | 439 | flr_config = pubspec_config["flr"] 440 | 441 | resource_dir_result_tuple = Checker.check_flr_assets_is_legal(flutter_project_root_dir, flr_config) 442 | 443 | rescue Exception => e 444 | puts(e.message) 445 | puts("[x]: generate for #{flutter_project_root_dir} failed".error_style) 446 | puts("--------------------------------------------------------------------------------------") 447 | return 448 | end 449 | 450 | is_package_project_type = FileUtil.is_package_project_type?(flutter_project_root_dir) 451 | package_name = pubspec_config["name"] 452 | 453 | # ----- Step-1 End ----- 454 | 455 | # ----- Step-2 Begin ----- 456 | # 进行核心逻辑版本检测: 457 | # 检测flr_config中的core_version和当前工具的core_version是否一致;若不一致,则按照以下规则处理: 458 | # - 更新flr_config中的core_version的值为当前工具的core_version; 459 | # - 生成“核心逻辑版本不一致”的警告日志,存放到警告日志数组。 460 | # 461 | 462 | flr_core_version = flr_config["core_version"] 463 | 464 | if flr_core_version.nil? 465 | flr_core_version = "unknown" 466 | end 467 | 468 | if flr_core_version != Flr::CORE_VERSION 469 | flr_config["core_version"] = Flr::CORE_VERSION 470 | 471 | message = <<-MESSAGE 472 | #{"[!]: warning, some team members may be using Flr tool with core_version #{flr_core_version}, while you are using Flr tool with core_version #{Flr::CORE_VERSION}".warning_style} 473 | #{"[*]: to fix it, you and your team members should use the Flr tool with same core_version".tips_style} 474 | #{"[*]: \"core_version\" is the core logic version of Flr tool, you can run \"flr version\" to get it".tips_style} 475 | 476 | MESSAGE 477 | 478 | warning_messages.push(message) 479 | end 480 | 481 | # ----- Step-2 End ----- 482 | 483 | # ----- Step-3 Begin ----- 484 | # 获取assets_legal_resource_dir数组、fonts_legal_resource_dir数组和illegal_resource_dir数组: 485 | # - 从flr_config中的assets配置获取assets_legal_resource_dir数组和assets_illegal_resource_dir数组; 486 | # - 从flr_config中的fonts配置获取fonts_legal_resource_dir数组和fonts_illegal_resource_dir数组; 487 | # - 合并assets_illegal_resource_dir数组和fonts_illegal_resource_dir数组为illegal_resource_dir数组‘;若illegal_resource_dir数组长度大于0,则生成“存在非法的资源目录”的警告日志,存放到警告日志数组。 488 | 489 | # 合法的资源目录数组 490 | assets_legal_resource_dir_array = resource_dir_result_tuple[0] 491 | fonts_legal_resource_dir_array = resource_dir_result_tuple[1] 492 | # 非法的资源目录数组 493 | illegal_resource_dir_array = resource_dir_result_tuple[2] 494 | 495 | if illegal_resource_dir_array.length > 0 496 | message = "[!]: warning, found the following resource directory which is not existed: ".warning_style 497 | illegal_resource_dir_array.each do |resource_dir| 498 | message = message + "\n" + " - #{resource_dir}".warning_style 499 | end 500 | 501 | warning_messages.push(message) 502 | end 503 | 504 | # ----- Step-3 End ----- 505 | 506 | # 扫描资源 507 | puts("scan assets now ...") 508 | 509 | # ----- Step-4 Begin ----- 510 | # 扫描assets_legal_resource_dir数组中的legal_resource_dir,输出有序的image_asset数组、non_svg_image_asset数组、svg_image_asset数组、illegal_image_file数组: 511 | # - 创建image_asset数组、illegal_image_file数组; 512 | # - 遍历assets_legal_resource_dir数组,按照如下处理每个资源目录: 513 | # - 扫描当前资源目录和其所有层级的子目录,查找所有image_file; 514 | # - 根据legal_resource_file的标准,筛选查找结果生成legal_image_file子数组和illegal_image_file子数组;illegal_image_file子数组合并到illegal_image_file数组; 515 | # - 根据image_asset的定义,遍历legal_image_file子数组,生成image_asset子数;组;image_asset子数组合并到image_asset数组。 516 | # - 对image_asset数组做去重处理; 517 | # - 按照字典顺序对image_asset数组做升序排列(一般使用开发语言提供的默认的sort算法即可); 518 | # - 按照SVG分类,从image_asset数组筛选得到有序的non_svg_image_asset数组和svg_image_asset数组: 519 | # - 按照SVG分类,从image_asset数组筛选得到non_svg_image_asset数组和svg_image_asset数组; 520 | # - 按照字典顺序对non_svg_image_asset数组和svg_image_asset数组做升序排列(一般使用开发语言提供的默认的sort算法即可); 521 | # - 输出有序的image_asset数组、non_svg_image_asset数组、svg_image_asset数组、illegal_image_file数组。 522 | 523 | image_asset_array = [] 524 | illegal_image_file_array = [] 525 | 526 | assets_legal_resource_dir_array.each do |resource_dir| 527 | image_file_result_tuple = FileUtil.find_image_files(resource_dir) 528 | legal_image_file_subarray = image_file_result_tuple[0] 529 | illegal_image_file_subarray = image_file_result_tuple[1] 530 | 531 | illegal_image_file_array += illegal_image_file_subarray 532 | 533 | image_asset_subarray = AssetUtil.generate_image_assets(flutter_project_root_dir, package_name, legal_image_file_subarray) 534 | image_asset_array += image_asset_subarray 535 | end 536 | 537 | image_asset_array.uniq! 538 | image_asset_array.sort! 539 | 540 | non_svg_image_asset_array = [] 541 | svg_image_asset_array = [] 542 | 543 | image_asset_array.each do |image_asset| 544 | if FileUtil.is_svg_image_resource_file?(image_asset) 545 | svg_image_asset_array.push(image_asset) 546 | else 547 | non_svg_image_asset_array.push(image_asset) 548 | end 549 | 550 | end 551 | 552 | non_svg_image_asset_array.sort! 553 | svg_image_asset_array.sort! 554 | 555 | # ----- Step-4 End ----- 556 | 557 | # ----- Step-5 Begin ----- 558 | # 扫描assets_legal_resource_dir数组中的legal_resource_dir,输出text_asset数组和illegal_text_file数组: 559 | # - 创建text_asset数组、illegal_text_file数组; 560 | # - 遍历assets_legal_resource_dir数组,按照如下处理每个资源目录: 561 | # - 扫描当前资源目录和其所有层级的子目录,查找所有text_file; 562 | # - 根据legal_resource_file的标准,筛选查找结果生成legal_text_file子数组和illegal_text_file子数组;illegal_text_file子数组合并到illegal_text_file数组; 563 | # - 根据text_asset的定义,遍历legal_text_file子数组,生成text_asset子数组;text_asset子数组合并到text_asset数组。 564 | # - 对text_asset数组做去重处理; 565 | # - 按照字典顺序对text_asset数组做升序排列(一般使用开发语言提供的默认的sort算法即可); 566 | # - 输出text_asset数组和illegal_image_file数组。 567 | # 568 | 569 | text_asset_array = [] 570 | illegal_text_file_array = [] 571 | 572 | assets_legal_resource_dir_array.each do |resource_dir| 573 | text_file_result_tuple = FileUtil.find_text_files(resource_dir) 574 | legal_text_file_subarray = text_file_result_tuple[0] 575 | illegal_text_file_subarray = text_file_result_tuple[1] 576 | 577 | illegal_text_file_array += illegal_text_file_subarray 578 | 579 | text_asset_subarray = AssetUtil.generate_text_assets(flutter_project_root_dir, package_name, legal_text_file_subarray) 580 | text_asset_array += text_asset_subarray 581 | end 582 | 583 | text_asset_array.uniq! 584 | text_asset_array.sort! 585 | 586 | # ----- Step-5 End ----- 587 | 588 | # ----- Step-6 Begin ----- 589 | # 扫描fonts_legal_resource_dir数组中的legal_resource_dir,输出font_family_config数组、illegal_font_file数组: 590 | # - 创建font_family_config数组、illegal_font_file数组; 591 | # - 遍历fonts_legal_resource_dir数组,按照如下处理每个资源目录: 592 | # - 扫描当前资源目录,获得其第1级子目录数组,并按照字典顺序对数组做升序排列(一般使用开发语言提供的默认的sort算法即可); 593 | # - 遍历第1级子目录数组,按照如下处理每个子目录: 594 | # - 获取当前子目录的名称,生成font_family_name; 595 | # - 扫描当前子目录和其所有子目录,查找所有font_file; 596 | # - 根据legal_resource_file的标准,筛选查找结果生成legal_font_file数组和illegal_font_file子数组;illegal_font_file子数组合并到illegal_font_file数组; 597 | # - 据font_asset的定义,遍历legal_font_file数组,生成font_asset_config数组; 598 | # - 按照字典顺序对生成font_asset_config数组做升序排列(比较asset的值); 599 | # - 根据font_family_config的定义,为当前子目录组织font_family_name和font_asset_config数组生成font_family_config对象,添加到font_family_config子数组;font_family_config子数组合并到font_family_config数组。 600 | # - 输出font_family_config数组、illegal_font_file数组; 601 | # - 按照字典顺序对font_family_config数组做升序排列(比较family的值)。 602 | # 603 | 604 | font_family_config_array = [] 605 | illegal_font_file_array = [] 606 | 607 | fonts_legal_resource_dir_array.each do |resource_dir| 608 | font_family_dir_array = FileUtil.find_top_child_dirs(resource_dir) 609 | 610 | font_family_dir_array.each do |font_family_dir| 611 | font_family_name = File.basename(font_family_dir) 612 | 613 | font_file_result_tuple = FileUtil.find_font_files_in_font_family_dir(font_family_dir) 614 | legal_font_file_array = font_file_result_tuple[0] 615 | illegal_font_file_subarray = font_file_result_tuple[1] 616 | 617 | illegal_font_file_array += illegal_font_file_subarray 618 | 619 | unless legal_font_file_array.length > 0 620 | next 621 | end 622 | 623 | font_asset_config_array = AssetUtil.generate_font_asset_configs(flutter_project_root_dir, package_name, legal_font_file_array) 624 | font_asset_config_array.sort!{|a, b| a["asset"] <=> b["asset"]} 625 | 626 | font_family_config = Hash["family" => font_family_name , "fonts" => font_asset_config_array] 627 | font_family_config_array.push(font_family_config) 628 | end 629 | end 630 | 631 | font_family_config_array.sort!{|a, b| a["family"] <=> b["family"]} 632 | 633 | # ----- Step-6 End ----- 634 | 635 | puts("scan assets done !!!") 636 | 637 | # ----- Step-7 Begin ----- 638 | # 检测是否存在illegal_resource_file: 639 | # - 合并illegal_image_file数组、illegal_text_file数组和illegal_font_file数组为illegal_resource_file数组; 640 | # - 若illegal_resource_file数组长度大于0,则生成“存在非法的资源文件”的警告日志,存放到警告日志数组。 641 | 642 | illegal_resource_file_array = illegal_image_file_array + illegal_text_file_array + illegal_font_file_array 643 | if illegal_resource_file_array.length > 0 644 | message = "[!]: warning, found the following illegal resource file who's file basename contains illegal characters: ".warning_style 645 | illegal_resource_file_array.each do |resource_file| 646 | message = message + "\n" + " - #{resource_file}".warning_style 647 | end 648 | message = message + "\n" + "[*]: to fix it, you should only use letters (a-z, A-Z), numbers (0-9), and the other legal characters ('_', '+', '-', '.', '·', '!', '@', '&', '$', '¥') to name the file".tips_style 649 | 650 | warning_messages.push(message) 651 | end 652 | 653 | # ----- Step-7 End ----- 654 | 655 | puts("specify scanned assets in pubspec.yaml now ...") 656 | 657 | # ----- Step-8 Begin ----- 658 | # 为扫描得到的legal_resource_file添加资源声明到pubspec.yaml: 659 | # - 合并image_asset数组和text_asset数组为new_asset_array(image_asset数组元素在前); 660 | # - 读取pubspec.yaml中flutter-assets配置,获得old_asset_array,然后和new_asset_array合并为asset数组; 661 | # - 修改pubspec.yaml中flutter-assets配置的值为asset数组; 662 | # - 修改pubspec.yaml中flutter-fonts配置的值为font_family_config数组。 663 | 664 | flutter_config = pubspec_config["flutter"] 665 | if flutter_config.nil? 666 | flutter_config = {} 667 | end 668 | 669 | new_asset_array = image_asset_array + text_asset_array 670 | old_asset_array = flutter_config["assets"] 671 | if old_asset_array.nil? or old_asset_array.is_a?(Array) == false 672 | old_asset_array = [] 673 | end 674 | 675 | asset_array = AssetUtil.mergeFlutterAssets(flutter_project_root_dir, package_name, new_asset_array, old_asset_array) 676 | if asset_array.length > 0 677 | flutter_config["assets"] = asset_array 678 | else 679 | flutter_config.delete("assets") 680 | end 681 | 682 | if font_family_config_array.length > 0 683 | flutter_config["fonts"] = font_family_config_array 684 | else 685 | flutter_config.delete("fonts") 686 | end 687 | 688 | pubspec_config["flutter"] = flutter_config 689 | FileUtil.dump_pubspec_config_to_file(pubspec_config, pubspec_file_path) 690 | 691 | # ----- Step-8 End ----- 692 | 693 | puts("specify scanned assets in pubspec.yaml done !!!") 694 | 695 | # ----- Step-9 Begin ----- 696 | # 分别遍历non_svg_image_asset数组、svg_image_asset数组、text_asset数组, 697 | # 根据asset_id生成算法,分别输出non_svg_image_asset_id字典、svg_image_asset_id 字典、text_asset_id字典。 698 | # 字典的key为asset,value为asset_id。 699 | # 700 | non_svg_image_asset_id_dict = Hash[] 701 | svg_image_asset_id_dict = Hash[] 702 | text_asset_id_dict = Hash[] 703 | 704 | non_svg_image_asset_array.each do |asset| 705 | used_asset_id_array = non_svg_image_asset_id_dict.values 706 | asset_id = CodeUtil.generate_asset_id(asset, used_asset_id_array, Flr::PRIOR_NON_SVG_IMAGE_FILE_TYPE) 707 | non_svg_image_asset_id_dict[asset] = asset_id 708 | end 709 | 710 | svg_image_asset_array.each do |asset| 711 | used_asset_id_array = svg_image_asset_id_dict.values 712 | asset_id = CodeUtil.generate_asset_id(asset, used_asset_id_array, Flr::PRIOR_SVG_IMAGE_FILE_TYPE) 713 | svg_image_asset_id_dict[asset] = asset_id 714 | end 715 | 716 | text_asset_array.each do |asset| 717 | used_asset_id_array = text_asset_id_dict.values 718 | asset_id = CodeUtil.generate_asset_id(asset, used_asset_id_array, Flr::PRIOR_TEXT_FILE_TYPE) 719 | text_asset_id_dict[asset] = asset_id 720 | end 721 | 722 | # ----- Step-9 End ----- 723 | 724 | puts("generate \"r.g.dart\" now ...") 725 | 726 | # ----- Step-10 Begin ----- 727 | # 在当前根目录下创建新的r.g.dart文件。 728 | # 729 | 730 | r_dart_path = "#{flutter_project_root_dir}/lib/r.g.dart" 731 | r_dart_file = File.open(r_dart_path, "w") 732 | 733 | # ----- Step-10 End ----- 734 | 735 | # ----- Step-11 Begin ----- 736 | # 生成 R 类的代码,追加写入r.g.dart 737 | # 738 | 739 | g_R_class_code = CodeUtil.generate_R_class(package_name) 740 | r_dart_file.puts(g_R_class_code) 741 | 742 | # ----- Step-11 End ----- 743 | 744 | # ----- Step-12 Begin ----- 745 | # 生成 AssetResource 类的代码,追加写入r.g.dart 746 | # 747 | 748 | r_dart_file.puts("\n") 749 | g_AssetResource_class_code = CodeUtil.generate_AssetResource_class(package_name) 750 | r_dart_file.puts(g_AssetResource_class_code) 751 | 752 | # ----- Step-12 End ----- 753 | 754 | # ----- Step-13 Begin ----- 755 | # 遍历 non_svg_image_asset 数组,生成 _R_Image_AssetResource 类,追加写入 r.g.dart 756 | # 757 | 758 | r_dart_file.puts("\n") 759 | g__R_Image_AssetResource_class_code = CodeUtil.generate__R_Image_AssetResource_class(non_svg_image_asset_array, non_svg_image_asset_id_dict, package_name, is_package_project_type) 760 | r_dart_file.puts(g__R_Image_AssetResource_class_code) 761 | 762 | # ----- Step-13 End ----- 763 | 764 | # ----- Step-14 Begin ----- 765 | # 遍历 svg_image_asset 数组,生成 _R_Svg_AssetResource 类,追加写入 r.g.dart。 766 | # 767 | 768 | r_dart_file.puts("\n") 769 | g__R_Svg_AssetResource_class_code = CodeUtil.generate__R_Svg_AssetResource_class(svg_image_asset_array, svg_image_asset_id_dict, package_name, is_package_project_type) 770 | r_dart_file.puts(g__R_Svg_AssetResource_class_code) 771 | 772 | # ----- Step-14 End ----- 773 | 774 | # ----- Step-15 Begin ----- 775 | # 遍历 text_asset 数组,生成 _R_Image_AssetResource 类,追加写入 r.g.dart 776 | # 777 | 778 | r_dart_file.puts("\n") 779 | g__R_Text_AssetResource_class_code = CodeUtil.generate__R_Text_AssetResource_class(text_asset_array, text_asset_id_dict, package_name, is_package_project_type) 780 | r_dart_file.puts(g__R_Text_AssetResource_class_code) 781 | 782 | # ----- Step-15 End ----- 783 | 784 | 785 | # ----- Step-16 Begin ----- 786 | # 遍历non_svg_image_asset数组,生成 _R_Image 类,追加写入 r.g.dart 787 | # 788 | 789 | r_dart_file.puts("\n") 790 | g__R_Image_class_code = CodeUtil.generate__R_Image_class(non_svg_image_asset_array, non_svg_image_asset_id_dict, package_name) 791 | r_dart_file.puts(g__R_Image_class_code) 792 | 793 | # ----- Step-16 End ----- 794 | 795 | # ----- Step-17 Begin ----- 796 | # 遍历 svg_image_asset 数组,生成 _R_Svg 类,追加写入 r.g.dart。 797 | # 798 | 799 | r_dart_file.puts("\n") 800 | g__R_Svg_class_code = CodeUtil.generate__R_Svg_class(svg_image_asset_array, svg_image_asset_id_dict, package_name) 801 | r_dart_file.puts(g__R_Svg_class_code) 802 | 803 | # ----- Step-17 End ----- 804 | 805 | # ----- Step-18 Begin ----- 806 | # 遍历 text_asset 数组,生成 _R_Image 类,追加写入 r.g.dart。 807 | # 808 | 809 | r_dart_file.puts("\n") 810 | g__R_Text_class_code = CodeUtil.generate__R_Text_class(text_asset_array, text_asset_id_dict, package_name) 811 | r_dart_file.puts(g__R_Text_class_code) 812 | 813 | # ----- Step-18 End ----- 814 | 815 | # ----- Step-19 Begin ----- 816 | # 遍历font_family_config数组,根据下面的模板生成_R_Font_Family类,追加写入r.g.dart。 817 | 818 | r_dart_file.puts("\n") 819 | g__R_Font_Family_class_code = CodeUtil.generate__R_FontFamily_class(font_family_config_array, package_name) 820 | r_dart_file.puts(g__R_Font_Family_class_code) 821 | 822 | # ----- Step-19 End ----- 823 | 824 | # ----- Step-20 Begin ----- 825 | # 结束操作,保存 r.g.dart 826 | # 827 | 828 | r_dart_file.close 829 | puts("generate \"r.g.dart\" done !!!") 830 | 831 | # ----- Step-20 End ----- 832 | 833 | # ----- Step-21 Begin ----- 834 | # 调用 flutter 工具对 r.g.dart 进行格式化操作 835 | # 836 | 837 | dartfmt_line_length = flr_config["dartfmt_line_length"] 838 | if dartfmt_line_length.nil? or dartfmt_line_length.is_a?(Integer) == false 839 | dartfmt_line_length = Flr::DARTFMT_LINE_LENGTH 840 | end 841 | 842 | if dartfmt_line_length < Flr::DARTFMT_LINE_LENGTH 843 | dartfmt_line_length = Flr::DARTFMT_LINE_LENGTH 844 | end 845 | 846 | flutter_format_cmd = "flutter format #{r_dart_path} -l #{dartfmt_line_length}" 847 | puts("execute \"#{flutter_format_cmd}\" now ...") 848 | system(flutter_format_cmd) 849 | puts("execute \"#{flutter_format_cmd}\" done !!!") 850 | 851 | # ----- Step-21 End ----- 852 | 853 | # ----- Step-22 Begin ----- 854 | # 判断警告日志数组是否为空,若不为空,输出所有警告日志 855 | # 856 | 857 | if warning_messages.length > 0 858 | warning_messages.each do |warning_message| 859 | puts("") 860 | puts(warning_message) 861 | end 862 | end 863 | 864 | # ----- Step-22 End ----- 865 | 866 | puts("[√]: generate for #{flutter_project_root_dir} done !!!") 867 | puts("--------------------------------------------------------------------------------------") 868 | 869 | end 870 | 871 | # 启动一个资源变化监控服务,若检测到flutter主工程和其子工程有资源变化,就自动执行generate_all操作; 872 | # 手动输入`Ctrl-C`,可终止当前服务 873 | def self.start_monitor 874 | 875 | flutter_main_project_root_dir = FileUtil.get_flutter_main_project_root_dir 876 | 877 | # ----- Step-1 Begin ----- 878 | # 对flutter主工程进行环境检测: 879 | # - 检测当前flutter主工程根目录是否存在 pubspec.yaml 880 | # 881 | 882 | begin 883 | Checker.check_pubspec_file_is_existed(flutter_main_project_root_dir) 884 | rescue Exception => e 885 | puts(e.message) 886 | return 887 | end 888 | 889 | # ----- Step-1 End ----- 890 | 891 | 892 | # ----- Step-2 Begin ----- 893 | # 对flutter工程进行合法资源目录检测: 894 | # - 获取主工程的所有子工程根目录,生成工程根目录数组flutter_project_root_dir_array 895 | # - 遍历flutter_project_root_dir_array,获取每个工程的legal_resource_dir数组: 896 | # - 从flr_config中的assets配置获取assets_legal_resource_dir数组; 897 | # - 从flr_config中的fonts配置获取fonts_legal_resource_dir数组; 898 | # - 合并assets_legal_resource_dir数组和fonts_legal_resource_dir数组到legal_resource_dir数组。 899 | # - 检测legal_resource_dir数组是否为空,为空则结束运行。 900 | # 901 | puts("") 902 | puts("get the valid resource directories of all projects now ...") 903 | 904 | flutter_main_project_root_dir = FileUtil.get_flutter_main_project_root_dir 905 | flutter_sub_project_root_dir_array = FileUtil.get_flutter_sub_project_root_dirs(flutter_main_project_root_dir) 906 | 907 | flutter_project_root_dir_array = [] 908 | flutter_project_root_dir_array.push(flutter_main_project_root_dir) 909 | flutter_project_root_dir_array += flutter_sub_project_root_dir_array 910 | 911 | # 合法的资源目录数组 912 | legal_resource_dir_array = [] 913 | # 非法的资源目录数组 914 | illegal_resource_dir_array = [] 915 | 916 | flutter_project_root_dir_array.each do |flutter_project_root_dir| 917 | begin 918 | puts("") 919 | puts("--------------------------- get info of specified project ----------------------------") 920 | puts("get the valid resource directories from #{flutter_project_root_dir} now...") 921 | 922 | Checker.check_pubspec_file_is_existed(flutter_project_root_dir) 923 | 924 | pubspec_file_path = FileUtil.get_pubspec_file_path(flutter_project_root_dir) 925 | 926 | pubspec_config = FileUtil.load_pubspec_config_from_file(pubspec_file_path) 927 | 928 | Checker.check_flr_config_is_existed(pubspec_config) 929 | 930 | flr_config = pubspec_config["flr"] 931 | 932 | resource_dir_result_tuple = Checker.check_flr_assets_is_legal(flutter_project_root_dir, flr_config) 933 | 934 | assets_legal_resource_dir_array = resource_dir_result_tuple[0] 935 | fonts_legal_resource_dir_array = resource_dir_result_tuple[1] 936 | legal_resource_dir_array += (assets_legal_resource_dir_array + fonts_legal_resource_dir_array) 937 | 938 | illegal_resource_dir_array += resource_dir_result_tuple[2] 939 | puts("get the valid resource directories from #{flutter_project_root_dir} done !!!") 940 | puts("--------------------------------------------------------------------------------------") 941 | rescue Exception => e 942 | puts(e.message) 943 | puts("[x]: #{flutter_project_root_dir} has no valid resource directories".error_style) 944 | puts("--------------------------------------------------------------------------------------") 945 | end 946 | end 947 | 948 | if legal_resource_dir_array.length <= 0 949 | puts("") 950 | puts("[x]: have no valid resource directories to be monitored".error_style) 951 | return 952 | end 953 | 954 | puts("") 955 | puts("get the valid resource directories of all projects done !!!") 956 | 957 | # ----- Step-2 End ----- 958 | 959 | 960 | # ----- Step-3 Begin ----- 961 | # 执行一次 generate_all 操作 962 | # 963 | 964 | puts("") 965 | puts("now generate for all projects once before launching the monitoring service ...") 966 | puts("") 967 | generate_all 968 | puts("") 969 | puts("did generate for all projects, now is going to launching the monitoring service ...") 970 | 971 | # ----- Step-3 End ----- 972 | 973 | 974 | # ----- Step-4 Begin ----- 975 | # 启动资源监控服务 976 | # - 启动一个文件监控服务,对 legal_resource_dir 数组中的资源目录进行文件监控 977 | # - 若服务检测到资源变化(资源目录下的发生增/删/改文件),则执行一次 flr generate 操作 978 | # 979 | 980 | puts("") 981 | now_str = Time.now.to_s 982 | puts("----------------------------- #{now_str} -----------------------------") 983 | puts("launch a monitoring service now ...") 984 | puts("launching ...") 985 | # stop the monitoring service if exists 986 | stop_monitor 987 | puts("launch a monitoring service done !!!") 988 | puts("the monitoring service is monitoring the following resource directory:") 989 | legal_resource_dir_array.each do |resource_dir| 990 | puts(" - #{resource_dir}") 991 | end 992 | if illegal_resource_dir_array.length > 0 993 | puts("") 994 | puts("[!]: warning, found the following resource directory which is not existed: ".warning_style) 995 | illegal_resource_dir_array.each do |resource_dir| 996 | puts(" - #{resource_dir}".warning_style) 997 | end 998 | end 999 | puts("--------------------------------------------------------------------------------------") 1000 | puts("\n") 1001 | 1002 | # Allow array of directories as input #92 1003 | # https://github.com/guard/listen/pull/92 1004 | @@listener = Listen.to(*legal_resource_dir_array, ignore: [/\.DS_Store/], latency: 0.5, wait_for_delay: 5, relative: true) do |modified, added, removed| 1005 | # for example: 2013-03-30 03:13:14 +0900 1006 | now_str = Time.now.to_s 1007 | puts("----------------------------- #{now_str} -----------------------------") 1008 | puts("modified resource files: #{modified}") 1009 | puts("added resource files: #{added}") 1010 | puts("removed resource files: #{removed}") 1011 | puts("generate for all projects now ...") 1012 | puts("\n") 1013 | generate_all 1014 | puts("\n") 1015 | puts("generate for all projects done !!!") 1016 | puts("--------------------------------------------------------------------------------------") 1017 | puts("\n") 1018 | puts("[*]: the monitoring service is monitoring the asset changes, and then auto scan assets, specifies assets and generates \"r.g.dart\" ...".tips_style) 1019 | puts("[*]: you can press \"Ctrl-C\" to terminate it".tips_style) 1020 | puts("\n") 1021 | end 1022 | # not blocking 1023 | @@listener.start 1024 | 1025 | # https://ruby-doc.org/core-2.5.0/Interrupt.html 1026 | begin 1027 | puts("[*]: the monitoring service is monitoring the asset changes, and then auto scan assets, specifies assets and generates \"r.g.dart\" ...".tips_style) 1028 | puts("[*]: you can press \"Ctrl-C\" to terminate it".tips_style) 1029 | puts("\n") 1030 | loop {} 1031 | rescue Interrupt => e 1032 | stop_monitor 1033 | puts("") 1034 | puts("[√]: terminate monitor service done !!!") 1035 | end 1036 | 1037 | # ----- Step-4 End ----- 1038 | 1039 | end 1040 | 1041 | # 停止资源变化监控服务 1042 | def self.stop_monitor 1043 | if @@listener.nil? == false 1044 | @@listener.stop 1045 | @@listener = nil 1046 | end 1047 | end 1048 | 1049 | end 1050 | 1051 | end -------------------------------------------------------------------------------- /lib/flr/constant.rb: -------------------------------------------------------------------------------- 1 | module Flr 2 | # Flr支持的非SVG类图片文件类型 3 | NON_SVG_IMAGE_FILE_TYPES = %w(.png .jpg .jpeg .gif .webp .icon .bmp .wbmp) 4 | # Flr支持的SVG类图片文件类型 5 | SVG_IMAGE_FILE_TYPES = %w(.svg) 6 | # Flr支持的图片文件类型 7 | IMAGE_FILE_TYPES = NON_SVG_IMAGE_FILE_TYPES + SVG_IMAGE_FILE_TYPES 8 | # Flr支持的文本文件类型 9 | TEXT_FILE_TYPES = %w(.txt .json .yaml .xml) 10 | # Flr支持的字体文件类型 11 | FONT_FILE_TYPES = %w(.ttf .otf .ttc) 12 | 13 | # Flr优先考虑的非SVG类图片文件类型 14 | PRIOR_NON_SVG_IMAGE_FILE_TYPE = ".png" 15 | # Flr优先考虑的SVG类图片文件类型 16 | PRIOR_SVG_IMAGE_FILE_TYPE = ".svg" 17 | # Flr优先考虑的文本文件类型 18 | # 当前值为 ".*", 意味所有文本文件类型的优先级都一样 19 | PRIOR_TEXT_FILE_TYPE = ".*" 20 | # Flr优先考虑的字体文件类型 21 | # 当前值为 ".*", 意味所有文本文件类型的优先级都一样 22 | PRIOR_FONT_FILE_TYPE = ".*" 23 | 24 | # dartfmt工具的默认行长 25 | DARTFMT_LINE_LENGTH = 80 26 | end 27 | -------------------------------------------------------------------------------- /lib/flr/string_extensions.rb: -------------------------------------------------------------------------------- 1 | # 参考:https://gist.github.com/lnznt/2663516 2 | class String 3 | def black 4 | "\e[30m#{self}\e[m" 5 | end 6 | 7 | def red 8 | "\e[31m#{self}\e[m" 9 | end 10 | 11 | def green 12 | "\e[32m#{self}\e[m" 13 | end 14 | 15 | def yellow 16 | "\e[33m#{self}\e[m" 17 | end 18 | 19 | def blue 20 | "\e[34m#{self}\e[m" 21 | end 22 | 23 | def magenta 24 | "\e[35m#{self}\e[m" 25 | end 26 | 27 | def cyan 28 | "\e[36m#{self}\e[m" 29 | end 30 | 31 | def white 32 | "\e[37m#{self}\e[m" 33 | end 34 | 35 | def bg_black 36 | "\e[40m#{self}\e[m" 37 | end 38 | 39 | def bg_red 40 | "\e[41m#{self}\e[m" 41 | end 42 | 43 | def bg_green 44 | "\e[42m#{self}\e[m" 45 | end 46 | 47 | def bg_yellow 48 | "\e[43m#{self}\e[m" 49 | end 50 | 51 | def bg_blue 52 | "\e[44m#{self}\e[m" 53 | end 54 | 55 | def bg_magenta 56 | "\e[45m#{self}\e[m" 57 | end 58 | 59 | def bg_cyan 60 | "\e[46m#{self}\e[m" 61 | end 62 | 63 | def bg_white 64 | "\e[47m#{self}\e[m" 65 | end 66 | 67 | def bold 68 | "\e[1m#{self}\e[m" 69 | end 70 | 71 | def bright 72 | "\e[2m#{self}\e[m" 73 | end 74 | 75 | def italic 76 | "\e[3m#{self}\e[m" 77 | end 78 | 79 | def underline 80 | "\e[4m#{self}\e[m" 81 | end 82 | 83 | def blink 84 | "\e[5m#{self}\e[m" 85 | end 86 | 87 | def reverse_color 88 | "\e[7m#{self}\e[m" 89 | end 90 | 91 | def error_style 92 | self.red 93 | end 94 | 95 | def warning_style 96 | self.yellow 97 | end 98 | 99 | def tips_style 100 | self.green 101 | end 102 | end -------------------------------------------------------------------------------- /lib/flr/util/asset_util.rb: -------------------------------------------------------------------------------- 1 | require 'yaml' 2 | require 'flr/constant' 3 | 4 | module Flr 5 | 6 | # 资产相关的工具类方法 7 | class AssetUtil 8 | 9 | # is_asset_variant?(legal_resource_file) -> true or false 10 | # 11 | # 判断当前的资源文件是不是资产变体(asset_variant)类型 12 | # 13 | # 判断的核心算法是: 14 | # - 获取资源文件的父目录; 15 | # - 判断父目录是否符合资产变体目录的特征 16 | # 资产变体映射的的资源文件要求存放在“与 main_asset 在同一个目录下的”、“符合指定特征的”子目录中; 17 | # 截止目前,Flutter只支持一种变体类型:倍率变体; 18 | # 倍率变体只适用于非SVG类图片资源; 19 | # 倍率变体目录特征可使用此正则来判断:“^((0\.[0-9]+)|([1-9]+[0-9]*(\.[0-9]+)?))[x]$”; 20 | # 倍率变体目录名称示例:“0.5x”、“1.5x”、“2.0x”、“3.0x”,“2x”、“3x”; 21 | # 22 | def self.is_asset_variant?(legal_resource_file) 23 | 24 | if FileUtil.is_non_svg_image_resource_file?(legal_resource_file) 25 | dirname = File.dirname(legal_resource_file) 26 | parent_dir_name = File.basename(dirname) 27 | 28 | ratio_regex = /^((0\.[0-9]+)|([1-9]+[0-9]*(\.[0-9]+)?))[x]$/ 29 | if parent_dir_name =~ ratio_regex 30 | return true 31 | end 32 | end 33 | 34 | return false 35 | end 36 | 37 | # is_image_asset?(asset) -> true or false 38 | # 39 | # 判断当前资产是不是图片类资产 40 | # 41 | # === Examples 42 | # 43 | # === Example-1 44 | # asset = "packages/flutter_r_demo/assets/images/test.png" 45 | # @return true 46 | # 47 | # === Example-2 48 | # asset = "assets/images/test.png" 49 | # @return true 50 | # 51 | def self.is_image_asset?(asset) 52 | if FileUtil.is_image_resource_file?(asset) 53 | return true 54 | end 55 | 56 | return false 57 | end 58 | 59 | # is_package_asset?(asset) -> true or false 60 | # 61 | # 判断当前资产是不是package类资产 62 | # 63 | # === Examples 64 | # 65 | # === Example-1 66 | # asset = "packages/flutter_r_demo/assets/images/test.png" 67 | # @return true 68 | # 69 | # === Example-2 70 | # asset = "assets/images/test.png" 71 | # @return false 72 | # 73 | def self.is_package_asset?(asset) 74 | package_prefix = "packages/" 75 | if asset =~ /\A#{package_prefix}/ 76 | return true 77 | end 78 | 79 | return false 80 | end 81 | 82 | # is_specified_package_asset?(package_name, asset) -> true or false 83 | # 84 | # 判断当前资产是不是指定的package的资产 85 | # 86 | # === Examples 87 | # package_name = "flutter_r_demo" 88 | # 89 | # === Example-1 90 | # asset = "packages/flutter_r_demo/assets/images/test.png" 91 | # @return true 92 | # 93 | # === Example-2 94 | # asset = "packages/hello_demo/assets/images/test.png" 95 | # @return false 96 | # 97 | def self.is_specified_package_asset?(package_name, asset) 98 | specified_package_prefix = "packages/" + package_name + "/" 99 | if asset =~ /\A#{specified_package_prefix}/ 100 | return true 101 | end 102 | 103 | return false 104 | end 105 | 106 | # get_main_resource_file(flutter_project_dir, package_name, asset) -> main_resource_file 107 | # 108 | # 获取指定flutter工程的asset对应的主资源文件 109 | # 注意:主资源文件不一定存在,比如图片资产可能只存在变体资源文件 110 | # 111 | # === Examples 112 | # flutter_project_dir = "~/path/to/flutter_r_demo" 113 | # package_name = "flutter_r_demo" 114 | # 115 | # === Example-1 116 | # asset = "packages/flutter_r_demo/assets/images/test.png" 117 | # main_resource_file = "~/path/to/flutter_r_demo/lib/assets/images/test.png" 118 | # 119 | # === Example-2 120 | # asset = "assets/images/test.png" 121 | # main_resource_file = "~/path/to/flutter_r_demo/assets/images/test.png" 122 | # 123 | def self.get_main_resource_file(flutter_project_dir, package_name, asset) 124 | if is_specified_package_asset?(package_name, asset) 125 | specified_package_prefix = "packages/" + package_name + "/" 126 | 127 | # asset: packages/flutter_r_demo/assets/images/test.png 128 | # to get implied_relative_resource_file: lib/assets/images/test.png 129 | implied_relative_resource_file = asset.dup 130 | implied_relative_resource_file[specified_package_prefix] = "" 131 | implied_relative_resource_file = "lib/" + implied_relative_resource_file 132 | 133 | # main_resource_file: ~/path/to/flutter_r_demo/lib/assets/images/test.png 134 | main_resource_file = flutter_project_dir + "/" + implied_relative_resource_file 135 | return main_resource_file 136 | else 137 | # asset: assets/images/test.png 138 | # main_resource_file: ~/path/to/flutter_r_demo/assets/images/test.png 139 | main_resource_file = flutter_project_dir + "/" + asset 140 | return main_resource_file 141 | end 142 | end 143 | 144 | # is_asset_existed?(flutter_project_dir, package_name, asset) -> true or false 145 | # 146 | # 判断指定flutter工程的asset是不是存在;存在的判断标准是:asset需要存在对应的资源文件 147 | # 148 | # === Examples 149 | # flutter_project_dir = "~/path/to/flutter_r_demo" 150 | # package_name = "flutter_r_demo" 151 | # 152 | # === Example-1 153 | # asset = "packages/flutter_r_demo/assets/images/test.png" 154 | # @return true 155 | # 156 | # === Example-2 157 | # asset = "packages/flutter_r_demo/404/not-existed.png" 158 | # @return false 159 | # 160 | def self.is_asset_existed?(flutter_project_dir, package_name, asset) 161 | # 处理指定flutter工程的asset 162 | # 1. 获取asset对应的main_resource_file 163 | # 2. 若main_resource_file是非SVG类图片资源文件,判断asset是否存在的标准是:主资源文件或者至少一个变体资源文件存在 164 | # 3. 若main_resource_file是SVG类图片资源文件或者其他资源文件,判断asset是否存在的标准是:主资源文件存在 165 | # 166 | main_resource_file = get_main_resource_file(flutter_project_dir, package_name, asset) 167 | if FileUtil.is_non_svg_image_resource_file?(main_resource_file) 168 | if File.exist?(main_resource_file) 169 | return true 170 | end 171 | 172 | file_name = File.basename(main_resource_file) 173 | file_dir = File.dirname(main_resource_file) 174 | did_find_variant_resource_file = false 175 | Dir.glob(["#{file_dir}/*/#{file_name}"]).each do |file| 176 | if is_asset_variant?(file) 177 | did_find_variant_resource_file = true 178 | end 179 | end 180 | 181 | if did_find_variant_resource_file 182 | return true 183 | end 184 | else 185 | if File.exist?(main_resource_file) 186 | return true 187 | end 188 | end 189 | 190 | return false 191 | end 192 | 193 | # generate_main_asset(flutter_project_dir, package_name, legal_resource_file) -> main_asset 194 | # 195 | # 为当前资源文件生成 main_asset 196 | # 197 | # === Examples 198 | # flutter_project_dir = "~/path/to/flutter_r_demo" 199 | # package_name = "flutter_r_demo" 200 | # 201 | # === Example-1 202 | # legal_resource_file = "~/path/to/flutter_r_demo/lib/assets/images/test.png" 203 | # main_asset = "packages/flutter_r_demo/assets/images/test.png" 204 | # 205 | # === Example-2 206 | # legal_resource_file = "~/path/to/flutter_r_demo/lib/assets/images/3.0x/test.png" 207 | # main_asset = "packages/flutter_r_demo/assets/images/test.png" 208 | # 209 | # === Example-3 210 | # legal_resource_file = "~/path/to/flutter_r_demo/lib/assets/texts/3.0x/test.json" 211 | # main_asset = "packages/flutter_r_demo/assets/texts/3.0x/test.json" 212 | # 213 | # === Example-3 214 | # legal_resource_file = "~/path/to/flutter_r_demo/lib/assets/fonts/Amiri/Amiri-Regular.ttf" 215 | # main_asset = "packages/flutter_r_demo/fonts/Amiri/Amiri-Regular.ttf" 216 | # 217 | # === Example-4 218 | # legal_resource_file = "~/path/to/flutter_r_demo/assets/images/test.png" 219 | # main_asset = "assets/images/test.png" 220 | # 221 | # === Example-5 222 | # legal_resource_file = "~/path/to/flutter_r_demo/assets/images/3.0x/test.png" 223 | # main_asset = "assets/images/test.png" 224 | # 225 | def self.generate_main_asset(flutter_project_dir, package_name, legal_resource_file) 226 | # legal_resource_file: ~/path/to/flutter_r_demo/lib/assets/images/3.0x/test.png 227 | # to get main_resource_file: ~/path/to/flutter_r_demo/lib/assets/images/test.png 228 | main_resource_file = legal_resource_file 229 | if is_asset_variant?(legal_resource_file) 230 | # test.png 231 | file_basename = File.basename(legal_resource_file) 232 | # ~/path/to/flutter_r_demo/lib/assets/images/3.0x 233 | file_dir = File.dirname(legal_resource_file) 234 | # ~/path/to/flutter_r_demo/lib/assets/images 235 | main_resource_file_dir = File.dirname(file_dir) 236 | # ~/path/to/flutter_r_demo/lib/assets/images/test.png 237 | main_resource_file = main_resource_file_dir + "/" + file_basename 238 | end 239 | 240 | # main_resource_file: ~/path/to/flutter_r_demo/lib/assets/images/test.png 241 | # to get main_relative_resource_file: lib/assets/images/test.png 242 | flutter_project_dir_prefix = "#{flutter_project_dir}/" 243 | main_relative_resource_file = main_resource_file 244 | if main_relative_resource_file =~ /\A#{flutter_project_dir_prefix}/ 245 | main_relative_resource_file["#{flutter_project_dir_prefix}"] = "" 246 | end 247 | 248 | # 判断 main_relative_resource_file 是不是 implied_resource_file 类型 249 | # implied_resource_file 的定义是:放置在 "lib/" 目录内 resource_file 250 | # 具体实现是:main_relative_resource_file 的前缀若是 "lib/" ,则其是 implied_resource_file 类型; 251 | # 252 | # implied_relative_resource_file 生成 main_asset 的算法是: main_asset = "packages/#{package_name}/#{asset_name}" 253 | # non-implied_relative_resource_file 生成 main_asset 的算法是: main_asset = "#{asset_name}" 254 | # 255 | lib_prefix = "lib/" 256 | if main_relative_resource_file =~ /\A#{lib_prefix}/ 257 | # main_relative_resource_file: lib/assets/images/test.png 258 | # to get asset_name: assets/images/test.png 259 | asset_name = main_relative_resource_file 260 | asset_name[lib_prefix] = "" 261 | 262 | main_asset = "packages/#{package_name}/#{asset_name}" 263 | return main_asset 264 | else 265 | # main_relative_resource_file: assets/images/test.png 266 | # to get asset_name: assets/images/test.png 267 | asset_name = main_relative_resource_file 268 | 269 | main_asset = asset_name 270 | return main_asset 271 | end 272 | end 273 | 274 | # generate_image_assets(flutter_project_dir, package_name, legal_image_file_array) -> image_asset_array 275 | # 276 | # 遍历指定资源目录下扫描找到的legal_image_file数组生成image_asset数组 277 | # 278 | # === Examples 279 | # flutter_project_dir = "~/path/to/flutter_r_demo" 280 | # package_name = "flutter_r_demo" 281 | # legal_image_file_array = ["~/path/to/flutter_r_demo/lib/assets/images/test.png", "~/path/to/flutter_r_demo/lib/assets/images/3.0x/test.png"] 282 | # image_asset_array = ["packages/flutter_r_demo/assets/images/test.png"] 283 | # 284 | def self.generate_image_assets(flutter_project_dir, package_name, legal_image_file_array) 285 | 286 | image_asset_array = [] 287 | 288 | legal_image_file_array.each do |legal_image_file| 289 | image_asset = generate_main_asset(flutter_project_dir, package_name, legal_image_file) 290 | image_asset_array.push(image_asset) 291 | end 292 | 293 | image_asset_array.uniq! 294 | return image_asset_array 295 | end 296 | 297 | # generate_text_assets(flutter_project_dir, package_name, legal_text_file_array) -> text_asset_array 298 | # 299 | # 遍历指定资源目录下扫描找到的legal_text_file数组生成text_asset数组 300 | # 301 | # === Examples 302 | # flutter_project_dir = "~/path/to/flutter_r_demo" 303 | # package_name = "flutter_r_demo" 304 | # legal_text_file_array = ["~path/to/flutter_r_demo/lib/assets/jsons/test.json"] 305 | # text_asset_array = ["packages/flutter_r_demo/assets/jsons/test.json"] 306 | # 307 | def self.generate_text_assets(flutter_project_dir, package_name, legal_text_file_array) 308 | 309 | text_asset_array = [] 310 | 311 | legal_text_file_array.each do |legal_text_file| 312 | text_asset = generate_main_asset(flutter_project_dir, package_name, legal_text_file) 313 | text_asset_array.push(text_asset) 314 | end 315 | 316 | text_asset_array.uniq! 317 | return text_asset_array 318 | end 319 | 320 | # generate_font_asset_configs(flutter_project_dir, package_name, legal_font_file_array) -> font_asset_config_array 321 | # 322 | # 遍历指定资源目录下扫描找到的legal_font_file数组生成font_asset_config数组 323 | # 324 | # === Examples 325 | # flutter_project_dir = "~/path/to/flutter_r_demo" 326 | # package_name = "flutter_r_demo" 327 | # legal_font_file_array = ["~path/to/flutter_r_demo/lib/assets/fonts/Amiri/Amiri-Regular.ttf"] 328 | # font_asset_config_array -> [{"asset": "packages/flutter_r_demo/assets/fonts/Amiri/Amiri-Regular.ttf"}] 329 | # 330 | def self.generate_font_asset_configs(flutter_project_dir, package_name, legal_font_file_array) 331 | 332 | font_asset_config_array = [] 333 | 334 | legal_font_file_array.each do |legal_font_file| 335 | font_asset = generate_main_asset(flutter_project_dir, package_name, legal_font_file) 336 | font_asset_config = Hash["asset" => font_asset] 337 | font_asset_config_array.push(font_asset_config) 338 | end 339 | 340 | font_asset_config_array.uniq!{|config| config["asset"]} 341 | return font_asset_config_array 342 | end 343 | 344 | # mergeFlutterAssets(new_asset_array, old_asset_array) -> merged_asset_array 345 | # 346 | # 合并新旧2个asset数组: 347 | # - old_asset_array - new_asset_array = diff_asset_array,获取old_asset_array与new_asset_array的差异集合 348 | # - 遍历diff_asset_array,筛选合法的asset得到legal_old_asset_array;合法的asset标准是:非图片资源 + 存在对应的资源文件 349 | # - 按照字典序对legal_old_asset_array进行排序,并追加到new_asset_array 350 | # - 返回合并结果merged_asset_array 351 | # 352 | # === Examples 353 | # flutter_project_dir = "~/path/to/flutter_r_demo" 354 | # package_name = "flutter_r_demo" 355 | # new_asset_array = ["packages/flutter_r_demo/assets/images/test.png", "packages/flutter_r_demo/assets/jsons/test.json"] 356 | # old_asset_array = ["packages/flutter_r_demo/assets/htmls/test.html"] 357 | # merged_asset_array = ["packages/flutter_r_demo/assets/images/test.png", "packages/flutter_r_demo/assets/jsons/test.json", "packages/flutter_r_demo/assets/htmls/test.html"] 358 | def self.mergeFlutterAssets(flutter_project_dir, package_name, new_asset_array, old_asset_array) 359 | legal_old_asset_array = [] 360 | 361 | diff_asset_array = old_asset_array - new_asset_array; 362 | diff_asset_array.each do |asset| 363 | # 若是第三方package的资源,则合并到new_asset_array 364 | # 引用第三方package的资源的推荐做法是:通过引用第三方package的R类来访问 365 | if is_package_asset?(asset) 366 | if is_specified_package_asset?(package_name, asset) == false 367 | legal_old_asset_array.push(asset) 368 | next 369 | end 370 | end 371 | 372 | # 处理指定flutter工程的asset 373 | # 1. 判断asset是否存在 374 | # 2. 若asset存在,则合并到new_asset_array 375 | # 376 | if is_asset_existed?(flutter_project_dir, package_name, asset) 377 | legal_old_asset_array.push(asset) 378 | end 379 | end 380 | 381 | legal_old_asset_array.sort! 382 | merged_asset_array = new_asset_array + legal_old_asset_array 383 | return merged_asset_array 384 | end 385 | 386 | end 387 | end 388 | -------------------------------------------------------------------------------- /lib/flr/util/code_util.rb: -------------------------------------------------------------------------------- 1 | require 'flr/constant' 2 | 3 | module Flr 4 | 5 | # 代码生成相关的工具类方法 6 | class CodeUtil 7 | 8 | @@should_support_nullsafety = nil 9 | 10 | def self.should_support_nullsafety 11 | if @@should_support_nullsafety != nil 12 | return @@should_support_nullsafety 13 | end 14 | 15 | # $ flutter --version 16 | # Flutter 1.12.13+hotfix.5 • channel stable • https://github.com/flutter/flutter.git 17 | # Framework • revision 27321ebbad (5 weeks ago) • 2019-12-10 18:15:01 -0800 18 | # Engine • revision 2994f7e1e6 19 | # Tools • Dart 2.7.0 20 | flutter_version_result = `flutter --version` 21 | if (flutter_version_result.nil? == true || flutter_version_result.empty? == true) 22 | @@should_support_nullsafety = false 23 | return @@should_support_nullsafety 24 | end 25 | 26 | dart_version_str = flutter_version_result.split("Dart")[1] 27 | dart_version_str.strip! 28 | 29 | if Version.new(dart_version_str) >= Version.new("2.12.0") 30 | @@should_support_nullsafety = true 31 | return @@should_support_nullsafety 32 | end 33 | 34 | @@should_support_nullsafety = false 35 | return @@should_support_nullsafety 36 | end 37 | 38 | # generate_R_class(package_name) -> string 39 | # 40 | # 根据模板生成 R class 的代码 41 | # 42 | def self.generate_R_class(package_name) 43 | code = <<-CODE 44 | // IT IS GENERATED BY FLR - DO NOT MODIFY BY HAND 45 | // YOU CAN GET MORE DETAILS ABOUT FLR FROM: 46 | // - https://github.com/Fly-Mix/flr-cli 47 | // - https://github.com/Fly-Mix/flr-vscode-extension 48 | // - https://github.com/Fly-Mix/flr-as-plugin 49 | // 50 | 51 | // ignore: unused_import 52 | import 'package:flutter/widgets.dart'; 53 | // ignore: unused_import 54 | import 'package:flutter/services.dart' show rootBundle; 55 | // ignore: unused_import 56 | import 'package:path/path.dart' as path; 57 | // ignore: unused_import 58 | import 'package:flutter_svg/flutter_svg.dart'; 59 | // ignore: unused_import 60 | import 'package:r_dart_library/asset_svg.dart'; 61 | 62 | /// This `R` class is generated and contains references to static asset resources. 63 | class R { 64 | /// package name: #{package_name} 65 | static const package = "#{package_name}"; 66 | 67 | /// This `R.image` struct is generated, and contains static references to static non-svg type image asset resources. 68 | static const image = _R_Image(); 69 | 70 | /// This `R.svg` struct is generated, and contains static references to static svg type image asset resources. 71 | static const svg = _R_Svg(); 72 | 73 | /// This `R.text` struct is generated, and contains static references to static text asset resources. 74 | static const text = _R_Text(); 75 | 76 | /// This `R.fontFamily` struct is generated, and contains static references to static font asset resources. 77 | static const fontFamily = _R_FontFamily(); 78 | } 79 | CODE 80 | 81 | return code 82 | end 83 | 84 | 85 | # generate_AssetResource_class(package_name) -> string 86 | # 87 | # 根据模板生成 AssetResource class 的代码 88 | # 89 | def self.generate_AssetResource_class(package_name) 90 | code = <<-CODE 91 | /// Asset resource’s metadata class. 92 | /// For example, here is the metadata of `packages/flutter_demo/assets/images/example.png` asset: 93 | /// - packageName:flutter_demo 94 | /// - assetName:assets/images/example.png 95 | /// - fileDirname:assets/images 96 | /// - fileBasename:example.png 97 | /// - fileBasenameNoExtension:example 98 | /// - fileExtname:.png 99 | CODE 100 | 101 | if should_support_nullsafety 102 | code += <<-CODE 103 | class AssetResource { 104 | /// Creates an object to hold the asset resource’s metadata. 105 | const AssetResource(this.assetName, {this.packageName}); 106 | 107 | /// The name of the main asset from the set of asset resources to choose from. 108 | final String assetName; 109 | 110 | /// The name of the package from which the asset resource is included. 111 | final String? packageName; 112 | 113 | /// The name used to generate the key to obtain the asset resource. For local assets 114 | /// this is [assetName], and for assets from packages the [assetName] is 115 | /// prefixed 'packages//'. 116 | String get keyName => packageName == null ? assetName : "packages/$packageName/$assetName"; 117 | 118 | /// The file basename of the asset resource. 119 | String get fileBasename { 120 | final basename = path.basename(assetName); 121 | return basename; 122 | } 123 | 124 | /// The no extension file basename of the asset resource. 125 | String get fileBasenameNoExtension { 126 | final basenameWithoutExtension = path.basenameWithoutExtension(assetName); 127 | return basenameWithoutExtension; 128 | } 129 | 130 | /// The file extension name of the asset resource. 131 | String get fileExtname { 132 | final extension = path.extension(assetName); 133 | return extension; 134 | } 135 | 136 | /// The directory path name of the asset resource. 137 | String get fileDirname { 138 | var dirname = assetName; 139 | if (packageName != null) { 140 | final packageStr = "packages/$packageName/"; 141 | dirname = dirname.replaceAll(packageStr, ""); 142 | } 143 | final filenameStr = "$fileBasename/"; 144 | dirname = dirname.replaceAll(filenameStr, ""); 145 | return dirname; 146 | } 147 | } 148 | CODE 149 | else 150 | code += <<-CODE 151 | class AssetResource { 152 | /// Creates an object to hold the asset resource’s metadata. 153 | const AssetResource(this.assetName, {this.packageName}) : assert(assetName != null); 154 | 155 | /// The name of the main asset from the set of asset resources to choose from. 156 | final String assetName; 157 | 158 | /// The name of the package from which the asset resource is included. 159 | final String packageName; 160 | 161 | /// The name used to generate the key to obtain the asset resource. For local assets 162 | /// this is [assetName], and for assets from packages the [assetName] is 163 | /// prefixed 'packages//'. 164 | String get keyName => packageName == null ? assetName : "packages/$packageName/$assetName"; 165 | 166 | /// The file basename of the asset resource. 167 | String get fileBasename { 168 | final basename = path.basename(assetName); 169 | return basename; 170 | } 171 | 172 | /// The no extension file basename of the asset resource. 173 | String get fileBasenameNoExtension { 174 | final basenameWithoutExtension = path.basenameWithoutExtension(assetName); 175 | return basenameWithoutExtension; 176 | } 177 | 178 | /// The file extension name of the asset resource. 179 | String get fileExtname { 180 | final extension = path.extension(assetName); 181 | return extension; 182 | } 183 | 184 | /// The directory path name of the asset resource. 185 | String get fileDirname { 186 | var dirname = assetName; 187 | if (packageName != null) { 188 | final packageStr = "packages/$packageName/"; 189 | dirname = dirname.replaceAll(packageStr, ""); 190 | } 191 | final filenameStr = "$fileBasename/"; 192 | dirname = dirname.replaceAll(filenameStr, ""); 193 | return dirname; 194 | } 195 | } 196 | CODE 197 | end 198 | 199 | return code 200 | end 201 | 202 | # generate_asset_id (asset, used_asset_id_array, prior_asset_type) -> string 203 | # 204 | # - prior_asset_type: 优先的资源类型;默认值为 ".*",意味当前不存在任何优先的资源类型 205 | # 对于优先的资源类型,其 asset_id 不会携带类型信息,详细见例子 206 | # 207 | # 为当前 asset 生成 asset_id(资产ID);asset_id 一般为 asset 的 file_basename_no_extension; 208 | # 但是为了保证 asset_id 的健壮性,需要对 file_basename_no_extension 做以下加工处理: 209 | # - 处理非法字符:把除了字母(a-z, A-Z)、数字(0-9)、'_' 字符、'$' 字符之外的字符转换为 '_' 字符 210 | # - 首字母转化为小写 211 | # - 处理首字符异常情况:检测首字符是不是数字、'_'、'$',若是则添加前缀字符"a" 212 | # - 处理 asset_id 重名的情况 213 | # 214 | # === Examples 215 | # 216 | # ===== Example-1 217 | # asset = "packages/flutter_r_demo/assets/images/test.png" 218 | # asset = "packages/flutter_r_demo/assets/images/test.jpg" 219 | # used_asset_id_array = [] 220 | # prior_asset_type = ".png" 221 | # asset_id = "test" 222 | # 223 | # ===== Example-2 224 | # asset = "packages/flutter_r_demo/assets/images/test.jpg" 225 | # used_asset_id_array = [test] 226 | # prior_asset_type = ".png" 227 | # asset_id = "test_jpg" 228 | # 229 | # ===== Example-3 230 | # asset = "packages/flutter_r_demo/assets/home-images/test.jpg" 231 | # used_asset_id_array = [test, test_jpg] 232 | # prior_asset_type = ".png" 233 | # asset_id = "test_jpg_1" 234 | # 235 | # ===== Example-4 236 | # asset = "packages/flutter_r_demo/assets/texts/test.json" 237 | # used_asset_id_array = [] 238 | # prior_asset_type = ".*" 239 | # asset_id = "test_json" 240 | # 241 | def self.generate_asset_id(asset, used_asset_id_array, prior_asset_type = ".*") 242 | file_extname = File.extname(asset).downcase 243 | 244 | dirname = File.dirname(asset) 245 | parent_dir_name = File.basename(dirname) 246 | file_basename = File.basename(asset) 247 | 248 | file_basename_no_extension = File.basename(asset, ".*") 249 | asset_id = file_basename_no_extension.dup 250 | if prior_asset_type.eql?(".*") or file_extname.eql?(prior_asset_type) == false 251 | ext_info = file_extname 252 | ext_info[0] = "_" 253 | asset_id = asset_id + ext_info 254 | end 255 | 256 | # 处理非法字符 257 | asset_id = asset_id.gsub(/[^a-zA-Z0-9_$]/, "_") 258 | 259 | # 首字母转化为小写 260 | capital = asset_id[0].downcase 261 | asset_id[0] = capital 262 | 263 | # 处理首字符异常情况 264 | if capital =~ /[0-9_$]/ 265 | asset_id = "a" + asset_id 266 | end 267 | 268 | # 处理 asset_id 重名的情况 269 | if used_asset_id_array.include?(asset_id) 270 | # 当前asset_id重名次数,初始值为1 271 | repeat_count = 1 272 | 273 | # 查找当前asset_id衍生出来的asset_id_brother(id兄弟) 274 | # asset_id_brother = #{asset_id}$#{repeat_count} 275 | # 其中,repeat_count >= 1 276 | # 277 | # Example: 278 | # asset_id = test 279 | # asset_id_brother = test$1 280 | # 281 | id_brother_regx = /^#{asset_id}\$[1-9][0-9]*$/ 282 | cur_asset_id_brothers = used_asset_id_array.select{ |id| id =~ id_brother_regx } 283 | 284 | repeat_count += cur_asset_id_brothers.size 285 | asset_id = "#{asset_id}$#{repeat_count}" 286 | end 287 | 288 | return asset_id 289 | end 290 | 291 | # generate_asset_comment (asset, package_name) -> string 292 | # 293 | # 为当前asset生成注释 294 | # 295 | # === Examples 296 | # package_name = "flutter_r_demo" 297 | # 298 | # === Example-1 299 | # asset = "packages/flutter_r_demo/assets/images/test.png" 300 | # asset_comment = "asset: lib/assets/images/test.png" 301 | # 302 | # === Example-2 303 | # asset = "assets/images/test.png" 304 | # asset_comment = "asset: assets/images/test.png" 305 | # 306 | def self.generate_asset_comment (asset, package_name) 307 | packages_prefix = "packages/#{package_name}/" 308 | 309 | if asset =~ /\A#{packages_prefix}/ 310 | # asset: packages/flutter_r_demo/assets/images/test.png 311 | # to get assetName: assets/images/test.png 312 | asset_name = asset.dup 313 | asset_name[packages_prefix] = "" 314 | 315 | asset_comment = "asset: lib/#{asset_name}" 316 | return asset_comment 317 | else 318 | # asset: assets/images/test.png 319 | # to get assetName: assets/images/test.png 320 | asset_name = asset.dup 321 | 322 | asset_comment = "asset: #{asset_name}" 323 | return asset_comment 324 | end 325 | 326 | end 327 | 328 | # generate_AssetResource_property(asset, asset_id_dict, package_name, is_package_project_type, prior_asset_type) -> string 329 | # 330 | # 为当前 asset 生成 AssetResource property 的代码 331 | # 332 | def self.generate_AssetResource_property(asset, asset_id_dict, package_name, is_package_project_type, prior_asset_type = ".*") 333 | asset_id = asset_id_dict[asset] 334 | asset_comment = generate_asset_comment(asset, package_name) 335 | 336 | asset_name = "" 337 | needPackage = false 338 | 339 | packages_prefix = "packages/#{package_name}/" 340 | if asset =~ /\A#{packages_prefix}/ 341 | # asset: packages/flutter_r_demo/assets/images/test.png 342 | # to get asset_name: assets/images/test.png 343 | asset_name = asset.dup 344 | asset_name[packages_prefix] = "" 345 | 346 | needPackage = true 347 | else 348 | # asset: assets/images/test.png 349 | # to get asset_name: assets/images/test.png 350 | asset_name = asset.dup 351 | 352 | if is_package_project_type 353 | needPackage = true 354 | else 355 | needPackage = false 356 | end 357 | 358 | end 359 | 360 | # 对字符串中的 '$' 进行转义处理:'$' -> '\$' 361 | # asset_name: assets/images/test$.png 362 | # to get escaped_asset_name: assets/images/test\$.png 363 | escaped_asset_name = asset_name.gsub(/[$]/, "\\$") 364 | 365 | if needPackage 366 | code = <<-CODE 367 | /// #{asset_comment} 368 | // ignore: non_constant_identifier_names 369 | final #{asset_id} = const AssetResource("#{escaped_asset_name}", packageName: R.package); 370 | CODE 371 | 372 | return code 373 | else 374 | code = <<-CODE 375 | /// #{asset_comment} 376 | // ignore: non_constant_identifier_names 377 | final #{asset_id} = const AssetResource("#{escaped_asset_name}", packageName: null); 378 | CODE 379 | 380 | return code 381 | end 382 | end 383 | 384 | # generate__R_Image_AssetResource_class(non_svg_image_asset_array, non_svg_image_asset_id_dict, package_name, is_package_project_type) -> string 385 | # 386 | # 根据模板,为 non_svg_image_asset_array(非svg类的图片资产数组)生成 _R_Image_AssetResource class 的代码 387 | # 388 | def self.generate__R_Image_AssetResource_class(non_svg_image_asset_array, non_svg_image_asset_id_dict, package_name, is_package_project_type) 389 | 390 | all_g_AssetResource_property_code = "" 391 | 392 | non_svg_image_asset_array.each do |image_asset| 393 | all_g_AssetResource_property_code += "\n" 394 | g_AssetResource_property_code = generate_AssetResource_property(image_asset, non_svg_image_asset_id_dict, package_name, is_package_project_type, Flr::PRIOR_NON_SVG_IMAGE_FILE_TYPE) 395 | all_g_AssetResource_property_code += g_AssetResource_property_code 396 | end 397 | 398 | code = <<-CODE 399 | // ignore: camel_case_types 400 | class _R_Image_AssetResource { 401 | const _R_Image_AssetResource(); 402 | #{all_g_AssetResource_property_code} 403 | } 404 | CODE 405 | 406 | return code 407 | end 408 | 409 | # generate__R_Svg_AssetResource_class(svg_image_asset_array, svg_image_asset_id_dict, package_name, is_package_project_type) -> string 410 | # 411 | # 根据模板,为 svg_image_asset_array(svg类的图片资产数组)生成 _R_Svg_AssetResource class 的代码 412 | # 413 | def self.generate__R_Svg_AssetResource_class(svg_image_asset_array, svg_image_asset_id_dict, package_name, is_package_project_type) 414 | 415 | all_g_AssetResource_property_code = "" 416 | 417 | svg_image_asset_array.each do |image_asset| 418 | all_g_AssetResource_property_code += "\n" 419 | g_AssetResource_property_code = generate_AssetResource_property(image_asset, svg_image_asset_id_dict, package_name, is_package_project_type, Flr::PRIOR_SVG_IMAGE_FILE_TYPE) 420 | all_g_AssetResource_property_code += g_AssetResource_property_code 421 | end 422 | 423 | code = <<-CODE 424 | // ignore: camel_case_types 425 | class _R_Svg_AssetResource { 426 | const _R_Svg_AssetResource(); 427 | #{all_g_AssetResource_property_code} 428 | } 429 | CODE 430 | 431 | return code 432 | end 433 | 434 | # generate__R_Text_AssetResource_class(text_asset_array, text_asset_id_dict, package_name, is_package_project_typ) -> string 435 | # 436 | # 根据模板,为 text_asset_array(文本资产数组)生成 _R_Text_AssetResource class 的代码 437 | # 438 | def self.generate__R_Text_AssetResource_class(text_asset_array, text_asset_id_dict, package_name, is_package_project_type) 439 | 440 | all_g_AssetResource_property_code = "" 441 | 442 | text_asset_array.each do |text_asset| 443 | all_g_AssetResource_property_code += "\n" 444 | g_AssetResource_property_code = generate_AssetResource_property(text_asset, text_asset_id_dict, package_name, is_package_project_type, Flr::PRIOR_TEXT_FILE_TYPE) 445 | all_g_AssetResource_property_code += g_AssetResource_property_code 446 | end 447 | 448 | code = <<-CODE 449 | // ignore: camel_case_types 450 | class _R_Text_AssetResource { 451 | const _R_Text_AssetResource(); 452 | #{all_g_AssetResource_property_code} 453 | } 454 | CODE 455 | 456 | return code 457 | end 458 | 459 | # generate__R_Image_class(non_svg_image_asset_array, non_svg_image_asset_id_dict, package_name) -> string 460 | # 461 | # 根据模板,为 non_svg_image_asset_array(非svg类的图片资产数组)生成 _R_Image class 的代码 462 | # 463 | def self.generate__R_Image_class(non_svg_image_asset_array, non_svg_image_asset_id_dict, package_name) 464 | 465 | all_g_Asset_method_code = "" 466 | 467 | non_svg_image_asset_array.each do |image_asset| 468 | all_g_Asset_method_code += "\n" 469 | 470 | asset_id = non_svg_image_asset_id_dict[image_asset] 471 | asset_comment = generate_asset_comment(image_asset, package_name) 472 | 473 | g_Asset_method_code = <<-CODE 474 | /// #{asset_comment} 475 | // ignore: non_constant_identifier_names 476 | AssetImage #{asset_id}() { 477 | return AssetImage(asset.#{asset_id}.keyName); 478 | } 479 | CODE 480 | 481 | all_g_Asset_method_code += g_Asset_method_code 482 | end 483 | 484 | code = <<-CODE 485 | /// This `_R_Image` class is generated and contains references to static non-svg type image asset resources. 486 | // ignore: camel_case_types 487 | class _R_Image { 488 | const _R_Image(); 489 | 490 | final asset = const _R_Image_AssetResource(); 491 | #{all_g_Asset_method_code} 492 | } 493 | CODE 494 | 495 | return code 496 | end 497 | 498 | # generate__R_Svg_class(svg_image_asset_array, svg_image_asset_id_dict, package_name) -> string 499 | # 500 | # 根据模板,为 svg_image_asset_array(svg类的图片资产数组)生成 _R_Svg class 的代码 501 | # 502 | def self.generate__R_Svg_class(svg_image_asset_array, svg_image_asset_id_dict, package_name) 503 | 504 | all_g_Asset_method_code = "" 505 | 506 | svg_image_asset_array.each do |image_asset| 507 | all_g_Asset_method_code += "\n" 508 | 509 | asset_id = svg_image_asset_id_dict[image_asset] 510 | asset_comment = generate_asset_comment(image_asset, package_name) 511 | 512 | if should_support_nullsafety 513 | g_Asset_method_code = <<-CODE 514 | /// #{asset_comment} 515 | // ignore: non_constant_identifier_names 516 | AssetSvg #{asset_id}({required double width, required double height}) { 517 | final imageProvider = AssetSvg(asset.#{asset_id}.keyName, width: width, height: height); 518 | return imageProvider; 519 | } 520 | CODE 521 | else 522 | g_Asset_method_code = <<-CODE 523 | /// #{asset_comment} 524 | // ignore: non_constant_identifier_names 525 | AssetSvg #{asset_id}({@required double width, @required double height}) { 526 | final imageProvider = AssetSvg(asset.#{asset_id}.keyName, width: width, height: height); 527 | return imageProvider; 528 | } 529 | CODE 530 | end 531 | 532 | all_g_Asset_method_code += g_Asset_method_code 533 | end 534 | 535 | code = <<-CODE 536 | /// This `_R_Svg` class is generated and contains references to static svg type image asset resources. 537 | // ignore: camel_case_types 538 | class _R_Svg { 539 | const _R_Svg(); 540 | 541 | final asset = const _R_Svg_AssetResource(); 542 | #{all_g_Asset_method_code} 543 | } 544 | CODE 545 | 546 | return code 547 | end 548 | 549 | # generate__R_Text_class(text_asset_array, text_asset_id_dict, package_name) -> string 550 | # 551 | # 根据模板,为 text_asset_array(文本资产数组)生成 _R_Text class 的代码 552 | # 553 | def self.generate__R_Text_class(text_asset_array, text_asset_id_dict, package_name) 554 | 555 | all_g_Asset_method_code = "" 556 | 557 | text_asset_array.each do |text_asset| 558 | all_g_Asset_method_code += "\n" 559 | 560 | asset_id = text_asset_id_dict[text_asset] 561 | asset_comment = generate_asset_comment(text_asset, package_name) 562 | 563 | g_Asset_method_code = <<-CODE 564 | /// #{asset_comment} 565 | // ignore: non_constant_identifier_names 566 | Future #{asset_id}() { 567 | final str = rootBundle.loadString(asset.#{asset_id}.keyName); 568 | return str; 569 | } 570 | CODE 571 | 572 | all_g_Asset_method_code += g_Asset_method_code 573 | end 574 | 575 | code = <<-CODE 576 | /// This `_R_Text` class is generated and contains references to static text asset resources. 577 | // ignore: camel_case_types 578 | class _R_Text { 579 | const _R_Text(); 580 | 581 | final asset = const _R_Text_AssetResource(); 582 | #{all_g_Asset_method_code} 583 | } 584 | CODE 585 | 586 | return code 587 | end 588 | 589 | # generate_font_family_id(font_family_name) -> string 590 | # 591 | # 为当前 font_family_name 生成 font_family_id;font_family_id 一般为 asset 的 font_family_name; 592 | # 但是为了保证 font_family_id 的健壮性,需要对 font_family_name 做以下加工处理: 593 | # - 处理非法字符:把除了字母(a-z, A-Z)、数字(0-9)、'_' 字符、'$' 字符之外的字符转换为 '_' 字符 594 | # - 首字母转化为小写 595 | # - 处理首字符异常情况:检测首字符是不是数字、'_'、'$',若是则添加前缀字符"a" 596 | # 597 | # === Examples 598 | # a_font_family_name = "Amiri" 599 | # b_font_family_name = "Baloo-Thambi-2" 600 | # a_font_family_id = "amiri" 601 | # b_font_family_id = "baloo_Thambi_2" 602 | # 603 | # 604 | def self.generate_font_family_id(font_family_name) 605 | 606 | font_family_id = font_family_name.dup 607 | 608 | # 处理非法字符 609 | font_family_id = font_family_id.gsub(/[^a-zA-Z0-9_$]/, "_") 610 | 611 | # 首字母转化为小写 612 | capital = font_family_id[0].downcase 613 | font_family_id[0] = capital 614 | 615 | # 处理首字符异常情况 616 | if capital =~ /[0-9_$]/ 617 | font_family_id = "a" + font_family_id 618 | end 619 | 620 | return font_family_id 621 | end 622 | 623 | # generate__R_FontFamily_class(font_family_config_array, package_name) -> string 624 | # 625 | # 根据模板,为 font_family_config_array(字体家族配置数组)生成 _R_FontFamily class 的代码 626 | # 627 | def self.generate__R_FontFamily_class(font_family_config_array, package_name) 628 | 629 | all_g_AssetResource_property_code = "" 630 | 631 | font_family_config_array.each do |font_family_config| 632 | all_g_AssetResource_property_code += "\n" 633 | 634 | font_family_name = font_family_config["family"] 635 | 636 | font_family_id = generate_font_family_id(font_family_name) 637 | font_family_comment = "font family: #{font_family_name}" 638 | 639 | g_AssetResource_property_code = <<-CODE 640 | /// #{font_family_comment} 641 | // ignore: non_constant_identifier_names 642 | final #{font_family_id} = "#{font_family_name}"; 643 | CODE 644 | 645 | all_g_AssetResource_property_code += g_AssetResource_property_code 646 | end 647 | 648 | code = <<-CODE 649 | /// This `_R_FontFamily` class is generated and contains references to static font asset resources. 650 | // ignore: camel_case_types 651 | class _R_FontFamily { 652 | const _R_FontFamily(); 653 | #{all_g_AssetResource_property_code} 654 | } 655 | CODE 656 | 657 | return code 658 | end 659 | end 660 | 661 | end -------------------------------------------------------------------------------- /lib/flr/util/file_util.rb: -------------------------------------------------------------------------------- 1 | require 'yaml' 2 | require 'flr/constant' 3 | 4 | module Flr 5 | 6 | # 资源文件相关的工具类方法 7 | class FileUtil 8 | 9 | # get_cur_flutter_project_root_dir -> String 10 | # 11 | # 获取flutter主工程的根目录 12 | # 13 | def self.get_flutter_main_project_root_dir 14 | flutter_project_root_dir = "#{Pathname.pwd}" 15 | return flutter_project_root_dir 16 | end 17 | 18 | # get_flutter_sub_project_root_dirs -> [sub_project_root_dir] 19 | # 20 | # 获取flutter主工程的所有子工程的根目录 21 | # 22 | def self.get_flutter_sub_project_root_dirs(flutter_main_project_root_dir) 23 | flutter_sub_project_root_dir_array = [] 24 | Dir.glob(["#{flutter_main_project_root_dir}/*/pubspec.yaml"]).each do |file| 25 | flutter_project_root_dir = File.dirname(file) 26 | flutter_sub_project_root_dir_array.push(flutter_project_root_dir) 27 | end 28 | return flutter_sub_project_root_dir_array 29 | end 30 | 31 | # get_pubspec_file_path(flutter_project_dir) -> String 32 | # 33 | # 获取当前flutter工程的pubspec.yaml文件的路径 34 | # 35 | # === Examples 36 | # flutter_project_dir = "~/path/to/flutter_r_demo" 37 | # pubspec_file_path = "~/path/to/flutter_r_demo/pubspec.yaml" 38 | # 39 | def self.get_pubspec_file_path(flutter_project_dir) 40 | file_path = flutter_project_dir + "/pubspec.yaml" 41 | return file_path 42 | end 43 | 44 | # load_pubspec_config_from_file -> Hash 45 | # 46 | # 读取pubspec.yaml到pubspec_config 47 | # 若读取成功,返回一个Hash对象pubspec_config 48 | # 若读取失败,则抛出异常 49 | # 50 | def self.load_pubspec_config_from_file(pubspec_file_path) 51 | begin 52 | pubspec_file = File.open(pubspec_file_path, 'r') 53 | pubspec_config = YAML.load(pubspec_file) 54 | rescue YAML::SyntaxError => e 55 | puts("YAML Syntax Error: #{e}".error_style) 56 | puts("") 57 | 58 | message = <<-MESSAGE 59 | 60 | #{"[x]: pubspec.yaml is damaged with syntax error".error_style} 61 | #{"[*]: please correct the pubspec.yaml file at #{pubspec_file_path}".tips_style} 62 | MESSAGE 63 | 64 | raise(message) 65 | ensure 66 | pubspec_file.close 67 | end 68 | 69 | return pubspec_config 70 | end 71 | 72 | # dump_pubspec_config_to_file -> true 73 | # 74 | # 保存pubspec_config到pubspec.yaml 75 | # 76 | def self.dump_pubspec_config_to_file(pubspec_config, pubspec_file_path) 77 | pubspec_file = File.open(pubspec_file_path, 'w') 78 | yaml_content = pubspec_config.to_yaml 79 | 80 | # Because pubspec.yaml is only one document remove, 81 | # and I want to shortcut it, 82 | # so I choose to remove three dashes (“---”). 83 | # 84 | # To get the details about three dashes (“---”) 85 | # see: https://yaml.org/spec/1.2/spec.html#id2760395 86 | # 87 | document_separate_maker = "---\n" 88 | regx = /\A#{document_separate_maker}/ 89 | if yaml_content =~ regx 90 | yaml_content[document_separate_maker] = "" 91 | end 92 | 93 | pubspec_file.write(yaml_content) 94 | pubspec_file.close 95 | return true 96 | end 97 | 98 | # is_package_project_type?(flutter_project_dir) -> true or false 99 | # 100 | # 判断当前flutter工程的工程类型是不是Package工程类型 101 | # 102 | # flutter工程共有4种工程类型: 103 | # - app:Flutter App工程,用于开发纯Flutter的App 104 | # - module:Flutter Component工程,用于开发Flutter组件以嵌入iOS和Android原生工程 105 | # - package:General Dart Package工程,用于开发一个供应用层开发者使用的包 106 | # - plugin:Plugin Package工程(属于特殊的Dart Package工程),用于开发一个调用特定平台API的包 107 | # 108 | # flutter工程的工程类型可从flutter工程目录的 .metadata 文件中读取获得 109 | # 如果不存在 .metadata 文件,则判断 pubspec.yaml 是否存在 author 配置,若存在,说明是一个 Package工程 110 | # 111 | def self.is_package_project_type?(flutter_project_dir) 112 | metadata_file_path = flutter_project_dir + "/.metadata" 113 | 114 | if File.exist?(metadata_file_path) 115 | begin 116 | metadata_file = File.open(metadata_file_path, 'r') 117 | metadata_config = YAML.load(metadata_file) 118 | project_type = metadata_config["project_type"] 119 | if project_type.nil? 120 | project_type = "unknown" 121 | end 122 | project_type = project_type.downcase 123 | 124 | if project_type == "package" || project_type == "plugin" 125 | return true 126 | end 127 | 128 | rescue YAML::SyntaxError => e 129 | puts("YAML Syntax Error: #{e}".error_style) 130 | puts("") 131 | ensure 132 | metadata_file.close 133 | end 134 | else 135 | message = <<-MESSAGE 136 | #{"[!]: warning, metadata file is missed, flr can not make sure to get a right project type of this flutter project".warning_style} 137 | #{"[!]: then flr maybe generate buggy r.g.dart".warning_style} 138 | #{"[*]: to fix it, you can manually copy the metadata file of a flutter project with same project type to #{metadata_file_path}".tips_style} 139 | 140 | MESSAGE 141 | puts(message) 142 | 143 | begin 144 | pubspec_file_path = get_pubspec_file_path(flutter_project_dir) 145 | pubspec_config = load_pubspec_config_from_file(pubspec_file_path) 146 | if pubspec_config.has_key?("author") 147 | return true 148 | end 149 | rescue Exception => e 150 | puts(e.message) 151 | end 152 | end 153 | 154 | return false 155 | end 156 | 157 | # 判断当前文件是不是非SVG类图片资源文件 158 | def self.is_non_svg_image_resource_file?(file) 159 | file_extname = File.extname(file).downcase 160 | 161 | if Flr::NON_SVG_IMAGE_FILE_TYPES.include?(file_extname) 162 | return true; 163 | end 164 | 165 | return false 166 | end 167 | 168 | # 判断当前文件是不是SVG类图片资源文件 169 | def self.is_svg_image_resource_file?(file) 170 | file_extname = File.extname(file).downcase 171 | 172 | if Flr::SVG_IMAGE_FILE_TYPES.include?(file_extname) 173 | return true; 174 | end 175 | 176 | return false 177 | end 178 | 179 | # 判断当前文件是不是图片资源文件 180 | def self.is_image_resource_file?(file) 181 | file_extname = File.extname(file).downcase 182 | 183 | if Flr::IMAGE_FILE_TYPES.include?(file_extname) 184 | return true; 185 | end 186 | 187 | return false 188 | end 189 | 190 | # 判断当前文件是不是文本资源文件 191 | def self.is_text_resource_file?(file) 192 | file_extname = File.extname(file).downcase 193 | 194 | if Flr::TEXT_FILE_TYPES.include?(file_extname) 195 | return true; 196 | end 197 | 198 | return false 199 | end 200 | 201 | # 判断当前文件是不是字体资源文件 202 | def self.is_font_resource_file?(file) 203 | file_extname = File.extname(file).downcase 204 | 205 | if Flr::FONT_FILE_TYPES.include?(file_extname) 206 | return true; 207 | end 208 | 209 | return false 210 | end 211 | 212 | # is_legal_resource_file??(file) -> true or false 213 | # 214 | # 判断当前资源文件是否合法 215 | # 216 | # 判断资源文件合法的标准是: 217 | # 其file_basename_no_extension 由字母(a-z、A-Z)、数字(0-9)、其他合法字符('_', '+', '-', '.', '·', '!', '@', '&', '$', '¥')组成 218 | # 219 | # === Examples 220 | # good_file = "~/path/to/flutter_project/lib/assets/images/test.png" 221 | # bad_file = "~/path/to/flutter_project/lib/assets/images/~.png" 222 | # is_legal_resource_file?(good_file) -> true 223 | # is_legal_resource_file?(bad_file) -> false 224 | # 225 | def self.is_legal_resource_file?(file) 226 | file_basename_no_extension = File.basename(file, ".*") 227 | regx = /^[a-zA-Z0-9_\+\-\.·!@&$¥]+$/ 228 | 229 | if file_basename_no_extension =~ regx 230 | return true 231 | else 232 | return false 233 | end 234 | end 235 | 236 | # find_image_files(resource_dir) -> image_file_result_tuple 237 | # 238 | # 扫描指定的资源目录和其所有层级的子目录,查找所有图片文件 239 | # 返回图片文件结果二元组 image_file_result_tuple 240 | # image_file_result_tuple = [legal_image_file_array, illegal_image_file_array] 241 | # 242 | # 判断文件合法的标准参考 self.is_legal_resource_file? 方法 243 | # 244 | # === Examples 245 | # resource_dir = "~/path/to/flutter_project/lib/assets/images" 246 | # legal_image_file_array = ["~/path/to/flutter_project/lib/assets/images/test.png", "~/path/to/flutter_project/lib/assets/images/2.0x/test.png"] 247 | # illegal_image_file_array = ["~/path/to/flutter_project/lib/assets/images/~.png"] 248 | # 249 | def self.find_image_files(resource_dir) 250 | legal_image_file_array = [] 251 | illegal_image_file_array = [] 252 | 253 | pattern_file_types = Flr::IMAGE_FILE_TYPES.join(",") 254 | # dir/*{.png,.jpg} : 查找当前目录的指定类型文件 255 | # dir/*/*{.png,.jpg}: 查找当前目录的第1级子目录的指定类型文件 256 | # dir/**/*{.png,.jpg}: 查找当前目录和其所有子目录的指定类型文件 257 | Dir.glob(["#{resource_dir}/**/*{#{pattern_file_types}}"]).each do |file| 258 | if is_legal_resource_file?(file) 259 | legal_image_file_array.push(file) 260 | else 261 | illegal_image_file_array.push(file) 262 | end 263 | end 264 | 265 | image_file_result_tuple = [legal_image_file_array, illegal_image_file_array] 266 | return image_file_result_tuple 267 | end 268 | 269 | # find_text_files(resource_dir) -> text_file_result_tuple 270 | # 271 | # 扫描指定的资源目录和其所有层级的子目录,查找所有文本文件 272 | # 返回文本文件结果二元组 text_file_result_tuple 273 | # text_file_result_tuple = [legal_text_file_array, illegal_text_file_array] 274 | # 275 | # 判断文件合法的标准参考 self.is_legal_resource_file? 方法 276 | # 277 | # === Examples 278 | # resource_dir = "~/path/to/flutter_project/lib/assets/jsons" 279 | # legal_text_file_array = ["~/path/to/flutter_project/lib/assets/jsons/city.json", "~/path/to/flutter_project/lib/assets/jsons/mock/city.json"] 280 | # illegal_text_file_array = ["~/path/to/flutter_project/lib/assets/jsons/~.json"] 281 | # 282 | def self.find_text_files(resource_dir) 283 | legal_text_file_array = [] 284 | illegal_text_file_array = [] 285 | 286 | pattern_file_types = Flr::TEXT_FILE_TYPES.join(",") 287 | # dir/**/*{.json.,.yaml} : 查找当前目录和其所有子目录的指定类型文件 288 | Dir.glob(["#{resource_dir}/**/*{#{pattern_file_types}}"]).each do |file| 289 | if is_legal_resource_file?(file) 290 | legal_text_file_array.push(file) 291 | else 292 | illegal_text_file_array.push(file) 293 | end 294 | end 295 | 296 | text_file_result_tuple = [legal_text_file_array, illegal_text_file_array] 297 | return text_file_result_tuple 298 | end 299 | 300 | # find_top_child_dirs(resource_dir) -> top_child_dir_array 301 | # 302 | # 扫描指定的资源目录,返回其所有第一级子目录 303 | # 304 | # === Examples 305 | # resource_dir = "~/path/to/flutter_project/lib/assets/fonts" 306 | # top_child_dir_array = ["~/path/to/flutter_project/lib/assets/fonts/Amiri", "~/path/to/flutter_project/lib/assets/fonts/Open_Sans"] 307 | # 308 | def self.find_top_child_dirs(resource_dir) 309 | top_child_dir_array = [] 310 | 311 | Dir.glob(["#{resource_dir}/*"]).each do |file| 312 | if File.directory?(file) 313 | top_child_dir_array.push(file) 314 | end 315 | end 316 | 317 | return top_child_dir_array 318 | end 319 | 320 | # find_font_files_in_font_family_dir(font_family_dir) -> font_file_result_tuple 321 | # 322 | # 扫描指定的字体家族目录和其所有层级的子目录,查找所有字体文件 323 | # 返回字体文件结果二元组 font_file_result_tuple 324 | # font_file_result_tuple = [legal_font_file_array, illegal_font_file_array] 325 | # 326 | # 判断文件合法的标准参考 self.is_legal_resource_file? 方法 327 | # 328 | # === Examples 329 | # font_family_dir = "~/path/to/flutter_project/lib/assets/fonts/Amiri" 330 | # legal_font_file_array = ["~/path/to/flutter_project/lib/assets/fonts/Amiri/Amiri-Regular.ttf", "~/path/to/flutter_project/lib/assets/fonts/Amiri/Amiri-Bold.ttf"] 331 | # illegal_font_file_array = ["~/path/to/flutter_project/lib/assets/fonts/Amiri/~.ttf"] 332 | # 333 | def self.find_font_files_in_font_family_dir(font_family_dir) 334 | legal_font_file_array = [] 335 | illegal_font_file_array = [] 336 | 337 | pattern_file_types = Flr::FONT_FILE_TYPES.join(",") 338 | # dir/**/*{.ttf.,.ott} : 查找当前目录和其所有子目录的指定类型文件 339 | Dir.glob(["#{font_family_dir}/**/*{#{pattern_file_types}}"]).each do |file| 340 | if is_legal_resource_file?(file) 341 | legal_font_file_array.push(file) 342 | else 343 | illegal_font_file_array.push(file) 344 | end 345 | end 346 | 347 | font_file_result_tuple = [legal_font_file_array, illegal_font_file_array] 348 | return font_file_result_tuple 349 | end 350 | 351 | end 352 | 353 | end 354 | -------------------------------------------------------------------------------- /lib/flr/version.rb: -------------------------------------------------------------------------------- 1 | module Flr 2 | # 工具版本号 3 | VERSION = "3.2.0" 4 | 5 | # 核心逻辑版本号 6 | CORE_VERSION = "3.2.0" 7 | 8 | class Version < Array 9 | def initialize str 10 | super(str.split('.').map { |e| e.to_i }) 11 | end 12 | 13 | def < x 14 | (self <=> x) < 0 15 | end 16 | 17 | def > x 18 | (self <=> x) > 0 19 | end 20 | 21 | def == x 22 | (self <=> x) == 0 23 | end 24 | 25 | def >= x 26 | a = self > x 27 | b = self == x 28 | return (a || b) 29 | end 30 | 31 | def <= x 32 | a = self < x 33 | b = self == x 34 | return (a || b) 35 | end 36 | 37 | end 38 | end 39 | --------------------------------------------------------------------------------