create-ruby-app 1.2.0 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "open3"
4
+
5
+ module CreateRubyApp
6
+ module Actions
7
+ class NoRubyImplementationFoundError < StandardError; end
8
+
9
+ class SetRubyImplementation
10
+ def initialize(app)
11
+ @app = app
12
+ end
13
+
14
+ def self.call(app)
15
+ new(app).call
16
+ end
17
+
18
+ def call
19
+ return app if app.version
20
+
21
+ RUBY_IMPLEMENTATIONS.find do |ruby_implementation|
22
+ stdout, status = fetch_ruby_implementation(ruby_implementation)
23
+
24
+ if status.success? && !stdout.empty?
25
+ app.version = transform_output_to_ruby_version(stdout)
26
+ return app
27
+ end
28
+ end
29
+
30
+ raise NoRubyImplementationFoundError,
31
+ "No version of Ruby is found or provided"
32
+ end
33
+
34
+ def transform_output_to_ruby_version(output)
35
+ output.strip.split(" ")[0..1].join("-")
36
+ end
37
+
38
+ def fetch_ruby_implementation(ruby_implementation)
39
+ stdout, _stderr, status = Open3.capture3("#{ruby_implementation} -v")
40
+ [stdout, status]
41
+ end
42
+
43
+ RUBY_IMPLEMENTATIONS = %w[
44
+ ruby
45
+ truffleruby
46
+ jruby
47
+ mruby
48
+ rubinius
49
+ ].freeze
50
+
51
+ attr_accessor :app
52
+ end
53
+ end
54
+ end
@@ -7,8 +7,8 @@ module CreateRubyApp
7
7
  def initialize(
8
8
  name: "app",
9
9
  gems: [],
10
- version: RUBY_VERSION,
11
- logger: Logger.new(STDOUT)
10
+ version: nil,
11
+ logger: Logger.new($stdout)
12
12
  )
13
13
  @name = name
14
14
  @gems = gems
@@ -18,19 +18,22 @@ module CreateRubyApp
18
18
 
19
19
  def run!
20
20
  with_logger("Creating directories...", Actions::CreateDirectories)
21
+ with_logger(
22
+ "Setting Ruby implementations...",
23
+ Actions::SetRubyImplementation
24
+ )
21
25
  with_logger("Generating files...", Actions::GenerateFiles)
22
26
  with_logger("Making script executable...", Actions::MakeScriptExecutable)
23
27
  with_logger("Installing gems...", Actions::InstallGems)
24
- with_logger("Happy hacking!", Actions::NullAction)
28
+ with_logger("Happy hacking!", ->(_) {})
25
29
  end
26
30
 
27
31
  def classify_name
28
32
  name.split("_").collect(&:capitalize).join
29
33
  end
30
34
 
31
- attr_reader :name, :gems, :version, :logger
32
-
33
- RUBY_VERSION = "ruby-2.7.1"
35
+ attr_reader :name, :gems, :logger
36
+ attr_accessor :version
34
37
 
35
38
  private
36
39
 
@@ -1,52 +1,71 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "thor"
3
+ require "dry/cli"
4
4
 
5
5
  module CreateRubyApp
6
- class CLI < Thor
7
- def self.exit_on_failure?
8
- true
9
- end
6
+ module Commands
7
+ extend Dry::CLI::Registry
8
+
9
+ class Version < Dry::CLI::Command
10
+ desc "Print the current version and exit"
10
11
 
11
- desc "version", "Print the current version and exit"
12
- map %w[--version -v] => :version
13
- def version
14
- say VERSION
12
+ def call(*)
13
+ puts VERSION
14
+ end
15
15
  end
16
16
 
