Skip to content
Merged
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
21 changes: 21 additions & 0 deletions lib/busser/command/plugin_create.rb
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,22 @@ class PluginCreate < Busser::Thor::BaseGroup
# Generates a new runner plugin project.
#
# @return [void]
# A plugin name becomes three things: a require path, a directory name and
# a Ruby constant. Only the last is fussy, and Thor's camel_case only
# folds underscores -- so "my-junit" produced `module My-junit`, which is
# not parseable Ruby. The generator ran to completion and wrote a project
# that could not be loaded at all.
NAME_PATTERN = /\A[a-z][a-z0-9_]*\z/

def create
unless NAME_PATTERN.match?(name)
raise ::Thor::Error,
"'#{name}' is not a usable plugin name. A name becomes a Ruby " \
"constant, so it must start with a lowercase letter and contain " \
"only lowercase letters, digits and underscores. Try " \
"'#{suggested_name}'."
end

self.class.source_root(Busser.source_root.join("templates", "plugin"))

create_core_files
Expand Down Expand Up @@ -126,6 +141,12 @@ def create_template(erb, dest)
end

# @return [String] directory the new plugin is generated into
# @return [String] the given name reshaped into something usable, for the
# error message
def suggested_name
name.to_s.downcase.gsub(/[^a-z0-9_]+/, "_").sub(/\A[^a-z]+/, "").squeeze("_")
end

def target_dir
File.join(Dir.pwd, "busser-#{name}")
end
Expand Down
46 changes: 44 additions & 2 deletions spec/busser/command/plugin_create_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -152,9 +152,51 @@ def dev_dependencies
end
end

def generate(license: "apachev2")
# A name becomes a Ruby constant, and Thor's camel_case only folds
# underscores. "my-junit" produced `module My-junit`, which does not parse --
# and the generator ran to completion anyway, leaving a project that could
# not be loaded at all.
describe "the plugin name" do

%w{demo junit my_junit j2 x}.each do |name|
it "accepts #{name.inspect}" do
generate(name: name)

_(Dir.exist?(File.join(@tmpdir, "busser-#{name}"))).must_equal true
end
end

{
"my-junit" => "a hyphen cannot appear in a constant",
"My_Junit" => "an uppercase start is not a plugin name",
"2fast" => "a constant cannot start with a digit",
"my junit" => "a space cannot appear in a constant or a path",
"" => "an empty name",
"../escaped" => "a path fragment",
}.each do |name, why|
it "rejects #{name.inspect} -- #{why}" do
err = _(proc { generate(name: name) }).must_raise ::Thor::Error

_(err.message).must_include "not a usable plugin name"
end
end

it "writes nothing when the name is rejected" do
_(proc { generate(name: "my-junit") }).must_raise ::Thor::Error

_(Dir.glob(File.join(@tmpdir, "*"))).must_be_empty
end

it "suggests a usable name" do
err = _(proc { generate(name: "my-junit") }).must_raise ::Thor::Error

_(err.message).must_include "'my_junit'"
end
end

def generate(license: "apachev2", name: "demo")
command = Busser::Command::PluginCreate.new(
["demo"], { "type" => "runner", "license" => license }
[name], { "type" => "runner", "license" => license }
)
command.stubs(:initialize_git)
capture_stdout { command.create }
Expand Down
Loading