├── table.png ├── .github ├── banner.png ├── sponsor.png ├── workflows │ ├── stars.yml │ ├── quality.yml │ ├── publish_docs.yml │ ├── tests.yaml │ ├── publish.yml │ ├── formula.yml │ └── artifacts.yaml └── ISSUE_TEMPLATE │ ├── feature_request.md │ └── bug_report.md ├── lib ├── src │ ├── version.dart │ ├── cli │ │ ├── commands │ │ │ ├── commands.dart │ │ │ ├── base_command.dart │ │ │ ├── create_command.dart │ │ │ └── build_command.dart │ │ ├── utils │ │ │ ├── utils.dart │ │ │ ├── extensions.dart │ │ │ ├── result.dart │ │ │ ├── config_creator.dart │ │ │ ├── config_retriever.dart │ │ │ ├── helpers.dart │ │ │ ├── logging.dart │ │ │ └── constants.dart │ │ ├── flag_commands │ │ │ ├── flag_commands.dart │ │ │ ├── version_flag_command.dart │ │ │ ├── license_flag_command.dart │ │ │ ├── about_flag_command.dart │ │ │ ├── docs_flag_command.dart │ │ │ ├── base_flag_command.dart │ │ │ └── check_updates_flag_command.dart │ │ ├── models │ │ │ ├── command_names.dart │ │ │ ├── subgroup_property.dart │ │ │ ├── flag_names.dart │ │ │ ├── asset_subgroup.dart │ │ │ ├── asset_group.dart │ │ │ ├── default_config_templates.dart │ │ │ └── spider_config.dart │ │ ├── registry.dart │ │ ├── process_terminator.dart │ │ ├── base_command_runner.dart │ │ └── cli_runner.dart │ ├── data │ │ ├── class_template.dart │ │ ├── export_template.dart │ │ └── test_template.dart │ ├── formatter.dart │ ├── fonts_generator.dart │ ├── generation_utils.dart │ └── dart_class_generator.dart └── spider.dart ├── cov.bat ├── .copyright ├── dart_test.yaml ├── scripts.yaml ├── analysis_options.yaml ├── bin └── spider.dart ├── .gitignore ├── example ├── example.md ├── config.json └── config.yaml ├── test ├── version_test.dart ├── formatter_test.dart ├── test_utils.dart ├── spider_test.dart └── data_class_generator_test.dart ├── ISSUE_TEMPLATE.md ├── update_formula.dart ├── pubspec.yaml ├── PULL_REQUEST_TEMPLATE.md ├── CONTRIBUTING.md ├── CODE_OF_CONDUCT.md ├── CHANGELOG.md ├── LICENSE └── README.md /table.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BirjuVachhani/spider/HEAD/table.png -------------------------------------------------------------------------------- /.github/banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BirjuVachhani/spider/HEAD/.github/banner.png -------------------------------------------------------------------------------- /.github/sponsor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BirjuVachhani/spider/HEAD/.github/sponsor.png -------------------------------------------------------------------------------- /lib/src/version.dart: -------------------------------------------------------------------------------- 1 | // Generated code. Do not modify. 2 | const packageVersion = '4.2.3'; 3 | -------------------------------------------------------------------------------- /cov.bat: -------------------------------------------------------------------------------- 1 | dart pub run test --coverage=coverage 2 | C:\Strawberry\perl\bin\perl C:\ProgramData\chocolatey\lib\lcov\tools\bin\genhtml -o coverage\html coverage\lcov.info -------------------------------------------------------------------------------- /.copyright: -------------------------------------------------------------------------------- 1 | // Copyright © 2020 Birju Vachhani. All rights reserved. 2 | // Use of this source code is governed by an Apache license that can be 3 | // found in the LICENSE file. -------------------------------------------------------------------------------- /dart_test.yaml: -------------------------------------------------------------------------------- 1 | # Configure dart test to run serially to prevent race conditions 2 | # Tests share config files and assets directories, so parallel execution causes failures 3 | concurrency: 1 4 | -------------------------------------------------------------------------------- /scripts.yaml: -------------------------------------------------------------------------------- 1 | deps: dart pub get 2 | install: dart pub global activate --source path . 3 | uninstall: dart pub global deactivate spider 4 | codegen: dart pub run build_runner build --delete-conflicting-outputs -------------------------------------------------------------------------------- /.github/workflows/stars.yml: -------------------------------------------------------------------------------- 1 | name: Stars 2 | 3 | on: 4 | watch: 5 | types: [started] 6 | 7 | jobs: 8 | notify: 9 | uses: BirjuVachhani/shared-workflows/.github/workflows/stars.yml@main 10 | secrets: 11 | ntfy_url: ${{ secrets.NTFY_TOPIC }} 12 | -------------------------------------------------------------------------------- /lib/src/cli/commands/commands.dart: -------------------------------------------------------------------------------- 1 | // Copyright © 2020 Birju Vachhani. All rights reserved. 2 | // Use of this source code is governed by an Apache license that can be 3 | // found in the LICENSE file. 4 | 5 | export 'base_command.dart'; 6 | export 'build_command.dart'; 7 | export 'create_command.dart'; 8 | -------------------------------------------------------------------------------- /analysis_options.yaml: -------------------------------------------------------------------------------- 1 | include: package:lints/recommended.yaml 2 | 3 | analyzer: 4 | exclude: 5 | - lib/src/version.dart 6 | 7 | linter: 8 | rules: 9 | constant_identifier_names: false 10 | prefer_relative_imports: true 11 | package_api_docs: true 12 | public_member_api_docs: true -------------------------------------------------------------------------------- /bin/spider.dart: -------------------------------------------------------------------------------- 1 | // Copyright © 2020 Birju Vachhani. All rights reserved. 2 | // Use of this source code is governed by an Apache license that can be 3 | // found in the LICENSE file. 4 | 5 | import 'package:spider/src/cli/cli_runner.dart'; 6 | 7 | Future main(List args) async => await CliRunner().run(args); 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Files and directories created by pub 2 | .dart_tool/ 3 | .packages 4 | # Remove the following pattern if you wish to check in your lock file 5 | pubspec.lock 6 | 7 | # Conventional directory for build outputs 8 | build/ 9 | 10 | # Directory created by dartdoc 11 | doc/api/ 12 | 13 | .idea/ 14 | 15 | .atom 16 | 17 | assets 18 | coverage/ 19 | test/.test_coverage.dart 20 | coverage_badge.svg -------------------------------------------------------------------------------- /lib/src/cli/utils/utils.dart: -------------------------------------------------------------------------------- 1 | // Copyright © 2020 Birju Vachhani. All rights reserved. 2 | // Use of this source code is governed by an Apache license that can be 3 | // found in the LICENSE file. 4 | 5 | export 'config_creator.dart'; 6 | export 'config_retriever.dart'; 7 | export 'constants.dart'; 8 | export 'extensions.dart'; 9 | export 'helpers.dart'; 10 | export 'logging.dart'; 11 | export 'result.dart'; 12 | -------------------------------------------------------------------------------- /lib/src/cli/flag_commands/flag_commands.dart: -------------------------------------------------------------------------------- 1 | // Copyright © 2020 Birju Vachhani. All rights reserved. 2 | // Use of this source code is governed by an Apache license that can be 3 | // found in the LICENSE file. 4 | 5 | export 'about_flag_command.dart'; 6 | export 'base_flag_command.dart'; 7 | export 'check_updates_flag_command.dart'; 8 | export 'docs_flag_command.dart'; 9 | export 'license_flag_command.dart'; 10 | export 'version_flag_command.dart'; 11 | -------------------------------------------------------------------------------- /lib/src/cli/models/command_names.dart: -------------------------------------------------------------------------------- 1 | // Copyright © 2020 Birju Vachhani. All rights reserved. 2 | // Use of this source code is governed by an Apache license that can be 3 | // found in the LICENSE file. 4 | 5 | // ignore_for_file: public_member_api_docs 6 | 7 | /// Contains names of all commands available in the CLI. 8 | class CommandNames { 9 | CommandNames._(); 10 | 11 | static const String create = 'create'; 12 | static const String build = 'build'; 13 | } 14 | -------------------------------------------------------------------------------- /lib/src/cli/models/subgroup_property.dart: -------------------------------------------------------------------------------- 1 | // Copyright © 2020 Birju Vachhani. All rights reserved. 2 | // Use of this source code is governed by an Apache license that can be 3 | // found in the LICENSE file. 4 | 5 | /// Represents a sub_group property. 6 | class SubgroupProperty { 7 | /// Subgroup prefix. 8 | final String prefix; 9 | 10 | /// Subgroup file map (fileName: path). 11 | final Map files; 12 | 13 | /// Creates an instance of [SubgroupProperty]. 14 | SubgroupProperty(this.prefix, this.files); 15 | } 16 | -------------------------------------------------------------------------------- /example/example.md: -------------------------------------------------------------------------------- 1 | ### Basic Example 2 | 3 | Just use following command: 4 | 5 | ```shell 6 | spider build 7 | ``` 8 | 9 | #### Before 10 | ```dart 11 | Widget build(BuildContext context) { 12 | return Image(image: AssetImage('assets/background.png')); 13 | } 14 | ``` 15 | 16 | #### After 17 | ```dart 18 | Widget build(BuildContext context) { 19 | return Image(image: AssetImage(Assets.background)); 20 | } 21 | ``` 22 | 23 | #### Generated Assets Class 24 | ```dart 25 | class Assets { 26 | static const String background = 'assets/background.png'; 27 | } 28 | ``` -------------------------------------------------------------------------------- /test/version_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright © 2020 Birju Vachhani. All rights reserved. 2 | // Use of this source code is governed by an Apache license that can be 3 | // found in the LICENSE file. 4 | 5 | import 'dart:io'; 6 | 7 | import 'package:spider/src/version.dart'; 8 | import 'package:test/test.dart'; 9 | import 'package:yaml/yaml.dart'; 10 | 11 | void main() { 12 | test('checks for embedded version to match with pubspec version̥', () { 13 | final pubspec = loadYaml(File('pubspec.yaml').readAsStringSync()); 14 | expect(pubspec['version'].toString(), packageVersion); 15 | }); 16 | } 17 | -------------------------------------------------------------------------------- /lib/src/cli/commands/base_command.dart: -------------------------------------------------------------------------------- 1 | // Copyright © 2020 Birju Vachhani. All rights reserved. 2 | // Use of this source code is governed by an Apache license that can be 3 | // found in the LICENSE file. 4 | 5 | import 'package:args/command_runner.dart'; 6 | 7 | import '../utils/utils.dart'; 8 | 9 | /// A Base class for all the commands created in this cli app. 10 | abstract class BaseCommand extends Command with LoggingMixin { 11 | @override 12 | final BaseLogger logger; 13 | 14 | /// Default constructor. 15 | /// [logger] is used to output all kinds of logs, errors and exceptions. 16 | /// e.g. [ConsoleLogger] for logging to console. 17 | BaseCommand(this.logger); 18 | } 19 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ### Prerequisites 2 | 3 | * [ ] Put an X between the brackets on this line if you have done all of the following: 4 | * Reproduced the problem 5 | * Checked that your issue isn't already filed: https://github.com/issues?utf8=✓&q=is%3Aissue+user%3ABirjuVachhani 6 | 7 | ### Description 8 | 9 | [Description of the issue] 10 | 11 | ### Steps to Reproduce 12 | 13 | 1. [First Step] 14 | 2. [Second Step] 15 | 3. [and so on...] 16 | 17 | **Expected behavior:** [What you expect to happen] 18 | 19 | **Actual behavior:** [What actually happens] 20 | 21 | **Reproduces how often:** [What percentage of the time does it reproduce?] 22 | 23 | ### Additional Information 24 | 25 | Any additional information, configuration or data that might be necessary to reproduce the issue. 26 | -------------------------------------------------------------------------------- /.github/workflows/quality.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: 4 | push: 5 | paths: 6 | - '**.dart' 7 | - '**.yaml' 8 | - '**.yml' 9 | branches: 10 | - main 11 | tags-ignore: 12 | - '**' 13 | pull_request: 14 | workflow_dispatch: 15 | 16 | jobs: 17 | build: 18 | runs-on: macos-latest 19 | steps: 20 | - uses: dart-lang/setup-dart@v1 21 | - name: Dart SDK 22 | run: dart --version 23 | - uses: actions/checkout@v5 24 | - name: Install dependencies 25 | run: dart pub get 26 | - name: Code Formatting 27 | run: dart format --set-exit-if-changed . 28 | - name: Build & Install Locally 29 | run: dart pub global activate --source path . 30 | - name: Check Publish Warnings 31 | run: dart pub publish --dry-run -------------------------------------------------------------------------------- /.github/workflows/publish_docs.yml: -------------------------------------------------------------------------------- 1 | name: Publish docs 2 | 3 | on: 4 | push: 5 | branches: 6 | - docs 7 | workflow_dispatch: 8 | 9 | jobs: 10 | deploy_docs: 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - uses: actions/checkout@v5 15 | with: 16 | ref: 'docs' 17 | - name: Setup Python 18 | uses: actions/setup-python@v6 19 | with: 20 | python-version: '3.x' 21 | 22 | - name: Install dependencies 23 | run: | 24 | python3 -m pip install --upgrade pip 25 | python3 -m pip install mkdocs 26 | python3 -m pip install mkdocs-material 27 | 28 | - name: Build site 29 | run: mkdocs build 30 | 31 | - name: Deploy 32 | uses: peaceiris/actions-gh-pages@v4 33 | with: 34 | github_token: ${{ secrets.GITHUB_TOKEN }} 35 | publish_dir: ./site 36 | -------------------------------------------------------------------------------- /update_formula.dart: -------------------------------------------------------------------------------- 1 | // Copyright © 2020 Birju Vachhani. All rights reserved. 2 | // Use of this source code is governed by an Apache license that can be 3 | // found in the LICENSE file. 4 | 5 | import 'dart:io'; 6 | 7 | // command: update_formula.dart url sha path 8 | void main(List args) { 9 | if (args.length != 3) { 10 | print('Version, SHA and file path not specified.'); 11 | exit(0); 12 | } 13 | 14 | final formulaFile = File(args[2]); 15 | String content = formulaFile.readAsStringSync(); 16 | // Replace URL 17 | content = content.replaceFirst( 18 | RegExp(r'url\s\".*\"'), 19 | 'url "https://github.com/BirjuVachhani/spider/archive/${args[0]}.tar.gz"', 20 | ); 21 | 22 | // Replace SHA 23 | content = content.replaceFirst( 24 | RegExp(r'sha256\s\".*\"'), 25 | 'sha256 "${args[1]}"', 26 | ); 27 | 28 | formulaFile.writeAsStringSync(content); 29 | } 30 | -------------------------------------------------------------------------------- /lib/src/data/class_template.dart: -------------------------------------------------------------------------------- 1 | // Copyright © 2020 Birju Vachhani. All rights reserved. 2 | // Use of this source code is governed by an Apache license that can be 3 | // found in the LICENSE file. 4 | 5 | /// A template for generating file author line. 6 | String get timeStampComment => '// Generated by spider on [TIME]\n\n'; 7 | 8 | /// A template for generating asset references class files. 9 | String get classTemplate => '''class [CLASS_NAME] { 10 | [CLASS_NAME]._(); 11 | 12 | [REFERENCES] 13 | 14 | [LIST_OF_ALL_REFERENCES] 15 | }'''; 16 | 17 | /// A template for generating reference variable declaration/signature. 18 | String get referenceTemplate => 19 | "[PROPERTIES] String [ASSET_NAME] = '[ASSET_PATH]';"; 20 | 21 | /// A template for generating list of all references variable. 22 | String get referencesTemplate => 23 | "[PROPERTIES] List values = [LIST_OF_ALL_REFERENCES];"; 24 | -------------------------------------------------------------------------------- /lib/src/cli/flag_commands/version_flag_command.dart: -------------------------------------------------------------------------------- 1 | // Copyright © 2020 Birju Vachhani. All rights reserved. 2 | // Use of this source code is governed by an Apache license that can be 3 | // found in the LICENSE file. 4 | 5 | import '../../version.dart'; 6 | import '../models/flag_names.dart'; 7 | import 'base_flag_command.dart'; 8 | 9 | /// A flag command to print current version of the CLI. 10 | /// e.g. Spider --version 11 | class VersionFlagCommand extends BaseFlagCommand { 12 | @override 13 | String get name => FlagNames.version; 14 | 15 | @override 16 | String get help => 'Show current version of this tool.'; 17 | 18 | @override 19 | String get abbr => 'v'; 20 | 21 | @override 22 | bool get negatable => false; 23 | 24 | /// Default constructor. 25 | VersionFlagCommand(super.logger); 26 | 27 | @override 28 | Future run() async => Future.sync(() => log(packageVersion)); 29 | } 30 | -------------------------------------------------------------------------------- /lib/src/data/export_template.dart: -------------------------------------------------------------------------------- 1 | // Copyright © 2020 Birju Vachhani. All rights reserved. 2 | // Use of this source code is governed by an Apache license that can be 3 | // found in the LICENSE file. 4 | 5 | /// A template to add library statement in dart source code. 6 | String get libraryTemplate => 'library [LIBRARY_NAME];\n\n'; 7 | 8 | /// A template to add ignored rules. 9 | String get ignoreRulesTemplate => '// ignore_for_file: [IGNORED_RULES]\n\n'; 10 | 11 | /// A template to generate export statements in dart source code. 12 | String get exportFileTemplate => "export '[FILE_NAME]';"; 13 | 14 | /// A template to generate `part` directive statement in the dart 15 | /// library source file. 16 | String get partTemplate => "part '[FILE_NAME]';"; 17 | 18 | /// A template to generate `part of` directive statements in the dart 19 | /// asset reference files. 20 | String get partOfTemplate => "part of '[FILE_NAME]';"; 21 | -------------------------------------------------------------------------------- /lib/src/cli/flag_commands/license_flag_command.dart: -------------------------------------------------------------------------------- 1 | // Copyright © 2020 Birju Vachhani. All rights reserved. 2 | // Use of this source code is governed by an Apache license that can be 3 | // found in the LICENSE file. 4 | 5 | import '../models/flag_names.dart'; 6 | import '../utils/utils.dart'; 7 | import 'base_flag_command.dart'; 8 | 9 | /// Represents the `license` flag command which prints license information. 10 | /// e.g. Spider --license 11 | class LicenseFlagCommand extends BaseFlagCommand { 12 | @override 13 | String get name => FlagNames.license; 14 | 15 | @override 16 | String get help => 'Show license information.'; 17 | 18 | @override 19 | String get abbr => 'l'; 20 | 21 | @override 22 | bool get negatable => false; 23 | 24 | /// Default constructor. 25 | LicenseFlagCommand(super.logger); 26 | 27 | @override 28 | Future run() async => Future.sync(() => log(Constants.LICENSE_SHORT)); 29 | } 30 | -------------------------------------------------------------------------------- /lib/src/cli/flag_commands/about_flag_command.dart: -------------------------------------------------------------------------------- 1 | // Copyright © 2020 Birju Vachhani. All rights reserved. 2 | // Use of this source code is governed by an Apache license that can be 3 | // found in the LICENSE file. 4 | 5 | import '../models/flag_names.dart'; 6 | import '../utils/utils.dart'; 7 | import 'base_flag_command.dart'; 8 | 9 | /// Represents the `about` flag command which prints tool information. 10 | /// e.g. Spider --about 11 | class AboutFlagCommand extends BaseFlagCommand { 12 | @override 13 | String get name => FlagNames.about; 14 | 15 | @override 16 | String get help => 'Show tool info.'; 17 | 18 | @override 19 | String get abbr => 'a'; 20 | 21 | @override 22 | bool get negatable => false; 23 | 24 | /// Default constructor. 25 | /// [logger] is used to output logs, errors and exceptions. 26 | AboutFlagCommand(super.logger); 27 | 28 | @override 29 | Future run() async => Future.sync(() => log(Constants.INFO)); 30 | } 31 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Smartphone (please complete the following information):** 32 | - Device: [e.g. iPhone6] 33 | - OS: [e.g. iOS8.1] 34 | - Browser [e.g. stock browser, safari] 35 | - Version [e.g. 22] 36 | 37 | **Additional context** 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: spider 2 | description: A small dart command-line tool for generating dart references of assets from the assets folder. 3 | version: 4.2.3 4 | 5 | repository: https://github.com/BirjuVachhani/spider 6 | homepage: https://github.com/BirjuVachhani/spider 7 | issue_tracker: https://github.com/BirjuVachhani/spider/issues 8 | documentation: https://birjuvachhani.github.io/spider/ 9 | 10 | environment: 11 | sdk: ">=3.9.0 <4.0.0" 12 | 13 | platforms: 14 | linux: 15 | windows: 16 | macos: 17 | 18 | dependencies: 19 | path: ^1.9.1 20 | yaml: ^3.1.3 21 | args: ^2.7.0 22 | dart_style: ^3.1.2 23 | logging: ^1.3.0 24 | watcher: ^1.1.4 25 | http: ^1.5.0 26 | html: ^0.15.6 27 | sprintf: ^7.0.0 28 | meta: ^1.17.0 29 | ansicolor: ^2.0.3 30 | collection: ^1.19.1 31 | 32 | dev_dependencies: 33 | # 2.4.5 doesn't work with logging. 34 | build_runner: ^2.9.0 35 | build_version: ^2.1.3 36 | test: ^1.26.3 37 | lints: ^6.0.0 38 | mockito: ^5.5.1 39 | 40 | executables: 41 | spider: spider 42 | 43 | scripts: scripts.yaml 44 | -------------------------------------------------------------------------------- /.github/workflows/tests.yaml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | 3 | on: 4 | push: 5 | paths: 6 | - '**.dart' 7 | branches: 8 | - main 9 | tags-ignore: 10 | - '**' 11 | pull_request: 12 | workflow_dispatch: 13 | 14 | jobs: 15 | tests_and_coverage: 16 | runs-on: macos-latest 17 | steps: 18 | - uses: dart-lang/setup-dart@v1 19 | - name: Dart SDK 20 | run: dart --version 21 | - uses: actions/checkout@v5 22 | - name: Install dependencies 23 | run: dart pub get 24 | - name: Run tests 25 | run: dart run test 26 | - name: Install coverage package 27 | run: dart pub global activate coverage 28 | - name: Generate coverage report 29 | run: dart pub global run coverage:test_with_coverage 30 | - name: Generate lcov file 31 | run: dart pub global run coverage:format_coverage --packages=.dart_tool/package_config.json --lcov -i coverage/coverage.json -o coverage/lcov.info 32 | - uses: codecov/codecov-action@v1 33 | with: 34 | file: coverage/lcov.info 35 | -------------------------------------------------------------------------------- /lib/src/cli/registry.dart: -------------------------------------------------------------------------------- 1 | // Copyright © 2020 Birju Vachhani. All rights reserved. 2 | // Use of this source code is governed by an Apache license that can be 3 | // found in the LICENSE file. 4 | 5 | import 'commands/commands.dart'; 6 | import 'flag_commands/flag_commands.dart'; 7 | import 'models/command_names.dart'; 8 | import 'models/flag_names.dart'; 9 | import 'utils/utils.dart'; 10 | 11 | /// A factory function for creating an instance of [T] with dependency on 12 | /// [BaseLogger]. 13 | typedef CommandCreator = T Function(BaseLogger logger); 14 | 15 | /// Holds all the available commands for the CLI. 16 | final Map> commandsCreatorRegistry = { 17 | CommandNames.create: CreateCommand.new, 18 | CommandNames.build: BuildCommand.new, 19 | }; 20 | 21 | /// Holds all the available flag commands for the CLI. 22 | final Map> flagCommandsCreatorRegistry = 23 | { 24 | FlagNames.version: VersionFlagCommand.new, 25 | FlagNames.about: AboutFlagCommand.new, 26 | FlagNames.checkForUpdates: CheckUpdatesFlagCommand.new, 27 | FlagNames.license: LicenseFlagCommand.new, 28 | FlagNames.docs: DocsFlagCommand.new, 29 | }; 30 | -------------------------------------------------------------------------------- /lib/src/cli/models/flag_names.dart: -------------------------------------------------------------------------------- 1 | // Copyright © 2020 Birju Vachhani. All rights reserved. 2 | // Use of this source code is governed by an Apache license that can be 3 | // found in the LICENSE file. 4 | 5 | // ignore_for_file: public_member_api_docs 6 | 7 | /// Contains the names of the flags available across the CLI. 8 | class FlagNames { 9 | FlagNames._(); 10 | 11 | // Top-level flags. 12 | static const String about = 'about'; 13 | static const String checkForUpdates = 'check-for-updates'; 14 | static const String docs = 'docs'; 15 | static const String license = 'license'; 16 | static const String verbose = 'verbose'; 17 | static const String version = 'version'; 18 | 19 | // shared 20 | static const String help = 'help'; 21 | 22 | // build command flags 23 | static const String smartWatch = 'smart-watch'; 24 | static const String watch = 'watch'; 25 | static const String fontsOnly = 'fonts-only'; 26 | 27 | // Create command flags. 28 | static const String addInPubspec = 'add-in-pubspec'; 29 | static const String json = 'json'; 30 | } 31 | 32 | /// Contains the names of the options available across the CLI. 33 | class OptionNames { 34 | OptionNames._(); 35 | 36 | // shared 37 | static const String path = 'path'; 38 | } 39 | -------------------------------------------------------------------------------- /lib/src/cli/flag_commands/docs_flag_command.dart: -------------------------------------------------------------------------------- 1 | // Copyright © 2020 Birju Vachhani. All rights reserved. 2 | // Use of this source code is governed by an Apache license that can be 3 | // found in the LICENSE file. 4 | 5 | import 'dart:io'; 6 | 7 | import '../models/flag_names.dart'; 8 | import '../utils/utils.dart'; 9 | import 'base_flag_command.dart'; 10 | 11 | /// Represents the `docs` flag command which opens tool documentation site. 12 | /// e.g. Spider --docs 13 | class DocsFlagCommand extends BaseFlagCommand { 14 | @override 15 | String get name => FlagNames.docs; 16 | 17 | @override 18 | String get help => 'Open tool documentation.'; 19 | 20 | @override 21 | String get abbr => 'd'; 22 | 23 | @override 24 | bool get negatable => false; 25 | 26 | /// Default constructor. 27 | DocsFlagCommand(super.logger); 28 | 29 | @override 30 | Future run() async { 31 | if (Platform.isMacOS) { 32 | await Process.run('open', [Constants.DOCS_URL]); 33 | return; 34 | } else if (Platform.isWindows) { 35 | await Process.run('explorer', [Constants.DOCS_URL]); 36 | return; 37 | } else if (Platform.isLinux) { 38 | await Process.run('xdg-open', [Constants.DOCS_URL]); 39 | return; 40 | } 41 | error(ConsoleMessages.unsupportedPlatform); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /lib/src/cli/models/asset_subgroup.dart: -------------------------------------------------------------------------------- 1 | // Copyright © 2020 Birju Vachhani. All rights reserved. 2 | // Use of this source code is governed by an Apache license that can be 3 | // found in the LICENSE file. 4 | 5 | import '../../generation_utils.dart'; 6 | 7 | /// Holds prefix, types and path info for [Group] 8 | class AssetSubgroup { 9 | /// Prefix to be appended to the reference names. 10 | late final String prefix; 11 | 12 | /// Whitelisted types that are part of this sub-group. 13 | late final List types; 14 | 15 | /// Paths covered under this sub-group. 16 | late final List paths; 17 | 18 | /// Default constructor. 19 | AssetSubgroup({ 20 | required this.paths, 21 | this.prefix = '', 22 | this.types = const [], 23 | }); 24 | 25 | /// Creates [AssetSubgroup] from [json] map data. 26 | AssetSubgroup.fromJson(Map json) { 27 | prefix = json['prefix']?.toString() ?? ''; 28 | types = []; 29 | json['types']?.forEach( 30 | (group) => types.add(formatExtension(group.toString()).toLowerCase()), 31 | ); 32 | paths = []; 33 | if (json['paths'] != null) { 34 | paths.addAll(List.from(json['paths'])); 35 | } else if (json['path'] != null) { 36 | paths.add(json['path'].toString()); 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish Package 2 | 3 | on: 4 | push: 5 | tags: 6 | # must align with the tag-pattern configured on pub.dev, often just replace 7 | # with [0-9]+.[0-9]+.[0-9]+* 8 | # - 'v[0-9]+.[0-9]+.[0-9]+*' # tag-pattern on pub.dev: 'v' 9 | # If you prefer tags like '1.2.3', without the 'v' prefix, then use: 10 | - '[0-9]+.[0-9]+.[0-9]+*' # tag-pattern on pub.dev: '' 11 | # If you repository contains multiple packages consider a pattern like: 12 | # - 'my_package_name-v[0-9]+.[0-9]+.[0-9]+*' 13 | workflow_dispatch: 14 | 15 | jobs: 16 | publish: 17 | permissions: 18 | id-token: write # This is required for authentication using OIDC 19 | runs-on: ubuntu-latest 20 | steps: 21 | - uses: dart-lang/setup-dart@v1 22 | - name: Dart SDK 23 | run: dart --version 24 | - uses: actions/checkout@v5 25 | - name: Install dependencies 26 | run: dart pub get 27 | - name: Run tests 28 | run: dart run test 29 | - name: Build & Install Locally 30 | run: dart pub global activate --source path . 31 | - name: Format code 32 | run: dart format . 33 | - name: Build Runner 34 | run: dart run build_runner build --delete-conflicting-outputs 35 | - name: Check Publish Warnings 36 | run: dart pub publish --dry-run 37 | - name: Publish Package 38 | run: dart pub publish --force -------------------------------------------------------------------------------- /.github/workflows/formula.yml: -------------------------------------------------------------------------------- 1 | name: Update Formula 2 | 3 | on: 4 | release: 5 | types: [ published ] 6 | workflow_dispatch: 7 | 8 | jobs: 9 | update_formula: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: dart-lang/setup-dart@v1 13 | - name: Dart SDK 14 | run: dart --version 15 | - uses: actions/checkout@v5 16 | - name: Update Formula 17 | env: 18 | GITHUB_TOKEN: ${{ secrets.PAT }} 19 | run: | 20 | git clone --single-branch "https://${{ secrets.PAT }}@github.com/birjuvachhani/homebrew-spider.git" "clone_dir" 21 | GIT_TAG=`git describe --tags --abbrev=0` 22 | SPIDER_URL="https://github.com/BirjuVachhani/spider/archive/refs/tags/$GIT_TAG.tar.gz" 23 | curl -L -o spider.tar.gz "$SPIDER_URL" 24 | export SPIDER_SHA=`sha256sum spider.tar.gz | cut -d' ' -f1` 25 | echo "SHA: $SPIDER_SHA" 26 | echo "VERSION: $GIT_TAG" 27 | dart update_formula.dart "$GIT_TAG" "$SPIDER_SHA" "clone_dir/Formula/spider.rb" 28 | cd clone_dir 29 | git config user.name github-actions 30 | git config user.email github-actions@github.com 31 | git add Formula/spider.rb 32 | git commit -m 'update formula' 33 | git remote set-url origin "https://${{ secrets.PAT }}@github.com/birjuvachhani/homebrew-spider.git" 34 | git pull --rebase 35 | git push 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /lib/src/cli/utils/extensions.dart: -------------------------------------------------------------------------------- 1 | // Copyright © 2020 Birju Vachhani. All rights reserved. 2 | // Use of this source code is governed by an Apache license that can be 3 | // found in the LICENSE file. 4 | 5 | import 'package:args/args.dart'; 6 | import 'package:collection/collection.dart'; 7 | 8 | import '../flag_commands/flag_commands.dart'; 9 | 10 | /// Extension methods for [ArgParser] class. 11 | extension ArgResultsExt on ArgResults { 12 | /// Returns true if [ArgResults] has a command. 13 | bool get hasCommand => command != null; 14 | 15 | /// Returns true if the [flag] was passed and its value is true. 16 | /// For non-negatable flags, this will always return true if they are passed. 17 | /// For negatable flags, this will return true if the flag is passed and its 18 | /// value is true, false otherwise. 19 | bool getFlag(String flag) => wasParsed(flag) && this[flag] == true; 20 | 21 | /// This would return all the flag that are actually parsed in the command. 22 | List get passedFlags => options.where(wasParsed).toList(); 23 | } 24 | 25 | /// Extension methods for [FlagCommand] collection. 26 | extension BaseFlagCommandListExt on Iterable { 27 | /// Finds the flag command with where [FlagCommand.name] is [name]. 28 | T? findByName(String name) => 29 | firstWhereOrNull((element) => element.name == name); 30 | 31 | /// Returns true [name] is in the flag commands list. 32 | bool hasFlag(String name) => findByName(name) != null; 33 | } 34 | -------------------------------------------------------------------------------- /lib/src/cli/process_terminator.dart: -------------------------------------------------------------------------------- 1 | // Copyright © 2020 Birju Vachhani. All rights reserved. 2 | // Use of this source code is governed by an Apache license that can be 3 | // found in the LICENSE file. 4 | 5 | import 'dart:io'; 6 | 7 | import 'package:meta/meta.dart'; 8 | 9 | import 'utils/utils.dart'; 10 | 11 | /// A Singleton helper class that has functionality of terminating the process. 12 | /// This allows to override the termination behavior which is kind useful for 13 | /// testing. It can also be useful in places where it is not being executed 14 | /// from the command line. 15 | class ProcessTerminator { 16 | static ProcessTerminator? _instance; 17 | 18 | const ProcessTerminator._(); 19 | 20 | /// Returns the singleton instance of this class. 21 | factory ProcessTerminator.getInstance() { 22 | _instance ??= ProcessTerminator._(); 23 | return _instance!; 24 | } 25 | 26 | @visibleForTesting 27 | /// Allows to set mocked instance for testing. 28 | static void setMock(ProcessTerminator? mock) => _instance = mock; 29 | 30 | @visibleForTesting 31 | /// Removes stored mock instance in testing. 32 | static void clearMock() => _instance = null; 33 | 34 | /// Terminates the process. 35 | /// [message] will be printed via [logger] as an error message 36 | /// before termination. 37 | /// [stacktrace] if provided will also be printed via [logger]. 38 | void terminate(String message, dynamic stackTrace, [BaseLogger? logger]) { 39 | logger?.error(message, stackTrace); 40 | exitCode = 2; 41 | exit(2); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /example/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "generate_tests": true, 3 | "groups": [ 4 | { 5 | "class_name": "Images", 6 | "path": "assets/images", 7 | "types": [ 8 | ".png", 9 | ".jpg", 10 | ".jpeg", 11 | ".webp", 12 | ".webm", 13 | ".bmp" 14 | ] 15 | }, 16 | { 17 | "class_name": "Svgs", 18 | "sub_groups": [ 19 | { 20 | "path": "assets/svgsMenu", 21 | "prefix": "menu", 22 | "types": [ 23 | ".svg" 24 | ] 25 | }, 26 | { 27 | "path": "assets/svgsOther", 28 | "prefix": "other", 29 | "types": [ 30 | ".svg" 31 | ] 32 | } 33 | ] 34 | }, 35 | { 36 | "class_name": "Ico", 37 | "types": [ 38 | ".ico" 39 | ], 40 | "prefix": "ico", 41 | "sub_groups": [ 42 | { 43 | "path": "assets/icons", 44 | "prefix": "test1", 45 | "types": [ 46 | ".ttf" 47 | ] 48 | }, 49 | { 50 | "path": "assets/vectors", 51 | "prefix": "test2", 52 | "types": [ 53 | ".pdf" 54 | ] 55 | } 56 | ] 57 | }, 58 | { 59 | "class_name": "Video", 60 | "types": [ 61 | ".mp4" 62 | ], 63 | "path": "assets/moviesOnly", 64 | "sub_groups": [ 65 | { 66 | "path": "assets/movies", 67 | "prefix": "common" 68 | }, 69 | { 70 | "path": "assets/moviesExtra", 71 | "prefix": "extra" 72 | } 73 | ] 74 | } 75 | ] 76 | } -------------------------------------------------------------------------------- /PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ### Requirements 2 | 3 | * Filling out the template is required. Any pull request that does not include enough information to be reviewed in a timely manner may be closed at the maintainers' discretion. 4 | * All new code requires tests to ensure against regressions 5 | 6 | ### Description of the Change 7 | 8 | 13 | 14 | ### Alternate Designs 15 | 16 | 17 | 18 | ### Why Should This Be In Core? 19 | 20 | 21 | 22 | ### Benefits 23 | 24 | 25 | 26 | ### Possible Drawbacks 27 | 28 | 29 | 30 | ### Verification Process 31 | 32 | 43 | 44 | ### Applicable Issues 45 | 46 | 47 | -------------------------------------------------------------------------------- /lib/src/cli/utils/result.dart: -------------------------------------------------------------------------------- 1 | // Copyright © 2020 Birju Vachhani. All rights reserved. 2 | // Use of this source code is governed by an Apache license that can be 3 | // found in the LICENSE file. 4 | 5 | /// Represents the result an action. 6 | enum ResultState { 7 | /// No result. 8 | empty, 9 | 10 | /// Successful execution. 11 | success, 12 | 13 | /// Error occurred. 14 | error, 15 | } 16 | 17 | /// A data class that can be used to either return data or error as a result 18 | /// of a process/action. 19 | class Result { 20 | /// Represents the state of this result. 21 | final ResultState state; 22 | 23 | final T? _data; 24 | 25 | final String? _error; 26 | 27 | /// Exception that occurred while performing the action if any. 28 | final Object? exception; 29 | 30 | /// Stacktrace when an exception occurred if any. 31 | final StackTrace? stacktrace; 32 | 33 | /// Data that was returned as a result of the action when it succeeded. 34 | T get data => _data!; 35 | 36 | /// Error that was returned as a result of the action when it failed. 37 | String get error => _error!; 38 | 39 | /// Returns true if this result is successful. 40 | bool get isSuccess => state == ResultState.success; 41 | 42 | /// Returns true if this result is an error. 43 | bool get isError => state == ResultState.error && _error != null; 44 | 45 | /// Returns true if this result is empty. 46 | bool get isEmpty => state == ResultState.empty; 47 | 48 | /// Constructor that creates an empty result. 49 | const Result.empty() 50 | : _data = null, 51 | _error = null, 52 | exception = null, 53 | stacktrace = null, 54 | state = ResultState.empty; 55 | 56 | /// Constructor that creates a successful result. 57 | const Result.success([this._data]) 58 | : _error = null, 59 | exception = null, 60 | stacktrace = null, 61 | state = ResultState.success; 62 | 63 | /// Constructor that creates an error result. 64 | const Result.error(this._error, [this.exception, this.stacktrace]) 65 | : _data = null, 66 | state = ResultState.error; 67 | } 68 | -------------------------------------------------------------------------------- /lib/src/cli/flag_commands/base_flag_command.dart: -------------------------------------------------------------------------------- 1 | // Copyright © 2020 Birju Vachhani. All rights reserved. 2 | // Use of this source code is governed by an Apache license that can be 3 | // found in the LICENSE file. 4 | 5 | import '../utils/utils.dart'; 6 | 7 | /// Represents a top-level flag that does not need to be specified on a command 8 | /// to be executed and are only meant to be used on the main executable itself. 9 | /// Since these flags can execute on their own, they are like commands. 10 | /// e.g. --version, --check-updates, etc. 11 | /// Since it is a flag by definition, this class also contains 12 | /// properties of a flag. 13 | abstract class FlagCommand { 14 | /// The name of the flag without initial `--`(double dashes). 15 | abstract final String name; 16 | 17 | /// Help menu/information for this flag. 18 | abstract final String help; 19 | 20 | /// a short form of the flag without initial `-`(dash). 21 | final String? abbr = null; 22 | 23 | /// Represents default value of the flag. 24 | final bool defaultsTo = false; 25 | 26 | /// Defines whether this flag is toggleable or not. 27 | /// e.g. if the flag is `--print` then toggled flag would be `--no-print` 28 | /// which is automatically allowed if this is set to true. 29 | final bool negatable = true; 30 | 31 | /// Represents alias names for this flag. 32 | final List aliases = const []; 33 | 34 | /// A logger that is used to output logs, errors and exceptions upon 35 | /// execution of this flag command. 36 | final BaseLogger logger; 37 | 38 | /// Default constructor. 39 | /// [logger] is used to output all kinds of logs that is produce as a result 40 | /// of the execution of this flag command. 41 | const FlagCommand(this.logger); 42 | 43 | /// This method is executed upon running this command. Should contain actual 44 | /// execution code. 45 | Future run(); 46 | } 47 | 48 | /// A base class for this cli to use as parent class. 49 | abstract class BaseFlagCommand extends FlagCommand with LoggingMixin { 50 | /// Default constructor. 51 | const BaseFlagCommand(super.logger); 52 | } 53 | -------------------------------------------------------------------------------- /lib/src/formatter.dart: -------------------------------------------------------------------------------- 1 | // Copyright © 2020 Birju Vachhani. All rights reserved. 2 | // Use of this source code is governed by an Apache license that can be 3 | // found in the LICENSE file. 4 | 5 | // Author: Birju Vachhani 6 | // Created Date: February 09, 2020 7 | 8 | import 'cli/utils/utils.dart'; 9 | 10 | /// A utility class that can format various dart source code tokens like 11 | /// variable names, file paths, file names etc. 12 | class Formatter { 13 | /// Formats variable name to be pascal case or with underscores 14 | /// if [use_underscores] is true 15 | static String formatName( 16 | String name, { 17 | String prefix = '', 18 | bool useUnderScores = false, 19 | }) { 20 | // appending prefix if provided any 21 | name = (prefix.isEmpty ? '' : '${prefix}_') + name; 22 | name = name 23 | // adds preceding _ for capital letters and lowers them 24 | .replaceAllMapped( 25 | RegExp(r'[A-Z]+'), 26 | (match) => '_${match.group(0)!.toLowerCase()}', 27 | ) 28 | // replaces all the special characters with _ 29 | .replaceAll(Constants.specialSymbolRegex, '_') 30 | // removes _ in the beginning of the name 31 | .replaceFirst(RegExp(r'^_+'), '') 32 | // removes any numbers in the beginning of the name 33 | .replaceFirst(RegExp(r'^\d+'), '') 34 | // lowers the first character of the string 35 | .replaceFirstMapped( 36 | RegExp(r'^[A-Za-z]'), 37 | (match) => match.group(0)!.toLowerCase(), 38 | ); 39 | return useUnderScores 40 | ? name 41 | : name 42 | // removes _ and capitalize the next character of the _ 43 | .replaceAllMapped( 44 | RegExp(Constants.CAPITALIZE_REGEX), 45 | (match) => match.group(2)!.toUpperCase(), 46 | ); 47 | } 48 | 49 | /// formats path string to match with flutter's standards 50 | static String formatPath(String value) => value.replaceAll('\\', '/'); 51 | 52 | /// formats file name according to effective dart 53 | static String formatFileName(String name) { 54 | name = name 55 | .replaceAllMapped( 56 | RegExp(r'[A-Z]+'), 57 | (match) => '_${match.group(0)!.toLowerCase()}', 58 | ) 59 | .replaceFirst(RegExp(r'^_+'), ''); 60 | return name.endsWith('.dart') ? name : '$name.dart'; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /lib/src/cli/commands/create_command.dart: -------------------------------------------------------------------------------- 1 | // Copyright © 2020 Birju Vachhani. All rights reserved. 2 | // Use of this source code is governed by an Apache license that can be 3 | // found in the LICENSE file. 4 | 5 | import 'package:args/args.dart'; 6 | 7 | import '../flag_commands/flag_commands.dart'; 8 | import '../models/command_names.dart'; 9 | import '../models/flag_names.dart'; 10 | import '../utils/utils.dart'; 11 | import 'base_command.dart'; 12 | 13 | /// Command to create a Spider configuration file. 14 | class CreateCommand extends BaseCommand { 15 | @override 16 | String get description => 17 | "creates 'spider.yaml' file in the current working directory. This " 18 | "config file is used to control and manage generation of the dart code."; 19 | 20 | @override 21 | String get name => CommandNames.create; 22 | 23 | @override 24 | String get summary => 'Creates config file in the root of the project.'; 25 | 26 | /// Default constructor for create command. 27 | /// [logger] is used to output all kinds of logs, errors and exceptions. 28 | CreateCommand(super.logger) { 29 | argParser 30 | ..addFlag( 31 | FlagNames.addInPubspec, 32 | negatable: false, 33 | help: 'Adds the generated config file to the pubspec.yaml file.', 34 | ) 35 | ..addFlag( 36 | FlagNames.json, 37 | abbr: 'j', 38 | negatable: false, 39 | help: 'Generates config file of type JSON rather than YAML.', 40 | ) 41 | ..addOption( 42 | OptionNames.path, 43 | abbr: 'p', 44 | help: 'Allows to provide custom directory path for the config file.', 45 | ); 46 | } 47 | 48 | @override 49 | Future run() async { 50 | final ArgResults results = argResults!; 51 | 52 | // Check for updates. 53 | await CheckUpdatesFlagCommand.checkForNewVersion(logger); 54 | 55 | final bool isJson = results.getFlag(FlagNames.json); 56 | final bool addInPubspec = results.getFlag(FlagNames.addInPubspec); 57 | final String? path = results[OptionNames.path]; 58 | 59 | final result = ConfigCreator( 60 | logger, 61 | ).create(addInPubspec: addInPubspec, isJson: isJson, path: path); 62 | 63 | if (result.isError) { 64 | if (result.exception != null) verbose(result.exception.toString()); 65 | if (result.stacktrace != null) verbose(result.stacktrace.toString()); 66 | exitWith(result.error); 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /test/formatter_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright © 2020 Birju Vachhani. All rights reserved. 2 | // Use of this source code is governed by an Apache license that can be 3 | // found in the LICENSE file. 4 | 5 | // Author: Birju Vachhani 6 | // Created Date: February 02, 2020 7 | 8 | import 'package:spider/src/formatter.dart'; 9 | import 'package:test/test.dart'; 10 | 11 | /// runs all the test cases 12 | void main() { 13 | test('format name test', () { 14 | expect(Formatter.formatName('test_case'), 'testCase'); 15 | expect(Formatter.formatName('test case'), 'testCase'); 16 | expect(Formatter.formatName('Test Case'), 'testCase'); 17 | expect(Formatter.formatName('__Test Case'), 'testCase'); 18 | expect(Formatter.formatName('Test Case'), 'testCase'); 19 | expect(Formatter.formatName('Test@)(Case'), 'testCase'); 20 | expect(Formatter.formatName('TestCase'), 'testCase'); 21 | expect( 22 | Formatter.formatName('test-case-with some_word'), 23 | 'testCaseWithSomeWord', 24 | ); 25 | expect( 26 | Formatter.formatName('test_case', useUnderScores: true), 27 | 'test_case', 28 | ); 29 | expect( 30 | Formatter.formatName('test case', useUnderScores: true), 31 | 'test_case', 32 | ); 33 | expect( 34 | Formatter.formatName('Test Case', useUnderScores: true), 35 | 'test_case', 36 | ); 37 | expect( 38 | Formatter.formatName('__Test Case', useUnderScores: true), 39 | 'test_case', 40 | ); 41 | expect( 42 | Formatter.formatName('Test Case', useUnderScores: true), 43 | 'test_case', 44 | ); 45 | expect( 46 | Formatter.formatName('Test@)(Case', useUnderScores: true), 47 | 'test_case', 48 | ); 49 | expect(Formatter.formatName('TestCase', useUnderScores: true), 'test_case'); 50 | expect( 51 | Formatter.formatName('test-case-with some_word', useUnderScores: true), 52 | 'test_case_with_some_word', 53 | ); 54 | }); 55 | 56 | test('format path test', () { 57 | expect(Formatter.formatPath('assets\\abc.png'), 'assets/abc.png'); 58 | expect(Formatter.formatPath('assets/temp.png'), 'assets/temp.png'); 59 | expect( 60 | Formatter.formatPath('assets/temp/temp.png'), 61 | 'assets/temp/temp.png', 62 | ); 63 | expect( 64 | Formatter.formatPath('assets\\temp\\temp.png'), 65 | 'assets/temp/temp.png', 66 | ); 67 | }); 68 | 69 | test('format file name tests', () { 70 | expect(Formatter.formatFileName('Images'), 'images.dart'); 71 | }); 72 | } 73 | -------------------------------------------------------------------------------- /lib/src/fonts_generator.dart: -------------------------------------------------------------------------------- 1 | // Copyright © 2020 Birju Vachhani. All rights reserved. 2 | // Use of this source code is governed by an Apache license that can be 3 | // found in the LICENSE file. 4 | 5 | import 'package:dart_style/dart_style.dart'; 6 | 7 | import 'cli/models/spider_config.dart'; 8 | import 'cli/utils/utils.dart'; 9 | import 'formatter.dart'; 10 | import 'generation_utils.dart'; 11 | 12 | /// A dart code generator for fonts references. 13 | class FontsGenerator { 14 | /// Entry point for this generator. 15 | /// [fonts] is a list of 16 | void generate( 17 | List fonts, 18 | GlobalConfigs globals, 19 | BaseLogger? logger, 20 | ) { 21 | logger?.info('Generating fonts references'); 22 | final List fontFamilies = fonts 23 | .map((item) => item['family'].toString()) 24 | .toList(); 25 | final String references = fontFamilies 26 | .map((name) { 27 | logger?.verbose('processing $name'); 28 | return getReference( 29 | properties: 'static const', 30 | assetName: Formatter.formatName( 31 | name, 32 | prefix: globals.fontConfigs.prefix ?? '', 33 | useUnderScores: globals.fontConfigs.useUnderScores, 34 | ), 35 | assetPath: name, 36 | ); 37 | }) 38 | .toList() 39 | .join(); 40 | 41 | final valuesList = globals.useReferencesList 42 | ? getListOfReferences( 43 | properties: 'static const', 44 | assetNames: fontFamilies 45 | .map( 46 | (name) => Formatter.formatName( 47 | name, 48 | prefix: '', 49 | useUnderScores: false, 50 | ), 51 | ) 52 | .toList(), 53 | ) 54 | : null; 55 | 56 | logger?.verbose('Constructing dart class for fonts'); 57 | final content = getDartClass( 58 | className: globals.fontConfigs.className, 59 | references: references, 60 | noComments: globals.noComments, 61 | usePartOf: globals.export && globals.usePartOf!, 62 | exportFileName: Formatter.formatFileName(globals.exportFileName), 63 | valuesList: valuesList, 64 | ignoredRules: globals.ignoredRules, 65 | ); 66 | logger?.verbose('Writing class Fonts to file fonts.dart'); 67 | writeToFile( 68 | name: Formatter.formatFileName(globals.fontConfigs.fileName), 69 | path: globals.package, 70 | content: DartFormatter( 71 | languageVersion: DartFormatter.latestLanguageVersion, 72 | ).format(content), 73 | ); 74 | logger?.success('Generated fonts references successfully.'); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /lib/src/cli/base_command_runner.dart: -------------------------------------------------------------------------------- 1 | // Copyright © 2020 Birju Vachhani. All rights reserved. 2 | // Use of this source code is governed by an Apache license that can be 3 | // found in the LICENSE file. 4 | 5 | import 'package:args/args.dart'; 6 | import 'package:args/command_runner.dart'; 7 | import 'package:collection/collection.dart'; 8 | import 'package:meta/meta.dart'; 9 | 10 | import 'flag_commands/flag_commands.dart'; 11 | import 'models/flag_names.dart'; 12 | import 'utils/utils.dart'; 13 | 14 | /// A base [CommandRunner] that add capabilities to handle top level flags 15 | /// as commands. 16 | abstract class BaseCommandRunner extends CommandRunner { 17 | /// This is a list of all flags that are top-level flags and does not need 18 | /// be specified on a command to be executed and are only meant to be 19 | /// used on the main executable itself. Since these flags can execute on 20 | /// their own, they are like commands. 21 | /// e.g. --version, --check-updates, etc. 22 | final List flagCommands = []; 23 | 24 | /// Default constructor. 25 | /// [executableName] is the name of the executable for this CLI. 26 | /// [description] should be information about the CLI executable and its 27 | /// parameters and sub commands. 28 | BaseCommandRunner(super.executableName, super.description); 29 | 30 | /// Adds a top-level flag command to the list of top-level flag commands and 31 | /// registers it as a flag on the parser. 32 | /// If a command is already registered with the same name, it will be ignored. 33 | void addFlagCommand(BaseFlagCommand command) { 34 | if (flagCommands.any((item) => item.name == command.name)) return; 35 | flagCommands.add(command); 36 | argParser.addFlag( 37 | command.name, 38 | abbr: command.abbr, 39 | defaultsTo: command.defaultsTo, 40 | negatable: command.negatable, 41 | help: command.help, 42 | aliases: command.aliases, 43 | ); 44 | } 45 | 46 | @override 47 | @mustCallSuper 48 | Future runCommand(ArgResults topLevelResults) async { 49 | // Flag commands are top level flags that work without a command. So we 50 | // only check for them if we don't have a command. We also check if help 51 | // flag is specified or not. If help flag is specified, we don't want to 52 | // run any flag commands. 53 | if (!topLevelResults.hasCommand && 54 | !topLevelResults.getFlag(FlagNames.help)) { 55 | // Find first flag command and run it if found. 56 | final String? flagCommandName = topLevelResults.passedFlags 57 | .firstWhereOrNull((flag) => flagCommands.hasFlag(flag)); 58 | if (flagCommandName != null) { 59 | final flagCommand = flagCommands.findByName(flagCommandName)!; 60 | return await flagCommand.run(); 61 | } 62 | } 63 | 64 | return super.runCommand(topLevelResults); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /lib/src/cli/cli_runner.dart: -------------------------------------------------------------------------------- 1 | // Copyright © 2020 Birju Vachhani. All rights reserved. 2 | // Use of this source code is governed by an Apache license that can be 3 | // found in the LICENSE file. 4 | 5 | import 'dart:io'; 6 | 7 | import 'package:args/args.dart'; 8 | import 'package:args/command_runner.dart'; 9 | import 'package:sprintf/sprintf.dart'; 10 | 11 | import 'base_command_runner.dart'; 12 | import 'models/flag_names.dart'; 13 | import 'registry.dart'; 14 | import 'utils/utils.dart'; 15 | 16 | /// Entry point for all the command process. 17 | class CliRunner extends BaseCommandRunner { 18 | late final BaseLogger _logger; 19 | 20 | /// Default constructor. 21 | /// [output] is the output sink for logging. 22 | /// [errorSink] is the error sink for logging errors and exceptions. 23 | /// [logger] is if provided, will be used for logging. If not provided then 24 | /// a [ConsoleLogger] will be created using [output] and [errorSink]. 25 | CliRunner([IOSink? output, IOSink? errorSink, BaseLogger? logger]) 26 | : super( 27 | 'spider', 28 | 'A command line tool for generating dart asset references.', 29 | ) { 30 | // Create logger for CLI. 31 | _logger = logger ?? ConsoleLogger(output: output, errorSink: errorSink); 32 | 33 | // Add all the commands from registry. 34 | commandsCreatorRegistry.values 35 | .map((creator) => creator(_logger)) 36 | .forEach(addCommand); 37 | 38 | // Add all the top-level flag commands from registry. 39 | flagCommandsCreatorRegistry.values 40 | .map((creator) => creator(_logger)) 41 | .forEach(addFlagCommand); 42 | 43 | // Top-level flags and options 44 | argParser 45 | ..addFlag( 46 | FlagNames.verbose, 47 | /*abbr: 'v', */ negatable: false, 48 | help: 'Increase logging.', 49 | ) 50 | ..addOption( 51 | OptionNames.path, 52 | abbr: 'p', 53 | help: 54 | 'Path of the config file if it is not in the root directory ' 55 | 'of the project.', 56 | ); 57 | } 58 | 59 | @override 60 | Future run(Iterable args) async { 61 | try { 62 | await super.run(args); 63 | } on UsageException catch (e) { 64 | _logger 65 | ..error(e.message) 66 | ..info(e.usage); 67 | 68 | exit(64); 69 | } on Exception catch (e, stacktrace) { 70 | _logger.error( 71 | sprintf(ConsoleMessages.exitedUnexpectedly, [e.toString()]), 72 | stacktrace, 73 | ); 74 | exit(1); 75 | } 76 | } 77 | 78 | @override 79 | Future runCommand(ArgResults topLevelResults) async { 80 | /// Set level for logging. 81 | _logger.setLogLevel( 82 | topLevelResults.getFlag(FlagNames.verbose) 83 | ? LogLevel.verbose 84 | : LogLevel.info, 85 | ); 86 | return super.runCommand(topLevelResults); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /example/config.yaml: -------------------------------------------------------------------------------- 1 | # Generated by Spider 2 | generate_tests: true 3 | 4 | groups: 5 | - class_name: Images 6 | path: assets/images 7 | types: [ .png, .jpg, .jpeg, .webp, .webm, .bmp ] 8 | 9 | - class_name: Svgs 10 | sub_groups: 11 | - path: assets/svgsMenu 12 | prefix: menu 13 | types: [ .svg ] 14 | 15 | - path: assets/svgsOther 16 | prefix: other 17 | types: [ .svg ] 18 | 19 | - class_name: Ico 20 | types: [ .ico ] 21 | prefix: ico 22 | sub_groups: 23 | - path: assets/icons 24 | prefix: test1 25 | types: [ .ttf ] 26 | 27 | - path: assets/vectors 28 | prefix: test2 29 | types: [ .pdf ] 30 | 31 | - class_name: Video 32 | types: [ .mp4 ] 33 | path: assets/moviesOnly 34 | sub_groups: 35 | - path: assets/movies 36 | prefix: common 37 | 38 | - path: assets/moviesExtra 39 | prefix: extra 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | # MANUAL: 48 | 49 | # =========================================================================================== 50 | # KEY TYPE DESCRIPTION REQUIRED? 51 | # =========================================================================================== 52 | # path/paths [STRING] Where to locate assets? [REQUIRED] 53 | # ------------------------------------------------------------------------------------------- 54 | # class_name [STRING] What will be the name of [REQUIRED] 55 | # generated dart class? 56 | # ------------------------------------------------------------------------------------------- 57 | # package [STRING] Where to generate dart code [OPTIONAL] 58 | # in the lib folder? 59 | # ------------------------------------------------------------------------------------------- 60 | # file_name [STRING] What will be the name of the [OPTIONAL] 61 | # generated dart file? 62 | # ------------------------------------------------------------------------------------------- 63 | # prefix [STRING] What will be the prefix of [OPTIONAL] 64 | # generated dart references? 65 | # ------------------------------------------------------------------------------------------- 66 | # types [LIST] Which types of assets should [OPTIONAL] 67 | # be included? 68 | # ------------------------------------------------------------------------------------------- 69 | # generate_test [LIST] generate test cases to make sure [OPTIONAL] 70 | # that asssets are still present in 71 | # the project? 72 | # =========================================================================================== 73 | 74 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | We'd love for you to contribute to our source code and to make the project even better than it is today! 3 | Here are the guidelines we'd like you to follow: 4 | 5 | - [Code of Conduct](#coc) 6 | - [Git Commit Messages](#commit) 7 | - [Got a Question or Problem?](#question) 8 | - [Found an Issue?](#issue) 9 | 10 | ## Code of Conduct 11 | [Code of Conduct](https://github.com/BirjuVachhani/spider/blob/main/CODE_OF_CONDUCT.md) 12 | 13 | ## Git Commit Messages 14 | 15 | * Use the present tense ("Add feature" not "Added feature") 16 | * Use the imperative mood ("Move cursor to..." not "Moves cursor to...") 17 | * Limit the first line to 72 characters or less 18 | * Reference issues and pull requests liberally after the first line 19 | * When only changing documentation, include `[ci skip]` in the commit title 20 | * Consider starting the commit message with an applicable emoji: 21 | * :art: `:art:` when improving the format/structure of the code 22 | * :racehorse: `:racehorse:` when improving performance 23 | * :non-potable_water: `:non-potable_water:` when plugging memory leaks 24 | * :memo: `:memo:` when writing docs 25 | * :bug: `:bug:` when fixing a bug 26 | * :fire: `:fire:` when removing code or files 27 | * :green_heart: `:green_heart:` when fixing the CI build 28 | * :white_check_mark: `:white_check_mark:` when adding tests 29 | * :lock: `:lock:` when dealing with security 30 | * :arrow_up: `:arrow_up:` when upgrading dependencies 31 | * :arrow_down: `:arrow_down:` when downgrading dependencies 32 | * :shirt: `:shirt:` when removing lint/checkstyle warnings 33 | 34 | Find all the available emojis [here](https://gitmoji.carloscuesta.me/). 35 | 36 | ## Got a Question or Problem? 37 | 38 | If you feel that we're missing an important bit of documentation, feel free to 39 | file an issue so we can help. Here's an example to get you started: 40 | 41 | ``` 42 | What are you trying to do or find out more about? 43 | 44 | Where have you looked? 45 | 46 | Where did you expect to find this information? 47 | ``` 48 | 49 | ## Found an Issue? 50 | If you find a bug in the source code or a mistake in the documentation, you can help us by 51 | submitting an issue to our project. 52 | 53 | To submit an issue, please check the [Issue Template](https://github.com/BirjuVachhani/spider/blob/main/ISSUE_TEMPLATE.md). 54 | 55 | Even better you can submit a Pull Request with a fix. 56 | 57 | ### Pull Request 58 | To generate a pull request, please consider following [Pull Request Template](https://github.com/BirjuVachhani/spider/blob/main/PULL_REQUEST_TEMPLATE.md). 59 | 60 | * Search [GitHub](https://github.com/BirjuVachhani/spider/pulls) for an open or closed Pull Request 61 | that relates to your submission. You don't want to duplicate effort. 62 | * Please have a look at [License](https://github.com/BirjuVachhani/spider/blob/main/LICENSE) before sending pull 63 | requests. We cannot accept code without this. 64 | 65 | That's it! Thank you for your contribution! 66 | -------------------------------------------------------------------------------- /lib/src/cli/flag_commands/check_updates_flag_command.dart: -------------------------------------------------------------------------------- 1 | // Copyright © 2020 Birju Vachhani. All rights reserved. 2 | // Use of this source code is governed by an Apache license that can be 3 | // found in the LICENSE file. 4 | 5 | import 'dart:convert'; 6 | import 'dart:io'; 7 | 8 | import 'package:html/parser.dart' as parser; 9 | import 'package:http/http.dart' as http; 10 | 11 | import '../../version.dart'; 12 | import '../models/flag_names.dart'; 13 | import '../utils/utils.dart'; 14 | import 'base_flag_command.dart'; 15 | 16 | /// A flag command to check for updates for the CLI. 17 | /// e.g. Spider --check-updates. 18 | class CheckUpdatesFlagCommand extends BaseFlagCommand { 19 | /// Default constructor. 20 | CheckUpdatesFlagCommand(super.logger); 21 | 22 | @override 23 | String get help => 'Check for updates.'; 24 | 25 | @override 26 | String get name => FlagNames.checkForUpdates; 27 | 28 | @override 29 | String get abbr => 'u'; 30 | 31 | @override 32 | Future run() async { 33 | log('Checking for updates...'); 34 | 35 | final result = await checkForNewVersion(logger); 36 | 37 | if (result.isError) { 38 | exitWith(result.error); 39 | } else if (!result.data) { 40 | success(ConsoleMessages.noUpdatesAvailable); 41 | } 42 | } 43 | 44 | /// Checks whether a new version of this tool is released or not. 45 | /// Returns true if a newer version is available, false otherwise. 46 | /// Also returns error if there is an error or exception. 47 | static Future> checkForNewVersion([BaseLogger? logger]) async { 48 | try { 49 | final latestVersion = await fetchLatestVersion(); 50 | if (packageVersion != latestVersion && latestVersion.isNotEmpty) { 51 | logger?.success( 52 | Constants.NEW_VERSION_AVAILABLE 53 | .replaceAll('X.X.X', packageVersion) 54 | .replaceAll('Y.Y.Y', latestVersion), 55 | ); 56 | sleep(Duration(seconds: 1)); 57 | return Result.success(true); 58 | } 59 | return Result.success(false); 60 | } catch (err, stacktrace) { 61 | logger?.verbose(err.toString()); 62 | logger?.verbose(stacktrace.toString()); 63 | // something wrong happened! 64 | return Result.error(ConsoleMessages.unableToCheckForUpdates); 65 | } 66 | } 67 | 68 | /// A web scraping script to get latest version available for this package 69 | /// on https://pub.dev. 70 | /// Returns an empty string if fails to extract it. 71 | static Future fetchLatestVersion([BaseLogger? logger]) async { 72 | try { 73 | final html = await http 74 | .get(Uri.parse(Constants.PUB_DEV_URL)) 75 | .timeout(Duration(seconds: 3)); 76 | 77 | final document = parser.parse(html.body); 78 | 79 | var jsonScript = document.querySelector( 80 | 'script[type="application/ld+json"]', 81 | )!; 82 | var json = jsonDecode(jsonScript.innerHtml); 83 | final version = json['version'] ?? ''; 84 | return RegExp(Constants.VERSION_REGEX).hasMatch(version) ? version : ''; 85 | } catch (error, stacktrace) { 86 | logger?.verbose(error.toString()); 87 | logger?.verbose(stacktrace.toString()); 88 | // unable to get version 89 | return ''; 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /lib/src/cli/models/asset_group.dart: -------------------------------------------------------------------------------- 1 | // Copyright © 2020 Birju Vachhani. All rights reserved. 2 | // Use of this source code is governed by an Apache license that can be 3 | // found in the LICENSE file. 4 | 5 | // Author: Birju Vachhani 6 | // Created Date: February 09, 2020 7 | 8 | import '../../formatter.dart'; 9 | import '../../generation_utils.dart'; 10 | import 'asset_subgroup.dart'; 11 | 12 | /// Holds group information for assets sub directories 13 | class AssetGroup { 14 | /// Name of the dart class for the group. 15 | late final String className; 16 | 17 | /// name of the dart file for the group. 18 | late final String fileName; 19 | 20 | /// Whether to use underscore over camel case for the reference names. 21 | late final bool useUnderScores; 22 | 23 | /// Whether to generate static references or not. 24 | late final bool useStatic; 25 | 26 | /// Whether to generate constant references or not. 27 | late final bool useConst; 28 | 29 | /// Subgroups of the group. 30 | late final List? subgroups; 31 | 32 | /// Whitelisted file types for the group. 33 | late final List? types; 34 | 35 | /// Paths covered by the group. 36 | late final List? paths; 37 | 38 | /// Prefix to append to the reference names. 39 | late final String? prefix; 40 | 41 | /// Default constructor. 42 | AssetGroup({ 43 | required this.className, 44 | String? fileName, 45 | this.useUnderScores = false, 46 | this.useStatic = true, 47 | this.useConst = true, 48 | }) : fileName = Formatter.formatFileName(fileName ?? className); 49 | 50 | /// Generates [AssetGroup] from the [json] map data. 51 | AssetGroup.fromJson(Map json) { 52 | className = json['class_name'].toString(); 53 | fileName = Formatter.formatFileName( 54 | json['file_name']?.toString() ?? className, 55 | ); 56 | paths = json['path'] == null && json['paths'] == null ? null : []; 57 | if (json['paths'] != null) { 58 | paths!.addAll(List.from(json['paths'])); 59 | } else if (json['path'] != null) { 60 | paths!.add(json['path'].toString()); 61 | } 62 | // If paths are implemented in group scope we don't need 63 | // sub_groups at all. 64 | if (paths != null) { 65 | subgroups = null; 66 | prefix = json['prefix']?.toString() ?? ''; 67 | types = []; 68 | } else { 69 | subgroups = json['sub_groups'] == null && json['sub_group'] == null 70 | ? null 71 | : []; 72 | prefix = json['prefix']?.toString(); 73 | types = json['types'] == null ? null : []; 74 | } 75 | 76 | if (types != null && json['types'] != null) { 77 | json['types']!.forEach( 78 | (type) => types!.add(formatExtension(type.toString()).toLowerCase()), 79 | ); 80 | } 81 | if (subgroups != null) { 82 | if (json['sub_groups'] != null) { 83 | json['sub_groups'].forEach( 84 | (subgroup) => subgroups!.add(AssetSubgroup.fromJson(subgroup)), 85 | ); 86 | } else if (json['sub_group'] != null) { 87 | subgroups!.add(AssetSubgroup.fromJson(json['sub_group'])); 88 | } 89 | } 90 | // Diff constants. 91 | useUnderScores = json['use_underscores'] == true; 92 | useStatic = true; 93 | useConst = true; 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /.github/workflows/artifacts.yaml: -------------------------------------------------------------------------------- 1 | name: Artifacts 2 | 3 | on: 4 | release: 5 | types: [ published ] 6 | workflow_dispatch: 7 | inputs: 8 | dry: 9 | description: 'Run the workflow in dry mode' 10 | type: boolean 11 | required: false 12 | default: false 13 | 14 | jobs: 15 | ubuntu: 16 | runs-on: ubuntu-latest 17 | steps: 18 | - uses: dart-lang/setup-dart@v1 19 | - name: Dart SDK 20 | run: dart --version 21 | - uses: actions/checkout@v5 22 | - name: Install dependencies 23 | run: dart pub get 24 | - name: Build Artifact 25 | run: dart compile exe bin/spider.dart -o bin/spider 26 | - name: Archive 27 | run: tar -czf spider-${{ github.event.release.tag_name }}-linux-amd64.tar.gz bin/spider 28 | - uses: actions/upload-artifact@v4 29 | with: 30 | name: ubuntu 31 | path: spider-${{ github.event.release.tag_name }}-linux-amd64.tar.gz 32 | 33 | mac: 34 | runs-on: macos-latest 35 | steps: 36 | - uses: dart-lang/setup-dart@v1 37 | - name: Dart SDK 38 | run: dart --version 39 | - uses: actions/checkout@v5 40 | - name: Install dependencies 41 | run: dart pub get 42 | - name: Build Artifact 43 | run: dart compile exe bin/spider.dart -o bin/spider 44 | - name: Archive 45 | run: tar -czf spider-${{ github.event.release.tag_name }}-macos.tar.gz bin/spider 46 | - uses: actions/upload-artifact@v4 47 | with: 48 | name: macos 49 | path: spider-${{ github.event.release.tag_name }}-macos.tar.gz 50 | 51 | windows: 52 | runs-on: windows-latest 53 | 54 | steps: 55 | - uses: dart-lang/setup-dart@v1 56 | - name: Dart SDK 57 | run: dart --version 58 | - uses: actions/checkout@v5 59 | - name: Install dependencies 60 | run: dart pub get 61 | - name: Build Artifact 62 | run: dart compile exe bin/spider.dart -o bin/spider.exe 63 | - name: Archive 64 | run: tar.exe -a -c -f spider-${{ github.event.release.tag_name }}-windows.zip bin/spider.exe 65 | - uses: actions/upload-artifact@v4 66 | with: 67 | name: windows 68 | path: spider-${{ github.event.release.tag_name }}-windows.zip 69 | 70 | upload: 71 | needs: [ ubuntu, mac, windows ] 72 | runs-on: ubuntu-latest 73 | steps: 74 | - uses: actions/download-artifact@v5 75 | with: 76 | path: dist 77 | merge-multiple: true 78 | - name: Display structure of downloaded files 79 | run: ls -R dist 80 | - name: Upload binaries to release 81 | uses: svenstaro/upload-release-action@v2 82 | # Do not run for dry run 83 | if: ${{ github.event_name != 'workflow_dispatch' || github.event.inputs.dry == false }} 84 | with: 85 | repo_token: ${{ secrets.GITHUB_TOKEN }} 86 | file: dist/* 87 | tag: ${{ github.ref }} 88 | file_glob: true 89 | - name: Call Update Homebrew formula webhook 90 | uses: peter-evans/repository-dispatch@v4 91 | if: ${{ github.event_name != 'workflow_dispatch' || github.event.inputs.dry == false }} 92 | with: 93 | token: ${{ secrets.PAT_HOMEBREW_SPIDER_REPO }} 94 | repository: BirjuVachhani/homebrew-spider 95 | event-type: update-formula 96 | client-payload: '{"version": "${{ github.event.release.tag_name }}"}' 97 | -------------------------------------------------------------------------------- /lib/src/cli/models/default_config_templates.dart: -------------------------------------------------------------------------------- 1 | // Copyright © 2020 Birju Vachhani. All rights reserved. 2 | // Use of this source code is governed by an Apache license that can be 3 | // found in the LICENSE file. 4 | 5 | // ignore_for_file: public_member_api_docs 6 | 7 | /// Contains configuration creation templates. 8 | class DefaultConfigTemplates { 9 | DefaultConfigTemplates._(); 10 | 11 | static const String jsonFormat = '''{ 12 | "generate_tests":true, 13 | "no_comments":true, 14 | "export":true, 15 | "use_part_of":true, 16 | "use_references_list": false, 17 | "fonts": false, 18 | "package":"resources", 19 | "groups": [ 20 | { 21 | "path": "assets/images", 22 | "class_name": "Assets", 23 | "types": ["jpg", "jpeg", "png", "webp", "gif", "bmp", "wbmp"] 24 | }, 25 | { 26 | "path": "assets/vectors", 27 | "class_name": "Svgs", 28 | "package": "res", 29 | "types": ["svg"] 30 | } 31 | ] 32 | }'''; 33 | static const String pubspecFormat = ''' 34 | # Generated by Spider 35 | # For more info on configuration, visit https://birjuvachhani.github.io/spider/grouping/ 36 | spider: 37 | # Generates unit tests to verify that the assets exists in assets directory 38 | generate_tests: true 39 | # Use this to remove vcs noise created by the `generated` comments in dart code 40 | no_comments: true 41 | # Exports all the generated file as the one library 42 | export: true 43 | # This allows you to import all the generated references with 1 single import! 44 | use_part_of: true 45 | # Generates a variable that contains a list of all asset values. 46 | use_references_list: false 47 | # Generates files with given ignore rules for file. 48 | # ignored_rules: 49 | # - public_member_api_docs 50 | # Generates dart font family references for fonts specified in pubspec.yaml 51 | # fonts: true 52 | # -------- OR -------- 53 | # fonts: 54 | # class_name: MyFonts 55 | # file_name: my_fonts 56 | # Location where all the generated references will be stored 57 | package: resources 58 | groups: 59 | - path: assets/images 60 | class_name: Images 61 | types: [ .png, .jpg, .jpeg, .webp, .webm, .bmp ]'''; 62 | 63 | static const String yamlFormat = ''' 64 | # Generated by Spider 65 | # For more info on configuration, visit https://birjuvachhani.github.io/spider/grouping/ 66 | # Generates unit tests to verify that the assets exists in assets directory 67 | generate_tests: true 68 | 69 | # Use this to remove vcs noise created by the `generated` comments in dart code 70 | no_comments: true 71 | 72 | # Exports all the generated file as the one library 73 | export: true 74 | 75 | # This allows you to import all the generated references with 1 single import! 76 | use_part_of: true 77 | 78 | # Generates a variable that contains a list of all asset values. 79 | use_references_list: false 80 | 81 | # Generates files with given ignore rules for file. 82 | # ignored_rules: 83 | # - public_member_api_docs 84 | 85 | # Generates dart font family references for fonts specified in pubspec.yaml 86 | # fonts: true 87 | # -------- OR -------- 88 | # fonts: 89 | # class_name: MyFonts 90 | # file_name: my_fonts 91 | 92 | # Location where all the generated references will be stored 93 | package: resources 94 | 95 | groups: 96 | - path: assets/images 97 | class_name: Images 98 | types: [ .png, .jpg, .jpeg, .webp, .webm, .bmp ]'''; 99 | } 100 | -------------------------------------------------------------------------------- /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, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and 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 brvachhani@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 https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq 77 | -------------------------------------------------------------------------------- /lib/src/cli/commands/build_command.dart: -------------------------------------------------------------------------------- 1 | // Copyright © 2020 Birju Vachhani. All rights reserved. 2 | // Use of this source code is governed by an Apache license that can be 3 | // found in the LICENSE file. 4 | 5 | import 'package:args/args.dart'; 6 | import 'package:sprintf/sprintf.dart'; 7 | 8 | import '../../../spider.dart'; 9 | import '../flag_commands/flag_commands.dart'; 10 | import '../models/command_names.dart'; 11 | import '../models/flag_names.dart'; 12 | import '../models/spider_config.dart'; 13 | import '../utils/utils.dart'; 14 | import 'base_command.dart'; 15 | 16 | /// Build command to execute code generation for assets. 17 | class BuildCommand extends BaseCommand { 18 | @override 19 | String get description => 20 | "Generates dart code for assets of your flutter project. 'spider.yaml' " 21 | "file is used by this command to generate dart code."; 22 | 23 | @override 24 | String get name => CommandNames.build; 25 | 26 | @override 27 | String get summary => 28 | 'Generates dart code for assets of your flutter project.'; 29 | 30 | /// Default constructor for build command. 31 | /// [logger] is used to output all kinds of logs, errors and exceptions. 32 | BuildCommand(super.logger) { 33 | argParser 34 | ..addFlag( 35 | FlagNames.watch, 36 | negatable: false, 37 | help: 38 | 'Watches assets directory for file changes and re-generates ' 39 | 'dart references upon file creation, deletion or modification.', 40 | ) 41 | ..addFlag( 42 | FlagNames.smartWatch, 43 | negatable: false, 44 | help: 45 | "Smartly watches assets directory for file changes and " 46 | "re-generates dart references by ignoring events and files " 47 | "that doesn't fall under the group configuration.", 48 | ) 49 | ..addFlag( 50 | FlagNames.fontsOnly, 51 | negatable: false, 52 | help: 53 | "Only triggers code-gen for fonts when fonts is set in " 54 | "config file.", 55 | ); 56 | } 57 | 58 | @override 59 | Future run() async { 60 | if (!isFlutterProject()) { 61 | exitWith(ConsoleMessages.notFlutterProjectError); 62 | return; 63 | } 64 | 65 | final ArgResults results = argResults!; 66 | 67 | // Check for updates. 68 | await CheckUpdatesFlagCommand.checkForNewVersion(logger); 69 | 70 | final Result result = retrieveConfigs( 71 | customPath: globalResults!['path'], 72 | logger: logger, 73 | allowEmpty: true, 74 | fontsOnly: results.getFlag(FlagNames.fontsOnly), 75 | ); 76 | if (result.isSuccess) { 77 | if (results.getFlag(FlagNames.fontsOnly)) { 78 | // if fonts-only flag is provided and fonts is not set in 79 | // config then exit with error. 80 | if (!result.data.globals.fontConfigs.generate) { 81 | exitWith( 82 | sprintf(ConsoleMessages.fontsOnlyExecutedWithoutSetTemplate, [ 83 | FlagNames.fontsOnly, 84 | ]), 85 | ); 86 | return; 87 | } 88 | } else if (result.data.groups.isEmpty && 89 | !result.data.globals.fontConfigs.generate) { 90 | // If fonts-only flag is not set and no groups are provided too then 91 | // exit with error. 92 | exitWith(ConsoleMessages.nothingToGenerate); 93 | return; 94 | } 95 | 96 | final spider = Spider(result.data); 97 | spider.build( 98 | watch: results.getFlag(FlagNames.watch), 99 | smartWatch: results.getFlag(FlagNames.watch), 100 | fontsOnly: results.getFlag(FlagNames.fontsOnly), 101 | logger: logger, 102 | ); 103 | } else { 104 | if (result.exception != null) verbose(result.exception.toString()); 105 | if (result.stacktrace != null) verbose(result.stacktrace.toString()); 106 | exitWith(result.error); 107 | } 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /lib/spider.dart: -------------------------------------------------------------------------------- 1 | // Copyright © 2020 Birju Vachhani. All rights reserved. 2 | // Use of this source code is governed by an Apache license that can be 3 | // found in the LICENSE file. 4 | 5 | import 'package:path/path.dart' as p; 6 | import 'package:sprintf/sprintf.dart' hide Formatter; 7 | 8 | import 'src/cli/models/flag_names.dart'; 9 | import 'src/cli/models/spider_config.dart'; 10 | import 'src/cli/utils/utils.dart'; 11 | import 'src/dart_class_generator.dart'; 12 | import 'src/fonts_generator.dart'; 13 | import 'src/formatter.dart'; 14 | import 'src/generation_utils.dart'; 15 | 16 | /// Entry point of all the command process 17 | /// provides various functions to execute commands 18 | /// Responsible for triggering dart code generation 19 | class Spider { 20 | /// Holds the typed configuration data parsed from config file. 21 | SpiderConfiguration config; 22 | 23 | /// Default Constructor. 24 | /// [config] provides the typed configuration data parsed from config file. 25 | Spider(this.config); 26 | 27 | /// Triggers build 28 | void build({ 29 | bool watch = false, 30 | bool smartWatch = false, 31 | bool fontsOnly = false, 32 | BaseLogger? logger, 33 | }) { 34 | // Only process for groups if fonts-only flag is not set and groups are 35 | // provided. 36 | if (!fontsOnly && config.groups.isNotEmpty) { 37 | for (final group in config.groups) { 38 | final generator = DartClassGenerator(group, config.globals, logger); 39 | generator.initAndStart(watch: watch, smartWatch: smartWatch); 40 | } 41 | } 42 | 43 | if (config.globals.export) { 44 | exportAsLibrary(); 45 | } 46 | 47 | if (config.globals.fontConfigs.generate) { 48 | generateFontReferences(logger, fontsOnly: fontsOnly); 49 | } else if (fontsOnly) { 50 | // [GlobalConfigs.generateForFonts] is not true and fonts-only flag is 51 | // given then exit with error. 52 | logger?.exitWith( 53 | sprintf(ConsoleMessages.fontsOnlyExecutedWithoutSetTemplate, [ 54 | FlagNames.fontsOnly, 55 | ]), 56 | ); 57 | } 58 | } 59 | 60 | /// Generates library export file for all the generated references files. 61 | void exportAsLibrary() { 62 | final List fileNames = config.groups 63 | .map((group) => Formatter.formatFileName(group.fileName)) 64 | .toList(); 65 | 66 | // Don't include files that does not exist. 67 | fileNames.removeWhere( 68 | (name) => file(p.join('lib', config.globals.package, name)) == null, 69 | ); 70 | 71 | if (config.globals.fontConfigs.generate && 72 | config.pubspec['flutter']?['fonts'] != null) { 73 | // Only add fonts.dart in exports if fonts are generated. 74 | fileNames.add( 75 | Formatter.formatFileName(config.globals.fontConfigs.fileName), 76 | ); 77 | } 78 | 79 | // Don't generate export file if nothing is generated. 80 | if (fileNames.isEmpty) return; 81 | 82 | final content = getExportContent( 83 | noComments: config.globals.noComments, 84 | usePartOf: config.globals.usePartOf ?? false, 85 | fileNames: fileNames, 86 | ); 87 | 88 | writeToFile( 89 | name: Formatter.formatFileName(config.globals.exportFileName), 90 | path: config.globals.package, 91 | content: DartClassGenerator.formatter.format(content), 92 | ); 93 | } 94 | 95 | /// Generates references for fonts defined in pubspec.yaml 96 | void generateFontReferences(BaseLogger? logger, {bool fontsOnly = false}) { 97 | if (config.pubspec['flutter']?['fonts'] == null) { 98 | if (fontsOnly) { 99 | logger?.exitWith(ConsoleMessages.noFontsFound); 100 | } else { 101 | logger?.info(ConsoleMessages.noFontsFound); 102 | } 103 | return; 104 | } 105 | final generator = FontsGenerator(); 106 | generator.generate( 107 | config.pubspec['flutter']['fonts'], 108 | config.globals, 109 | logger, 110 | ); 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /test/test_utils.dart: -------------------------------------------------------------------------------- 1 | // Copyright © 2020 Birju Vachhani. All rights reserved. 2 | // Use of this source code is governed by an Apache license that can be 3 | // found in the LICENSE file. 4 | 5 | import 'dart:convert'; 6 | import 'dart:io'; 7 | 8 | import 'package:mockito/mockito.dart'; 9 | import 'package:path/path.dart' as p; 10 | import 'package:spider/src/cli/process_terminator.dart'; 11 | import 'package:spider/src/cli/utils/utils.dart'; 12 | 13 | void deleteConfigFiles() { 14 | if (File('spider.json').existsSync()) { 15 | File('spider.json').deleteSync(); 16 | } 17 | if (File('spider.yaml').existsSync()) { 18 | File('spider.yaml').deleteSync(); 19 | } 20 | if (File('spider.yml').existsSync()) { 21 | File('spider.yml').deleteSync(); 22 | } 23 | } 24 | 25 | void createTestConfigs(Map config) { 26 | File( 27 | 'spider.json', 28 | ).writeAsStringSync(JsonEncoder.withIndent(' ').convert(config)); 29 | } 30 | 31 | void createTestAssets() { 32 | File(p.join('assets', 'images', 'test1.png')).createSync(recursive: true); 33 | File(p.join('assets', 'images', 'test2.jpg')).createSync(recursive: true); 34 | File(p.join('assets', 'images', 'test3.unknown')).createSync(recursive: true); 35 | File(p.join('assets', 'images2x', 'test1.png')).createSync(recursive: true); 36 | 37 | File(p.join('assets', 'svgsMenu', 'test4.svg')).createSync(recursive: true); 38 | File(p.join('assets', 'svgsOther', 'test5.ttf')).createSync(recursive: true); 39 | File(p.join('assets', 'svgsOther', 'test6.svg')).createSync(recursive: true); 40 | File(p.join('assets', 'svgsMenu2x', 'test4.svg')).createSync(recursive: true); 41 | 42 | File(p.join('assets', 'icons', 'test8.ico')).createSync(recursive: true); 43 | File(p.join('assets', 'icons', 'test9.ttf')).createSync(recursive: true); 44 | File(p.join('assets', 'icons', 'test10.pdf')).createSync(recursive: true); 45 | File(p.join('assets', 'iconsPng', 'test8.ico')).createSync(recursive: true); 46 | File(p.join('assets', 'vectors', 'test11.ico')).createSync(recursive: true); 47 | 48 | File(p.join('assets', 'movies', 'test12.mp4')).createSync(recursive: true); 49 | File(p.join('assets', 'movies', 'test13.mp4')).createSync(recursive: true); 50 | File( 51 | p.join('assets', 'moviesExtra', 'test14.mp4'), 52 | ).createSync(recursive: true); 53 | File( 54 | p.join('assets', 'moviesExtra', 'test15.mp4'), 55 | ).createSync(recursive: true); 56 | File( 57 | p.join('assets', 'moviesOnly', 'test16.mp4'), 58 | ).createSync(recursive: true); 59 | File( 60 | p.join('assets', 'moviesOnly', 'test17.ico'), 61 | ).createSync(recursive: true); 62 | } 63 | 64 | void createMoreTestAssets() { 65 | File(p.join('assets', 'fonts', 'test1.otf')).createSync(recursive: true); 66 | File(p.join('assets', 'fonts', 'test2.otf')).createSync(recursive: true); 67 | File(p.join('assets', 'fonts', 'test3.otf')).createSync(recursive: true); 68 | } 69 | 70 | void deleteTestAssets() { 71 | if (Directory('assets').existsSync()) { 72 | Directory('assets').deleteSync(recursive: true); 73 | } 74 | } 75 | 76 | void deleteGeneratedRefs() { 77 | if (Directory(p.join('lib', 'resources')).existsSync()) { 78 | Directory(p.join('lib', 'resources')).deleteSync(recursive: true); 79 | } 80 | } 81 | 82 | extension MapExt on Map { 83 | Map without(List keys) { 84 | final Map newMap = Map.from(this); 85 | for (final key in keys) { 86 | newMap.remove(key); 87 | } 88 | return newMap; 89 | } 90 | 91 | Map except(K key) => Map.from(this)..remove(key); 92 | 93 | Map copyWith(Map other) => Map.from(this)..addAll(other); 94 | } 95 | 96 | class MockProcessTerminator extends Mock implements ProcessTerminator { 97 | @override 98 | void terminate(String? message, dynamic stackTrace, [BaseLogger? logger]) => 99 | super.noSuchMethod( 100 | Invocation.method(#terminate, [message, stackTrace, logger]), 101 | returnValueForMissingStub: null, 102 | ); 103 | } 104 | -------------------------------------------------------------------------------- /lib/src/data/test_template.dart: -------------------------------------------------------------------------------- 1 | // Copyright © 2020 Birju Vachhani. All rights reserved. 2 | // Use of this source code is governed by an Apache license that can be 3 | // found in the LICENSE file. 4 | 5 | import 'package:meta/meta.dart'; 6 | 7 | /// A template for generating file author line. 8 | String get timeStampTemplate => '/// Generated by spider on [TIME]\n\n'; 9 | 10 | /// A template for generating test files. 11 | String get testTemplate => '''import 'dart:io'; 12 | 13 | [TEST_IMPORTS] 14 | 15 | void main() { 16 | test('[FILE_NAME] assets test', () { 17 | [TESTS] 18 | }); 19 | }'''; 20 | 21 | /// A template for importing test package 22 | String get testPackageImport => 23 | "import 'package:[TEST_IMPORT]/[TEST_IMPORT].dart';"; 24 | 25 | /// A template for importing project import file 26 | String get projectPackageImport => 27 | "import 'package:[PROJECT_NAME]/[PACKAGE]/[IMPORT_FILE_NAME]';"; 28 | 29 | /// A template for generating single test expect statement. 30 | String get expectTestTemplate => 31 | 'expect(File([CLASS_NAME].[ASSET_NAME]).existsSync(), isTrue);'; 32 | 33 | /// A template of yaml config for using in tests. 34 | @visibleForTesting 35 | String get testYamlConfigTemplate => ''' 36 | # Generated by Spider 37 | 38 | # Generates unit tests to verify that the assets exists in assets directory 39 | generate_tests: true 40 | 41 | # Use this to remove vcs noise created by the `generated` comments in dart code 42 | no_comments: true 43 | 44 | # Exports all the generated file as the one library 45 | export: true 46 | 47 | # This allows you to import all the generated references with 1 single import! 48 | use_part_of: true 49 | 50 | # Generates a variable that contains a list of all asset values 51 | use_references_list: false 52 | 53 | # Location where all the generated references will be stored 54 | package: resources 55 | 56 | # List of linter rules that will be ignored in generated files (except for tests) 57 | ignored_rules: [ public_member_api_docs, member-ordering-extended, test_rule ] 58 | 59 | groups: 60 | - class_name: Images 61 | path: assets/images 62 | types: [ .png, .jpg, .jpeg, .webp, .webm, .bmp ] 63 | 64 | - class_name: Svgs 65 | sub_groups: 66 | - path: assets/svgsMenu 67 | prefix: menu 68 | types: [ .svg ] 69 | 70 | - path: assets/svgsOther 71 | prefix: other 72 | types: [ .svg ] 73 | 74 | - class_name: Ico 75 | types: [ .ico ] 76 | prefix: ico 77 | sub_groups: 78 | - path: assets/icons 79 | prefix: test1 80 | types: [ .ttf ] 81 | 82 | - path: assets/vectors 83 | prefix: test2 84 | types: [ .pdf ] 85 | 86 | - class_name: Video 87 | types: [ .mp4 ] 88 | path: assets/moviesOnly 89 | sub_groups: 90 | - path: assets/movies 91 | prefix: common 92 | 93 | - path: assets/moviesExtra 94 | prefix: extra 95 | # - path: assets/vectors 96 | # class_name: Svgs 97 | # package: res 98 | # types: [ .svg ] 99 | '''; 100 | 101 | /// A template of json config for using in tests. 102 | @visibleForTesting 103 | String get testJsonConfigTemplate => ''' 104 | { 105 | "generate_tests":true, 106 | "no_comments":true, 107 | "export":true, 108 | "use_part_of":true, 109 | "use_references_list": false, 110 | "package":"resources", 111 | "ignored_rules": [ "public_member_api_docs", "member-ordering-extended", "test_rule" ], 112 | "groups": [ 113 | { 114 | "class_name": "Images", 115 | "path": "assets/images", 116 | "types": [ 117 | ".png", 118 | ".jpg", 119 | ".jpeg", 120 | ".webp", 121 | ".webm", 122 | ".bmp" 123 | ] 124 | }, 125 | { 126 | "class_name": "Svgs", 127 | "sub_groups": [ 128 | { 129 | "path": "assets/svgsMenu", 130 | "prefix": "menu", 131 | "types": [ 132 | ".svg" 133 | ] 134 | }, 135 | { 136 | "path": "assets/svgsOther", 137 | "prefix": "other", 138 | "types": [ 139 | ".svg" 140 | ] 141 | } 142 | ] 143 | }, 144 | { 145 | "class_name": "Ico", 146 | "types": [ 147 | ".ico" 148 | ], 149 | "prefix": "ico", 150 | "sub_groups": [ 151 | { 152 | "path": "assets/icons", 153 | "prefix": "test1", 154 | "types": [ 155 | ".ttf" 156 | ] 157 | }, 158 | { 159 | "path": "assets/vectors", 160 | "prefix": "test2", 161 | "types": [ 162 | ".pdf" 163 | ] 164 | } 165 | ] 166 | }, 167 | { 168 | "class_name": "Video", 169 | "types": [ 170 | ".mp4" 171 | ], 172 | "path": "assets/moviesOnly", 173 | "sub_groups": [ 174 | { 175 | "path": "assets/movies", 176 | "prefix": "common" 177 | }, 178 | { 179 | "path": "assets/moviesExtra", 180 | "prefix": "extra" 181 | } 182 | ] 183 | } 184 | ] 185 | } 186 | '''; 187 | -------------------------------------------------------------------------------- /lib/src/cli/utils/config_creator.dart: -------------------------------------------------------------------------------- 1 | // Copyright © 2020 Birju Vachhani. All rights reserved. 2 | // Use of this source code is governed by an Apache license that can be 3 | // found in the LICENSE file. 4 | 5 | import 'dart:io'; 6 | 7 | import 'package:path/path.dart' as p; 8 | import 'package:sprintf/sprintf.dart'; 9 | import 'package:yaml/yaml.dart'; 10 | 11 | import '../models/default_config_templates.dart'; 12 | import 'utils.dart'; 13 | 14 | /// A utility class that helps create config file using create command. 15 | class ConfigCreator { 16 | /// A logger for logging information, errors and exceptions. 17 | final BaseLogger? logger; 18 | 19 | /// Default constructor. 20 | /// [logger] is used to output all kinds of logs, errors and exceptions. 21 | ConfigCreator([this.logger]); 22 | 23 | /// Creates a config file in 3 different ways. 24 | /// 1. Creates a config file in the root directory of the project when [path] 25 | /// is not provided. 26 | /// 2. Creates a config file in the [path] directory if it is provided. 27 | /// 3. Appends configs in pubspec.yaml file if [addInPubspec] is true. 28 | /// [isJson] allows to create a json config file. This does not work if 29 | /// [addInPubspec] is true. 30 | Result create({ 31 | bool addInPubspec = false, 32 | bool isJson = false, 33 | String? path, 34 | }) { 35 | try { 36 | if (addInPubspec) { 37 | final result = createConfigsInPubspec(); 38 | if (result.isError) return result; 39 | return Result.success(); 40 | } 41 | if (path != null && path.isNotEmpty) { 42 | final result = createConfigFileAtCustomPath(path, isJson); 43 | if (result.isError) return result; 44 | return Result.success(); 45 | } 46 | final result = createConfigFileInCurrentDirectory(isJson); 47 | if (result.isError) return result; 48 | return Result.success(); 49 | } on Error catch (error, stacktrace) { 50 | return Result.error( 51 | ConsoleMessages.unableToCreateConfigFile, 52 | error, 53 | stacktrace, 54 | ); 55 | } 56 | } 57 | 58 | /// Adds configs in pubspec.yaml file of the project. This requires the 59 | /// pubspec.yaml file to be present in the current directory where command 60 | /// is being executed. 61 | Result createConfigsInPubspec() { 62 | final pubspecFile = 63 | file(p.join(Directory.current.path, 'pubspec.yaml')) ?? 64 | file(p.join(Directory.current.path, 'pubspec.yml')); 65 | if (pubspecFile == null) { 66 | return Result.error(ConsoleMessages.pubspecNotFound); 67 | } 68 | final pubspecContent = pubspecFile.readAsStringSync(); 69 | final pubspec = loadYaml(pubspecContent); 70 | if (pubspec['spider'] != null) { 71 | return Result.error(ConsoleMessages.configExistsInPubspec); 72 | } 73 | try { 74 | final lines = pubspecFile.readAsLinesSync(); 75 | String configContent = DefaultConfigTemplates.pubspecFormat; 76 | if (lines.last.trim().isNotEmpty || !lines.last.endsWith('\n')) { 77 | configContent = '\n\n$configContent'; 78 | } 79 | pubspecFile.openWrite(mode: FileMode.writeOnlyAppend) 80 | ..write(configContent) 81 | ..close(); 82 | logger?.success(ConsoleMessages.configCreatedInPubspec); 83 | return Result.success(); 84 | } on Error catch (error, stacktrace) { 85 | return Result.error( 86 | ConsoleMessages.unableToAddConfigInPubspec, 87 | error, 88 | stacktrace, 89 | ); 90 | } 91 | } 92 | 93 | /// Creates config file at custom path. 94 | Result createConfigFileAtCustomPath(String path, bool isJson) { 95 | String filePath; 96 | if (path.startsWith('/')) path = '.$path'; 97 | if (FileSystemEntity.isDirectorySync(path)) { 98 | // provided path is an existing directory 99 | Directory(path).createSync(recursive: true); 100 | filePath = p.join(path, isJson ? 'spider.json' : 'spider.yaml'); 101 | } else { 102 | final String extension = p.extension(path); 103 | if (extension.isNotEmpty) { 104 | return Result.error(ConsoleMessages.invalidDirectoryPath); 105 | } 106 | Directory(path).createSync(recursive: true); 107 | filePath = p.join(path, isJson ? 'spider.json' : 'spider.yaml'); 108 | } 109 | final content = p.extension(filePath) == '.json' 110 | ? DefaultConfigTemplates.jsonFormat 111 | : DefaultConfigTemplates.yamlFormat; 112 | final file = File(filePath); 113 | if (file.existsSync()) { 114 | return Result.error( 115 | sprintf(ConsoleMessages.configFileExistsTemplate, [filePath]), 116 | ); 117 | } 118 | file.writeAsStringSync(content); 119 | logger?.success( 120 | sprintf(ConsoleMessages.fileCreatedAtCustomPath, [filePath]), 121 | ); 122 | return Result.success(); 123 | } 124 | 125 | /// Creates config file in current directory where the command is being 126 | /// executed. 127 | /// [isJson] allows to create a json config file. 128 | Result createConfigFileInCurrentDirectory(bool isJson) { 129 | final filename = isJson ? 'spider.json' : 'spider.yaml'; 130 | final dest = File(p.join(Directory.current.path, filename)); 131 | final content = isJson 132 | ? DefaultConfigTemplates.jsonFormat 133 | : DefaultConfigTemplates.yamlFormat; 134 | if (dest.existsSync()) { 135 | logger?.info('Config file already exists. Overwriting configs...'); 136 | } 137 | dest.writeAsStringSync(content); 138 | logger?.success(ConsoleMessages.configFileCreated); 139 | return Result.success(); 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /lib/src/cli/models/spider_config.dart: -------------------------------------------------------------------------------- 1 | // Copyright © 2020 Birju Vachhani. All rights reserved. 2 | // Use of this source code is governed by an Apache license that can be 3 | // found in the LICENSE file. 4 | 5 | import '../../formatter.dart'; 6 | import '../utils/utils.dart'; 7 | import 'asset_group.dart'; 8 | 9 | /// Represents the config.yaml file in form of dart model. 10 | /// [groups] contains all the defined groups in the config file. 11 | /// [globals] holds all the global configuration values. 12 | class SpiderConfiguration { 13 | /// Parsed groups from the config file. 14 | late final List groups; 15 | 16 | /// Parsed global configuration values. 17 | late final GlobalConfigs globals; 18 | 19 | /// Parsed pubspec.yaml data. 20 | late final Map pubspec; 21 | 22 | /// Default constructor. 23 | SpiderConfiguration({ 24 | required this.globals, 25 | required this.groups, 26 | required this.pubspec, 27 | }); 28 | 29 | /// Creates [SpiderConfiguration] from given [json] map data. 30 | factory SpiderConfiguration.fromJson(Map json) { 31 | final groups = []; 32 | if (json['groups'] != null) { 33 | json['groups'].forEach((v) { 34 | groups.add(AssetGroup.fromJson(v)); 35 | }); 36 | } 37 | return SpiderConfiguration( 38 | globals: GlobalConfigs.fromJson(json, json['pubspec'] ?? {}), 39 | groups: groups, 40 | pubspec: json['pubspec'] ?? {}, 41 | ); 42 | } 43 | } 44 | 45 | /// Holds all the global configuration values defined in the config.yaml file. 46 | class GlobalConfigs { 47 | /// Whether to generate tests for the assets. 48 | final bool generateTests; 49 | 50 | /// Whether to add any additional comments in the generated code. 51 | final bool noComments; 52 | 53 | /// The name of the project. Used for generating correct imports. 54 | final String projectName; 55 | 56 | /// Whether to export as a library or not. 57 | final bool export; 58 | 59 | /// Defines where to put the generated code. 60 | String? package; 61 | 62 | /// Whether to use `part of` and `part` directives for the generated code. 63 | bool? usePartOf; 64 | 65 | /// lint rules to be ignored in the generated code. 66 | final List? ignoredRules; 67 | 68 | /// Name of the exported library file. 69 | final String exportFileName; 70 | 71 | /// Whether to generate a list that contains all the assets. 72 | final bool useReferencesList; 73 | 74 | /// Whether to use `flutter_test` or `test` import for the tests. 75 | final bool useFlutterTestImports; 76 | 77 | /// Whether to generate references for fonts defined in pubspec.yaml file. 78 | final FontConfigs fontConfigs; 79 | 80 | /// Default constructor. 81 | GlobalConfigs({ 82 | required this.ignoredRules, 83 | required this.generateTests, 84 | required this.noComments, 85 | required this.export, 86 | required this.usePartOf, 87 | required this.package, 88 | required this.exportFileName, 89 | required this.projectName, 90 | required this.useReferencesList, 91 | this.useFlutterTestImports = false, 92 | this.fontConfigs = const FontConfigs(generate: false), 93 | }); 94 | 95 | /// Creates [GlobalConfigs] from given [json] map data. 96 | factory GlobalConfigs.fromJson( 97 | Map json, 98 | Map pubspec, 99 | ) { 100 | List? ignoredRules; 101 | if (json['ignored_rules'] != null) { 102 | ignoredRules = []; 103 | json['ignored_rules'].forEach((rule) => ignoredRules!.add(rule)); 104 | } 105 | 106 | return GlobalConfigs( 107 | ignoredRules: ignoredRules, 108 | generateTests: json['generate_tests'] == true, 109 | noComments: json['no_comments'] == true, 110 | export: json['export'] == true, 111 | usePartOf: json['use_part_of'] == true, 112 | package: json['package'] ?? Constants.DEFAULT_PACKAGE, 113 | exportFileName: json['export_file'] ?? Constants.DEFAULT_EXPORT_FILE, 114 | projectName: pubspec['name'], 115 | useReferencesList: json['use_references_list'] == true, 116 | useFlutterTestImports: pubspec['dependencies']?['flutter'] != null, 117 | fontConfigs: FontConfigs.fromJson(json['fonts']), 118 | ); 119 | } 120 | } 121 | 122 | /// Holds configuration for fonts 123 | class FontConfigs { 124 | /// Whether to generate fonts references or not. 125 | final bool generate; 126 | 127 | /// Class name to use for fonts dart references. 128 | final String className; 129 | 130 | /// File name to use for fonts dart class. 131 | final String fileName; 132 | 133 | /// Prefix to append to the reference names. 134 | final String? prefix; 135 | 136 | /// Whether to use underscore over camel case for the reference names. 137 | final bool useUnderScores; 138 | 139 | /// Default constructor 140 | const FontConfigs({ 141 | required this.generate, 142 | this.className = 'FontFamily', 143 | this.fileName = 'fonts', 144 | this.prefix, 145 | this.useUnderScores = false, 146 | }); 147 | 148 | /// Generates [AssetGroup] from the [json] map data. 149 | factory FontConfigs.fromJson(dynamic json) { 150 | if (json == null) return FontConfigs(generate: false); 151 | if (json == true) return FontConfigs(generate: true); 152 | if (json is! Map) return FontConfigs(generate: false); 153 | 154 | final className = json['class_name'].toString(); 155 | final fileName = Formatter.formatFileName( 156 | json['file_name']?.toString() ?? className, 157 | ); 158 | final prefix = json['prefix']?.toString(); 159 | final useUnderScores = json['use_underscores'] == true; 160 | 161 | return FontConfigs( 162 | generate: true, 163 | className: className, 164 | fileName: fileName, 165 | prefix: prefix, 166 | useUnderScores: useUnderScores, 167 | ); 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /lib/src/cli/utils/config_retriever.dart: -------------------------------------------------------------------------------- 1 | // Copyright © 2020 Birju Vachhani. All rights reserved. 2 | // Use of this source code is governed by an Apache license that can be 3 | // found in the LICENSE file. 4 | 5 | import 'dart:convert'; 6 | import 'dart:io'; 7 | 8 | import 'package:path/path.dart' as p; 9 | import 'package:sprintf/sprintf.dart'; 10 | import 'package:yaml/yaml.dart'; 11 | 12 | import '../models/spider_config.dart'; 13 | import 'utils.dart'; 14 | 15 | /// A top-level method that would retrieve the config file from either root, 16 | /// pubspec or custom path and return parsed [SpiderConfiguration] object. 17 | /// [allowEmpty] if set to true, would consider configs as valid even if no 18 | /// groups are provided and fonts is not set. 19 | Result retrieveConfigs({ 20 | String? customPath, 21 | BaseLogger? logger, 22 | bool allowEmpty = false, 23 | bool fontsOnly = false, 24 | }) { 25 | final Result? result = 26 | readConfigFileFromPath(customPath, logger) ?? 27 | readConfigsFromPubspec(logger) ?? 28 | readConfigFileFromRoot(logger); 29 | 30 | if (result == null) { 31 | return Result.error(ConsoleMessages.configNotFoundDetailed); 32 | } 33 | 34 | if (result.isError) { 35 | return Result.error(result.error, result.exception, result.stacktrace); 36 | } 37 | 38 | final JsonMap configJson = result.data; 39 | 40 | try { 41 | logger?.verbose('Validating configs'); 42 | final validationResult = validateConfigs( 43 | configJson, 44 | allowEmpty: allowEmpty, 45 | fontsOnly: fontsOnly, 46 | ); 47 | if (validationResult.isError) { 48 | return Result.error( 49 | validationResult.error, 50 | validationResult.exception, 51 | validationResult.stacktrace, 52 | ); 53 | } 54 | 55 | final Result result = retrievePubspecData(); 56 | if (result.isError) { 57 | return Result.error(result.error, result.exception, result.stacktrace); 58 | } 59 | configJson['pubspec'] = result.data; 60 | final config = SpiderConfiguration.fromJson(configJson); 61 | return Result.success(config); 62 | } catch (error, stacktrace) { 63 | return Result.error(ConsoleMessages.parseError, error, stacktrace); 64 | } 65 | } 66 | 67 | /// Reads the config from the config file in the current directory. 68 | Result? readConfigFileFromRoot([BaseLogger? logger]) { 69 | try { 70 | var yamlFile = 71 | file(p.join(Directory.current.path, 'spider.yaml')) ?? 72 | file(p.join(Directory.current.path, 'spider.yml')); 73 | final jsonFile = file(p.join(Directory.current.path, 'spider.json')); 74 | Map map; 75 | if (yamlFile != null) { 76 | logger?.info( 77 | sprintf(ConsoleMessages.configFoundAtTemplate, [yamlFile.path]), 78 | ); 79 | logger?.info( 80 | sprintf(ConsoleMessages.loadingConfigsFromTemplate, [ 81 | p.basename(yamlFile.path), 82 | ]), 83 | ); 84 | map = yamlToMap(yamlFile.path); 85 | } else if (jsonFile != null) { 86 | logger?.info( 87 | sprintf(ConsoleMessages.configFoundAtTemplate, [jsonFile.path]), 88 | ); 89 | logger?.info( 90 | sprintf(ConsoleMessages.loadingConfigsFromTemplate, [ 91 | p.basename(jsonFile.path), 92 | ]), 93 | ); 94 | map = json.decode(jsonFile.readAsStringSync()); 95 | } else { 96 | return null; 97 | } 98 | if (map.isEmpty) { 99 | return Result.error(ConsoleMessages.invalidConfigFile); 100 | } 101 | return Result.success(map); 102 | } catch (error, stacktrace) { 103 | return Result.error(ConsoleMessages.parseError, error, stacktrace); 104 | } 105 | } 106 | 107 | /// Reads config file from pubspec.yaml file. 108 | Result? readConfigsFromPubspec([BaseLogger? logger]) { 109 | try { 110 | final File? pubspecFile = file('pubspec.yaml') ?? file('pubspec.yml'); 111 | if (pubspecFile == null || !pubspecFile.existsSync()) return null; 112 | final parsed = yamlToMap(pubspecFile.path)['spider']; 113 | if (parsed == null) return null; 114 | logger?.info( 115 | sprintf(ConsoleMessages.configFoundAtTemplate, [pubspecFile.path]), 116 | ); 117 | return Result.success(JsonMap.from(parsed)); 118 | } catch (error, stacktrace) { 119 | return Result.error(ConsoleMessages.parseError, error, stacktrace); 120 | } 121 | } 122 | 123 | /// Reads the config from the path provided in the command line. 124 | Result? readConfigFileFromPath( 125 | String? customPath, [ 126 | BaseLogger? logger, 127 | ]) { 128 | if (customPath == null) return null; 129 | 130 | final File? configFile = file(customPath); 131 | if (configFile == null) { 132 | return Result.error(ConsoleMessages.invalidConfigFilePath); 133 | } 134 | try { 135 | final extension = p.extension(configFile.path); 136 | logger?.info( 137 | sprintf(ConsoleMessages.configFoundAtTemplate, [configFile.path]), 138 | ); 139 | if (extension == '.yaml' || extension == '.yml') { 140 | return Result.success(yamlToMap(configFile.path)); 141 | } else if (extension == '.json') { 142 | return Result.success(json.decode(configFile.readAsStringSync())); 143 | } 144 | return Result.error(ConsoleMessages.invalidConfigFile); 145 | } catch (error, stacktrace) { 146 | return Result.error(ConsoleMessages.parseError, error, stacktrace); 147 | } 148 | } 149 | 150 | /// Reads pubspec.yaml file content. 151 | Result retrievePubspecData() { 152 | try { 153 | final pubspecFile = 154 | file(p.join(Directory.current.path, 'pubspec.yaml')) ?? 155 | file(p.join(Directory.current.path, 'pubspec.yml')); 156 | if (pubspecFile != null) { 157 | final pubspec = loadYaml(pubspecFile.readAsStringSync()); 158 | return Result.success(json.decode(json.encode(pubspec))); 159 | } 160 | return Result.error(ConsoleMessages.unableToLoadPubspecFile); 161 | } catch (error, stacktrace) { 162 | return Result.error( 163 | ConsoleMessages.unableToLoadPubspecFile, 164 | error, 165 | stacktrace, 166 | ); 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /lib/src/cli/utils/helpers.dart: -------------------------------------------------------------------------------- 1 | // Copyright © 2020 Birju Vachhani. All rights reserved. 2 | // Use of this source code is governed by an Apache license that can be 3 | // found in the LICENSE file. 4 | 5 | import 'dart:convert'; 6 | import 'dart:io'; 7 | 8 | import 'package:path/path.dart' as p; 9 | import 'package:sprintf/sprintf.dart'; 10 | import 'package:yaml/yaml.dart'; 11 | 12 | import 'utils.dart'; 13 | 14 | /// Represents a typical json map. 15 | typedef JsonMap = Map; 16 | 17 | /// Returns an instance of [File] if given [path] exists, null otherwise. 18 | File? file(String path) { 19 | var file = File(path); 20 | return file.existsSync() ? file : null; 21 | } 22 | 23 | /// Checks whether the directory in which the command has been fired is a 24 | /// dart/flutter project or not. 25 | bool isFlutterProject() { 26 | final pubspecFile = 27 | file(p.join(Directory.current.path, 'pubspec.yaml')) ?? 28 | file(p.join(Directory.current.path, 'pubspec.yml')); 29 | return pubspecFile != null && pubspecFile.existsSync(); 30 | } 31 | 32 | /// converts yaml file content into json compatible map 33 | Map yamlToMap(String path) { 34 | final content = loadYaml(File(path).readAsStringSync()); 35 | return json.decode(json.encode(content)); 36 | } 37 | 38 | /// validates the configs of the configuration file. 39 | /// [allowEmpty] if set to true, would consider configs as valid even if no 40 | /// groups are provided and fonts is not set. 41 | Result validateConfigs( 42 | Map conf, { 43 | bool allowEmpty = false, 44 | bool fontsOnly = false, 45 | }) { 46 | try { 47 | final groups = conf['groups']; 48 | bool validGroups = false; 49 | bool validFonts = false; 50 | if (groups != null) { 51 | if (groups is! Iterable) { 52 | return Result.error(ConsoleMessages.invalidGroupsType); 53 | } 54 | for (final group in groups) { 55 | for (final entry in group.entries) { 56 | if (entry.value == null) { 57 | return Result.error( 58 | sprintf(ConsoleMessages.nullValueError, [entry.key]), 59 | ); 60 | } 61 | } 62 | if (group['paths'] != null || group['path'] != null) { 63 | final paths = List.from(group['paths'] ?? []); 64 | if (paths.isEmpty && group['path'] != null) { 65 | paths.add(group['path'].toString()); 66 | } 67 | if (paths.isEmpty) { 68 | return Result.error(ConsoleMessages.noPathInGroupError); 69 | } 70 | if (!fontsOnly) { 71 | // This asserts if the path is not a directory. But we don't want to 72 | // do that if we are only generating fonts. 73 | for (final dir in paths) { 74 | final result = _assertDir(dir); 75 | if (result.isError) return result; 76 | } 77 | } 78 | } else { 79 | if (group['sub_groups'] == null) { 80 | return Result.error(ConsoleMessages.noPathInGroupError); 81 | } 82 | for (final subgroup in group['sub_groups']) { 83 | final paths = List.from(subgroup['paths'] ?? []); 84 | if (paths.isEmpty && subgroup['path'] != null) { 85 | paths.add(subgroup['path'].toString()); 86 | } 87 | if (paths.isEmpty) { 88 | return Result.error(ConsoleMessages.noPathInGroupError); 89 | } 90 | for (final dir in paths) { 91 | final result = _assertDir(dir); 92 | if (result.isError) return result; 93 | } 94 | } 95 | } 96 | if (group['class_name'] == null) { 97 | return Result.error(ConsoleMessages.noClassNameError); 98 | } 99 | if (group['class_name'].toString().trim().isEmpty) { 100 | return Result.error(ConsoleMessages.emptyClassNameError); 101 | } 102 | if (group['class_name'].contains(' ')) { 103 | return Result.error(ConsoleMessages.classNameContainsSpacesError); 104 | } 105 | } 106 | validGroups = true; 107 | } else if (fontsOnly) { 108 | // if we are only generating fonts, we don't need groups. So we mark it 109 | // as valid. 110 | validGroups = true; 111 | } 112 | if (conf.containsKey('fonts')) { 113 | // Check fonts validation. 114 | final fontsConfig = conf['fonts']; 115 | if (fontsConfig == false) { 116 | return Result.error(ConsoleMessages.nothingToGenerate); 117 | } 118 | 119 | if (fontsConfig == true) { 120 | return Result.success(true); 121 | } 122 | 123 | if (fontsConfig is! Map) { 124 | return Result.error(ConsoleMessages.invalidFontsConfig); 125 | } 126 | 127 | validFonts = true; 128 | } else { 129 | // fonts config is not set. So we mark it as valid. 130 | validFonts = true; 131 | } 132 | 133 | // Check for other configs here if required. 134 | 135 | // We return true if neither groups nor fonts are invalid. 136 | if (validGroups && validFonts) return Result.success(true); 137 | 138 | if (allowEmpty) return Result.success(true); 139 | 140 | return Result.error(ConsoleMessages.nothingToGenerate); 141 | } on Error catch (e) { 142 | return Result.error(ConsoleMessages.configValidationFailed, e.stackTrace); 143 | } 144 | } 145 | 146 | /// validates the path to directory 147 | Result _assertDir(String dir) { 148 | if (dir.contains('*')) { 149 | return Result.error(sprintf(ConsoleMessages.noWildcardInPathError, [dir])); 150 | } 151 | 152 | final uri = Uri.parse(dir); 153 | final resolvedDir = 154 | uri.pathSegments.first != Constants.PACKAGE_ASSET_PATH_PREFIX 155 | ? dir 156 | : p.join(Constants.LIB_FOLDER, p.joinAll(uri.pathSegments.sublist(2))); 157 | if (!FileSystemEntity.isDirectorySync(resolvedDir)) { 158 | return Result.error(sprintf(ConsoleMessages.pathNotExistsError, [dir])); 159 | } 160 | 161 | final dirName = p.basename(dir); 162 | if (RegExp(r'^\d.\dx$').hasMatch(dirName)) { 163 | return Result.error(sprintf(ConsoleMessages.invalidAssetDirError, [dir])); 164 | } 165 | return Result.success(true); 166 | } 167 | -------------------------------------------------------------------------------- /lib/src/cli/utils/logging.dart: -------------------------------------------------------------------------------- 1 | // Copyright © 2020 Birju Vachhani. All rights reserved. 2 | // Use of this source code is governed by an Apache license that can be 3 | // found in the LICENSE file. 4 | 5 | import 'dart:io'; 6 | 7 | import 'package:ansicolor/ansicolor.dart'; 8 | import 'package:logging/logging.dart'; 9 | 10 | import '../commands/commands.dart'; 11 | import '../flag_commands/flag_commands.dart'; 12 | import '../process_terminator.dart'; 13 | 14 | /// Custom error log level. 15 | const Level errorLevel = Level('ERROR', 1100); 16 | 17 | /// Custom success log level. 18 | const Level successLevel = Level('SUCCESS', 1050); 19 | 20 | /// Custom verbose log level. 21 | const Level verboseLevel = Level('DEBUG', 600); 22 | 23 | /// Represents logging level for [BaseLogger]. 24 | enum LogLevel { 25 | /// Logs all kinds of stuff. even ones that you don't need to see usually. 26 | verbose, 27 | 28 | /// Logs informational stuff. Something that is useful in some way. 29 | info, 30 | 31 | /// Logs warnings. Something that the user needs to pay attention to. 32 | warning, 33 | 34 | /// Logs errors and failures. 35 | error, 36 | 37 | /// Logs success messages/indication. 38 | success, 39 | 40 | /// No Logs. 41 | none, 42 | } 43 | 44 | /// A base class for logging. [BaseCommand] and [BaseFlagCommand] has an 45 | /// instance of this class as a member for logging outputs/errors to the console. 46 | abstract class BaseLogger { 47 | /// Output sink for logging all kinds of things except errors. 48 | final IOSink output; 49 | 50 | /// Output sink for specifically logging errors. 51 | final IOSink errorSink; 52 | 53 | /// Default constructor. 54 | /// [output] is the output sink for logging. 55 | /// [errorSink] is the output sink for logging errors. 56 | const BaseLogger({required this.output, required this.errorSink}); 57 | 58 | /// Logs given [msg] as an error to the console. [stackTrace] gets logged as 59 | /// well if provided. 60 | void error(String msg, [StackTrace? stackTrace]); 61 | 62 | /// Logs given [msg] at info level to the console. 63 | void info(String msg); 64 | 65 | /// Logs given [msg] at warning level to the console. 66 | void warning(String msg); 67 | 68 | /// Logs given [msg] at verbose level to the console. 69 | void verbose(String msg); 70 | 71 | /// Logs given [msg] at success level to the console. 72 | void success(String msg); 73 | 74 | /// Logs given [msg] using [output] sink directly without any formatting. 75 | /// Useful for raw printing. 76 | void log(String msg); 77 | 78 | /// Allows to set the log level. 79 | void setLogLevel(LogLevel level); 80 | 81 | /// Should log given [msg] as an error to the console and 82 | /// indicate termination of the process unlike [error] method. 83 | /// [stackTrace] gets logged as well if provided. 84 | void exitWith(String msg, [StackTrace? stackTrace]); 85 | } 86 | 87 | /// A CLI logger that logs to the console. 88 | class ConsoleLogger extends BaseLogger { 89 | /// used for structured logging. 90 | final Logger logger; 91 | 92 | final AnsiPen _errorPen = AnsiPen()..red(bold: false); 93 | final AnsiPen _successPen = AnsiPen()..green(bold: false); 94 | 95 | /// Default constructor. 96 | /// [output] is the output sink for all kinds of logging except errors. 97 | /// Defaults to [stdout] as this is a console logger. 98 | /// [errorSink] is the output sink for specially logging errors. Defaults to 99 | /// [stderr] as this is a console logger. 100 | ConsoleLogger({IOSink? output, IOSink? errorSink, Logger? logger}) 101 | : logger = logger ?? Logger('spider-console'), 102 | super(output: output ?? stdout, errorSink: errorSink ?? stderr) { 103 | setupLogging(); 104 | } 105 | 106 | /// sets up logging for stacktrace and logs. 107 | void setupLogging() { 108 | Logger.root.level = Level.INFO; // defaults to Level.INFO 109 | recordStackTraceAtLevel = Level.ALL; 110 | Logger.root.onRecord.listen((record) { 111 | if (record.level == errorLevel) { 112 | stderr.writeln(record.message); 113 | if (Logger.root.level.value <= verboseLevel.value && 114 | record.stackTrace != null) { 115 | final pen = AnsiPen()..red(); 116 | stderr.writeln(pen(record.stackTrace.toString())); 117 | } 118 | } else { 119 | stdout.writeln( 120 | '${record.level != successLevel ? '[${record.level.name}] ' : ''}${record.message}', 121 | ); 122 | } 123 | }); 124 | } 125 | 126 | @override 127 | void error(String msg, [StackTrace? stackTrace]) => 128 | logger.log(errorLevel, _errorPen(msg), null, stackTrace); 129 | 130 | @override 131 | void info(String msg) => logger.info(msg); 132 | 133 | @override 134 | void warning(String msg) => logger.warning(msg); 135 | 136 | @override 137 | void verbose(String msg) => logger.log(verboseLevel, msg); 138 | 139 | @override 140 | void success(String msg) => logger.log(successLevel, _successPen(msg)); 141 | 142 | @override 143 | void log(String msg) => output.writeln(msg); 144 | 145 | @override 146 | void setLogLevel(LogLevel level) => Logger.root.level = toLevel(level); 147 | 148 | /// exits process with a message on command-line 149 | @override 150 | void exitWith(String msg, [StackTrace? stackTrace]) { 151 | ProcessTerminator.getInstance().terminate(msg, stackTrace, this); 152 | } 153 | 154 | /// Converts [LogLevel] to [Level]. 155 | Level toLevel(LogLevel level) { 156 | switch (level) { 157 | case LogLevel.verbose: 158 | return verboseLevel; 159 | case LogLevel.info: 160 | return Level.INFO; 161 | case LogLevel.warning: 162 | return Level.WARNING; 163 | case LogLevel.error: 164 | return errorLevel; 165 | case LogLevel.success: 166 | return successLevel; 167 | case LogLevel.none: 168 | return Level.OFF; 169 | } 170 | } 171 | } 172 | 173 | /// A logging mixin used on Commands for easy logging. 174 | mixin LoggingMixin { 175 | /// A logger for logging outputs/errors.. 176 | abstract final BaseLogger logger; 177 | 178 | /// Delegation method for [BaseLogger.error]. 179 | void error(String msg, [StackTrace? stackTrace]) => 180 | logger.error(msg, stackTrace); 181 | 182 | /// Delegation method for [BaseLogger.info]. 183 | void info(String msg) => logger.info(msg); 184 | 185 | /// Delegation method for [BaseLogger.warning]. 186 | void warning(String msg) => logger.warning(msg); 187 | 188 | /// Delegation method for [BaseLogger.verbose]. 189 | void verbose(String msg) => logger.verbose(msg); 190 | 191 | /// Delegation method for [BaseLogger.success]. 192 | void success(String msg) => logger.success(msg); 193 | 194 | /// Delegation method for [BaseLogger.log]. 195 | void log(String msg) => logger.log(msg); 196 | 197 | /// Delegation method for [BaseLogger.exitWith]. 198 | void exitWith(String msg, [StackTrace? stackTrace]) => 199 | logger.exitWith(msg, stackTrace); 200 | } 201 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 4.2.3 2 | 3 | - [BUG] fix config parsing failing if no types are provided for an asset group. 4 | - Upgrade dependencies. 5 | - General cleanup. 6 | 7 | ## 4.2.2 8 | 9 | - Upgrade dependencies. 10 | - Use `isTrue` matcher instead of `true` in tests. Contributed by [@dkrutskikh](https://github.com/dkrutskikh) 11 | 12 | ## 4.2.1 13 | 14 | - Upgrade dependencies. 15 | - Specify platforms in `pubspec.yaml` file. 16 | 17 | ## 4.2.0 18 | 19 | - Add support for **font family** code-gen. Checkout docs for more info. 20 | - Fix typo in `KEY_IGNORED_RULES`. 21 | - Implement fonts code generation based on fonts declaration in `pubspec.yaml` file. 22 | - Fix duplicated success logs. 23 | - Upgrade dependencies. 24 | - Add documentation links in default config templates. 25 | 26 | ## 4.1.0 27 | 28 | - Adds support for code generation on bundled package assets. Contributed by [@Aqluse](https://github.com/Aqluse) 29 | - Upgraded dependencies. 30 | 31 | ## 4.0.0 32 | 33 | - Structural rewrite of the internal workings of the library that utilizes `CommandRunner`, `Command` and `FlagCommand` 34 | classes for better overall structure. 35 | - Renamed `info` command to `about`. 36 | - Refactored `check-updates` flag to `check-for-updates`. 37 | - Added `--license` flag command. 38 | - Added `--docs` flag command. 39 | - `create` command no longer checks for current directory to be a Flutter project. 40 | - Fixes existing config detection for `--add-in-pubspec` flag in `create` command. 41 | - Fixes stacktrace logging on error or exception. 42 | - Color-codes success and error messages for better visual differentiation. 43 | 44 | ## 3.2.0 45 | 46 | - Fixes pubspec config detection. 47 | - Fixes watch flag abbr parsing. 48 | - Introduces `ignored_rules` feature which allows to specify ignore rules for generated files. 49 | - Introduces `sub_groups` feature. 50 | 51 | ## 3.1.0 52 | 53 | - Use `flutter_test` imports when generating tests for flutter project. 54 | 55 | ## 3.0.0 56 | 57 | - Fix flag abbr not working for some commands. 58 | - Add support for creating configs in `pubspec.yaml` file. 59 | - Add support for creating config file at custom directory path. 60 | - Add support for specifying custom config file path when running `build` command. 61 | - Build command now displays which config file is being used if there's more than one config file. 62 | - Document new capabilities and commands. 63 | 64 | ## 2.2.2 65 | 66 | - PR[[#45](https://github.com/BirjuVachhani/spider/pull/45)] Sort generated file maps by file basenames 67 | by [@WSydnA](https://github.com/WSydnA) 68 | 69 | ## 2.2.1 70 | 71 | - PR[[#41](https://github.com/BirjuVachhani/spider/pull/41)]: values is not formatted in camel case if asset is written 72 | in snake case 73 | 74 | ## 2.2.0 75 | 76 | - [[#38]](https://github.com/BirjuVachhani/spider/issues/38): Add option to generate values list just like enums. 77 | 78 | ## 2.1.0 79 | 80 | - Uses official lints package for static analysis. 81 | - Added more code comments. 82 | - Fix lint warnings. 83 | - Fix [#35](https://github.com/BirjuVachhani/spider/issues/35) Constants names have no prefixes. 84 | - Add option to use underscores in reference names. 85 | 86 | ## 2.0.0 87 | 88 | - Migrated to null safety 89 | 90 | ## 1.1.1 91 | 92 | - Fixed part of directive for generated classes. 93 | 94 | ## 1.1.0 95 | 96 | - Added private constructor for generated classes to restrict instantiation 97 | - Format fixes 98 | - Upgraded dependencies 99 | 100 | ## 1.0.1 101 | 102 | - fix dart format warnings 103 | - update dependencies 104 | 105 | ## 1.0.0 106 | 107 | - Added support for exporting generated dart code which is enabled by 108 | default This can be helpful in cases where you want to use a single file 109 | to import all of the generated classes. (Accessible individual classes 110 | when importing) 111 | 112 | - Added support to use opt in for usage of `part of` feature of dart. It 113 | allows to avoid false imports when using export option. It makes all the 114 | generated dart code files to behave like one file and one import. 115 | - Added support to remove `Generated by spider...` comment line from all 116 | the generated dart code. Allows to minimize vcs noise. 117 | - `export_file` can be used to provide name of the export file. 118 | 119 | #### Breaking Changes 120 | 121 | - Instead of providing `package` to every group, now you have to define 122 | global `package` name as it makes more sense. Providing package name for 123 | individual groups won't work. 124 | 125 | ## 0.5.0 126 | 127 | - Added support for `check updates` 128 | - Updated help manuals 129 | - Updated example configs files 130 | - Fixed verbose logs 131 | 132 | ## 0.4.1 133 | 134 | l̥ 135 | 136 | - Fix build command failing when there's no test generation specified 137 | - Fix embedded version 138 | - Added test to make sure that release version and embedded version matches 139 | 140 | ## 0.4.0 141 | 142 | - Spider now allows to specify multiple paths to generate dart 143 | references under a single class. 144 | - Spider now generates test cases for dart references to make sure that 145 | the file is present in the project. 146 | 147 | ## 0.3.6 148 | 149 | - Fixed issue of creating references for files like `.DS_Store` 150 | - Now Spider shows error if you try to create a group with flutter specific assets directories like `2.0x` and `3.0x`. 151 | 152 | ## 0.3.5 153 | 154 | - fixes common commands execution issue. Now you can execute command anywhere you like. 155 | 156 | ## 0.3.4 157 | 158 | - Fix build command when there are sub-directories in assets directories 159 | 160 | ## 0.3.3 161 | 162 | - Added smart watch feature. 163 | - use `--smart-watch` option when running build command to enable it. 164 | 165 | ## 0.3.2 166 | 167 | - Fix create command 168 | 169 | ## 0.3.1 170 | 171 | - Fix version command. 172 | - Formatted outputs and added more verbose logs. 173 | - Added config validation before processing assets. 174 | 175 | ## 0.3.0 176 | 177 | - Add support for categorization by file types. 178 | - Add support for using prefixes for generated dart references. 179 | 180 | ## 0.2.1 181 | 182 | - Add support for JSON format for config files 183 | - Add flag `--json` to `create` command to create json config file. 184 | - Update readme to add json option for config file. 185 | 186 | ## 0.2.0 187 | 188 | - Add support for multiple assets directories 189 | - Add support for separate class generation 190 | - Uses sample config file config creation 191 | 192 | ## 0.1.3 193 | 194 | - Add support for watching directories for file changes 195 | - Rename `init` command to `create` command 196 | - Implemented verbose flag for build command 197 | - add `build` command 198 | 199 | ## 0.1.2 200 | 201 | - add emojis for console logs 202 | 203 | ## 0.1.1 204 | 205 | - fix issues of pub.dev health report 206 | - refactor code 207 | 208 | ## 0.1.0 209 | 210 | - added dart class generator 211 | - fix pub.dev warnings 212 | - add code documentation 213 | 214 | ## 0.0.1 215 | 216 | - pre-alpha release 217 | - Initial version for demo purpose 218 | - avoid using in production. 219 | -------------------------------------------------------------------------------- /lib/src/generation_utils.dart: -------------------------------------------------------------------------------- 1 | // Copyright © 2020 Birju Vachhani. All rights reserved. 2 | // Use of this source code is governed by an Apache license that can be 3 | // found in the LICENSE file. 4 | 5 | import 'dart:io'; 6 | 7 | import 'package:path/path.dart' as p; 8 | 9 | import 'cli/utils/utils.dart'; 10 | import 'data/class_template.dart'; 11 | import 'data/export_template.dart'; 12 | import 'data/test_template.dart'; 13 | 14 | /// Writes given [content] to the file with given [name] at given [path]. 15 | void writeToFile({ 16 | String? name, 17 | String? path, 18 | required String content, 19 | BaseLogger? logger, 20 | }) { 21 | if (!Directory(p.join(Constants.LIB_FOLDER, path)).existsSync()) { 22 | Directory(p.join(Constants.LIB_FOLDER, path)).createSync(recursive: true); 23 | } 24 | var classFile = File(p.join(Constants.LIB_FOLDER, path, name)); 25 | classFile.writeAsStringSync(content); 26 | logger?.verbose('File ${p.basename(classFile.path)} is written successfully'); 27 | } 28 | 29 | /// formats file extensions and adds preceding dot(.) if missing 30 | String formatExtension(String ext) => ext.startsWith('.') ? ext : '.$ext'; 31 | 32 | /// Constructs source code for a dart class from [classTemplate]. 33 | /// [className] is a valid dart class name. 34 | /// [references] defines all the generated asset references that are 35 | /// to be put inside this class as constants. 36 | /// [noComments] flag determines whether to add auto generated comments 37 | /// to this class or not. 38 | /// [usePartOf] flag determines whether to generate a `part of` directive 39 | /// in this file or not. It helps to unify imports for all the asset classes. 40 | /// [exportFileName] defines the dart compatible file name for this class. 41 | /// 42 | /// Returns source code of a dart class file that contains all the references 43 | /// as constants. 44 | String getDartClass({ 45 | required String className, 46 | required String references, 47 | bool noComments = false, 48 | required bool usePartOf, 49 | String? exportFileName, 50 | required String? valuesList, 51 | required List? ignoredRules, 52 | }) { 53 | var content = ''; 54 | if (ignoredRules != null && ignoredRules.isNotEmpty) { 55 | content = ignoreRulesTemplate.replaceAll( 56 | Constants.KEY_IGNORED_RULES, 57 | ignoredRules.join(', '), 58 | ); 59 | } 60 | 61 | if (!noComments) { 62 | content += timeStampComment.replaceAll( 63 | Constants.KEY_TIME, 64 | DateTime.now().toString(), 65 | ); 66 | } 67 | if (usePartOf) { 68 | content += partOfTemplate.replaceAll( 69 | Constants.KEY_FILE_NAME, 70 | exportFileName!, 71 | ); 72 | } 73 | 74 | content += classTemplate 75 | .replaceAll(Constants.KEY_CLASS_NAME, className) 76 | .replaceAll(Constants.KEY_REFERENCES, references) 77 | .replaceAll(Constants.KEY_LIST_OF_ALL_REFERENCES, valuesList ?? ''); 78 | return content; 79 | } 80 | 81 | /// Generates library export file contents like export statements for all the 82 | /// generated files. 83 | /// [fileNames] contains all the generated file names that are to be exported. 84 | /// [noComments] flag determines whether to add auto generated comments 85 | /// to this class or not. 86 | /// [usePartOf] flag determines whether to generate a `part` directive 87 | /// in this file or not. It helps to unify imports for all the asset classes. 88 | String getExportContent({ 89 | required List fileNames, 90 | required bool noComments, 91 | bool usePartOf = false, 92 | }) { 93 | var content = ''; 94 | if (!noComments) { 95 | content += timeStampComment.replaceAll( 96 | Constants.KEY_TIME, 97 | DateTime.now().toString(), 98 | ); 99 | } 100 | content += fileNames 101 | .map( 102 | (item) => (usePartOf ? partTemplate : exportFileTemplate).replaceAll( 103 | Constants.KEY_FILE_NAME, 104 | item, 105 | ), 106 | ) 107 | .toList() 108 | .join('\n\n'); 109 | return content; 110 | } 111 | 112 | /// Generates a single constant declaration dart code. 113 | /// [properties] defines access modifiers and type of the reference variable. 114 | /// e.g. const, static. 115 | /// [assetName] defines the reference variable name to be used. 116 | /// [assetPath] defines the actual asset location that will be value of the 117 | /// generated reference variable. 118 | String getReference({ 119 | required String properties, 120 | required String assetName, 121 | required String assetPath, 122 | }) { 123 | return referenceTemplate 124 | .replaceAll(Constants.KEY_PROPERTIES, properties) 125 | .replaceAll(Constants.KEY_ASSET_NAME, assetName) 126 | .replaceAll(Constants.KEY_ASSET_PATH, assetPath); 127 | } 128 | 129 | /// Generates a value declaration dart code which contains a list of references. 130 | /// [properties] defines access modifiers and type of the reference variable. 131 | /// e.g. const, static. 132 | /// [assetNames] defines the list of reference variable names. 133 | String getListOfReferences({ 134 | required String properties, 135 | required List assetNames, 136 | }) { 137 | return referencesTemplate 138 | .replaceAll(Constants.KEY_PROPERTIES, properties) 139 | .replaceAll(Constants.KEY_LIST_OF_ALL_REFERENCES, assetNames.toString()); 140 | } 141 | 142 | /// Generates test class for the generated references file. 143 | /// [project] and [package] and [importFileName] is used to 144 | /// generate required import statements. 145 | /// [importFileName] defines file to be imported for this test. 146 | /// [fileName] defines the file name for tests to be generated. 147 | /// [noComments] flag determines whether to add auto generated comments 148 | /// to this class or not. 149 | /// [tests] defines all the tests to be put inside this test file. 150 | String getTestClass({ 151 | required String project, 152 | required String package, 153 | required String fileName, 154 | required String tests, 155 | required bool noComments, 156 | required String importFileName, 157 | required String testImport, 158 | }) { 159 | final importsContent = [ 160 | testPackageImport.replaceAll(Constants.TEST_IMPORT, testImport), 161 | projectPackageImport 162 | .replaceAll(Constants.KEY_PROJECT_NAME, project) 163 | .replaceAll(Constants.KEY_PACKAGE, package) 164 | .replaceAll(Constants.KEY_IMPORT_FILE_NAME, importFileName), 165 | ]..sort(); 166 | 167 | var content = ''; 168 | if (!noComments) { 169 | content += timeStampTemplate.replaceAll( 170 | Constants.KEY_TIME, 171 | DateTime.now().toString(), 172 | ); 173 | } 174 | content += testTemplate 175 | .replaceAll(Constants.KEY_FILE_NAME, fileName) 176 | .replaceAll(Constants.KEY_TESTS, tests) 177 | .replaceAll(Constants.TEST_IMPORTS, importsContent.join('\n')); 178 | return content; 179 | } 180 | 181 | /// Generates a single test for given [assetName] asset. 182 | /// [className] denotes the name of the class this asset reference variable 183 | /// belongs to. 184 | String getTestCase(String className, String assetName) { 185 | return expectTestTemplate 186 | .replaceAll(Constants.KEY_CLASS_NAME, className) 187 | .replaceAll(Constants.KEY_ASSET_NAME, assetName); 188 | } 189 | -------------------------------------------------------------------------------- /lib/src/cli/utils/constants.dart: -------------------------------------------------------------------------------- 1 | // Copyright © 2020 Birju Vachhani. All rights reserved. 2 | // Use of this source code is governed by an Apache license that can be 3 | // found in the LICENSE file. 4 | 5 | // Author: Birju Vachhani 6 | // Created Date: February 02, 2020 7 | 8 | // ignore_for_file: public_member_api_docs 9 | 10 | import '../../version.dart'; 11 | 12 | /// Holds all the constants 13 | class Constants { 14 | static const String LIB_FOLDER = 'lib'; 15 | static const String TEST_FOLDER = 'test'; 16 | static const String DEFAULT_PATH = 'assets'; 17 | static const String DEFAULT_CLASS_NAME = 'Assets'; 18 | static const String DEFAULT_PACKAGE = 'resources'; 19 | static const PACKAGE_ASSET_PATH_PREFIX = 'packages'; 20 | 21 | static const String CAPITALIZE_REGEX = r'(_)(\S)'; 22 | static const String SPECIAL_SYMBOLS = 23 | "[,.\\/;'\\[\\]\\-=<>?:\\\"\\{}_+!@#\$%^&*()\\\\|\\s]+"; 24 | static final Pattern specialSymbolRegex = RegExp(SPECIAL_SYMBOLS); 25 | 26 | static const String KEY_IGNORED_RULES = '[IGNORED_RULES]'; 27 | static const String KEY_PROJECT_NAME = '[PROJECT_NAME]'; 28 | static const String KEY_PACKAGE = '[PACKAGE]'; 29 | static const String TEST_IMPORT = '[TEST_IMPORT]'; 30 | static const String TEST_IMPORTS = '[TEST_IMPORTS]'; 31 | static const String KEY_FILE_NAME = '[FILE_NAME]'; 32 | static const String KEY_TESTS = '[TESTS]'; 33 | static const String KEY_CLASS_NAME = '[CLASS_NAME]'; 34 | static const String KEY_ASSET_NAME = '[ASSET_NAME]'; 35 | static const String KEY_TIME = '[TIME]'; 36 | static const String KEY_REFERENCES = '[REFERENCES]'; 37 | static const String KEY_LIST_OF_ALL_REFERENCES = '[LIST_OF_ALL_REFERENCES]'; 38 | static const String KEY_ASSET_PATH = '[ASSET_PATH]'; 39 | static const String KEY_PROPERTIES = '[PROPERTIES]'; 40 | static const String KEY_LIBRARY_NAME = '[LIBRARY_NAME]'; 41 | static const String KEY_IMPORT_FILE_NAME = '[IMPORT_FILE_NAME]'; 42 | static const String DEFAULT_EXPORT_FILE = 'resources.dart'; 43 | static const String DOCS_URL = 'https://birjuvachhani.github.io/spider'; 44 | static const String PUB_DEV_URL = 'https://pub.dev/packages/spider'; 45 | 46 | static const String NEW_VERSION_AVAILABLE = ''' 47 | 48 | =================================================================== 49 | | New Version Available | 50 | |=================================================================| 51 | | | 52 | | New Version Available with more stability and improvements. | 53 | | | 54 | | Current Version: X.X.X | 55 | | Latest Version: Y.Y.Y | 56 | | | 57 | | Checkout for more info: https://pub.dev/packages/spider | 58 | | | 59 | | Run following command to update to the latest version: | 60 | | | 61 | | dart pub global activate spider | 62 | | | 63 | =================================================================== 64 | 65 | '''; 66 | 67 | static const String VERSION_REGEX = '^([0-9]+).([0-9]+).([0-9]+)\$'; 68 | 69 | static const String INFO = 70 | ''' 71 | 72 | SPIDER: 73 | A small dart command-line tool for generating dart references of assets from 74 | the assets folder. 75 | 76 | VERSION $packageVersion 77 | AUTHOR Birju Vachhani (https://birju.dev) 78 | HOMEPAGE https://github.com/birjuvachhani/spider 79 | SDK VERSION 2.6 80 | 81 | see spider --help for more available commands. 82 | '''; 83 | 84 | static const String LICENSE_SHORT = ''' 85 | 86 | Copyright © 2020 Birju Vachhani 87 | 88 | Licensed under the Apache License, Version 2.0 (the "License"); 89 | you may not use this file except in compliance with the License. 90 | You may obtain a copy of the License at 91 | 92 | https://www.apache.org/licenses/LICENSE-2.0 93 | 94 | Unless required by applicable law or agreed to in writing, software 95 | distributed under the License is distributed on an "AS IS" BASIS, 96 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 97 | See the License for the specific language governing permissions and 98 | limitations under the License. 99 | 100 | See full license at https://github.com/BirjuVachhani/spider/blob/main/LICENSE 101 | '''; 102 | } 103 | 104 | class ConsoleMessages { 105 | static const String configNotFound = 106 | 'Config not found. Create one using "spider create" command.'; 107 | static const String unableToLoadPubspecFile = 108 | 'Unable to load pubspec file. Make sure your pubspec.yaml file is valid.'; 109 | static const String unableToGetProjectName = 110 | 'Unable to retrieve project name from pubspec.yaml. Make sure your ' 111 | 'pubspec.yaml file is valid.'; 112 | static const String configNotFoundDetailed = """Config not found... 113 | 114 | 1. Create one using "spider create" command. 115 | 2. If using custom config file path, make sure the path is correct. 116 | 3. If using pubspec.yaml, make sure spider block is not invalid. 117 | 4. If using default config file path, make sure the file is present in the root of the project. 118 | """; 119 | static const String invalidConfigFile = 120 | 'Invalid config. Please check your config file.'; 121 | static const String invalidConfigFilePath = 122 | 'Invalid config file path. Please check your config file path.'; 123 | static const String parseError = 'Unable to parse configs!'; 124 | static const String noGroupsFound = 'No groups found in the config file.'; 125 | static const String nothingToGenerate = 126 | "Configs doesn't contain anything to generate. Provide groups to specify which assets to generate."; 127 | static const String noSubgroupsFound = 128 | 'No subgroups found in the config file.'; 129 | static const String invalidGroupsType = 130 | 'Groups must be a list of configurations.'; 131 | static const String noPathInGroupError = 132 | 'Either no path is specified in the config or specified path is empty'; 133 | static const String noWildcardInPathError = 134 | 'Path %s must not contain any wildcard.'; 135 | static const String nullValueError = '%s cannot be null'; 136 | static const String pathNotExistsError = 'Path %s does not exist!'; 137 | static const String notDirectoryError = 'Path %s is not a directory'; 138 | static const String invalidAssetDirError = 139 | '%s is not a valid asset directory.'; 140 | static const String noClassNameError = 141 | 'Class name not specified for one of the groups.'; 142 | static const String emptyClassNameError = 'Empty class name is not allowed'; 143 | static const String classNameContainsSpacesError = 144 | 'Class name must not contain spaces.'; 145 | static const String configValidationFailed = 'Configs Validation failed'; 146 | static const String notFlutterProjectError = 147 | 'Current directory is not flutter project. Please execute ' 148 | 'this command in a flutter project root path.'; 149 | static const String pubspecNotFound = 150 | 'Unable to find pubspec.yaml or pubspec.yml'; 151 | static const String configExistsInPubspec = 152 | 'Spider config already exists in pubspec.yaml'; 153 | static const String configCreatedInPubspec = 'Configs added to pubspec.yaml'; 154 | static const String unableToAddConfigInPubspec = 155 | 'Unable to add config file to pubspec.yaml'; 156 | static const String fileCreatedAtCustomPath = 157 | 'Configuration file created successfully at %s.'; 158 | static const String customPathIsPubspec = 159 | "You provided pubspec file as custom path? Did you mean to include " 160 | "configs in pubspec file? If you did then use " 161 | "'spider create --add-in-pubspec' command."; 162 | static const String unableToCheckForUpdates = 163 | 'Something went wrong! Unable to check for updates!'; 164 | static const String noUpdatesAvailable = 'No updates available!'; 165 | static const String unsupportedPlatform = 'Unsupported platform.'; 166 | static const String unableToCreateConfigFile = 'Unable to create config file'; 167 | static const String invalidDirectoryPath = 168 | 'Provided path is not a valid directory.'; 169 | static const String configFileExistsTemplate = 170 | 'Config file already exists at %s.'; 171 | static const String configFileCreated = 172 | 'Configuration file created successfully.'; 173 | static const String configFoundAtTemplate = 'Configs found at %s'; 174 | static const String loadingConfigsFromTemplate = 'Loading configs from %s'; 175 | static const String exitedUnexpectedly = 176 | 'Oops; spider has exited unexpectedly: "%s"'; 177 | static const String watchingForChangesInDirectory = 178 | 'Watching for changes in directory %s...'; 179 | static const String processedItemsForClassTemplate = 180 | 'Processed items for class %s: %i ' 181 | 'in %i ms.'; 182 | static const String directoryEmpty = 183 | 'Directory %s does not contain any assets!'; 184 | static const String generatingTestsForClass = 'Generating tests for class %s'; 185 | static const String fontsOnlyExecutedWithoutSetTemplate = 186 | '--%s flag only works if fonts is set in config file.'; 187 | static const String invalidFontsConfig = 'Invalid fonts configs!'; 188 | static const String noFontsFound = 'No fonts found in pubspec.yaml'; 189 | 190 | ConsoleMessages._(); 191 | } 192 | -------------------------------------------------------------------------------- /test/spider_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright © 2020 Birju Vachhani. All rights reserved. 2 | // Use of this source code is governed by an Apache license that can be 3 | // found in the LICENSE file. 4 | 5 | // Author: Birju Vachhani 6 | // Created Date: August 20, 2020 7 | 8 | import 'dart:io'; 9 | 10 | import 'package:mockito/mockito.dart'; 11 | import 'package:path/path.dart' as p; 12 | import 'package:spider/spider.dart'; 13 | import 'package:spider/src/cli/models/spider_config.dart'; 14 | import 'package:spider/src/cli/process_terminator.dart'; 15 | import 'package:spider/src/cli/utils/utils.dart'; 16 | import 'package:spider/src/data/test_template.dart'; 17 | import 'package:test/test.dart'; 18 | 19 | import 'test_utils.dart'; 20 | 21 | void main() { 22 | final MockProcessTerminator processTerminatorMock = MockProcessTerminator(); 23 | const Map testConfig = { 24 | "generate_tests": false, 25 | "no_comments": true, 26 | "export": true, 27 | "use_part_of": false, 28 | "use_references_list": true, 29 | "package": "resources", 30 | "groups": [ 31 | { 32 | "class_name": "Images", 33 | "path": "assets/images", 34 | "types": [".png", ".jpg", ".jpeg", ".webp", ".webm", ".bmp"], 35 | }, 36 | { 37 | "class_name": "Svgs", 38 | "sub_groups": [ 39 | { 40 | "path": "assets/svgsMenu", 41 | "prefix": "menu", 42 | "types": [".svg"], 43 | }, 44 | { 45 | "path": "assets/svgsOther", 46 | "prefix": "other", 47 | "types": [".svg"], 48 | }, 49 | ], 50 | }, 51 | { 52 | "class_name": "Ico", 53 | "types": [".ico"], 54 | "prefix": "ico", 55 | "sub_groups": [ 56 | { 57 | "path": "assets/icons", 58 | "prefix": "test1", 59 | "types": [".ttf"], 60 | }, 61 | { 62 | "path": "assets/vectors", 63 | "prefix": "test2", 64 | "types": [".pdf"], 65 | }, 66 | ], 67 | }, 68 | { 69 | "class_name": "Video", 70 | "types": [".mp4"], 71 | "path": "assets/moviesOnly", 72 | "sub_groups": [ 73 | {"path": "assets/movies", "prefix": "common"}, 74 | {"path": "assets/moviesExtra", "prefix": "extra"}, 75 | ], 76 | }, 77 | ], 78 | }; 79 | 80 | test('create config test test', () { 81 | Result creationResult = ConfigCreator().create(isJson: true); 82 | 83 | expect(creationResult.isSuccess, isTrue); 84 | expect(File('spider.json').existsSync(), true); 85 | File('spider.json').deleteSync(); 86 | 87 | creationResult = ConfigCreator().create(isJson: false); 88 | 89 | expect(creationResult.isSuccess, isTrue); 90 | expect(File('spider.yaml').existsSync(), true); 91 | File('spider.yaml').deleteSync(); 92 | }); 93 | 94 | test('exportAsLibrary tests', () { 95 | // Spider.exportAsLibrary(); 96 | // TODO: add test if possible. 97 | }); 98 | 99 | group('Spider tests', () { 100 | setUp(() { 101 | ProcessTerminator.setMock(processTerminatorMock); 102 | deleteConfigFiles(); 103 | }); 104 | 105 | test('asset generation test on spider', () async { 106 | File('spider.yaml').writeAsStringSync(testYamlConfigTemplate); 107 | createTestAssets(); 108 | 109 | final Result result = retrieveConfigs(); 110 | expect( 111 | result.isSuccess, 112 | isTrue, 113 | reason: 'valid config file should not return error but it did.', 114 | ); 115 | 116 | final SpiderConfiguration config = result.data; 117 | 118 | final spider = Spider(config); 119 | verifyNever(processTerminatorMock.terminate(any, any)); 120 | 121 | spider.build(); 122 | 123 | final genFile1 = File(p.join('lib', 'resources', 'images.dart')); 124 | expect(genFile1.existsSync(), isTrue); 125 | final genFile2 = File(p.join('lib', 'resources', 'svgs.dart')); 126 | expect(genFile2.existsSync(), isTrue); 127 | final genFile3 = File(p.join('lib', 'resources', 'ico.dart')); 128 | expect(genFile3.existsSync(), isTrue); 129 | final genFile4 = File(p.join('lib', 'resources', 'video.dart')); 130 | expect(genFile4.existsSync(), isTrue); 131 | 132 | addTearDown(() { 133 | File(p.join('test', 'images_test.dart')).deleteSync(); 134 | }); 135 | addTearDown(() { 136 | File(p.join('test', 'svgs_test.dart')).deleteSync(); 137 | }); 138 | addTearDown(() { 139 | File(p.join('test', 'ico_test.dart')).deleteSync(); 140 | }); 141 | addTearDown(() { 142 | File(p.join('test', 'video_test.dart')).deleteSync(); 143 | }); 144 | 145 | final classContent1 = genFile1.readAsStringSync(); 146 | final classContent2 = genFile2.readAsStringSync(); 147 | final classContent3 = genFile3.readAsStringSync(); 148 | final classContent4 = genFile4.readAsStringSync(); 149 | 150 | expect( 151 | classContent1, 152 | contains( 153 | '// ignore_for_file: public_member_api_docs, member-ordering-extended, test_rule', 154 | ), 155 | ); 156 | expect(classContent1, contains('class Images')); 157 | expect(classContent1, contains('static const String test1')); 158 | expect(classContent1, contains('static const String test2')); 159 | expect(classContent1, contains('assets/images/test1.png')); 160 | expect(classContent1, contains('assets/images/test2.jpg')); 161 | 162 | expect(classContent2, contains('class Svgs')); 163 | expect(classContent2, contains('static const String menuTest4')); 164 | expect(classContent2, contains('static const String otherTest6')); 165 | expect(classContent2, contains('assets/svgsMenu/test4.svg')); 166 | expect(classContent2, contains('assets/svgsOther/test6.svg')); 167 | 168 | expect(classContent3, contains('class Ico')); 169 | expect(classContent3, contains('static const String icoTest8')); 170 | expect(classContent3, contains('static const String icoTest11')); 171 | expect(classContent3, contains('assets/icons/test8.ico')); 172 | expect(classContent3, contains('assets/vectors/test11.ico')); 173 | 174 | expect(classContent4, contains('class Video')); 175 | expect(classContent4, contains('static const String test16')); 176 | expect(classContent4, contains('assets/moviesOnly/test16.mp4')); 177 | 178 | final exportFile = File(p.join('lib', 'resources', 'resources.dart')); 179 | expect(exportFile.existsSync(), isTrue); 180 | final exportContent = exportFile.readAsStringSync(); 181 | 182 | expect(exportContent, contains("part 'images.dart';")); 183 | expect(exportContent, contains("part 'svgs.dart';")); 184 | expect(exportContent, contains("part 'ico.dart';")); 185 | expect(exportContent, contains("part 'video.dart';")); 186 | }); 187 | 188 | test('asset generation test with library export on spider', () async { 189 | createTestConfigs(testConfig); 190 | createTestAssets(); 191 | 192 | final Result result = retrieveConfigs(); 193 | expect( 194 | result.isSuccess, 195 | isTrue, 196 | reason: 'valid config file should not return error but it did.', 197 | ); 198 | 199 | final SpiderConfiguration config = result.data; 200 | 201 | final spider = Spider(config); 202 | 203 | verifyNever(processTerminatorMock.terminate(any, any)); 204 | 205 | spider.build(); 206 | 207 | final genFile1 = File(p.join('lib', 'resources', 'images.dart')); 208 | expect(genFile1.existsSync(), isTrue); 209 | final genFile2 = File(p.join('lib', 'resources', 'svgs.dart')); 210 | expect(genFile2.existsSync(), isTrue); 211 | final genFile3 = File(p.join('lib', 'resources', 'ico.dart')); 212 | expect(genFile3.existsSync(), isTrue); 213 | final genFile4 = File(p.join('lib', 'resources', 'video.dart')); 214 | expect(genFile4.existsSync(), isTrue); 215 | 216 | final classContent1 = genFile1.readAsStringSync(); 217 | final classContent2 = genFile2.readAsStringSync(); 218 | final classContent3 = genFile3.readAsStringSync(); 219 | final classContent4 = genFile4.readAsStringSync(); 220 | 221 | expect(classContent1, contains('class Images')); 222 | expect(classContent1, contains('static const String test1')); 223 | expect(classContent1, contains('static const String test2')); 224 | expect(classContent1, contains('assets/images/test1.png')); 225 | expect(classContent1, contains('assets/images/test2.jpg')); 226 | 227 | expect(classContent2, contains('class Svgs')); 228 | expect(classContent2, contains('static const String menuTest4')); 229 | expect(classContent2, contains('static const String otherTest6')); 230 | expect(classContent2, contains('assets/svgsMenu/test4.svg')); 231 | expect(classContent2, contains('assets/svgsOther/test6.svg')); 232 | 233 | expect(classContent3, contains('class Ico')); 234 | expect(classContent3, contains('static const String icoTest8')); 235 | expect(classContent3, contains('static const String icoTest11')); 236 | expect(classContent3, contains('assets/icons/test8.ico')); 237 | expect(classContent3, contains('assets/vectors/test11.ico')); 238 | 239 | expect(classContent4, contains('class Video')); 240 | expect(classContent4, contains('static const String test16')); 241 | expect(classContent4, contains('assets/moviesOnly/test16.mp4')); 242 | 243 | final exportFile = File(p.join('lib', 'resources', 'resources.dart')); 244 | expect(exportFile.existsSync(), isTrue); 245 | final exportContent = exportFile.readAsStringSync(); 246 | expect(exportContent, contains("export 'images.dart';")); 247 | expect(exportContent, contains("export 'svgs.dart';")); 248 | expect(exportContent, contains("export 'ico.dart';")); 249 | expect(exportContent, contains("export 'video.dart';")); 250 | }); 251 | 252 | tearDown(() { 253 | ProcessTerminator.clearMock(); 254 | reset(processTerminatorMock); 255 | deleteTestAssets(); 256 | deleteConfigFiles(); 257 | deleteGeneratedRefs(); 258 | }); 259 | }); 260 | } 261 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright © 2020 Birju Vachhani 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. -------------------------------------------------------------------------------- /test/data_class_generator_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright © 2020 Birju Vachhani. All rights reserved. 2 | // Use of this source code is governed by an Apache license that can be 3 | // found in the LICENSE file. 4 | 5 | import 'dart:async'; 6 | import 'dart:io'; 7 | 8 | import 'package:mockito/mockito.dart'; 9 | import 'package:path/path.dart' as p; 10 | import 'package:spider/src/cli/models/spider_config.dart'; 11 | import 'package:spider/src/cli/process_terminator.dart'; 12 | import 'package:spider/src/cli/utils/utils.dart'; 13 | import 'package:spider/src/dart_class_generator.dart'; 14 | import 'package:test/test.dart'; 15 | 16 | import 'test_utils.dart'; 17 | 18 | void main() { 19 | final MockProcessTerminator processTerminatorMock = MockProcessTerminator(); 20 | const Map testConfig = { 21 | "generate_tests": false, 22 | "no_comments": true, 23 | "export": true, 24 | "use_part_of": false, 25 | "use_references_list": false, 26 | "package": "resources", 27 | "groups": [ 28 | { 29 | "class_name": "Assets", 30 | "sub_groups": [ 31 | { 32 | "path": "assets/images", 33 | "types": ["png"], 34 | "prefix": "png", 35 | }, 36 | { 37 | "path": "assets/images", 38 | "types": ["jpg"], 39 | "prefix": "jpg", 40 | }, 41 | ], 42 | }, 43 | ], 44 | }; 45 | 46 | group('process tests', () { 47 | setUp(() { 48 | ProcessTerminator.setMock(processTerminatorMock); 49 | }); 50 | 51 | test('asset generation test #1', () async { 52 | createTestConfigs(testConfig); 53 | createTestAssets(); 54 | 55 | final Result result = retrieveConfigs(); 56 | expect( 57 | result.isSuccess, 58 | isTrue, 59 | reason: 'valid config file should not return error but it did.', 60 | ); 61 | 62 | final SpiderConfiguration config = result.data; 63 | 64 | final generator = DartClassGenerator(config.groups.first, config.globals); 65 | 66 | generator.process(); 67 | verifyNever(processTerminatorMock.terminate(any, any)); 68 | final genFile = File(p.join('lib', 'resources', 'assets.dart')); 69 | expect(genFile.existsSync(), isTrue); 70 | 71 | final classContent = genFile.readAsStringSync(); 72 | 73 | expect(classContent, contains('class Assets')); 74 | expect(classContent, contains('static const String pngTest1')); 75 | expect(classContent, contains('static const String jpgTest2')); 76 | expect(classContent, contains('assets/images/test1.png')); 77 | expect(classContent, contains('assets/images/test2.jpg')); 78 | }); 79 | 80 | test('asset generation test - comments', () async { 81 | createTestConfigs(testConfig.copyWith({'no_comments': false})); 82 | createTestAssets(); 83 | 84 | final Result result = retrieveConfigs(); 85 | expect( 86 | result.isSuccess, 87 | isTrue, 88 | reason: 'valid config file should not return error but it did.', 89 | ); 90 | 91 | final SpiderConfiguration config = result.data; 92 | 93 | final generator = DartClassGenerator(config.groups.first, config.globals); 94 | 95 | generator.process(); 96 | verifyNever(processTerminatorMock.terminate(any, any)); 97 | final genFile = File(p.join('lib', 'resources', 'assets.dart')); 98 | expect(genFile.existsSync(), isTrue); 99 | 100 | final classContent = genFile.readAsStringSync(); 101 | 102 | expect(classContent, contains('class Assets')); 103 | expect(classContent, contains('static const String pngTest1')); 104 | expect(classContent, contains('static const String jpgTest2')); 105 | expect(classContent, contains('assets/images/test1.png')); 106 | expect(classContent, contains('assets/images/test2.jpg')); 107 | expect(classContent, contains('// Generated by spider')); 108 | }); 109 | 110 | test('asset generation test no list of references', () async { 111 | createTestConfigs(testConfig.copyWith({"use_references_list": true})); 112 | createTestAssets(); 113 | 114 | final Result result = retrieveConfigs(); 115 | expect( 116 | result.isSuccess, 117 | isTrue, 118 | reason: 'valid config file should not return error but it did.', 119 | ); 120 | 121 | final SpiderConfiguration config = result.data; 122 | 123 | final generator = DartClassGenerator(config.groups.first, config.globals); 124 | 125 | generator.process(); 126 | verifyNever(processTerminatorMock.terminate(any, any)); 127 | final genFile = File(p.join('lib', 'resources', 'assets.dart')); 128 | expect(genFile.existsSync(), isTrue); 129 | 130 | final classContent = genFile.readAsStringSync(); 131 | 132 | expect(classContent, contains('class Assets')); 133 | expect(classContent, contains('static const String pngTest1')); 134 | expect(classContent, contains('static const String jpgTest2')); 135 | expect(classContent, contains('assets/images/test1.png')); 136 | expect(classContent, contains('assets/images/test2.jpg')); 137 | expect(classContent, contains('static const List values')); 138 | }); 139 | 140 | test('asset generation test - watch', () async { 141 | createTestConfigs(testConfig.copyWith({'no_comments': false})); 142 | createTestAssets(); 143 | 144 | final Result result = retrieveConfigs(); 145 | expect( 146 | result.isSuccess, 147 | isTrue, 148 | reason: 'valid config file should not return error but it did.', 149 | ); 150 | 151 | final SpiderConfiguration config = result.data; 152 | 153 | final generator = DartClassGenerator(config.groups.first, config.globals); 154 | 155 | void proc() async { 156 | generator.initAndStart(watch: true, smartWatch: false); 157 | } 158 | 159 | proc(); 160 | await Future.delayed(Duration(seconds: 5)); 161 | 162 | verifyNever(processTerminatorMock.terminate(any, any)); 163 | final genFile = File(p.join('lib', 'resources', 'assets.dart')); 164 | expect(genFile.existsSync(), isTrue); 165 | 166 | String classContent = genFile.readAsStringSync(); 167 | 168 | expect(classContent, contains('class Assets')); 169 | expect(classContent, contains('static const String pngTest1')); 170 | expect(classContent, contains('static const String jpgTest2')); 171 | expect(classContent, contains('assets/images/test1.png')); 172 | expect(classContent, contains('assets/images/test2.jpg')); 173 | expect(classContent, contains('// Generated by spider')); 174 | 175 | final newFile = File(p.join('assets', 'images', 'test3.png')); 176 | newFile.createSync(); 177 | expect(newFile.existsSync(), isTrue); 178 | 179 | await Future.delayed(Duration(seconds: 5)); 180 | classContent = genFile.readAsStringSync(); 181 | 182 | expect(classContent, contains('static const String pngTest3')); 183 | expect(classContent, contains('assets/images/test3.png')); 184 | 185 | generator.cancelSubscriptions(); 186 | }); 187 | 188 | test('asset generation test - smart watch', () async { 189 | createTestConfigs( 190 | testConfig.copyWith({ 191 | 'no_comments': false, 192 | "groups": [ 193 | { 194 | "class_name": "Assets", 195 | "sub_groups": [ 196 | { 197 | "path": "assets/images", 198 | "types": ["jpg", "jpeg", "png", "webp", "gif", "bmp", "wbmp"], 199 | }, 200 | ], 201 | }, 202 | { 203 | "class_name": "Fonts", 204 | "sub_groups": [ 205 | {"path": "assets/fonts"}, 206 | ], 207 | }, 208 | ], 209 | }), 210 | ); 211 | createTestAssets(); 212 | createMoreTestAssets(); 213 | 214 | final Result result = retrieveConfigs(); 215 | expect( 216 | result.isSuccess, 217 | isTrue, 218 | reason: 'valid config file should not return error but it did.', 219 | ); 220 | 221 | final SpiderConfiguration config = result.data; 222 | 223 | final generator1 = DartClassGenerator( 224 | config.groups.first, 225 | config.globals, 226 | ); 227 | 228 | final generator2 = DartClassGenerator(config.groups[1], config.globals); 229 | 230 | void proc1() async { 231 | generator1.initAndStart(watch: false, smartWatch: true); 232 | } 233 | 234 | void proc2() async { 235 | generator2.initAndStart(watch: false, smartWatch: true); 236 | } 237 | 238 | proc1(); 239 | proc2(); 240 | await Future.delayed(Duration(seconds: 5)); 241 | 242 | verifyNever(processTerminatorMock.terminate(any, any)); 243 | final genFile = File(p.join('lib', 'resources', 'fonts.dart')); 244 | expect(genFile.existsSync(), isTrue); 245 | 246 | String classContent = genFile.readAsStringSync(); 247 | 248 | expect(classContent, contains('class Fonts')); 249 | expect(classContent, contains('static const String test1')); 250 | expect(classContent, contains('static const String test2')); 251 | expect(classContent, contains('assets/fonts/test1.otf')); 252 | expect(classContent, contains('assets/fonts/test2.otf')); 253 | 254 | final String timestamp = genFile.readAsLinesSync().first; 255 | 256 | final newFile = File(p.join('assets', 'images', 'test3.png')); 257 | newFile.createSync(); 258 | expect(newFile.existsSync(), isTrue); 259 | 260 | await Future.delayed(Duration(seconds: 5)); 261 | classContent = File( 262 | p.join('lib', 'resources', 'assets.dart'), 263 | ).readAsStringSync(); 264 | 265 | expect(classContent, contains('static const String test3')); 266 | expect(classContent, contains('assets/images/test3.png')); 267 | 268 | final String newTimestamp = genFile.readAsLinesSync().first; 269 | expect(timestamp, equals(newTimestamp)); 270 | 271 | generator1.cancelSubscriptions(); 272 | generator2.cancelSubscriptions(); 273 | }); 274 | 275 | test('asset generation test - test cases', () async { 276 | createTestConfigs(testConfig.copyWith({'generate_tests': true})); 277 | 278 | createTestAssets(); 279 | final Result result = retrieveConfigs(); 280 | expect( 281 | result.isSuccess, 282 | isTrue, 283 | reason: 'valid config file should not return error but it did.', 284 | ); 285 | 286 | final SpiderConfiguration config = result.data; 287 | 288 | final generator = DartClassGenerator(config.groups.first, config.globals); 289 | 290 | generator.process(); 291 | verifyNever(processTerminatorMock.terminate(any, any)); 292 | final genFile = File(p.join('lib', 'resources', 'assets.dart')); 293 | expect(genFile.existsSync(), isTrue); 294 | 295 | final classContent = genFile.readAsStringSync(); 296 | 297 | expect(classContent, contains('class Assets')); 298 | expect(classContent, contains('static const String pngTest1')); 299 | expect(classContent, contains('static const String jpgTest2')); 300 | expect(classContent, contains('assets/images/test1.png')); 301 | expect(classContent, contains('assets/images/test2.jpg')); 302 | 303 | final genTestFile = File(p.join('test', 'assets_test.dart')); 304 | expect(genTestFile.existsSync(), isTrue); 305 | 306 | addTearDown(() => genTestFile.deleteSync()); 307 | 308 | final testContent = genTestFile.readAsStringSync(); 309 | 310 | expect(testContent, contains("import 'dart:io';")); 311 | expect(testContent, contains("import 'package:test/test.dart';")); 312 | expect( 313 | testContent, 314 | contains("import 'package:spider/resources/assets.dart';"), 315 | ); 316 | expect(testContent, contains("void main()")); 317 | expect( 318 | testContent, 319 | contains("expect(File(Assets.pngTest1).existsSync(), isTrue);"), 320 | ); 321 | expect( 322 | testContent, 323 | contains("expect(File(Assets.jpgTest2).existsSync(), isTrue);"), 324 | ); 325 | }); 326 | 327 | tearDown(() { 328 | ProcessTerminator.clearMock(); 329 | reset(processTerminatorMock); 330 | deleteTestAssets(); 331 | deleteConfigFiles(); 332 | deleteGeneratedRefs(); 333 | }); 334 | }); 335 | } 336 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Banner](https://raw.githubusercontent.com/BirjuVachhani/spider/main/.github/banner.png?raw=true) 2 | 3 | # Spider 4 | 5 | A small dart library to generate Assets dart code from assets folder. It generates dart class with static const 6 | variables in it which can be used to reference the assets safely anywhere in the flutter app. 7 | 8 | [![Build](https://github.com/BirjuVachhani/spider/workflows/Build/badge.svg?branch=main)](https://github.com/BirjuVachhani/spider/actions) [![Tests](https://github.com/BirjuVachhani/spider/workflows/Tests/badge.svg?branch=main)](https://github.com/BirjuVachhani/spider/actions) [![codecov](https://codecov.io/gh/birjuvachhani/spider/branch/main/graph/badge.svg?token=ZTYF9UQJID)](https://codecov.io/gh/birjuvachhani/spider) [![Pub Version](https://img.shields.io/pub/v/spider?label=Pub)](https://pub.dev/packages/spider) 9 | 10 | ### User Guide: [Spider Docs](https://birjuvachhani.github.io/spider/) 11 | 12 | ## Breaking Changes since v4.0.0: 13 | 14 | - `--info` flag command is now `--about` command. 15 | - `--check-updates` flag command is now `--check-for-updates`. 16 | 17 | ### Example 18 | 19 | #### Before 20 | 21 | ```dart 22 | Widget build(BuildContext context) { 23 | return Image(image: AssetImage('assets/background.png')); 24 | } 25 | ``` 26 | 27 | #### After 28 | 29 | ```dart 30 | Widget build(BuildContext context) { 31 | return Image(image: AssetImage(Assets.background)); 32 | } 33 | ``` 34 | 35 | #### Generated Assets Class 36 | 37 | ```dart 38 | class Assets { 39 | static const String background = 'assets/background.png'; 40 | } 41 | ``` 42 | 43 | This method allows no error scope for string typos. Also, it provides auto-complete in the IDE which comes very handy 44 | when you have large amount of assets. 45 | 46 | ## Installation 47 | 48 | ### Install using pub 49 | 50 | This is package is an independent library that is not linked to your project. So there's no need to add it to your 51 | flutter project as it works as a global command line tool for all of your projects. 52 | 53 | ```shell 54 | dart pub global activate spider 55 | ``` 56 | 57 | ### Install using Homebrew 58 | 59 | ```shell 60 | brew tap birjuvachhani/spider 61 | brew install spider 62 | ``` 63 | 64 | Run following command to see help: 65 | 66 | ```shell 67 | spider --help 68 | ``` 69 | 70 | ## Usage 71 | 72 | #### Create Configuration File 73 | 74 | Spider provides a very easy and straight forward way to create a configuration file. Execute following command, and it 75 | will create a configuration file with default configurations in it. 76 | 77 | ```shell 78 | spider create 79 | ``` 80 | 81 | To append configs in `pubspec.yaml` file, execute following command. 82 | 83 | ```shell 84 | spider create --add-in-pubspec 85 | ``` 86 | 87 | To use a custom directory path for configuration file, execute following command. 88 | 89 | ```shell 90 | spider create -p ./directory/path/for/config 91 | ``` 92 | 93 | Now you can modify available configurations and Spider will use those configs when generating dart code. 94 | 95 | #### Use JSON config file 96 | 97 | Though above command creates `YAML` format for config file, spider also supports `JSON` format for config file. Use this 98 | command to create `JSON` config file instead of `YAML`. 99 | 100 | ```shell 101 | # Create in root directory 102 | spider create --json 103 | 104 | # or 105 | 106 | # custom directory path 107 | spider create -p ./directory/path/for/config --json 108 | ``` 109 | 110 | No matter which config format you use, `JSON` or `YAML`, spider automatically detects it and uses it for code 111 | generation. 112 | 113 | Here's the default configuration that will be in the config file: 114 | 115 | ```yaml 116 | # Generated by Spider 117 | 118 | # Generates unit tests to verify that the assets exists in assets directory 119 | generate_tests: true 120 | 121 | # Use this to remove vcs noise created by the `generated` comments in dart code 122 | no_comments: true 123 | 124 | # Exports all the generated file as the one library 125 | export: true 126 | 127 | # This allows you to import all the generated references with 1 single import! 128 | use_part_of: true 129 | 130 | # Location where all the generated references will be stored 131 | package: resources 132 | 133 | groups: 134 | - path: assets/images 135 | class_name: Images 136 | types: [ .png, .jpg, .jpeg, .webp, .webm, .bmp ] 137 | ``` 138 | 139 | ### Generate Code 140 | 141 | Run following command to generate dart code: 142 | 143 | ```shell 144 | spider build 145 | ``` 146 | 147 | If you're using custom directory path for the configuration file, then you can specify the config file path like this: 148 | 149 | ```shell 150 | spider -p ./path/to/config/file/spider.yaml build 151 | ``` 152 | 153 | ### Manual 154 | 155 | ![Manual](https://raw.githubusercontent.com/BirjuVachhani/spider/main/table.png) 156 | 157 | 174 | 175 | ### Watch Directory 176 | 177 | Spider can also watch given directory for changes in files and rebuild dart code automatically. Use following command to 178 | watch for changes: 179 | 180 | ```shell 181 | spider build --watch 182 | ``` 183 | 184 | see help for more information: 185 | 186 | ```shell 187 | spider build --help 188 | ``` 189 | 190 | ### Smart Watch (Experimental) 191 | 192 | The normal `--watch` option watches for any kind of changes that happens in the directory. However, this can be improved 193 | my smartly watching the directory. It includes ignoring events that doesn't affect anything like file content changes. 194 | Also, it only watches allowed file types and rebuilds upon changes for those files only. 195 | 196 | Run following command to watch directories smartly. 197 | 198 | ```shell 199 | spider build --smart-watch 200 | ``` 201 | 202 | ### Categorizing by File Extension 203 | 204 | By default, Spider allows any file to be referenced in the dart code. but you can change that behavior. You can specify 205 | which files you want to be referenced. 206 | 207 | ```yaml 208 | path: assets 209 | class_name: Assets 210 | package: res 211 | types: [ jpg, png, jpeg, webp, bmp, gif ] 212 | ``` 213 | 214 | ### Use Prefix 215 | 216 | You can use prefixes for names of the generated dart references. Prefixes will be attached to the formatted reference 217 | names. 218 | 219 | ```yaml 220 | path: assets 221 | class_name: Assets 222 | package: res 223 | prefix: ic 224 | ``` 225 | 226 | ##### Output 227 | 228 | ```dart 229 | class Assets { 230 | static const String icCamera = 'assets/camera.png'; 231 | static const String icLocation = 'assets/location.png'; 232 | } 233 | ``` 234 | 235 | ## Advanced Configuration 236 | 237 | Spider provides supports for multiple configurations and classifications. If you want to group your assets by module, type 238 | or anything, you can do that using `groups` in spider. 239 | 240 | ### Example 241 | 242 | Suppose you have both vector(SVGs) and raster images in your project, and you want to me classified separately so that 243 | you can use them with separate classes. You can use groups here. Keep your vector and raster images in separate folder 244 | and specify them in the config file. 245 | 246 | `spider.yaml` 247 | 248 | ```yaml 249 | groups: 250 | - path: assets/images 251 | class_name: Images 252 | package: res 253 | - path: assets/vectors 254 | class_name: Svgs 255 | package: res 256 | ``` 257 | 258 | Here, first item in the list indicates to group assets of `assets/images` folder under class named `Images` and the 259 | second one indicates to group assets of `assets/vectors` directory under class named `Svgs`. 260 | 261 | So when you refer to `Images` class, auto-complete suggests raster images only, and you know that you can use them 262 | with `AssetImage` and other one with vector rendering library. 263 | 264 | ## Multi-path configuration 265 | 266 | From Spider `v0.4.0`, multiple paths can be specified for a single group to collect references from multiple directories 267 | and generate all the references under single dart class. 268 | 269 | #### Example 270 | 271 | ```yaml 272 | groups: 273 | - paths: 274 | - assets/images 275 | - assets/more_images/ 276 | class_name: Images 277 | package: res 278 | types: [ .png, .jpg, .jpeg, .webp, .webm, .bmp ] 279 | ``` 280 | 281 | By using `paths`, multiple source directories can be specified. Above example will generate references 282 | from `assets/images` and `assets/more_images/` under a single dart class named `Images`. 283 | 284 | ## Generating Tests 285 | 286 | Spider `v0.4.0` adds support for generating test cases for generated dart references to make sure that the asset file is 287 | present in the project. These tests can also be run on CI servers. To enable tests generation, specify `generate_tests` 288 | flag in `spider.yaml` or `spider.json` configuration file as shown below. 289 | 290 | ```yaml 291 | generate_tests: true 292 | ``` 293 | 294 | This flag will indicate spider to generate tests for all the generated dart references. 295 | 296 | ## Generate `values` list 297 | 298 | Familiar with `Enum.values` list which contains all the enum values? Spider also provides support for generating `values` 299 | list for all the asset references in given dart class. 300 | Use `use_references_list` global config to enable `values` list generation. This is disabled by default as it can be 301 | overwhelming to have this code-gen if you don't need it. 302 | 303 | ```yaml 304 | # global config 305 | use_references_list: true 306 | ``` 307 | 308 | 309 | ## Enable Verbose Logging 310 | 311 | Spider prefers not to overwhelm terminal with verbose logs that are redundant for most of the cases. However, those 312 | verbose logs come quite handy when it comes to debug anything. You can enable verbose logging by using `--verbose` 313 | option on build command. 314 | 315 | ```shell 316 | spider build --verbose 317 | 318 | # watching directories with verbose logs 319 | spider build --watch --verbose 320 | ``` 321 | 322 | ## Liked spider? 323 | 324 | Show some love and support by starring the repository. ⭐ 325 | 326 | Want to support my work? 327 | 328 | Sponsor Author 329 | 330 | Or You can 331 | 332 | Buy Me A Coffee 333 | 334 | ## License 335 | 336 | ``` 337 | Copyright © 2020 Birju Vachhani 338 | 339 | Licensed under the Apache License, Version 2.0 (the "License"); 340 | you may not use this file except in compliance with the License. 341 | You may obtain a copy of the License at 342 | 343 | https://www.apache.org/licenses/LICENSE-2.0 344 | 345 | Unless required by applicable law or agreed to in writing, software 346 | distributed under the License is distributed on an "AS IS" BASIS, 347 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 348 | See the License for the specific language governing permissions and 349 | limitations under the License. 350 | ``` 351 | -------------------------------------------------------------------------------- /lib/src/dart_class_generator.dart: -------------------------------------------------------------------------------- 1 | // Copyright © 2020 Birju Vachhani. All rights reserved. 2 | // Use of this source code is governed by an Apache license that can be 3 | // found in the LICENSE file. 4 | 5 | // Author: Birju Vachhani 6 | // Created Date: February 03, 2020 7 | 8 | import 'dart:async'; 9 | import 'dart:io'; 10 | 11 | import 'package:dart_style/dart_style.dart'; 12 | import 'package:path/path.dart' as path; 13 | import 'package:sprintf/sprintf.dart' hide Formatter; 14 | import 'package:watcher/watcher.dart'; 15 | 16 | import 'cli/models/asset_group.dart'; 17 | import 'cli/models/spider_config.dart'; 18 | import 'cli/models/subgroup_property.dart'; 19 | import 'cli/utils/utils.dart'; 20 | import 'formatter.dart'; 21 | import 'generation_utils.dart'; 22 | 23 | /// Generates dart class code using given data 24 | class DartClassGenerator { 25 | /// A group for which this generator will generate the code. 26 | final AssetGroup group; 27 | bool _processing = false; 28 | 29 | /// Dart code formatter used to format the generated code. 30 | static final formatter = DartFormatter( 31 | languageVersion: DartFormatter.latestLanguageVersion, 32 | ); 33 | 34 | /// Global configuration values retrieved from the config file. 35 | final GlobalConfigs globals; 36 | 37 | /// A list of all the stream subscriptions the paths are being watched. 38 | List subscriptions = []; 39 | 40 | /// A logger for logging information, errors and exceptions. 41 | final BaseLogger? logger; 42 | 43 | /// Default constructor. 44 | DartClassGenerator(this.group, this.globals, [this.logger]); 45 | 46 | /// generates dart class code and returns it as a single string 47 | void initAndStart({required bool watch, required bool smartWatch}) { 48 | if (watch) { 49 | if (group.paths != null) { 50 | logger?.verbose('path ${group.paths} is requested to be watched'); 51 | for (final dir in group.paths!) { 52 | _watchDirectory(dir); 53 | } 54 | } else { 55 | for (final subgroup in group.subgroups!) { 56 | logger?.verbose('path ${subgroup.paths} is requested to be watched'); 57 | for (final dir in subgroup.paths) { 58 | _watchDirectory(dir); 59 | } 60 | } 61 | } 62 | } else if (smartWatch) { 63 | if (group.paths != null) { 64 | logger?.verbose( 65 | 'path ${group.paths} is requested to be watched smartly', 66 | ); 67 | for (final path in group.paths!) { 68 | _smartWatchDirectory(dir: path, types: group.types!); 69 | } 70 | } else { 71 | for (final subgroup in group.subgroups!) { 72 | logger?.verbose( 73 | 'path ${subgroup.paths} is requested to be watched smartly', 74 | ); 75 | for (final path in subgroup.paths) { 76 | _smartWatchDirectory(dir: path, types: subgroup.types); 77 | } 78 | } 79 | } 80 | } 81 | process(); 82 | } 83 | 84 | /// Starts to process/scan all the asset files picked by the configuration 85 | /// and generates dart references code. 86 | void process() { 87 | final startTime = DateTime.now(); 88 | final properties = []; 89 | if (group.paths != null) { 90 | for (final path in group.paths!) { 91 | properties.add( 92 | SubgroupProperty( 93 | group.prefix!, 94 | createFileMap(dir: path, types: group.types!), 95 | ), 96 | ); 97 | } 98 | } else { 99 | for (final subgroup in group.subgroups!) { 100 | for (final path in subgroup.paths) { 101 | properties.add( 102 | SubgroupProperty( 103 | subgroup.prefix, 104 | createFileMap(dir: path, types: group.types ?? subgroup.types), 105 | ), 106 | ); 107 | } 108 | } 109 | } 110 | _generateDartCode(properties); 111 | if (globals.generateTests) _generateTests(properties); 112 | _processing = false; 113 | final endTime = DateTime.now(); 114 | final int elapsedTime = 115 | endTime.millisecondsSinceEpoch - startTime.millisecondsSinceEpoch; 116 | 117 | logger?.success( 118 | sprintf(ConsoleMessages.processedItemsForClassTemplate, [ 119 | group.className, 120 | if (properties.length > 1) 121 | properties.expand((element) => element.files.entries).length 122 | else 123 | properties.first.files.length, 124 | elapsedTime, 125 | ]), 126 | ); 127 | } 128 | 129 | /// Creates map from files list of a [dir] where key is the file name without 130 | /// extension and value is the path of the file 131 | Map createFileMap({ 132 | required String dir, 133 | required List types, 134 | }) { 135 | String? packageAssetPathPrefix; 136 | 137 | final uri = Uri.directory(dir); 138 | if (uri.pathSegments.first == Constants.PACKAGE_ASSET_PATH_PREFIX && 139 | !FileSystemEntity.isDirectorySync(dir) && 140 | !FileSystemEntity.isFileSync(dir)) { 141 | packageAssetPathPrefix = path.joinAll(uri.pathSegments.sublist(0, 2)); 142 | } 143 | 144 | final resolvedDir = packageAssetPathPrefix == null 145 | ? dir 146 | : path.join( 147 | Constants.LIB_FOLDER, 148 | path.joinAll(uri.pathSegments.sublist(2)), 149 | ); 150 | 151 | var files = 152 | Directory(resolvedDir).listSync().where((file) { 153 | final valid = _isValidFile(file, types); 154 | logger?.verbose('Valid: $file'); 155 | logger?.verbose( 156 | 'Asset - ${path.basename(file.path)} is ${valid ? 'selected' : 'not selected'}', 157 | ); 158 | return valid; 159 | }).toList()..sort( 160 | (a, b) => path.basename(a.path).compareTo(path.basename(b.path)), 161 | ); 162 | 163 | if (files.isEmpty) { 164 | logger?.info(sprintf(ConsoleMessages.directoryEmpty, [dir.toString()])); 165 | return {}; 166 | } 167 | return Map.fromEntries( 168 | files.map((file) { 169 | final resolvedFile = packageAssetPathPrefix == null 170 | ? file.path 171 | : path.join( 172 | packageAssetPathPrefix, 173 | path.joinAll(Uri.parse(file.path).pathSegments.sublist(1)), 174 | ); 175 | return MapEntry(path.basenameWithoutExtension(file.path), resolvedFile); 176 | }), 177 | ); 178 | } 179 | 180 | /// checks whether the file is valid file to be included or not 181 | /// 1. must be a file, not a directory 182 | /// 2. should be from one of the allowed types if specified any 183 | bool _isValidFile(dynamic file, List types) { 184 | return FileSystemEntity.isFileSync(file.path) && 185 | path.extension(file.path).isNotEmpty && 186 | (types.isEmpty || types.contains(path.extension(file.path))); 187 | } 188 | 189 | /// Watches assets dir for file changes and rebuilds dart code 190 | void _watchDirectory(String dir) { 191 | logger?.info( 192 | sprintf(ConsoleMessages.watchingForChangesInDirectory, [dir.toString()]), 193 | ); 194 | final watcher = DirectoryWatcher(dir); 195 | 196 | final subscription = watcher.events.listen((event) { 197 | logger?.verbose('something changed...'); 198 | if (!_processing) { 199 | _processing = true; 200 | Future.delayed(Duration(seconds: 1), () => process()); 201 | } 202 | }); 203 | subscriptions.add(subscription); 204 | } 205 | 206 | /// Smartly watches assets dir for file changes and rebuilds dart code 207 | void _smartWatchDirectory({ 208 | required String dir, 209 | required List types, 210 | }) { 211 | logger?.info( 212 | sprintf(ConsoleMessages.watchingForChangesInDirectory, [dir.toString()]), 213 | ); 214 | final watcher = DirectoryWatcher(dir); 215 | final subscription = watcher.events.listen((event) { 216 | logger?.verbose('something changed...'); 217 | final filename = path.basename(event.path); 218 | if (event.type == ChangeType.MODIFY) { 219 | logger?.verbose( 220 | '$filename is modified. ' 221 | '${group.className} class will not be rebuilt', 222 | ); 223 | return; 224 | } 225 | if (!types.contains(path.extension(event.path))) { 226 | logger?.verbose( 227 | '$filename does not have allowed extension for the group ' 228 | '$dir. ${group.className} class will not be rebuilt', 229 | ); 230 | return; 231 | } 232 | if (!_processing) { 233 | _processing = true; 234 | Future.delayed(Duration(seconds: 1), () => process()); 235 | } 236 | }); 237 | 238 | subscriptions.add(subscription); 239 | } 240 | 241 | void _generateDartCode(List properties) { 242 | final staticProperty = group.useStatic ? 'static' : ''; 243 | final constProperty = group.useConst ? ' const' : ''; 244 | var references = ''; 245 | 246 | for (final property in properties) { 247 | references += property.files.keys 248 | .map((name) { 249 | logger?.verbose( 250 | 'processing ${path.basename(property.files[name]!)}', 251 | ); 252 | return getReference( 253 | properties: staticProperty + constProperty, 254 | assetName: Formatter.formatName( 255 | name, 256 | prefix: group.paths != null 257 | ? group.prefix! 258 | : group.prefix ?? property.prefix, 259 | useUnderScores: group.useUnderScores, 260 | ), 261 | assetPath: Formatter.formatPath(property.files[name]!), 262 | ); 263 | }) 264 | .toList() 265 | .join(); 266 | } 267 | 268 | // Can be transformed into lambda function or simplified 269 | List getAssetNames() { 270 | final assetNames = []; 271 | for (final property in properties) { 272 | assetNames.addAll( 273 | property.files.keys 274 | .map( 275 | (name) => Formatter.formatName( 276 | name, 277 | prefix: group.paths != null 278 | ? group.prefix! 279 | : group.prefix ?? property.prefix, 280 | useUnderScores: group.useUnderScores, 281 | ), 282 | ) 283 | .toList(), 284 | ); 285 | } 286 | 287 | return assetNames; 288 | } 289 | 290 | final valuesList = globals.useReferencesList 291 | ? getListOfReferences( 292 | properties: staticProperty + constProperty, 293 | assetNames: getAssetNames(), 294 | ) 295 | : null; 296 | 297 | logger?.verbose('Constructing dart class for ${group.className}'); 298 | final content = getDartClass( 299 | ignoredRules: globals.ignoredRules, 300 | className: group.className, 301 | references: references, 302 | noComments: globals.noComments, 303 | usePartOf: globals.export && globals.usePartOf!, 304 | exportFileName: Formatter.formatFileName(globals.exportFileName), 305 | valuesList: valuesList, 306 | ); 307 | logger?.verbose( 308 | 'Writing class ${group.className} to file ${group.fileName}', 309 | ); 310 | writeToFile( 311 | name: Formatter.formatFileName(group.fileName), 312 | path: globals.package, 313 | content: formatter.format(content), 314 | logger: logger, 315 | ); 316 | } 317 | 318 | void _generateTests(List properties) { 319 | logger?.info( 320 | sprintf(ConsoleMessages.generatingTestsForClass, [group.className]), 321 | ); 322 | final fileName = path.basenameWithoutExtension( 323 | Formatter.formatFileName(group.fileName), 324 | ); 325 | var tests = ''; 326 | for (final property in properties) { 327 | tests += property.files.keys 328 | .map((key) { 329 | return getTestCase( 330 | group.className, 331 | Formatter.formatName( 332 | key, 333 | prefix: group.paths != null 334 | ? group.prefix! 335 | : group.prefix ?? property.prefix, 336 | useUnderScores: group.useUnderScores, 337 | ), 338 | ); 339 | }) 340 | .toList() 341 | .join(); 342 | } 343 | logger?.verbose('generating test dart code'); 344 | final content = getTestClass( 345 | project: globals.projectName, 346 | fileName: fileName, 347 | package: globals.package!, 348 | noComments: globals.noComments, 349 | tests: tests, 350 | testImport: globals.useFlutterTestImports ? 'flutter_test' : 'test', 351 | importFileName: globals.export && globals.usePartOf! 352 | ? Formatter.formatFileName(globals.exportFileName) 353 | : Formatter.formatFileName(group.fileName), 354 | ); 355 | 356 | // create test directory if doesn't exist 357 | if (!Directory(Constants.TEST_FOLDER).existsSync()) { 358 | Directory(Constants.TEST_FOLDER).createSync(); 359 | } 360 | var classFile = File( 361 | path.join(Constants.TEST_FOLDER, '${fileName}_test.dart'), 362 | ); 363 | logger?.verbose( 364 | 'writing test ${fileName}_test.dart for class ${group.className}', 365 | ); 366 | classFile.writeAsStringSync(formatter.format(content)); 367 | logger?.verbose( 368 | 'File ${path.basename(classFile.path)} is written successfully', 369 | ); 370 | } 371 | 372 | /// Cancels all subscriptions 373 | void cancelSubscriptions() { 374 | for (var element in subscriptions) { 375 | element.cancel(); 376 | } 377 | } 378 | } 379 | --------------------------------------------------------------------------------