17
- desc "new [NAME] [--ruby RUBY] [--gems GEMS]", "Generate new app"
18
- long_desc <<~DESC
19
- `create-ruby-app new NAME` will generate an app with the provided name.
20
-
21
- Examples:
22
- \x5$ create-ruby-app new my_app
23
- \x5$ create-ruby-app new -g sinatra,sequel -r ruby-2.6.0 web_app
24
- \x5$ create-ruby-app new --ruby jruby-2.9.6.0 my_app
25
- DESC
26
- method_option(
27
- :ruby,
28
- aliases: "-r",
29
- desc: "Specify which Ruby version to use for the project",
30
- default: App::RUBY_VERSION
31
- )
32
- method_option(
33
- :gems,
34
- aliases: "-g",
35
- desc: "Specify which gems to add to the project",
36
- default: ""
37
- )
38
- def new(name)
39
- App.new(
40
- name: replace_dashes_with_underscores(name),
41
- gems: options[:gems].split(","),
42
- version: options[:ruby]
43
- ).run!
17
+ class New < Dry::CLI::Command
18
+ desc "Generate new Ruby application"
19
+
20
+ argument :name, required: true, desc: "Application name"
21
+
22
+ option(
23
+ :ruby,
24
+ aliases: ["-r"],
25
+ desc: "Specify which Ruby version to use for the project"
26
+ )
27
+ option(
28
+ :gems,
29
+ aliases: ["-g"],
30
+ desc: "Specify which gems to add to the project",
31
+ default: ""
32
+ )
33
+
34
+ example [
35
+ "my_app",
36
+ "-r ruby-3.4.5 my_app",
37
+ "-g sinatra,sequel web_app",
38
+ "--ruby jruby-2.9.6.0 --gems rspec,pry api_service"
39
+ ]
40
+
41
+ def call(name:, **options)
42
+ App.new(
43
+ name: replace_dashes_with_underscores(name),
44
+ gems: parse_gems(options[:gems]),
45
+ version: options[:ruby]
46
+ ).run!
47
+ end
48
+
49
+ private
50
+
51
+ def replace_dashes_with_underscores(name)
52
+ name.tr("-", "_")
53
+ end
54
+
55
+ def parse_gems(gems_string)
56
+ return [] if gems_string.nil? || gems_string.strip.empty?
57
+
58
+ gems_string.split(",").map(&:strip).reject(&:empty?)
59
+ end
44
60
  end
45
61
 
46
- private
62
+ register "version", Version, aliases: ["--version", "-v"]
63
+ register "new", New
64
+ end
47
65
 
48
- def replace_dashes_with_underscores(name)
49
- name.tr("-", "_")
66
+ class CLI
67
+ def self.call
68
+ Dry::CLI.new(Commands).call
50
69
  end
51
70
  end
52
71
  end
@@ -7,6 +7,6 @@ group :test, :development do
7
7
  end
8
8
  <% unless locals[:gems].empty? %>
9
9
 
10
- <%= locals[:gems].map {|gem| "gem \"#{gem}\"" }.join("\n") %>
10
+ <%= locals[:gems] %>
11
11
 
12
12
  <% end %>
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module CreateRubyApp
4
- VERSION = "1.2.0"
4
+ VERSION = "1.4.0"
5
5
  end
@@ -7,7 +7,7 @@ require_relative "create_ruby_app/actions/create_directories"
7
7
  require_relative "create_ruby_app/actions/generate_files"
8
8
  require_relative "create_ruby_app/actions/install_gems"
9
9
  require_relative "create_ruby_app/actions/make_script_executable"
10
- require_relative "create_ruby_app/actions/null_action"
10
+ require_relative "create_ruby_app/actions/set_ruby_implementation"
11
11
 
12
12
  module CreateRubyApp
13
13
  class CreateRubyApp
