From ff2b6deceec7adb64ac13b6f33dbd0997795e7b8 Mon Sep 17 00:00:00 2001 From: zhandao <a@skipping.cat> Date: Fri, 15 Mar 2024 15:45:36 +0800 Subject: [PATCH] Impl `style` option --- README.md | 21 +++++--- config/job_backend.yml | 5 ++ config/styles/full/checkers.yml | 0 config/styles/full/code_snippets.yml | 0 config/styles/full/gems.yml | 0 .../{job.yml => styles/full/job_backend.yml} | 5 -- config/styles/full/workflows.yml | 0 lib/nextgen.rb | 24 +++++++++ lib/nextgen/cli.rb | 1 + lib/nextgen/commands/create.rb | 51 ++++++------------- lib/nextgen/commands/helpers.rb | 4 +- lib/nextgen/generators.rb | 12 ++--- .../{job => job_backend}/sidekiq.rb | 0 .../{job => job_backend}/solid_queue.rb | 0 14 files changed, 66 insertions(+), 57 deletions(-) create mode 100644 config/job_backend.yml create mode 100644 config/styles/full/checkers.yml create mode 100644 config/styles/full/code_snippets.yml create mode 100644 config/styles/full/gems.yml rename config/{job.yml => styles/full/job_backend.yml} (70%) create mode 100644 config/styles/full/workflows.yml rename lib/nextgen/generators/{job => job_backend}/sidekiq.rb (100%) rename lib/nextgen/generators/{job => job_backend}/solid_queue.rb (100%) diff --git a/README.md b/README.md index fb53478..03343bf 100644 --- a/README.md +++ b/README.md @@ -45,6 +45,13 @@ gem exec nextgen create myapp This will download the latest version of the `nextgen` gem and use it to create an app in the `myapp` directory. You'll be asked to configure the tech stack through several interactive prompts. If you have a `~/.railsrc` file, it will be ignored. +Options: +- `style`: control the **optional enhancements** you can choose in the generator. + - defaults to `default`, [enhancements list](config) + - presets: + - `full` (`--style=full`), [enhancements list](config/styles/full) + - your local configs: `--style=path/to/your/style_dir` + > [!TIP] > If you get an "Unknown command exec" error, fix it by upgrading rubygems: `gem update --system`. @@ -59,7 +66,7 @@ Check out the [examples directory](./examples) to see some Rails apps that were On top of that foundation, Nextgen offers dozens of useful enhancements to the vanilla Rails experience. You are free to pick and choose which (if any) of these to apply to your new project. Behind the scenes, **each enhancement is applied in a separate git commit,** so that you can later see what was applied and why, and revert the suggestions if necessary. > [!TIP] -> For the full list of what Nextgen provides, check out [config/generators.yml](https://github.com/mattbrictson/nextgen/tree/main/config/generators.yml). The source code of each generator can be found in [lib/nextgen/generators](https://github.com/mattbrictson/nextgen/tree/main/lib/nextgen/generators). +> For the full list of what Nextgen provides, check out [config/*.yml](https://github.com/mattbrictson/nextgen/tree/main/config). The source code of each generator can be found in [lib/nextgen/generators](https://github.com/mattbrictson/nextgen/tree/main/lib/nextgen/generators). Here are some highlights of what Nextgen brings to the table: @@ -71,16 +78,14 @@ Nextgen can optionally set up a GitHub Actions CI workflow for your app that aut Prefer RSpec? Nextgen can set you up with RSpec, plus the gems and configuration you need for system specs (browser testing). Or stick with the Rails Minitest defaults. In either case, Nextgen will set up a good default Rake task and appropriate CI job. -### Gems - -Nextgen can install and configure your choice of these recommended gems: - -#### Job Backends +### Job Backends - [sidekiq](https://github.com/sidekiq/sidekiq) -- [solid_queue](https://github.com/basecamp/solid_queue) +- [solid_queue](https://github.com/basecamp/solid_queue) (`--style=full`) + +### Gems -#### Other +Nextgen can install and configure your choice of these recommended gems: - [annotate](https://github.com/ctran/annotate_models) - [brakeman](https://github.com/presidentbeef/brakeman) diff --git a/config/job_backend.yml b/config/job_backend.yml new file mode 100644 index 0000000..09ea387 --- /dev/null +++ b/config/job_backend.yml @@ -0,0 +1,5 @@ + +sidekiq: + prompt: "Sidekiq (Redis-backed)" + description: "Install sidekiq gem to use in production" + requires: active_job diff --git a/config/styles/full/checkers.yml b/config/styles/full/checkers.yml new file mode 100644 index 0000000..e69de29 diff --git a/config/styles/full/code_snippets.yml b/config/styles/full/code_snippets.yml new file mode 100644 index 0000000..e69de29 diff --git a/config/styles/full/gems.yml b/config/styles/full/gems.yml new file mode 100644 index 0000000..e69de29 diff --git a/config/job.yml b/config/styles/full/job_backend.yml similarity index 70% rename from config/job.yml rename to config/styles/full/job_backend.yml index 71d2fe9..bfe741f 100644 --- a/config/job.yml +++ b/config/styles/full/job_backend.yml @@ -1,9 +1,4 @@ -sidekiq: - prompt: "Sidekiq (Redis-backed)" - description: "Install sidekiq gem to use in production" - requires: active_job - solid_queue: prompt: "SolidQueue (Database-backed)" description: "Install solid_queue as ActiveJob's backend" diff --git a/config/styles/full/workflows.yml b/config/styles/full/workflows.yml new file mode 100644 index 0000000..e69de29 diff --git a/lib/nextgen.rb b/lib/nextgen.rb index 7f8c2a2..79349e9 100644 --- a/lib/nextgen.rb +++ b/lib/nextgen.rb @@ -14,4 +14,28 @@ def self.generators_path(scope = "") def self.template_path Pathname.new(__dir__).join("../template") end + + def self.config_path(style: nil) + if style + if style.match?("/") + Pathname.new(style) + else + Pathname.new(__dir__).join("../config/styles", style) + end + else + Pathname.new(__dir__).join("../config") + end + end + + def self.config_for(scope:, style: nil) + base = YAML.load_file("#{Nextgen.config_path}/#{scope}.yml") + if style + base.merge!(YAML.load_file("#{Nextgen.config_path(style: style)}/#{scope}.yml") || {}) + end + base + end + + def self.scopes_for(style: nil) + Dir[Nextgen.config_path(style: style) + "*.yml"].map { _1.match(/([_a-z]*)\.yml/)[1] } + end end diff --git a/lib/nextgen/cli.rb b/lib/nextgen/cli.rb index 4ba5a94..a4d5197 100644 --- a/lib/nextgen/cli.rb +++ b/lib/nextgen/cli.rb @@ -6,6 +6,7 @@ class CLI < Thor map %w[-v --version] => "version" + option :style, type: :string, default: nil desc "create APP_PATH", "Generate a Rails app interactively in APP_PATH" def create(app_path) Commands::Create.run(app_path, options) diff --git a/lib/nextgen/commands/create.rb b/lib/nextgen/commands/create.rb index de7b086..9bb2b66 100644 --- a/lib/nextgen/commands/create.rb +++ b/lib/nextgen/commands/create.rb @@ -18,13 +18,14 @@ def self.run(app_path, options) new(app_path, options).run end - def initialize(app_path, _options) + def initialize(app_path, options) @app_path = File.expand_path(app_path) @app_name = File.basename(@app_path).gsub(/\W/, "_").squeeze("_").camelize @rails_opts = RailsOptions.new + @style = options[:style] end - def run # rubocop:disable Metrics/MethodLength Metrics/PerceivedComplexity + def run # rubocop:disable Metrics/MethodLength, Metrics/PerceivedComplexity say_banner continue_if "Ready to start?" @@ -39,12 +40,8 @@ def run # rubocop:disable Metrics/MethodLength Metrics/PerceivedComplexity ask_system_testing if rails_opts.frontend? && rails_opts.test_framework? say - if prompt.yes?("More detailed configuration? [ job, code snippets, gems ... ] ↵") - ask_job_backend if rails_opts.active_job? - ask_workflows - ask_checkers - ask_code_snippets - ask_optional_enhancements + if prompt.yes?("More enhancements? [ job, code snippets, gems ... ] ↵") + ask_styled_enhancements end say_summary @@ -98,7 +95,7 @@ def ask_full_stack_or_api "API only" => true ) rails_opts.api! if api - @generators = {basic: Generators.compatible_with(rails_opts: rails_opts, scope: "basic")} + @generators = {basic: Generators.compatible_with(rails_opts: rails_opts, style: nil, scope: "basic")} end def ask_frontend_management @@ -189,33 +186,17 @@ def ask_system_testing rails_opts.skip_system_test! unless system_testing end - def ask_job_backend - generators[:job] = Generators.compatible_with(rails_opts: rails_opts, scope: "job").tap do |it| - it.ask_select("Which #{underline("job backend")} would you like to use?", prompt: prompt) - end - end - - def ask_workflows - generators[:workflows] = Generators.compatible_with(rails_opts: rails_opts, scope: "workflows").tap do |it| - it.ask_select("Which #{underline("workflows")} would you like to add?", multi: true, prompt: prompt) - end - end - - def ask_checkers - generators[:checkers] = Generators.compatible_with(rails_opts: rails_opts, scope: "checkers").tap do |it| - it.ask_select("Which #{underline("checkers")} would you like to add?", multi: true, prompt: prompt) - end - end - - def ask_code_snippets - generators[:code_snippets] = Generators.compatible_with(rails_opts: rails_opts, scope: "code_snippets").tap do |it| - it.ask_select("Which #{underline("code snippets")} would you like to add?", multi: true, prompt: prompt) - end - end + def ask_styled_enhancements + say " ↪ style: #{cyan(@style || "default")}" + Nextgen.scopes_for(style: @style).each do |scope| + gen = Generators.compatible_with(rails_opts: rails_opts, style: @style, scope: scope) + next if gen.empty? || scope == "basic" - def ask_optional_enhancements - generators[:gems] = Generators.compatible_with(rails_opts: rails_opts, scope: "gems").tap do |it| - it.ask_select("Which optional enhancements would you like to add?", multi: true, sort: true, prompt: prompt) + key_word = underline(scope.tr("_", " ")) + multi = scope == scope.pluralize + sort = gen.optional.size > 10 + gen.ask_select("Which #{key_word} would you like to add?", prompt: prompt, multi: multi, sort: sort) + generators[scope.to_sym] = gen end end end diff --git a/lib/nextgen/commands/helpers.rb b/lib/nextgen/commands/helpers.rb index 4005e27..fcf9566 100644 --- a/lib/nextgen/commands/helpers.rb +++ b/lib/nextgen/commands/helpers.rb @@ -110,9 +110,7 @@ def capture_version(command) end def activated_generators - activated = generators[:gems].all_active_names - activated.prepend(generators[:job].all_active_names.first) unless generators[:job].nil? - + activated = generators.values.flat_map(&:all_active_names) activated.any? ? activated.sort_by(&:downcase) : ["<None>"] end diff --git a/lib/nextgen/generators.rb b/lib/nextgen/generators.rb index 6342167..0105871 100644 --- a/lib/nextgen/generators.rb +++ b/lib/nextgen/generators.rb @@ -2,10 +2,9 @@ module Nextgen class Generators - def self.compatible_with(rails_opts:, scope:) - yaml_path = File.expand_path("../../config/#{scope}.yml", __dir__) + def self.compatible_with(rails_opts:, style:, scope:) new(scope, api: rails_opts.api?).tap do |generators| - YAML.load_file(yaml_path).each do |name, options| + Nextgen.config_for(style: style, scope: scope).each do |name, options| options ||= {} requirements = Array(options["requires"]) next unless requirements.all? { |req| rails_opts.public_send(:"#{req}?") } @@ -18,7 +17,6 @@ def self.compatible_with(rails_opts:, scope:) questions: options["questions"] ) end - generators.deactivate_node unless rails_opts.requires_node? end end @@ -29,9 +27,11 @@ def initialize(scope, **vars) @scope = scope end + def empty? = @generators.empty? + def ask_select(question, multi: false, sort: false, prompt: TTY::Prompt.new) - opt = sort ? optional.sort_by { |label, _| label.downcase }.to_h : optional - args = [question, opt, {cycle: true, filter: true}] + opts = sort ? optional.sort_by { |label, _| label.downcase }.to_h : optional + args = [question, opts, {cycle: true, filter: true}] answers = multi ? prompt.multi_select(*args) : [prompt.select(*args)] answers.each do |answer| diff --git a/lib/nextgen/generators/job/sidekiq.rb b/lib/nextgen/generators/job_backend/sidekiq.rb similarity index 100% rename from lib/nextgen/generators/job/sidekiq.rb rename to lib/nextgen/generators/job_backend/sidekiq.rb diff --git a/lib/nextgen/generators/job/solid_queue.rb b/lib/nextgen/generators/job_backend/solid_queue.rb similarity index 100% rename from lib/nextgen/generators/job/solid_queue.rb rename to lib/nextgen/generators/job_backend/solid_queue.rb