Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions gem/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,12 @@ You can generate your components using `ruby_ui:component` generator.
bin/rails g ruby_ui:component Accordion
```

You can also generate multiple components at once.

```bash
bin/rails g ruby_ui:component Button Link Input Textarea
```

You also can generate all components using `ruby_ui:component:all` generator

## Documentation 📖
Expand Down
10 changes: 6 additions & 4 deletions gem/lib/generators/ruby_ui/component/all_generator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,13 @@ class AllGenerator < Rails::Generators::Base
def generate_components
say "Generating all components..."

Dir.children(self.class.source_root).each do |folder_name|
next if folder_name.ends_with?(".rb")

run "bin/rails generate ruby_ui:component #{folder_name} --force #{options["force"]}"
# Each component lives in its own directory; select directories only so stray
# files (e.g. base.rb or a macOS .DS_Store) are never passed as component names.
folder_names = Dir.children(self.class.source_root).select do |entry|
File.directory?(File.join(self.class.source_root, entry))
end

run "bin/rails generate ruby_ui:component #{folder_names.join(" ")} --force #{options["force"]}"
end
end
end
Expand Down
82 changes: 51 additions & 31 deletions gem/lib/generators/ruby_ui/component_generator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,69 +7,89 @@ class ComponentGenerator < Rails::Generators::Base
namespace "ruby_ui:component"

source_root File.expand_path("../../ruby_ui", __dir__)
argument :component_name, type: :string, required: true
argument :component_names, type: :array, required: true, banner: "Button Link Input"
class_option :force, type: :boolean, default: false
class_option :with_docs, type: :boolean, default: false

def generate_component
if component_not_found?
say "Component not found: #{component_name}", :red
exit
def generate_components
validate_components!

component_names.each do |component_name|
say "Generating #{component_name} files..."
copy_related_component_files(component_name)
copy_js_files(component_name)
install_dependencies(component_name)
end

say "Generating #{component_name} files..."
update_stimulus_manifest
end

def copy_related_component_files
private

def validate_components!
missing = component_names.reject { |name| component_exists?(name) }
return if missing.empty?

say "Component(s) not found: #{missing.join(", ")}", :red
exit 1
end

def copy_related_component_files(component_name)
say "Generating components"

components_file_paths.each do |file_path|
components_file_paths(component_name).each do |file_path|
component_file_name = file_path.split("/").last
copy_file file_path, Rails.root.join("app/components/ruby_ui", component_folder_name, component_file_name), force: options["force"]
copy_file file_path, Rails.root.join("app/components/ruby_ui", component_folder_name(component_name), component_file_name), force: options["force"]
end
end

def copy_js_files
return if js_controller_file_paths.empty?
def copy_js_files(component_name)
paths = js_controller_file_paths(component_name)
return if paths.empty?

say "Generating Stimulus controllers"

js_controller_file_paths.each do |file_path|
paths.each do |file_path|
controller_file_name = file_path.split("/").last
copy_file file_path, Rails.root.join("app/javascript/controllers/ruby_ui", controller_file_name), force: options["force"]
end

@stimulus_controllers_added = true
end

def update_stimulus_manifest
return unless @stimulus_controllers_added

# Importmap doesn't have controller manifest, instead it uses `eagerLoadControllersFrom("controllers", application)`
if !using_importmap?
say "Updating Stimulus controllers manifest"
run "rake stimulus:manifest:update"
end
return if using_importmap?

say "Updating Stimulus controllers manifest"
run "rake stimulus:manifest:update"
end

def install_dependencies
return if dependencies.blank?
def install_dependencies(component_name)
deps = dependencies(component_name)
return if deps.blank?

say "Installing dependencies"

install_components_dependencies(dependencies["components"])
install_gems_dependencies(dependencies["gems"])
install_js_packages(dependencies["js_packages"])
install_components_dependencies(deps["components"])
install_gems_dependencies(deps["gems"])
install_js_packages(deps["js_packages"])
end

private

def component_not_found? = !Dir.exist?(component_folder_path)
def component_exists?(component_name) = Dir.exist?(component_folder_path(component_name))

def component_folder_name = component_name.underscore
def component_folder_name(component_name) = component_name.underscore

def component_folder_path = File.join(self.class.source_root, component_folder_name)
def component_folder_path(component_name) = File.join(self.class.source_root, component_folder_name(component_name))

def components_file_paths
files = Dir.glob(File.join(component_folder_path, "*.rb"))
def components_file_paths(component_name)
files = Dir.glob(File.join(component_folder_path(component_name), "*.rb"))
options["with_docs"] ? files : files.reject { |f| f.end_with?("_docs.rb") }
end

def js_controller_file_paths = Dir.glob(File.join(component_folder_path, "*.js"))
def js_controller_file_paths(component_name) = Dir.glob(File.join(component_folder_path(component_name), "*.js"))

def install_components_dependencies(components)
components&.each do |component|
Expand All @@ -89,10 +109,10 @@ def install_js_packages(js_packages)
end
end

def dependencies
def dependencies(component_name)
@dependencies ||= YAML.load_file(File.join(__dir__, "dependencies.yml")).freeze

@dependencies[component_folder_name]
@dependencies[component_folder_name(component_name)]
end
end
end
Expand Down
21 changes: 21 additions & 0 deletions gem/test/generators/component_generator_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -82,4 +82,25 @@ def test_tooltip_installs_typography_for_text_component

assert_includes dependencies.fetch("tooltip").fetch("components"), "Typography"
end

def test_resolves_component_folders_for_multiple_components
source_root = File.expand_path("../../lib/ruby_ui", __dir__)

# Folder names mirror ComponentGenerator's `component_name.underscore`.
{"Button" => "button", "Link" => "link", "Input" => "input", "Textarea" => "textarea"}.each do |component_name, folder_name|
folder_path = File.join(source_root, folder_name)

assert(Dir.exist?(folder_path),
"Expected folder for #{component_name} to exist at #{folder_path}")
end
end

def test_validation_collects_all_missing_components
source_root = File.expand_path("../../lib/ruby_ui", __dir__)
folder_names = {"Button" => "button", "NotARealComponent" => "not_a_real_component", "AnotherFakeOne" => "another_fake_one"}

missing = folder_names.reject { |_name, folder| Dir.exist?(File.join(source_root, folder)) }.keys

assert_equal %w[NotARealComponent AnotherFakeOne], missing
end
end