@@ -0,0 +1,172 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../spec_helper"
4
+ require "tmpdir"
5
+ require "fileutils"
6
+
7
+ RSpec.describe "App creation" do
8
+ let(:temp_dir) { Dir.mktmpdir }
9
+ let(:original_dir) { Dir.pwd }
10
+
11
+ around do |example|
12
+ Dir.chdir(original_dir)
13
+ Dir.chdir(temp_dir) do
14
+ example.run
15
+ end
16
+
17
+ FileUtils.rm_rf(temp_dir) if Dir.exist?(temp_dir)
18
+ end
19
+
20
+ describe "creating an app with just a name" do
21
+ let(:app_name) { "test_app" }
22
+ let(:app) do
23
+ CreateRubyApp::App.new(
24
+ name: app_name,
25
+ gems: [],
26
+ version: nil,
27
+ logger: Logger.new(IO::NULL)
28
+ )
29
+ end
30
+
31
+ it "creates a complete app structure with the detected Ruby version" do
32
+ app.run!
33
+
34
+ # Check directory structure
35
+ expect(Dir.exist?(app_name)).to be(true)
36
+ expect(Dir.exist?("#{app_name}/bin")).to be(true)
37
+ expect(Dir.exist?("#{app_name}/lib")).to be(true)
38
+ expect(Dir.exist?("#{app_name}/spec")).to be(true)
39
+
40
+ # Check if main files exist
41
+ expect(File.exist?("#{app_name}/Gemfile")).to be(true)
42
+ expect(File.exist?("#{app_name}/.ruby-version")).to be(true)
43
+ expect(File.exist?("#{app_name}/lib/#{app_name}.rb")).to be(true)
44
+ expect(File.exist?("#{app_name}/bin/#{app_name}")).to be(true)
45
+ expect(File.exist?("#{app_name}/spec/spec_helper.rb")).to be(true)
46
+
47
+ # Check that a Ruby version was detected and set
48
+ expect(app.version).to match(
49
+ /^(ruby|jruby|truffleruby|mruby|rubinius)-\d+\.\d+/
50
+ )
51
+
52
+ # Check that the `.ruby-version` file contains the detected version
53
+ ruby_version_content = File.read("#{app_name}/.ruby-version")
54
+ expect(ruby_version_content.strip).to eq(app.version)
55
+
56
+ # Check if the main library file has the correct module and
57
+ # class structure
58
+ lib_content = File.read("#{app_name}/lib/#{app_name}.rb")
59
+ expect(lib_content).to include("module TestApp")
60
+ expect(lib_content).to include("class TestApp")
61
+
62
+ # Check if `spec_helper.rb` references the app correctly
63
+ spec_helper_content = File.read("#{app_name}/spec/spec_helper.rb")
64
+ expect(spec_helper_content).to include(
65
+ "require_relative \"../lib/#{app_name}\""
66
+ )
67
+
68
+ # Check that the executable script exists and is executable
69
+ expect(File.executable?("#{app_name}/bin/#{app_name}")).to be(true)
70
+
71
+ # Check that `Gemfile` has a basic structure
72
+ gemfile_content = File.read("#{app_name}/Gemfile")
73
+ expect(gemfile_content).to include('source "https://rubygems.org"')
74
+ end
75
+ end
76
+
77
+ describe "creating an app with a specified Ruby version" do
78
+ let(:app_name) { "custom_app" }
79
+ let(:specified_version) { "ruby-3.1.0" }
80
+ let(:app) do
81
+ CreateRubyApp::App.new(
82
+ name: app_name,
83
+ gems: %w[rspec pry],
84
+ version: specified_version,
85
+ logger: Logger.new(IO::NULL)
86
+ )
87
+ end
88
+
89
+ it "creates the app structure with the specified Ruby version and gems" do
90
+ app.run!
91
+
92
+ # Check directory structure
93
+ expect(Dir.exist?(app_name)).to be(true)
94
+ expect(Dir.exist?("#{app_name}/bin")).to be(true)
95
+ expect(Dir.exist?("#{app_name}/lib")).to be(true)
96
+ expect(Dir.exist?("#{app_name}/spec")).to be(true)
97
+
98
+ # Check that the version remains as specified
99
+ expect(app.version).to eq(specified_version)
100
+
101
+ # Check that the `.ruby-version` file contains the specified version
102
+ ruby_version_content = File.read("#{app_name}/.ruby-version")
103
+ expect(ruby_version_content.strip).to eq(specified_version)
104
+
105
+ # Check if `Gemfile` includes the specified gems
106
+ gemfile_content = File.read("#{app_name}/Gemfile")
107
+ expect(gemfile_content).to include('source "https://rubygems.org"')
108
+ expect(gemfile_content).to include('gem "pry"')
109
+ expect(gemfile_content).to include('gem "rspec"')
110
+
111
+ # Check if the main library file has the correct module and
112
+ # class structure
113
+ lib_content = File.read("#{app_name}/lib/#{app_name}.rb")
114
+ expect(lib_content).to include("module CustomApp")
115
+ expect(lib_content).to include("class CustomApp")
116
+ end
117
+ end
118
+
119
+ describe "creating an app with dashes in name" do
120
+ let(:app_name) { "my-cool-app" }
121
+ let(:normalized_name) { "my_cool_app" }
122
+ let(:app) do
123
+ CreateRubyApp::App.new(
124
+ name: normalized_name,
125
+ gems: [],
126
+ version: "ruby-3.2.0",
127
+ logger: Logger.new(IO::NULL)
128
+ )
129
+ end
130
+
131
+ it "normalizes the name correctly in files and classes" do
132
+ app.run!
133
+
134
+ # Check if the directory uses the normalized name
135
+ expect(Dir.exist?(normalized_name)).to be true
136
+
137
+ # Check if the main library file has the correct class name
138
+ lib_content = File.read("#{normalized_name}/lib/#{normalized_name}.rb")
139
+ expect(lib_content).to include("module MyCoolApp")
140
+ expect(lib_content).to include("class MyCoolApp")
141
+
142
+ # Check if `spec_helper.rb` references the correct file
143
+ spec_helper_content = File.read("#{normalized_name}/spec/spec_helper.rb")
144
+ expect(spec_helper_content).to include("require_relative \"../lib/#{normalized_name}\"")
145
+ end
146
+ end
147
+
148
+ describe "error handling" do
149
+ let(:app_name) { "error_test_app" }
150
+
151
+ it "raises an appropriate error when no Ruby implementation is found" do
152
+ app = CreateRubyApp::App.new(
153
+ name: app_name,
154
+ gems: [],
155
+ version: nil,
156
+ logger: Logger.new(IO::NULL)
157
+ )
158
+
159
+ # Mock all Ruby implementations to fail
160
+ %w[ruby truffleruby jruby mruby rubinius].each do |impl|
161
+ allow(Open3).to receive(:capture3).with("#{impl} -v").and_return(
162
+ ["", "", instance_double(Process::Status, success?: false)]
163
+ )
164
+ end
165
+
166
+ expect { app.run! }.to raise_error(
167
+ CreateRubyApp::Actions::NoRubyImplementationFoundError,
168
+ "No version of Ruby is found or provided"
169
+ )
170
+ end
171
+ end
172
+ end
@@ -4,7 +4,7 @@ require_relative "../../../spec_helper"
4
4
 
