gemsmith 15.5.0 → 17.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (127) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/LICENSE.adoc +207 -155
  4. data/README.adoc +189 -196
  5. data/exe/gemsmith +6 -0
  6. data/gemsmith.gemspec +40 -0
  7. data/lib/gemsmith/builders/bundler.rb +34 -0
  8. data/lib/gemsmith/builders/circle_ci.rb +32 -0
  9. data/lib/gemsmith/builders/cli.rb +86 -0
  10. data/lib/gemsmith/builders/documentation/readme.rb +37 -0
  11. data/lib/gemsmith/builders/git/commit.rb +46 -0
  12. data/lib/gemsmith/builders/git/ignore.rb +37 -0
  13. data/lib/gemsmith/builders/rspec/helper.rb +47 -0
  14. data/lib/gemsmith/builders/specification.rb +35 -0
  15. data/lib/gemsmith/cli/actions/build.rb +55 -0
  16. data/lib/gemsmith/cli/actions/config.rb +35 -0
  17. data/lib/gemsmith/cli/actions/edit.rb +39 -0
  18. data/lib/gemsmith/cli/actions/install.rb +37 -0
  19. data/lib/gemsmith/cli/actions/publish.rb +37 -0
  20. data/lib/gemsmith/cli/actions/view.rb +39 -0
  21. data/lib/gemsmith/cli/parser.rb +33 -0
  22. data/lib/gemsmith/cli/parsers/build.rb +54 -0
  23. data/lib/gemsmith/cli/parsers/core.rb +94 -0
  24. data/lib/gemsmith/cli/shell.rb +66 -0
  25. data/lib/gemsmith/configuration/enhancers/template_root.rb +20 -0
  26. data/lib/gemsmith/configuration/loader.rb +50 -0
  27. data/lib/gemsmith/container.rb +22 -0
  28. data/lib/gemsmith/templates/%project_name%/%project_name%.gemspec.erb +50 -0
  29. data/lib/gemsmith/templates/%project_name%/exe/%project_name%.erb +5 -0
  30. data/lib/gemsmith/templates/%project_name%/lib/%project_path%/cli/actions/config.rb.erb +35 -0
  31. data/lib/gemsmith/templates/%project_name%/lib/%project_path%/cli/parser.rb.erb +31 -0
  32. data/lib/gemsmith/templates/%project_name%/lib/%project_path%/cli/parsers/core.rb.erb +59 -0
  33. data/lib/gemsmith/templates/%project_name%/lib/%project_path%/cli/shell.rb.erb +40 -0
  34. data/lib/gemsmith/templates/%project_name%/lib/%project_path%/configuration/content.rb.erb +18 -0
  35. data/lib/gemsmith/templates/%project_name%/lib/%project_path%/configuration/defaults.yml.erb +0 -0
  36. data/lib/gemsmith/templates/%project_name%/lib/%project_path%/configuration/loader.rb.erb +33 -0
  37. data/lib/gemsmith/templates/%project_name%/lib/%project_path%/container.rb.erb +37 -0
  38. data/lib/gemsmith/templates/{%gem_name%/lib/%gem_path%/identity.rb.tt → %project_name%/lib/%project_path%/identity.rb.erb} +1 -1
  39. data/lib/gemsmith/templates/%project_name%/spec/lib/%project_path%/cli/actions/config_spec.rb.erb +24 -0
  40. data/lib/gemsmith/templates/%project_name%/spec/lib/%project_path%/cli/parser_spec.rb.erb +25 -0
  41. data/lib/gemsmith/templates/%project_name%/spec/lib/%project_path%/cli/parsers/core_spec.rb.erb +53 -0
  42. data/lib/gemsmith/templates/%project_name%/spec/lib/%project_path%/cli/shell_spec.rb.erb +43 -0
  43. data/lib/gemsmith/templates/%project_name%/spec/lib/%project_path%/configuration/content_spec.rb.erb +15 -0
  44. data/lib/gemsmith/templates/%project_name%/spec/lib/%project_path%/configuration/loader_spec.rb.erb +29 -0
  45. data/lib/gemsmith/templates/%project_name%/spec/support/shared_contexts/application_container.rb.erb +22 -0
  46. data/lib/gemsmith/templates/%project_name%/spec/support/shared_examples/a_parser.rb.erb +7 -0
  47. data/lib/gemsmith/tools/cleaner.rb +29 -0
  48. data/lib/gemsmith/tools/editor.rb +30 -0
  49. data/lib/gemsmith/tools/installer.rb +39 -0
  50. data/lib/gemsmith/tools/packager.rb +33 -0
  51. data/lib/gemsmith/tools/publisher.rb +34 -0
  52. data/lib/gemsmith/tools/pusher.rb +36 -0
  53. data/lib/gemsmith/tools/validator.rb +28 -0
  54. data/lib/gemsmith/tools/versioner.rb +43 -0
  55. data/lib/gemsmith/tools/viewer.rb +28 -0
  56. data/lib/gemsmith.rb +5 -3
  57. data.tar.gz.sig +0 -0
  58. metadata +88 -104
  59. metadata.gz.sig +0 -0
  60. data/bin/gemsmith +0 -9
  61. data/lib/gemsmith/authenticators/basic.rb +0 -28
  62. data/lib/gemsmith/authenticators/ruby_gems.rb +0 -41
  63. data/lib/gemsmith/cli.rb +0 -259
  64. data/lib/gemsmith/credentials.rb +0 -82
  65. data/lib/gemsmith/errors/base.rb +0 -9
  66. data/lib/gemsmith/errors/requirement_conversion.rb +0 -9
  67. data/lib/gemsmith/errors/requirement_operator.rb +0 -9
  68. data/lib/gemsmith/errors/specification.rb +0 -9
  69. data/lib/gemsmith/gem/inspector.rb +0 -30
  70. data/lib/gemsmith/gem/module_formatter.rb +0 -50
  71. data/lib/gemsmith/gem/requirement.rb +0 -55
  72. data/lib/gemsmith/gem/specification.rb +0 -74
  73. data/lib/gemsmith/generators/base.rb +0 -46
  74. data/lib/gemsmith/generators/bundler.rb +0 -19
  75. data/lib/gemsmith/generators/bundler_audit.rb +0 -15
  76. data/lib/gemsmith/generators/circle_ci.rb +0 -14
  77. data/lib/gemsmith/generators/cli.rb +0 -17
  78. data/lib/gemsmith/generators/documentation.rb +0 -36
  79. data/lib/gemsmith/generators/engine.rb +0 -77
  80. data/lib/gemsmith/generators/gem.rb +0 -29
  81. data/lib/gemsmith/generators/git.rb +0 -45
  82. data/lib/gemsmith/generators/git_hub.rb +0 -15
  83. data/lib/gemsmith/generators/git_lint.rb +0 -14
  84. data/lib/gemsmith/generators/guard.rb +0 -14
  85. data/lib/gemsmith/generators/pragma.rb +0 -49
  86. data/lib/gemsmith/generators/rake.rb +0 -76
  87. data/lib/gemsmith/generators/reek.rb +0 -17
  88. data/lib/gemsmith/generators/rspec.rb +0 -39
  89. data/lib/gemsmith/generators/rubocop.rb +0 -18
  90. data/lib/gemsmith/generators/ruby.rb +0 -12
  91. data/lib/gemsmith/helpers/cli.rb +0 -59
  92. data/lib/gemsmith/helpers/template.rb +0 -30
  93. data/lib/gemsmith/identity.rb +0 -12
  94. data/lib/gemsmith/rake/builder.rb +0 -57
  95. data/lib/gemsmith/rake/publisher.rb +0 -100
  96. data/lib/gemsmith/rake/setup.rb +0 -4
  97. data/lib/gemsmith/rake/tasks.rb +0 -83
  98. data/lib/gemsmith/templates/%gem_name%/%gem_name%.gemspec.tt +0 -44
  99. data/lib/gemsmith/templates/%gem_name%/.circleci/config.yml.tt +0 -31
  100. data/lib/gemsmith/templates/%gem_name%/.github/ISSUE_TEMPLATE.md.tt +0 -14
  101. data/lib/gemsmith/templates/%gem_name%/.github/PULL_REQUEST_TEMPLATE.md.tt +0 -11
  102. data/lib/gemsmith/templates/%gem_name%/.gitignore.tt +0 -6
  103. data/lib/gemsmith/templates/%gem_name%/.reek.yml.tt +0 -6
  104. data/lib/gemsmith/templates/%gem_name%/.rubocop.yml.tt +0 -15
  105. data/lib/gemsmith/templates/%gem_name%/.ruby-version.tt +0 -1
  106. data/lib/gemsmith/templates/%gem_name%/CHANGES.md.tt +0 -3
  107. data/lib/gemsmith/templates/%gem_name%/CODE_OF_CONDUCT.md.tt +0 -66
  108. data/lib/gemsmith/templates/%gem_name%/CONTRIBUTING.md.tt +0 -22
  109. data/lib/gemsmith/templates/%gem_name%/Gemfile.tt +0 -47
  110. data/lib/gemsmith/templates/%gem_name%/Guardfile.tt +0 -21
  111. data/lib/gemsmith/templates/%gem_name%/LICENSE.md.tt +0 -20
  112. data/lib/gemsmith/templates/%gem_name%/README.md.tt +0 -93
  113. data/lib/gemsmith/templates/%gem_name%/Rakefile.tt +0 -12
  114. data/lib/gemsmith/templates/%gem_name%/bin/%gem_name%.tt +0 -8
  115. data/lib/gemsmith/templates/%gem_name%/bin/console.tt +0 -10
  116. data/lib/gemsmith/templates/%gem_name%/bin/setup.tt +0 -8
  117. data/lib/gemsmith/templates/%gem_name%/lib/%gem_path%/cli.rb.tt +0 -61
  118. data/lib/gemsmith/templates/%gem_name%/lib/%gem_path%/engine.rb.tt +0 -6
  119. data/lib/gemsmith/templates/%gem_name%/lib/%gem_path%.rb.tt +0 -7
  120. data/lib/gemsmith/templates/%gem_name%/lib/generators/%gem_path%/install/USAGE.tt +0 -8
  121. data/lib/gemsmith/templates/%gem_name%/lib/generators/%gem_path%/install/install_generator.rb.tt +0 -12
  122. data/lib/gemsmith/templates/%gem_name%/lib/generators/%gem_path%/upgrade/USAGE.tt +0 -8
  123. data/lib/gemsmith/templates/%gem_name%/lib/generators/%gem_path%/upgrade/upgrade_generator.rb.tt +0 -10
  124. data/lib/gemsmith/templates/%gem_name%/spec/lib/%gem_path%/cli_spec.rb.tt +0 -81
  125. data/lib/gemsmith/templates/%gem_name%/spec/rails_helper.rb.tt +0 -14
  126. data/lib/gemsmith/templates/%gem_name%/spec/spec_helper.rb.tt +0 -37
  127. data/lib/gemsmith/templates/%gem_name%/spec/support/shared_contexts/temp_dir.rb.tt +0 -9
