gemsmith 15.5.0 → 16.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data/LICENSE.adoc +207 -155
- data/README.adoc +177 -189
- data/exe/gemsmith +6 -0
- data/gemsmith.gemspec +41 -0
- data/lib/gemsmith/builders/bundler.rb +34 -0
- data/lib/gemsmith/builders/cli.rb +86 -0
- data/lib/gemsmith/builders/git/commit.rb +46 -0
- data/lib/gemsmith/builders/git/ignore.rb +37 -0
- data/lib/gemsmith/builders/rspec/helper.rb +47 -0
- data/lib/gemsmith/builders/specification.rb +35 -0
- data/lib/gemsmith/cli/actions/build.rb +53 -0
- data/lib/gemsmith/cli/actions/config.rb +35 -0
- data/lib/gemsmith/cli/actions/edit.rb +38 -0
- data/lib/gemsmith/cli/actions/install.rb +36 -0
- data/lib/gemsmith/cli/actions/publish.rb +36 -0
- data/lib/gemsmith/cli/actions/view.rb +38 -0
- data/lib/gemsmith/cli/parser.rb +33 -0
- data/lib/gemsmith/cli/parsers/build.rb +54 -0
- data/lib/gemsmith/cli/parsers/core.rb +94 -0
- data/lib/gemsmith/cli/shell.rb +66 -0
- data/lib/gemsmith/configuration/enhancers/template_root.rb +20 -0
- data/lib/gemsmith/configuration/loader.rb +50 -0
- data/lib/gemsmith/container.rb +21 -0
- data/lib/gemsmith/gems/finder.rb +21 -0
- data/lib/gemsmith/gems/loader.rb +21 -0
- data/lib/gemsmith/gems/picker.rb +43 -0
- data/lib/gemsmith/gems/presenter.rb +50 -0
- data/lib/gemsmith/templates/%project_name%/%project_name%.gemspec.erb +49 -0
- data/lib/gemsmith/templates/%project_name%/exe/%project_name%.erb +5 -0
- data/lib/gemsmith/templates/%project_name%/lib/%project_path%/cli/actions/config.rb.erb +35 -0
- data/lib/gemsmith/templates/%project_name%/lib/%project_path%/cli/parser.rb.erb +31 -0
- data/lib/gemsmith/templates/%project_name%/lib/%project_path%/cli/parsers/core.rb.erb +54 -0
- data/lib/gemsmith/templates/%project_name%/lib/%project_path%/cli/shell.rb.erb +38 -0
- data/lib/gemsmith/templates/%project_name%/lib/%project_path%/configuration/content.rb.erb +18 -0
- data/lib/gemsmith/templates/%project_name%/lib/%project_path%/configuration/defaults.yml.erb +0 -0
- data/lib/gemsmith/templates/%project_name%/lib/%project_path%/configuration/loader.rb.erb +33 -0
- data/lib/gemsmith/templates/%project_name%/lib/%project_path%/container.rb.erb +35 -0
- data/lib/gemsmith/templates/{%gem_name%/lib/%gem_path%/identity.rb.tt → %project_name%/lib/%project_path%/identity.rb.erb} +1 -1
- data/lib/gemsmith/templates/%project_name%/spec/lib/%project_path%/cli/actions/config_spec.rb.erb +24 -0
- data/lib/gemsmith/templates/%project_name%/spec/lib/%project_path%/cli/parser_spec.rb.erb +25 -0
- data/lib/gemsmith/templates/%project_name%/spec/lib/%project_path%/cli/parsers/core_spec.rb.erb +53 -0
- data/lib/gemsmith/templates/%project_name%/spec/lib/%project_path%/cli/shell_spec.rb.erb +43 -0
- data/lib/gemsmith/templates/%project_name%/spec/lib/%project_path%/configuration/content_spec.rb.erb +15 -0
- data/lib/gemsmith/templates/%project_name%/spec/lib/%project_path%/configuration/loader_spec.rb.erb +29 -0
- data/lib/gemsmith/templates/%project_name%/spec/support/shared_contexts/application_container.rb.erb +22 -0
- data/lib/gemsmith/templates/%project_name%/spec/support/shared_examples/a_parser.rb.erb +7 -0
- data/lib/gemsmith/tools/cleaner.rb +29 -0
- data/lib/gemsmith/tools/editor.rb +30 -0
- data/lib/gemsmith/tools/installer.rb +39 -0
- data/lib/gemsmith/tools/packager.rb +33 -0
- data/lib/gemsmith/tools/publisher.rb +34 -0
- data/lib/gemsmith/tools/pusher.rb +36 -0
- data/lib/gemsmith/tools/validator.rb +28 -0
- data/lib/gemsmith/tools/versioner.rb +43 -0
- data/lib/gemsmith/tools/viewer.rb +28 -0
- data/lib/gemsmith.rb +5 -3
- data.tar.gz.sig +0 -0
- metadata +87 -115
- metadata.gz.sig +0 -0
- data/bin/gemsmith +0 -9
- data/lib/gemsmith/authenticators/basic.rb +0 -28
- data/lib/gemsmith/authenticators/ruby_gems.rb +0 -41
- data/lib/gemsmith/cli.rb +0 -259
- data/lib/gemsmith/credentials.rb +0 -82
- data/lib/gemsmith/errors/base.rb +0 -9
- data/lib/gemsmith/errors/requirement_conversion.rb +0 -9
- data/lib/gemsmith/errors/requirement_operator.rb +0 -9
- data/lib/gemsmith/errors/specification.rb +0 -9
- data/lib/gemsmith/gem/inspector.rb +0 -30
- data/lib/gemsmith/gem/module_formatter.rb +0 -50
- data/lib/gemsmith/gem/requirement.rb +0 -55
- data/lib/gemsmith/gem/specification.rb +0 -74
- data/lib/gemsmith/generators/base.rb +0 -46
- data/lib/gemsmith/generators/bundler.rb +0 -19
- data/lib/gemsmith/generators/bundler_audit.rb +0 -15
- data/lib/gemsmith/generators/circle_ci.rb +0 -14
- data/lib/gemsmith/generators/cli.rb +0 -17
- data/lib/gemsmith/generators/documentation.rb +0 -36
- data/lib/gemsmith/generators/engine.rb +0 -77
- data/lib/gemsmith/generators/gem.rb +0 -29
- data/lib/gemsmith/generators/git.rb +0 -45
- data/lib/gemsmith/generators/git_hub.rb +0 -15
- data/lib/gemsmith/generators/git_lint.rb +0 -14
- data/lib/gemsmith/generators/guard.rb +0 -14
- data/lib/gemsmith/generators/pragma.rb +0 -49
- data/lib/gemsmith/generators/rake.rb +0 -76
- data/lib/gemsmith/generators/reek.rb +0 -17
- data/lib/gemsmith/generators/rspec.rb +0 -39
- data/lib/gemsmith/generators/rubocop.rb +0 -18
- data/lib/gemsmith/generators/ruby.rb +0 -12
- data/lib/gemsmith/helpers/cli.rb +0 -59
- data/lib/gemsmith/helpers/template.rb +0 -30
- data/lib/gemsmith/identity.rb +0 -12
- data/lib/gemsmith/rake/builder.rb +0 -57
- data/lib/gemsmith/rake/publisher.rb +0 -100
- data/lib/gemsmith/rake/setup.rb +0 -4
- data/lib/gemsmith/rake/tasks.rb +0 -83
- data/lib/gemsmith/templates/%gem_name%/%gem_name%.gemspec.tt +0 -44
- data/lib/gemsmith/templates/%gem_name%/.circleci/config.yml.tt +0 -31
- data/lib/gemsmith/templates/%gem_name%/.github/ISSUE_TEMPLATE.md.tt +0 -14
- data/lib/gemsmith/templates/%gem_name%/.github/PULL_REQUEST_TEMPLATE.md.tt +0 -11
- data/lib/gemsmith/templates/%gem_name%/.gitignore.tt +0 -6
- data/lib/gemsmith/templates/%gem_name%/.reek.yml.tt +0 -6
- data/lib/gemsmith/templates/%gem_name%/.rubocop.yml.tt +0 -15
- data/lib/gemsmith/templates/%gem_name%/.ruby-version.tt +0 -1
- data/lib/gemsmith/templates/%gem_name%/CHANGES.md.tt +0 -3
- data/lib/gemsmith/templates/%gem_name%/CODE_OF_CONDUCT.md.tt +0 -66
- data/lib/gemsmith/templates/%gem_name%/CONTRIBUTING.md.tt +0 -22
- data/lib/gemsmith/templates/%gem_name%/Gemfile.tt +0 -47
- data/lib/gemsmith/templates/%gem_name%/Guardfile.tt +0 -21
- data/lib/gemsmith/templates/%gem_name%/LICENSE.md.tt +0 -20
- data/lib/gemsmith/templates/%gem_name%/README.md.tt +0 -93
- data/lib/gemsmith/templates/%gem_name%/Rakefile.tt +0 -12
- data/lib/gemsmith/templates/%gem_name%/bin/%gem_name%.tt +0 -8
- data/lib/gemsmith/templates/%gem_name%/bin/console.tt +0 -10
- data/lib/gemsmith/templates/%gem_name%/bin/setup.tt +0 -8
- data/lib/gemsmith/templates/%gem_name%/lib/%gem_path%/cli.rb.tt +0 -61
- data/lib/gemsmith/templates/%gem_name%/lib/%gem_path%/engine.rb.tt +0 -6
- data/lib/gemsmith/templates/%gem_name%/lib/%gem_path%.rb.tt +0 -7
- data/lib/gemsmith/templates/%gem_name%/lib/generators/%gem_path%/install/USAGE.tt +0 -8
- data/lib/gemsmith/templates/%gem_name%/lib/generators/%gem_path%/install/install_generator.rb.tt +0 -12
- data/lib/gemsmith/templates/%gem_name%/lib/generators/%gem_path%/upgrade/USAGE.tt +0 -8
- data/lib/gemsmith/templates/%gem_name%/lib/generators/%gem_path%/upgrade/upgrade_generator.rb.tt +0 -10
- data/lib/gemsmith/templates/%gem_name%/spec/lib/%gem_path%/cli_spec.rb.tt +0 -81
- data/lib/gemsmith/templates/%gem_name%/spec/rails_helper.rb.tt +0 -14
- data/lib/gemsmith/templates/%gem_name%/spec/spec_helper.rb.tt +0 -37
- data/lib/gemsmith/templates/%gem_name%/spec/support/shared_contexts/temp_dir.rb.tt +0 -9
@@ -0,0 +1,50 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "pathname"
|
4
|
+
require "refinements/arrays"
|
5
|
+
require "refinements/hashes"
|
6
|
+
require "refinements/structs"
|
7
|
+
require "runcom"
|
8
|
+
require "yaml"
|
9
|
+
|
10
|
+
module Gemsmith
|
11
|
+
module Configuration
|
12
|
+
# Represents the fully assembled Command Line Interface (CLI) configuration.
|
13
|
+
class Loader
|
14
|
+
using Refinements::Arrays
|
15
|
+
using Refinements::Hashes
|
16
|
+
using Refinements::Structs
|
17
|
+
|
18
|
+
DEFAULTS = Rubysmith::Configuration::Loader::DEFAULTS
|
19
|
+
CLIENT = Runcom::Config.new "gemsmith/configuration.yml", defaults: DEFAULTS
|
20
|
+
|
21
|
+
ENHANCERS = Rubysmith::Configuration::Loader::ENHANCERS.including(Enhancers::TemplateRoot.new)
|
22
|
+
.freeze
|
23
|
+
|
24
|
+
def self.call(...) = new(...).call
|
25
|
+
|
26
|
+
def self.with_defaults = new(client: DEFAULTS, enhancers: [])
|
27
|
+
|
28
|
+
def self.with_overrides = new(client: DEFAULTS, enhancers: [Enhancers::TemplateRoot.new])
|
29
|
+
|
30
|
+
def initialize content: Rubysmith::Configuration::Content.new,
|
31
|
+
client: CLIENT,
|
32
|
+
enhancers: ENHANCERS
|
33
|
+
@content = content
|
34
|
+
@client = client
|
35
|
+
@enhancers = enhancers
|
36
|
+
end
|
37
|
+
|
38
|
+
def call
|
39
|
+
enhancers.reduce(preload_content) { |preload, enhancer| enhancer.call preload }
|
40
|
+
.freeze
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
attr_reader :content, :client, :enhancers
|
46
|
+
|
47
|
+
def preload_content = content.merge(**client.to_h.flatten_keys)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "dry-container"
|
4
|
+
require "open3"
|
5
|
+
|
6
|
+
module Gemsmith
|
7
|
+
# Provides a global gem container for injection into other objects.
|
8
|
+
module Container
|
9
|
+
extend Dry::Container::Mixin
|
10
|
+
|
11
|
+
config.registry = ->(container, key, value, _options) { container[key.to_s] = value }
|
12
|
+
|
13
|
+
merge Rubysmith::Container
|
14
|
+
|
15
|
+
register(:configuration) { Gemsmith::Configuration::Loader.call }
|
16
|
+
register(:specification) { Gemsmith::Gems::Loader.call "#{__dir__}/../../gemsmith.gemspec" }
|
17
|
+
register(:environment) { ENV }
|
18
|
+
register(:executor) { Open3 }
|
19
|
+
register(:kernel) { Kernel }
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Gemsmith
|
4
|
+
module Gems
|
5
|
+
# Finds multiple versions of an installed gem (if any) and answers found specifications.
|
6
|
+
class Finder
|
7
|
+
def self.call(path, ...) = new(...).call path
|
8
|
+
|
9
|
+
def initialize client: ::Gem::Specification, presenter: Gems::Presenter
|
10
|
+
@client = client
|
11
|
+
@presenter = presenter
|
12
|
+
end
|
13
|
+
|
14
|
+
def call(name) = client.find_all_by_name(name).map { |record| presenter.new record }
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
attr_reader :client, :presenter
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Gemsmith
|
4
|
+
module Gems
|
5
|
+
# Loads a gem's specification.
|
6
|
+
class Loader
|
7
|
+
def self.call(path, ...) = new(...).call path
|
8
|
+
|
9
|
+
def initialize client: ::Gem::Specification, presenter: Gems::Presenter
|
10
|
+
@client = client
|
11
|
+
@presenter = presenter
|
12
|
+
end
|
13
|
+
|
14
|
+
def call(path) = client.load(path.to_s).then { |record| presenter.new record }
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
attr_reader :client, :presenter
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "dry/monads"
|
4
|
+
|
5
|
+
module Gemsmith
|
6
|
+
module Gems
|
7
|
+
# Picks a gem specification.
|
8
|
+
class Picker
|
9
|
+
include Dry::Monads[:result]
|
10
|
+
|
11
|
+
def self.call(name, ...) = new(...).call name
|
12
|
+
|
13
|
+
def initialize finder: Finder.new, kernel: Kernel
|
14
|
+
@finder = finder
|
15
|
+
@kernel = kernel
|
16
|
+
end
|
17
|
+
|
18
|
+
def call name
|
19
|
+
specifications = finder.call name
|
20
|
+
|
21
|
+
case specifications.size
|
22
|
+
when 1 then Success specifications.first
|
23
|
+
when 2.. then Success choose(specifications)
|
24
|
+
else Failure "Unknown gem or gem is not installed: #{name}."
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def choose specifications
|
29
|
+
specifications.each.with_index 1 do |specification, index|
|
30
|
+
kernel.puts "#{index}. #{specification.named_version}"
|
31
|
+
end
|
32
|
+
|
33
|
+
kernel.puts "\nPlease enter gem selection:"
|
34
|
+
ARGV.clear
|
35
|
+
specifications[kernel.gets.chomp.to_i - 1]
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
attr_reader :finder, :kernel
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "forwardable"
|
4
|
+
require "pathname"
|
5
|
+
require "refinements/arrays"
|
6
|
+
require "versionaire"
|
7
|
+
|
8
|
+
module Gemsmith
|
9
|
+
module Gems
|
10
|
+
# Provides a gem specification that is more useful than what is provided Ruby Gems.
|
11
|
+
class Presenter
|
12
|
+
extend Forwardable
|
13
|
+
|
14
|
+
using Refinements::Arrays
|
15
|
+
using Versionaire::Cast
|
16
|
+
|
17
|
+
delegate %i[metadata name summary] => :record
|
18
|
+
|
19
|
+
def initialize record
|
20
|
+
@record = record
|
21
|
+
end
|
22
|
+
|
23
|
+
def allowed_push_key = metadata.fetch "allowed_push_key", "rubygems_api_key"
|
24
|
+
|
25
|
+
def allowed_push_host = metadata.fetch "allowed_push_host", ::Gem::DEFAULT_HOST
|
26
|
+
|
27
|
+
def homepage_url = String record.homepage
|
28
|
+
|
29
|
+
def label = metadata.fetch "label", "Undefined"
|
30
|
+
|
31
|
+
def labeled_summary(delimiter: " - ") = [label, summary].compress.join delimiter
|
32
|
+
|
33
|
+
def labeled_version = "#{label} #{version}"
|
34
|
+
|
35
|
+
def named_version = "#{name} #{version}"
|
36
|
+
|
37
|
+
def package_path = Pathname("tmp").join package_name
|
38
|
+
|
39
|
+
def package_name = "#{name}-#{version}.gem"
|
40
|
+
|
41
|
+
def source_path = Pathname record.full_gem_path
|
42
|
+
|
43
|
+
def version = Version record.version.to_s
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
attr_reader :record
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
Gem::Specification.new do |spec|
|
2
|
+
spec.name = "<%= configuration.project_name %>"
|
3
|
+
spec.version = "<%= configuration.project_version %>"
|
4
|
+
spec.platform = Gem::Platform::RUBY
|
5
|
+
spec.authors = ["<%= configuration.author_name %>"]
|
6
|
+
spec.email = ["<%= configuration.author_email %>"]
|
7
|
+
spec.homepage = "<%= configuration.computed_project_url_home %>"
|
8
|
+
spec.summary = ""
|
9
|
+
spec.license = "<%= configuration.license_label_version %>"
|
10
|
+
|
11
|
+
spec.metadata = {
|
12
|
+
"bug_tracker_uri" => "<%= configuration.computed_project_url_issues %>",
|
13
|
+
"changelog_uri" => "<%= configuration.computed_project_url_versions %>",
|
14
|
+
"documentation_uri" => "<%= configuration.computed_project_url_home %>",
|
15
|
+
"label" => "<%= configuration.project_label %>",
|
16
|
+
"rubygems_mfa_required" => "true",
|
17
|
+
"source_code_uri" => "<%= configuration.computed_project_url_source %>"
|
18
|
+
}
|
19
|
+
|
20
|
+
<% if configuration.build_security %>
|
21
|
+
spec.signing_key = Gem.default_key_path
|
22
|
+
spec.cert_chain = [Gem.default_cert_path]
|
23
|
+
<% end %>
|
24
|
+
|
25
|
+
spec.required_ruby_version = "~> <%= RUBY_VERSION[/\d+\.\d+/] %>"
|
26
|
+
<% if configuration.build_cli %>
|
27
|
+
spec.add_dependency "dry-container", "~> 0.9"
|
28
|
+
<% end %>
|
29
|
+
<% if configuration.build_cli %>
|
30
|
+
spec.add_dependency "pastel", "~> 0.8"
|
31
|
+
<% end %>
|
32
|
+
<% if configuration.build_refinements %>
|
33
|
+
spec.add_dependency "refinements", "~> 9.1"
|
34
|
+
<% end %>
|
35
|
+
<% if configuration.build_cli %>
|
36
|
+
spec.add_dependency "runcom", "~> 8.0"
|
37
|
+
<% end %>
|
38
|
+
<% if configuration.build_zeitwerk %>
|
39
|
+
spec.add_dependency "zeitwerk", "~> 2.5"
|
40
|
+
<% end %>
|
41
|
+
|
42
|
+
<% if configuration.build_cli %>
|
43
|
+
spec.bindir = "exe"
|
44
|
+
spec.executables << "<%= configuration.project_name %>"
|
45
|
+
<% end %>
|
46
|
+
spec.extra_rdoc_files = Dir["README*", "LICENSE*"]
|
47
|
+
spec.files = Dir["*.gemspec", "lib/**/*"]
|
48
|
+
spec.require_paths = ["lib"]
|
49
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
<% namespace do %>
|
4
|
+
module CLI
|
5
|
+
module Actions
|
6
|
+
# Handles the config action.
|
7
|
+
class Config
|
8
|
+
def initialize client: Configuration::Loader::CLIENT, container: Container
|
9
|
+
@client = client
|
10
|
+
@container = container
|
11
|
+
end
|
12
|
+
|
13
|
+
def call selection
|
14
|
+
case selection
|
15
|
+
when :edit then edit
|
16
|
+
when :view then view
|
17
|
+
else logger.error { "Invalid configuration selection: #{selection}." }
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
attr_reader :client, :container
|
24
|
+
|
25
|
+
def edit = kernel.system("$EDITOR #{client.current}")
|
26
|
+
|
27
|
+
def view = kernel.system("cat #{client.current}")
|
28
|
+
|
29
|
+
def kernel = container[__method__]
|
30
|
+
|
31
|
+
def logger = container[__method__]
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
<% end %>
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require "optparse"
|
2
|
+
|
3
|
+
<% namespace do %>
|
4
|
+
module CLI
|
5
|
+
# Assembles and parses all Command Line Interface (CLI) options.
|
6
|
+
class Parser
|
7
|
+
CLIENT = OptionParser.new nil, 40, " "
|
8
|
+
|
9
|
+
# Order is important.
|
10
|
+
SECTIONS = [Parsers::Core].freeze
|
11
|
+
|
12
|
+
def initialize sections: SECTIONS, client: CLIENT, container: Container
|
13
|
+
@sections = sections
|
14
|
+
@client = client
|
15
|
+
@configuration = container[:configuration].dup
|
16
|
+
end
|
17
|
+
|
18
|
+
def call arguments = []
|
19
|
+
sections.each { |section| section.call configuration, client: }
|
20
|
+
client.parse arguments
|
21
|
+
configuration.freeze
|
22
|
+
end
|
23
|
+
|
24
|
+
def to_s = client.to_s
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
attr_reader :sections, :client, :configuration
|
29
|
+
end
|
30
|
+
end
|
31
|
+
<% end %>
|
@@ -0,0 +1,54 @@
|
|
1
|
+
require "refinements/structs"
|
2
|
+
|
3
|
+
<% namespace do %>
|
4
|
+
module CLI
|
5
|
+
module Parsers
|
6
|
+
# Handles parsing of Command Line Interface (CLI) core options.
|
7
|
+
class Core
|
8
|
+
using Refinements::Structs
|
9
|
+
|
10
|
+
def self.call(...) = new(...).call
|
11
|
+
|
12
|
+
def initialize configuration = Container[:configuration], client: Parser::CLIENT
|
13
|
+
@configuration = configuration
|
14
|
+
@client = client
|
15
|
+
end
|
16
|
+
|
17
|
+
def call arguments = []
|
18
|
+
client.banner = "<%= configuration.project_label %>"
|
19
|
+
client.separator "\nUSAGE:\n"
|
20
|
+
collate
|
21
|
+
client.parse arguments
|
22
|
+
configuration
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
attr_reader :configuration, :client
|
28
|
+
|
29
|
+
def collate = private_methods.sort.grep(/add_/).each { |method| __send__ method }
|
30
|
+
|
31
|
+
def add_config
|
32
|
+
client.on "-c",
|
33
|
+
"--config ACTION",
|
34
|
+
%i[edit view],
|
35
|
+
"Manage gem configuration: edit or view." do |action|
|
36
|
+
configuration.merge! action_config: action
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def add_version
|
41
|
+
client.on "-v", "--version", "Show gem version." do
|
42
|
+
configuration.merge! action_version: true
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def add_help
|
47
|
+
client.on "-h", "--help", "Show this message." do
|
48
|
+
configuration.merge! action_help: true
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
<% end %>
|
@@ -0,0 +1,38 @@
|
|
1
|
+
<% namespace do %>
|
2
|
+
module CLI
|
3
|
+
# The main Command Line Interface (CLI) object.
|
4
|
+
class Shell
|
5
|
+
ACTIONS = {config: Actions::Config.new}.freeze
|
6
|
+
|
7
|
+
def initialize parser: Parser.new, actions: ACTIONS, container: Container
|
8
|
+
@parser = parser
|
9
|
+
@actions = actions
|
10
|
+
@container = container
|
11
|
+
end
|
12
|
+
|
13
|
+
def call arguments = []
|
14
|
+
perform parser.call(arguments)
|
15
|
+
rescue OptionParser::ParseError => error
|
16
|
+
logger.error { error.message }
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
attr_reader :parser, :actions, :container
|
22
|
+
|
23
|
+
def perform configuration
|
24
|
+
case configuration
|
25
|
+
in action_config: Symbol => action then config action
|
26
|
+
in action_version: true then logger.info { "<%= configuration.project_label %> <%= configuration.project_version %>" }
|
27
|
+
else usage
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def config(action) = actions.fetch(__method__).call(action)
|
32
|
+
|
33
|
+
def usage = logger.unknown(parser.to_s)
|
34
|
+
|
35
|
+
def logger = container[__method__]
|
36
|
+
end
|
37
|
+
end
|
38
|
+
<% end %>
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
<% namespace do %>
|
4
|
+
module Configuration
|
5
|
+
# Defines the content of the configuration for use throughout the gem.
|
6
|
+
Content = Struct.new(
|
7
|
+
:action_config,
|
8
|
+
:action_help,
|
9
|
+
:action_version,
|
10
|
+
keyword_init: true
|
11
|
+
) do
|
12
|
+
def initialize *arguments
|
13
|
+
super
|
14
|
+
freeze
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
<% end %>
|
File without changes
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require "pathname"
|
2
|
+
require "refinements/hashes"
|
3
|
+
require "refinements/structs"
|
4
|
+
require "runcom"
|
5
|
+
require "yaml"
|
6
|
+
|
7
|
+
<% namespace do %>
|
8
|
+
module Configuration
|
9
|
+
# Represents the fully assembled Command Line Interface (CLI) configuration.
|
10
|
+
class Loader
|
11
|
+
using Refinements::Hashes
|
12
|
+
using Refinements::Structs
|
13
|
+
|
14
|
+
DEFAULTS = (YAML.load_file(Pathname(__dir__).join("defaults.yml")) || {}).freeze
|
15
|
+
CLIENT = Runcom::Config.new "<%= configuration.project_name %>/configuration.yml", defaults: DEFAULTS
|
16
|
+
|
17
|
+
def self.call = new.call
|
18
|
+
|
19
|
+
def self.with_defaults = new client: DEFAULTS
|
20
|
+
|
21
|
+
def initialize content: Content.new, client: CLIENT
|
22
|
+
@content = content
|
23
|
+
@client = client
|
24
|
+
end
|
25
|
+
|
26
|
+
def call = content.merge(**client.to_h.flatten_keys).freeze
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
attr_reader :content, :client
|
31
|
+
end
|
32
|
+
end
|
33
|
+
<% end %>
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require "dry-container"
|
2
|
+
require "logger"
|
3
|
+
require "pastel"
|
4
|
+
|
5
|
+
<% namespace do %>
|
6
|
+
# Provides a global gem container for injection into other objects.
|
7
|
+
module Container
|
8
|
+
extend Dry::Container::Mixin
|
9
|
+
|
10
|
+
register(:configuration) { Configuration::Loader.call }
|
11
|
+
register(:colorizer) { Pastel.new enabled: $stdout.tty? }
|
12
|
+
register(:kernel) { Kernel }
|
13
|
+
|
14
|
+
register :log_colors do
|
15
|
+
{
|
16
|
+
"DEBUG" => self[:colorizer].white.detach,
|
17
|
+
"INFO" => self[:colorizer].green.detach,
|
18
|
+
"WARN" => self[:colorizer].yellow.detach,
|
19
|
+
"ERROR" => self[:colorizer].red.detach,
|
20
|
+
"FATAL" => self[:colorizer].white.bold.on_red.detach,
|
21
|
+
"ANY" => self[:colorizer].white.bold.detach
|
22
|
+
}
|
23
|
+
end
|
24
|
+
|
25
|
+
register :logger do
|
26
|
+
Logger.new $stdout,
|
27
|
+
level: Logger.const_get(ENV.fetch("LOG_LEVEL", "INFO")),
|
28
|
+
formatter: (
|
29
|
+
lambda do |severity, _at, _name, message|
|
30
|
+
self[:log_colors][severity].call "#{message}\n"
|
31
|
+
end
|
32
|
+
)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
<% end %>
|
data/lib/gemsmith/templates/%project_name%/spec/lib/%project_path%/cli/actions/config_spec.rb.erb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
RSpec.describe <%= configuration.project_class %>::CLI::Actions::Config do
|
4
|
+
subject(:action) { described_class.new }
|
5
|
+
|
6
|
+
include_context "with application container"
|
7
|
+
|
8
|
+
describe "#call" do
|
9
|
+
it "edits configuration" do
|
10
|
+
action.call :edit
|
11
|
+
expect(kernel).to have_received(:system).with(include("EDITOR"))
|
12
|
+
end
|
13
|
+
|
14
|
+
it "views configuration" do
|
15
|
+
action.call :view
|
16
|
+
expect(kernel).to have_received(:system).with(include("cat"))
|
17
|
+
end
|
18
|
+
|
19
|
+
it "logs invalid configuration" do
|
20
|
+
expectation = proc { action.call :bogus }
|
21
|
+
expect(&expectation).to output(/Invalid configuration selection: bogus./).to_stdout
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
RSpec.describe <%= configuration.project_class %>::CLI::Parser do
|
4
|
+
subject(:parser) { described_class.new }
|
5
|
+
|
6
|
+
include_context "with application container"
|
7
|
+
|
8
|
+
describe "#call" do
|
9
|
+
it "answers hash with valid option" do
|
10
|
+
expect(parser.call(%w[--help])).to have_attributes(action_help: true)
|
11
|
+
end
|
12
|
+
|
13
|
+
it "fails with invalid option" do
|
14
|
+
expectation = proc { parser.call %w[--bogus] }
|
15
|
+
expect(&expectation).to raise_error(OptionParser::InvalidOption, /--bogus/)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
describe "#to_s" do
|
20
|
+
it "answers usage" do
|
21
|
+
parser.call
|
22
|
+
expect(parser.to_s).to match(/.+USAGE.+/m)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
data/lib/gemsmith/templates/%project_name%/spec/lib/%project_path%/cli/parsers/core_spec.rb.erb
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
RSpec.describe <%= configuration.project_class %>::CLI::Parsers::Core do
|
4
|
+
subject(:parser) { described_class.new configuration.dup }
|
5
|
+
|
6
|
+
include_context "with application container"
|
7
|
+
|
8
|
+
it_behaves_like "a parser"
|
9
|
+
|
10
|
+
describe "#call" do
|
11
|
+
it "answers config edit (short)" do
|
12
|
+
expect(parser.call(%w[-c edit])).to have_attributes(action_config: :edit)
|
13
|
+
end
|
14
|
+
|
15
|
+
it "answers config edit (long)" do
|
16
|
+
expect(parser.call(%w[--config edit])).to have_attributes(action_config: :edit)
|
17
|
+
end
|
18
|
+
|
19
|
+
it "answers config view (short)" do
|
20
|
+
expect(parser.call(%w[-c view])).to have_attributes(action_config: :view)
|
21
|
+
end
|
22
|
+
|
23
|
+
it "answers config view (long)" do
|
24
|
+
expect(parser.call(%w[--config view])).to have_attributes(action_config: :view)
|
25
|
+
end
|
26
|
+
|
27
|
+
it "fails with missing config action" do
|
28
|
+
expectation = proc { parser.call %w[--config] }
|
29
|
+
expect(&expectation).to raise_error(OptionParser::MissingArgument, /--config/)
|
30
|
+
end
|
31
|
+
|
32
|
+
it "fails with invalid config action" do
|
33
|
+
expectation = proc { parser.call %w[--config bogus] }
|
34
|
+
expect(&expectation).to raise_error(OptionParser::InvalidArgument, /bogus/)
|
35
|
+
end
|
36
|
+
|
37
|
+
it "answers version (short)" do
|
38
|
+
expect(parser.call(%w[-v])).to have_attributes(action_version: true)
|
39
|
+
end
|
40
|
+
|
41
|
+
it "answers version (long)" do
|
42
|
+
expect(parser.call(%w[--version])).to have_attributes(action_version: true)
|
43
|
+
end
|
44
|
+
|
45
|
+
it "enables help (short)" do
|
46
|
+
expect(parser.call(%w[-h])).to have_attributes(action_help: true)
|
47
|
+
end
|
48
|
+
|
49
|
+
it "enables help (long)" do
|
50
|
+
expect(parser.call(%w[--help])).to have_attributes(action_help: true)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
RSpec.describe <%= configuration.project_class %>::CLI::Shell do
|
4
|
+
using Refinements::Pathnames
|
5
|
+
|
6
|
+
subject(:shell) { described_class.new actions: described_class::ACTIONS.merge(config:) }
|
7
|
+
|
8
|
+
include_context "with temporary directory"
|
9
|
+
|
10
|
+
let(:config) { instance_spy <%= configuration.project_class %>::CLI::Actions::Config }
|
11
|
+
|
12
|
+
describe "#call" do
|
13
|
+
it "edits configuration" do
|
14
|
+
shell.call %w[--config edit]
|
15
|
+
expect(config).to have_received(:call).with(:edit)
|
16
|
+
end
|
17
|
+
|
18
|
+
it "views configuration" do
|
19
|
+
shell.call %w[--config view]
|
20
|
+
expect(config).to have_received(:call).with(:view)
|
21
|
+
end
|
22
|
+
|
23
|
+
it "prints version" do
|
24
|
+
expectation = proc { shell.call %w[--version] }
|
25
|
+
expect(&expectation).to output(/<%= configuration.project_label %>\s\d+\.\d+\.\d+/).to_stdout
|
26
|
+
end
|
27
|
+
|
28
|
+
it "prints help (usage)" do
|
29
|
+
expectation = proc { shell.call %w[--help] }
|
30
|
+
expect(&expectation).to output(/<%= configuration.project_label %>.+USAGE.+/m).to_stdout
|
31
|
+
end
|
32
|
+
|
33
|
+
it "prints usage when no options are given" do
|
34
|
+
expectation = proc { shell.call }
|
35
|
+
expect(&expectation).to output(/<%= configuration.project_label %>.+USAGE.+/m).to_stdout
|
36
|
+
end
|
37
|
+
|
38
|
+
it "prints error with invalid option" do
|
39
|
+
expectation = proc { shell.call %w[--bogus] }
|
40
|
+
expect(&expectation).to output(/invalid option.+bogus/).to_stdout
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|