5
5
  RSpec.describe CreateRubyApp::Actions::CreateDirectories do
6
6
  describe ".call" do
7
- let(:app) { instance_double("app", name: "foo_bar") }
7
+ let(:app) { instance_double(CreateRubyApp::App, name: "foo_bar") }
8
8
  let(:action) { described_class }
9
9
 
10
10
  it "creates the directories" do
@@ -5,7 +5,7 @@ require_relative "../../../spec_helper"
5
5
  RSpec.describe CreateRubyApp::Actions::GenerateFiles do
6
6
  describe "#generate_gemfile" do
7
7
  context "when no additional gems are added" do
8
- let(:app) { instance_double("app", gems: []) }
8
+ let(:app) { instance_double(CreateRubyApp::App, gems: []) }
9
9
  let(:action) { described_class.new(app) }
10
10
  let(:gemfile) do
11
11
  <<~GEMFILE
@@ -25,7 +25,9 @@ RSpec.describe CreateRubyApp::Actions::GenerateFiles do
25
25
  end
26
26
 
27
27
  context "when additional gems are added" do
28
- let(:app) { instance_double("app", gems: %w[sinatra sqlite]) }
28
+ let(:app) do
29
+ instance_double(CreateRubyApp::App, gems: %w[sinatra sqlite])
30
+ end
29
31
  let(:action) { described_class.new(app) }