@@ -0,0 +1,94 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "pathname"
4
+ require "refinements/structs"
5
+
6
+ module Gemsmith
7
+ module CLI
8
+ module Parsers
9
+ # Handles parsing of Command Line Interface (CLI) core options.
10
+ class Core
11
+ using Refinements::Structs
12
+
13
+ def self.call(...) = new(...).call
14
+
15
+ def initialize configuration = Container[:configuration],
16
+ client: Parser::CLIENT,
17
+ container: Container
18
+ @configuration = configuration
19
+ @client = client
20
+ @container = container
21
+ end
22
+
23
+ def call arguments = []
24
+ client.banner = specification.labeled_summary
25
+ client.separator "\nUSAGE:\n"
26
+ collate
27
+ client.parse arguments
28
+ configuration
29
+ end
30
+
31
+ private
32
+
33
+ attr_reader :configuration, :client, :container
34
+
35
+ def collate = private_methods.sort.grep(/add_/).each { |method| __send__ method }
36
+
37
+ def add_config
38
+ client.on "-c",
39
+ "--config ACTION",
40
+ %i[edit view],
41
+ "Manage gem configuration: edit or view." do |action|
42
+ configuration.merge! action_config: action
43
+ end
44
+ end
45
+
46
+ def add_build
47
+ client.on "-b", "--build NAME [options]", "Build new project." do |name|
48
+ configuration.merge! action_build: true, project_name: name
49
+ end
50
+ end
51
+
52
+ def add_edit
53
+ client.on "--edit GEM", "Edit installed gem in default editor." do |gem_name|
54
+ configuration.merge! action_edit: gem_name
55
+ end
56
+ end
57
+
58
+ def add_install
59
+ client.on "-i", "--install [NAME]", "Install gem for local development." do |name|
60
+ configuration.merge! action_install: true,
61
+ project_name: name || Pathname.pwd.basename.to_s
62
+ end
63
+ end
64
+
65
+ def add_publish
66
+ client.on "-p", "--publish [NAME]", "Publish gem to remote gem server." do |name|
67
+ configuration.merge! action_publish: true,
68
+ project_name: name || Pathname.pwd.basename.to_s
69
+ end
70
+ end
71
+
72
+ def add_view
73
+ client.on "--view GEM", "View installed gem in default browser." do |gem_name|
74
+ configuration.merge! action_view: gem_name
75
+ end
76
+ end
77
+
78
+ def add_version
79
+ client.on "-v", "--version", "Show gem version." do
80
+ configuration.merge! action_version: true
81
+ end
82
+ end
83
+
84
+ def add_help
85
+ client.on "-h", "--help", "Show this message." do
86
+ configuration.merge! action_help: true
87
+ end
88
+ end
89
+
90
+ def specification = container[__method__]
91
+ end
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Gemsmith
4
+ module CLI
5
+ # The main Command Line Interface (CLI) object.
6
+ class Shell
7
+ ACTIONS = {
8
+ config: Actions::Config.new,
9
+ build: Actions::Build.new,
10
+ install: Actions::Install.new,
11
+ publish: Actions::Publish.new,
12
+ edit: Actions::Edit.new,
13
+ view: Actions::View.new
14
+ }.freeze
15
+
16
+ def initialize parser: Parser.new, actions: ACTIONS, container: Container
17
+ @parser = parser
18
+ @actions = actions
19
+ @container = container
20
+ end
21
+
22
+ def call arguments = []
23
+ perform parser.call(arguments)
24
+ rescue OptionParser::ParseError => error
25
+ logger.error { error.message }
26
+ end
27
+
28
+ private
29
+
30
+ attr_reader :parser, :actions, :container
31
+
32
+ def perform configuration
33
+ case configuration
34
+ in action_config: Symbol => action then config action
35
+ in action_build: true then build configuration
36
+ in action_install: true then install configuration
37
+ in action_publish: true then publish configuration
38
+ in action_edit: String => gem_name then edit gem_name
39
+ in action_view: String => gem_name then view gem_name
40
+ in action_version: true then logger.info { specification.labeled_version }
41
+ else usage
42
+ end
43
+ end
44
+
45
+ def config(action) = actions.fetch(__method__).call(action)
46
+
47
+ def build(configuration) = actions.fetch(__method__).call(configuration)
48
+
49
+ def install(configuration) = actions.fetch(__method__).call(configuration)
50
+
51
+ def publish(configuration) = actions.fetch(__method__).call(configuration)
52
+
53
+ def edit(gem_name) = actions.fetch(__method__).call(gem_name)
54
+
55
+ def view(gem_name) = actions.fetch(__method__).call(gem_name)
56
+
57
+ def usage = logger.unknown { parser.to_s }
58
+
59
+ def logger = container[__method__]
60
+
61
+ def specification = container[__method__]
62
+
63
+ def process = container[__method__]
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Gemsmith
4
+ module Configuration
5
+ module Enhancers
6
+ # Adds gem specific roots to existing content.
7
+ class TemplateRoot
8
+ def initialize overrides = Pathname(__dir__).join("../../templates")
9
+ @overrides = Array overrides
10
+ end
11
+
12
+ def call(content) = content.add_template_roots(overrides)
13
+
14
+ private
15
+
16
+ attr_reader :overrides
17
+ end
18
+ end
19
+ end
20
+ end
@@ -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,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dry-container"
4
+ require "open3"
5
+ require "spek"
6
+
7
+ module Gemsmith
8
+ # Provides a global gem container for injection into other objects.
9
+ module Container
10
+ extend Dry::Container::Mixin
11
+
12
+ config.registry = ->(container, key, value, _options) { container[key.to_s] = value }
13
+
14
+ merge Rubysmith::Container
15
+
16
+ register(:configuration) { Gemsmith::Configuration::Loader.call }
17
+ register(:specification) { Spek::Loader.call "#{__dir__}/../../gemsmith.gemspec" }
18
+ register(:environment) { ENV }
19
+ register(:executor) { Open3 }
20
+ register(:kernel) { Kernel }
21
+ end
22
+ end
@@ -0,0 +1,50 @@
1
+ Gem::Specification.new do |spec|
2
+ spec.name = "<%= configuration.project_name %>"
3
+ spec.version = "<%= configuration.project_version %>"
4
+ spec.authors = ["<%= configuration.author_name %>"]
5
+ spec.email = ["<%= configuration.author_email %>"]
6
+ spec.homepage = "<%= configuration.computed_project_url_home %>"
7
+ spec.summary = ""
8
+ spec.license = "<%= configuration.license_label_version %>"
9
+
10
+ spec.metadata = {
11
+ "bug_tracker_uri" => "<%= configuration.computed_project_url_issues %>",
12
+ "changelog_uri" => "<%= configuration.computed_project_url_versions %>",
13
+ "documentation_uri" => "<%= configuration.computed_project_url_home %>",
14
+ "label" => "<%= configuration.project_label %>",
15
+ "rubygems_mfa_required" => "true",
16
+ "source_code_uri" => "<%= configuration.computed_project_url_source %>"
17
+ }
18
+
19
+ <% if configuration.build_security %>
20
+ spec.signing_key = Gem.default_key_path
21
+ spec.cert_chain = [Gem.default_cert_path]
22
+ <% end %>
23
+
24
+ spec.required_ruby_version = "~> <%= RUBY_VERSION[/\d+\.\d+/] %>"
25
+ <% if configuration.build_cli %>
26
+ spec.add_dependency "dry-container", "~> 0.9"
27
+ <% end %>
28
+ <% if configuration.build_cli %>
29
+ spec.add_dependency "pastel", "~> 0.8"
30
+ <% end %>
31
+ <% if configuration.build_refinements %>
32
+ spec.add_dependency "refinements", "~> 9.2"
33
+ <% end %>
34
+ <% if configuration.build_cli %>
35
+ spec.add_dependency "runcom", "~> 8.2"
36
+ <% end %>
37
+ <% if configuration.build_cli %>
38
+ spec.add_dependency "spek", "~> 0.0"
39
+ <% end %>
40
+ <% if configuration.build_zeitwerk %>
41
+ spec.add_dependency "zeitwerk", "~> 2.5"
42
+ <% end %>
43
+
44
+ <% if configuration.build_cli %>
45
+ spec.bindir = "exe"
46
+ spec.executables << "<%= configuration.project_name %>"
47
+ <% end %>
48
+ spec.extra_rdoc_files = Dir["README*", "LICENSE*"]
49
+ spec.files = Dir["*.gemspec", "lib/**/*"]
50
+ end
@@ -0,0 +1,5 @@
1
+ #! /usr/bin/env ruby
2
+
3
+ require "<%= configuration.project_name %>"
4
+
5
+ <%= configuration.project_namespaced_class %>::CLI::Shell.new.call ARGV
@@ -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,59 @@
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],
13
+ client: Parser::CLIENT,
14
+ container: Container
15
+ @configuration = configuration
16
+ @client = client
17
+ @container = container
18
+ end
19
+
20
+ def call arguments = []
21
+ client.banner = specification.labeled_summary
22
+ client.separator "\nUSAGE:\n"
23
+ collate
24
+ client.parse arguments
25
+ configuration
26
+ end
27
+
28
+ private
29
+
30
+ attr_reader :configuration, :client, :container
31
+
32
+ def collate = private_methods.sort.grep(/add_/).each { |method| __send__ method }
33
+
34
+ def add_config
35
+ client.on "-c",
36
+ "--config ACTION",
37
+ %i[edit view],
38
+ "Manage gem configuration: edit or view." do |action|
39
+ configuration.merge! action_config: action
40
+ end
41
+ end
42
+
43
+ def add_version
44
+ client.on "-v", "--version", "Show gem version." do
45
+ configuration.merge! action_version: true
46
+ end
47
+ end
48
+
49
+ def add_help
50
+ client.on "-h", "--help", "Show this message." do
51
+ configuration.merge! action_help: true
52
+ end
53
+ end
54
+
55
+ def specification = container[__method__]
56
+ end
57
+ end
58
+ end
59
+ <% end %>
@@ -0,0 +1,40 @@
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 { specification.labeled_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 specification = container[__method__]
36
+
37
+ def logger = container[__method__]
38
+ end
39
+ end
40
+ <% 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 %>
@@ -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,37 @@
1
+ require "dry-container"
2
+ require "logger"
3
+ require "pastel"
4
+ require "spek"
5
+
6
+ <% namespace do %>
7
+ # Provides a global gem container for injection into other objects.
8
+ module Container
9
+ extend Dry::Container::Mixin
10
+
11
+ register(:configuration) { Configuration::Loader.call }
12
+ register(:specification) { Spek::Loader.call "#{__dir__}/<%= Array.new(2 + configuration.project_levels, "../").join %><%= configuration.project_name %>.gemspec" }
13
+ register(:colorizer) { Pastel.new enabled: $stdout.tty? }
14
+ register(:kernel) { Kernel }
15
+
16
+ register :log_colors do
17
+ {
18
+ "DEBUG" => self[:colorizer].white.detach,
19
+ "INFO" => self[:colorizer].green.detach,
20
+ "WARN" => self[:colorizer].yellow.detach,
21
+ "ERROR" => self[:colorizer].red.detach,
22
+ "FATAL" => self[:colorizer].white.bold.on_red.detach,
23
+ "ANY" => self[:colorizer].white.bold.detach
24
+ }
25
+ end
26
+
27
+ register :logger do
28
+ Logger.new $stdout,
29
+ level: Logger.const_get(ENV.fetch("LOG_LEVEL", "INFO")),
30
+ formatter: (
31
+ lambda do |severity, _at, _name, message|
32
+ self[:log_colors][severity].call "#{message}\n"
33
+ end
34
+ )
35
+ end
36
+ end
37
+ <% end %>
@@ -4,6 +4,6 @@
4
4
  NAME = "<%= config.dig(:gem, :name) %>"
5
5
  LABEL = "<%= config.dig(:gem, :label) %>"
6
6
  VERSION = "0.1.0"
7
- VERSION_LABEL = "#{LABEL} #{VERSION}"
7
+ VERSION_LABEL = "#{LABEL} #{VERSION}".freeze
8
8
  end
9
9
  <% end %>
@@ -0,0 +1,24 @@
1
+ require "spec_helper"
2
+
3
+ RSpec.describe <%= configuration.project_namespaced_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_namespaced_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