30
32
  let(:gemfile) do
31
33
  <<~GEMFILE
@@ -51,7 +53,10 @@ RSpec.describe CreateRubyApp::Actions::GenerateFiles do
51
53
  describe "#generate_ruby_version_file" do
52
54
  context "when no version is specified" do
53
55
  let(:app) do
54
- instance_double("app", version: CreateRubyApp::App::RUBY_VERSION)
56
+ instance_double(
57
+ CreateRubyApp::App,
58
+ version: "ruby-2.7.1"
59
+ )
55
60
  end
56
61
  let(:action) { described_class.new(app) }
57
62
 
@@ -59,7 +64,7 @@ RSpec.describe CreateRubyApp::Actions::GenerateFiles do
59
64
  end
60
65
 
61
66
  context "when a version is specified" do
62
- let(:app) { instance_double("app", version: "ruby-2.5.0") }
67
+ let(:app) { instance_double(CreateRubyApp::App, version: "ruby-2.5.0") }
63
68
  let(:action) { described_class.new(app) }
64
69
 
65
70
  it { expect(action.ruby_version_file).to eq("ruby-2.5.0") }
@@ -68,7 +73,7 @@ RSpec.describe CreateRubyApp::Actions::GenerateFiles do
68
73
 
69
74
  describe "#generate_lib_file" do
70
75
  context "when generating a lib file" do
71
- let(:app) { instance_double("app") }
76
+ let(:app) { instance_double(CreateRubyApp::App) }
72
77
  let(:action) { described_class.new(app) }
73
78
  let(:lib_file) do
74
79
  <<~LIB_FILE
@@ -91,7 +96,7 @@ RSpec.describe CreateRubyApp::Actions::GenerateFiles do
91
96
 
92
97
  describe "#generate_spec_helper_file" do
93
98
  context "when generating a spec helper file" do
94
- let(:app) { instance_double("app", name: "this_is_an_app") }
99
+ let(:app) { instance_double(CreateRubyApp::App, name: "this_is_an_app") }
95
100
  let(:action) { described_class.new(app) }
96
101
  let(:spec_helper) do
97
102
  <<~SPEC_HELPER
@@ -118,7 +123,7 @@ RSpec.describe CreateRubyApp::Actions::GenerateFiles do
118
123
 
119
124
  describe "#generate_script_file" do
120
125
  context "when generating an executable script file" do
121
- let(:app) { instance_double("app") }
126
+ let(:app) { instance_double(CreateRubyApp::App) }
122
127
  let(:action) { described_class.new(app) }
123
128
  let(:script_file) do
124
129
  <<~SCRIPT_FILE
@@ -4,7 +4,7 @@ require_relative "../../../spec_helper"
4
4
 
5
5
  RSpec.describe CreateRubyApp::Actions::InstallGems do
6
6
  describe ".call" do
7
- let(:app) { instance_double("app", name: "foo_bar") }
7
+ let(:app) { instance_double(CreateRubyApp::App, name: "foo_bar") }
8
8
  let(:action) { described_class }
9
9
 
10
10
  it "installs the gems" do
@@ -4,7 +4,7 @@ require_relative "../../../spec_helper"
4
4
 
5
5
  RSpec.describe CreateRubyApp::Actions::MakeScriptExecutable do
6
6
  describe ".call" do
7
- let(:app) { instance_double("app", name: "foo_bar") }
7
+ let(:app) { instance_double(CreateRubyApp::App, name: "foo_bar") }
8
8
  let(:action) { described_class }
9
9
 
10
10
  it "creates the directories" do