console_buddy 0.1.1

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,216 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'pathname'
4
+ require 'active_support'
5
+ require 'active_support/all'
6
+
7
+ require_relative "console_buddy/method_store"
8
+ require_relative "console_buddy/augment"
9
+ require_relative "console_buddy/base"
10
+ require_relative "console_buddy/helpers"
11
+ require_relative "console_buddy/irb"
12
+ require_relative "console_buddy/version"
13
+
14
+ require_relative "console_buddy/one_off_job"
15
+ require_relative "console_buddy/job"
16
+
17
+ # Only load the one-off job classes if the gems are installed
18
+ #
19
+ begin
20
+ require 'sidekiq'
21
+ require_relative "console_buddy/jobs/sidekiq"
22
+ rescue LoadError
23
+ # puts "Sidekiq gem not installed, skipping sidekiq job integration."
24
+ end
25
+
26
+ begin
27
+ require 'resque'
28
+ require_relative "console_buddy/jobs/resque"
29
+ rescue LoadError
30
+ # puts "Resque gem not installed, skipping resque job integration."
31
+ end
32
+
33
+ begin
34
+ require 'activejob'
35
+ require_relative "console_buddy/jobs/active_job"
36
+ rescue LoadError
37
+ # puts "ActiveJob gem not installed, skipping active job integration."
38
+ end
39
+
40
+ module ConsoleBuddy
41
+ class << self
42
+ attr_accessor :verbose_console, :allowed_envs, :use_in_debuggers, :ignore_startup_errors, :use_in_tests, :one_off_job_service_type
43
+
44
+ def store
45
+ @store ||= ::ConsoleBuddy::MethodStore.new
46
+ end
47
+
48
+ def start!
49
+ # Initialize the default values
50
+ set_config_defaults
51
+ # Check if there is a .console_buddy/config file
52
+ load_console_buddy_config
53
+
54
+ # Only start the buddy in the allowed environments. e.g. development, test
55
+ return if !allowed_env?
56
+
57
+ # Do not start the buddy in test environment if use_in_tests is false
58
+ return if test? && !use_in_tests
59
+
60
+ begin
61
+ load_console_buddy_files
62
+ augment_classes
63
+ augment_console
64
+ start_buddy_in_irb
65
+ start_buddy_in_rails
66
+ start_buddy_in_byebug
67
+ puts "ConsoleBuddy session started! Debugger: #{use_in_debuggers} | Test: #{current_env}" if verbose_console
68
+ rescue ::StandardError => error
69
+ puts "ConsoleBuddy encountered an during startup. [Error]: #{error.message}" if ignore_startup_errors
70
+ end
71
+ end
72
+
73
+ def load_byebug!
74
+ start_buddy_in_byebug
75
+ end
76
+
77
+ private
78
+
79
+ def set_config_defaults
80
+ @verbose_console = true
81
+ @use_in_tests = false
82
+ @use_in_debuggers = false
83
+ @ignore_startup_errors = false
84
+ @allowed_envs = %w[development test]
85
+ @one_off_job_service_type = :inline
86
+ end
87
+
88
+ # Only start the buddy in the allowed environments
89
+ def allowed_env?
90
+ if current_env.present?
91
+ can_start = allowed_envs.include?(current_env)
92
+ if verbose_console && can_start
93
+ puts "ConsoleBuddy is starting in #{current_env} environment."
94
+ end
95
+ return can_start
96
+ end
97
+
98
+ true
99
+ end
100
+
101
+ def test?
102
+ current_env == 'test'
103
+ end
104
+
105
+ def current_env
106
+ ENV['RAILS_ENV'] == 'test' || ENV['RACK_ENV']
107
+ end
108
+
109
+ # Loads the .console_buddy/config file if present
110
+ def load_console_buddy_config
111
+ config_path = Pathname.new(File.join(Dir.pwd, '.console_buddy', 'config.rb'))
112
+ if config_path.exist? && config_path.file?
113
+ require config_path.to_s
114
+ else
115
+ puts ".console_buddy/config file not found."
116
+ end
117
+ end
118
+
119
+ # Loads all the files in the .console_buddy folder
120
+ # .console_buddy folder should be in the root of the project
121
+ def load_console_buddy_files
122
+ console_buddy_path = Pathname.new(File.join(Dir.pwd, '.console_buddy'))
123
+ if console_buddy_path.exist? && console_buddy_path.directory?
124
+ console_buddy_path.each_child do |file|
125
+ next unless file.file?
126
+ require file.to_s
127
+ end
128
+ else
129
+ puts ".console_buddy folder not found in the root of the project."
130
+ end
131
+ end
132
+
133
+ # Augment the classes with the methods defined in the store
134
+ def augment_classes
135
+ ::ConsoleBuddy.store.augment_helper_methods.each do |klass, methods|
136
+ begin
137
+ klass.constantize
138
+ rescue NameError
139
+ puts "Class #{klass} not found. Skipping..." if verbose_console
140
+ next
141
+ end
142
+
143
+ methods.each do |method|
144
+ case method[:method_type]
145
+ when :instance
146
+ klass.constantize.define_method(method[:method_name]) do |*args|
147
+ instance_exec(*args, &method[:block])
148
+ end
149
+ when :class
150
+ klass.constantize.define_singleton_method(method[:method_name]) do |*args|
151
+ instance_exec(*args, &method[:block])
152
+ end
153
+ else
154
+ next
155
+ end
156
+ end
157
+ end
158
+ end
159
+
160
+ # Augment the console with the methods defined in the store
161
+ def augment_console
162
+ ::ConsoleBuddy.store.console_method.each do |method_name, block|
163
+ ::ConsoleBuddy::IRB.define_method(method_name, block)
164
+ end
165
+ end
166
+
167
+ # This will add the buddy methods to the IRB session
168
+ # So that they can be called without instantiating the `ConsoleBuddy::Base` class
169
+ def start_buddy_in_irb
170
+ if defined? IRB::ExtendCommandBundle
171
+ IRB::ExtendCommandBundle.include(ConsoleBuddy::IRB)
172
+ require 'progress_bar/core_ext/enumerable_with_progress'
173
+ end
174
+ end
175
+
176
+ # This will add the buddy methods to the Rails console
177
+ # So that they can be called without instantiating the `ConsoleBuddy::Base` class
178
+ def start_buddy_in_rails
179
+ if defined? Rails::ConsoleMethods
180
+ Rails::ConsoleMethods.include(ConsoleBuddy::IRB)
181
+ require 'progress_bar/core_ext/enumerable_with_progress'
182
+ end
183
+ end
184
+
185
+ # This will add the buddy methods to the Byebug console
186
+ # TODO: Add support for Pry
187
+ def start_buddy_in_byebug
188
+ return if !use_in_debuggers
189
+
190
+ if defined?(Byebug)
191
+ byebug_path = Pathname.new(File.join(__dir__, 'console_buddy', 'byebug'))
192
+
193
+ if byebug_path.exist? && byebug_path.directory?
194
+ byebug_path.each_child do |file|
195
+ next unless file.file?
196
+ require file.to_s
197
+ end
198
+ end
199
+
200
+ [
201
+ ConsoleBuddy::Byebug::HelloCommand,
202
+ ConsoleBuddy::Byebug::BuddyCommand,
203
+ ].each do |command_class|
204
+ ::Byebug.const_set(
205
+ command_class.name.split('::').last,
206
+ command_class,
207
+ )
208
+ end
209
+ end
210
+ end
211
+ end
212
+ end
213
+
214
+ require_relative "console_buddy/initializers/byebug"
215
+ require_relative "console_buddy/initializers/rails"
216
+ require_relative "console_buddy/initializers/rspec"
@@ -0,0 +1,65 @@
1
+ require 'spec_helper'
2
+ require_relative '../../lib/console_buddy/augment'
3
+ require_relative '../../lib/console_buddy/method_store'
4
+
5
+ RSpec.describe ConsoleBuddy::Augment do
6
+ describe '.define' do
7
+ it 'executes the given block in the context of ConsoleBuddy::Augment::DSL' do
8
+ dsl_instance = instance_double(ConsoleBuddy::Augment::DSL)
9
+ allow(ConsoleBuddy::Augment::DSL).to receive(:new).and_return(dsl_instance)
10
+ expect(dsl_instance).to receive(:instance_eval)
11
+
12
+ described_class.define {}
13
+ end
14
+
15
+ it 'does nothing if no block is given' do
16
+ expect { described_class.define }.not_to raise_error
17
+ end
18
+ end
19
+
20
+ describe ConsoleBuddy::Augment::DSL do
21
+ let(:klass) { Class.new }
22
+ let(:method_name) { :test_method }
23
+ let(:new_method_name) { :new_test_method }
24
+ let(:block) { proc { 'test' } }
25
+ let(:store) { ConsoleBuddy::MethodStore.new }
26
+
27
+ before do
28
+ allow(ConsoleBuddy).to receive(:store).and_return(store)
29
+ end
30
+
31
+ describe '#method' do
32
+ it 'stores the method definition in the augment_helper_methods store' do
33
+ subject.method(klass, method_name, &block)
34
+ expect(store.augment_helper_methods[klass.to_s]).to include(
35
+ method_name: method_name,
36
+ block: block,
37
+ method_type: :instance
38
+ )
39
+ end
40
+
41
+ it 'raises an error for invalid method type' do
42
+ expect {
43
+ subject.method(klass, method_name, type: :invalid, &block)
44
+ }.to raise_error(ConsoleBuddy::Augment::DSL::InvalidTypeError, "Invalid method type. Must be either :instance or :class")
45
+ end
46
+ end
47
+
48
+ describe '#method_alias' do
49
+ it 'stores the method alias definition in the augment_helper_methods store' do
50
+ subject.method_alias(klass, method_name, new_method_name)
51
+ expect(store.augment_helper_methods[klass.to_s]).to include(
52
+ method_name: new_method_name,
53
+ block: an_instance_of(Proc),
54
+ method_type: :instance
55
+ )
56
+ end
57
+
58
+ it 'raises an error for invalid method type' do
59
+ expect {
60
+ subject.method_alias(klass, method_name, new_method_name, type: :invalid)
61
+ }.to raise_error(ConsoleBuddy::Augment::DSL::InvalidTypeError, "Invalid method type. Must be either :instance or :class")
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,74 @@
1
+ require 'spec_helper'
2
+ require 'csv'
3
+ require 'fileutils'
4
+ require 'tempfile'
5
+ require_relative '../../lib/console_buddy/csv'
6
+
7
+ RSpec.describe ConsoleBuddy::CSV do
8
+ include ConsoleBuddy::CSV
9
+
10
+ describe '#generate_csv' do
11
+ let(:headers) { ['Name', 'Age'] }
12
+ let(:rows) { [['Alice', "30"], ['Bob', "25"]] }
13
+ let(:dir) { 'tmp' }
14
+ let(:filename) { 'test_csv' }
15
+ let(:file_path) { File.join(dir, "#{filename}.csv") }
16
+
17
+ before do
18
+ FileUtils.mkdir_p(dir)
19
+ end
20
+
21
+ after do
22
+ FileUtils.rm_rf(dir)
23
+ end
24
+
25
+ it 'creates a CSV file with the given headers and rows' do
26
+ generate_csv(headers, rows, filename: filename, dir: dir)
27
+ expect(File).to exist(file_path)
28
+
29
+ csv_content = ::CSV.read(file_path)
30
+ expect(csv_content).to eq([headers, *rows])
31
+ end
32
+
33
+ it 'creates the directory if it does not exist' do
34
+ new_dir = 'new_tmp'
35
+ new_file_path = File.join(new_dir, "#{filename}.csv")
36
+
37
+ generate_csv(headers, rows, filename: filename, dir: new_dir)
38
+ expect(File).to exist(new_file_path)
39
+
40
+ csv_content = ::CSV.read(new_file_path)
41
+ expect(csv_content).to eq([headers, *rows])
42
+
43
+ FileUtils.rm_rf(new_dir)
44
+ end
45
+ end
46
+
47
+ describe '#read_csv' do
48
+ let(:headers) { ['Name', 'Age'] }
49
+ let(:rows) { [['Alice', "30"], ['Bob', "25"]] }
50
+ let(:tempfile) { Tempfile.new(['test_csv', '.csv']) }
51
+
52
+ before do
53
+ ::CSV.open(tempfile.path, 'w') do |csv|
54
+ csv << headers
55
+ rows.each { |row| csv << row }
56
+ end
57
+ end
58
+
59
+ after do
60
+ tempfile.close
61
+ tempfile.unlink
62
+ end
63
+
64
+ it 'reads the CSV file and returns the data without headers' do
65
+ data = read_csv(tempfile.path, skip_headers: true)
66
+ expect(data.count).to eq(rows.count)
67
+ end
68
+
69
+ it 'reads the CSV file and returns the data with headers' do
70
+ data = read_csv(tempfile.path, skip_headers: false)
71
+ expect(data).to eq([headers, *rows])
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,36 @@
1
+ require 'spec_helper'
2
+ require_relative '../../lib/console_buddy/helpers'
3
+ require_relative '../../lib/console_buddy/method_store'
4
+
5
+ RSpec.describe ConsoleBuddy::Helpers do
6
+ describe '.define' do
7
+ it 'executes the given block in the context of ConsoleBuddy::Helpers::DSL' do
8
+ dsl_instance = instance_double(ConsoleBuddy::Helpers::DSL)
9
+ allow(ConsoleBuddy::Helpers::DSL).to receive(:new).and_return(dsl_instance)
10
+ expect(dsl_instance).to receive(:instance_eval)
11
+
12
+ described_class.define {}
13
+ end
14
+
15
+ it 'does nothing if no block is given' do
16
+ expect { described_class.define }.not_to raise_error
17
+ end
18
+ end
19
+
20
+ describe ConsoleBuddy::Helpers::DSL do
21
+ let(:method_name) { :call_api }
22
+ let(:block) { proc { |status, message| puts "RESPONSE: { #{status}, #{message} }" } }
23
+ let(:store) { ConsoleBuddy::MethodStore.new }
24
+
25
+ before do
26
+ allow(ConsoleBuddy).to receive(:store).and_return(store)
27
+ end
28
+
29
+ describe '#console_method' do
30
+ it 'stores the method definition in the console_method store' do
31
+ subject.console_method(method_name, &block)
32
+ expect(store.console_method[method_name]).to eq(block)
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,27 @@
1
+ require 'spec_helper'
2
+ require 'webmock/rspec'
3
+ require 'httparty'
4
+ require_relative '../../lib/console_buddy/http_request'
5
+
6
+ RSpec.describe ConsoleBuddy::HttpRequest do
7
+ include ConsoleBuddy::HttpRequest
8
+
9
+ describe '#ping' do
10
+ let(:url) { 'http://example.com' }
11
+ let(:response_body) { { 'message' => 'Hello, world!' }.to_json }
12
+
13
+ before do
14
+ stub_request(:get, url).to_return(body: response_body, headers: { 'Content-Type' => 'application/json' })
15
+ end
16
+
17
+ it 'makes an HTTP GET request to the given URL' do
18
+ expect(HTTParty).to receive(:get).with(url).and_call_original
19
+ ping(url)
20
+ end
21
+
22
+ it 'parses the JSON response' do
23
+ result = ping(url)
24
+ expect(result).to eq('message' => 'Hello, world!')
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,19 @@
1
+ require 'spec_helper'
2
+ require_relative '../../lib/console_buddy/irb'
3
+ require_relative '../../lib/console_buddy/base'
4
+
5
+ RSpec.describe ConsoleBuddy::IRB do
6
+ include ConsoleBuddy::IRB
7
+
8
+ describe '#buddy' do
9
+ it 'returns a new instance of ConsoleBuddy::Base' do
10
+ expect(buddy).to be_an_instance_of(ConsoleBuddy::Base)
11
+ end
12
+ end
13
+
14
+ describe '#console_buddy' do
15
+ it 'is an alias for #buddy' do
16
+ expect(console_buddy).to be_an_instance_of(ConsoleBuddy::Base)
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,100 @@
1
+ require 'spec_helper'
2
+ require 'sidekiq'
3
+ require 'active_job'
4
+ require 'resque'
5
+
6
+ require_relative '../../lib/console_buddy/job'
7
+ require_relative '../../lib/console_buddy/jobs/resque'
8
+ require_relative '../../lib/console_buddy/jobs/sidekiq'
9
+ require_relative '../../lib/console_buddy/jobs/active_job'
10
+
11
+ RSpec.describe ConsoleBuddy::Job do
12
+ let(:args) { ['foo', 'bar'] }
13
+
14
+ describe '#perform' do
15
+ subject { described_class.new }
16
+
17
+ context 'when service_type is :resque' do
18
+ before { allow(ConsoleBuddy::OneOffJob).to receive(:service_type).and_return(:resque) }
19
+
20
+ it 'delegates to ConsoleBuddy::Jobs::Resque.perform' do
21
+ expect(ConsoleBuddy::Jobs::Resque).to receive(:perform).with(*args)
22
+ subject.perform(*args)
23
+ end
24
+ end
25
+
26
+ context 'when service_type is :sidekiq' do
27
+ before { allow(ConsoleBuddy::OneOffJob).to receive(:service_type).and_return(:sidekiq) }
28
+
29
+ it 'delegates to ConsoleBuddy::Jobs::Sidekiq.new.perform' do
30
+ sidekiq_instance = instance_double(ConsoleBuddy::Jobs::Sidekiq)
31
+ allow(ConsoleBuddy::Jobs::Sidekiq).to receive(:new).and_return(sidekiq_instance)
32
+ expect(sidekiq_instance).to receive(:perform).with(*args)
33
+ subject.perform(*args)
34
+ end
35
+ end
36
+
37
+ context 'when service_type is :active_job' do
38
+ before { allow(ConsoleBuddy::OneOffJob).to receive(:service_type).and_return(:active_job) }
39
+
40
+ it 'delegates to ConsoleBuddy::Jobs::ActiveJob.perform' do
41
+ expect(ConsoleBuddy::Jobs::ActiveJob).to receive(:perform).with(*args)
42
+ subject.perform(*args)
43
+ end
44
+ end
45
+
46
+ context 'when service_type is unknown' do
47
+ before { allow(ConsoleBuddy::OneOffJob).to receive(:service_type).and_return(:unknown) }
48
+
49
+ it 'delegates to ConsoleBuddy::OneOffJob.perform' do
50
+ expect(ConsoleBuddy::OneOffJob).to receive(:perform).with(*args)
51
+ subject.perform(*args)
52
+ end
53
+ end
54
+ end
55
+
56
+ describe '.perform_async' do
57
+ context 'when service_type is :resque' do
58
+ before { allow(ConsoleBuddy::OneOffJob).to receive(:service_type).and_return(:resque) }
59
+
60
+ it 'delegates to ConsoleBuddy::Jobs::Resque.perform_later' do
61
+ expect(ConsoleBuddy::Jobs::Resque).to receive(:perform_later).with(*args)
62
+ described_class.perform_async(*args)
63
+ end
64
+ end
65
+
66
+ context 'when service_type is :sidekiq' do
67
+ before { allow(ConsoleBuddy::OneOffJob).to receive(:service_type).and_return(:sidekiq) }
68
+
69
+ it 'delegates to ConsoleBuddy::Jobs::Sidekiq.perform_async' do
70
+ expect(ConsoleBuddy::Jobs::Sidekiq).to receive(:perform_async).with(*args)
71
+ described_class.perform_async(*args)
72
+ end
73
+ end
74
+
75
+ context 'when service_type is :active_job' do
76
+ before { allow(ConsoleBuddy::OneOffJob).to receive(:service_type).and_return(:active_job) }
77
+
78
+ it 'delegates to ConsoleBuddy::Jobs::ActiveJob.perform_later' do
79
+ expect(ConsoleBuddy::Jobs::ActiveJob).to receive(:perform_later).with(*args)
80
+ described_class.perform_async(*args)
81
+ end
82
+ end
83
+
84
+ context 'when service_type is unknown' do
85
+ before { allow(ConsoleBuddy::OneOffJob).to receive(:service_type).and_return(:unknown) }
86
+
87
+ it 'delegates to ConsoleBuddy::OneOffJob.perform' do
88
+ expect(ConsoleBuddy::OneOffJob).to receive(:perform).with(*args)
89
+ described_class.perform_async(*args)
90
+ end
91
+ end
92
+ end
93
+
94
+ describe '.perform_later' do
95
+ it 'delegates to .perform_async' do
96
+ expect(described_class).to receive(:perform_async).with(*args)
97
+ described_class.perform_later(*args)
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,33 @@
1
+ require 'spec_helper'
2
+ require_relative '../../lib/console_buddy/method_store'
3
+
4
+ RSpec.describe ConsoleBuddy::MethodStore do
5
+ describe '#initialize' do
6
+ it 'initializes augment_helper_methods as a hash with default array values' do
7
+ method_store = ConsoleBuddy::MethodStore.new
8
+ expect(method_store.augment_helper_methods).to be_a(Hash)
9
+ expect(method_store.augment_helper_methods.default_proc).not_to be_nil
10
+ expect(method_store.augment_helper_methods[:some_key]).to eq([])
11
+ end
12
+
13
+ it 'initializes console_method as an empty hash' do
14
+ method_store = ConsoleBuddy::MethodStore.new
15
+ expect(method_store.console_method).to be_a(Hash)
16
+ expect(method_store.console_method).to be_empty
17
+ end
18
+ end
19
+
20
+ describe 'attributes' do
21
+ let(:method_store) { ConsoleBuddy::MethodStore.new }
22
+
23
+ it 'allows reading and writing for :augment_helper_methods' do
24
+ method_store.augment_helper_methods[:test] = ['method1']
25
+ expect(method_store.augment_helper_methods[:test]).to eq(['method1'])
26
+ end
27
+
28
+ it 'allows reading and writing for :console_method' do
29
+ method_store.console_method[:test_method] = 'some_method'
30
+ expect(method_store.console_method[:test_method]).to eq('some_method')
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,29 @@
1
+ require 'spec_helper'
2
+ require 'table_print'
3
+ require 'terminal-table'
4
+ require_relative '../../lib/console_buddy/report'
5
+
6
+ RSpec.describe ConsoleBuddy::Report do
7
+ include ConsoleBuddy::Report
8
+
9
+ describe '#table_print' do
10
+ let(:data) { [{ username: 'user1' }, { username: 'user2' }] }
11
+ let(:options) { { columns: [:username] } }
12
+
13
+ it 'prints the table using TablePrint' do
14
+ expect(::TablePrint::Printer).to receive(:table_print).with(data, options)
15
+ table_print(data, options)
16
+ end
17
+ end
18
+
19
+ describe '#table_for' do
20
+ let(:rows) { [['foo', 'bar'], ['baz', 'qux']] }
21
+ let(:headers) { ['col1', 'col2'] }
22
+
23
+ it 'prints the table using Terminal::Table' do
24
+ table = ::Terminal::Table.new(headings: headers, rows: rows)
25
+ expect(::Terminal::Table).to receive(:new).with(headings: headers, rows: rows).and_return(table)
26
+ expect { table_for(rows, headers) }.to output("#{table}\n").to_stdout
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,29 @@
1
+ require "bundler/setup"
2
+ require "byebug"
3
+ require 'rails'
4
+
5
+ require "console_buddy"
6
+
7
+ ENV['RAILS_ENV'] ||= 'test'
8
+
9
+ RSpec.configure do |config|
10
+ config.example_status_persistence_file_path = ".rspec_status"
11
+ config.disable_monkey_patching!
12
+
13
+ config.expect_with :rspec do |c|
14
+ c.syntax = :expect
15
+ end
16
+ end
17
+
18
+
19
+ # RSpec.configure do |config|
20
+ # config.expect_with :rspec do |expectations|
21
+ # expectations.include_chain_clauses_in_custom_matcher_descriptions = true
22
+ # end
23
+
24
+ # config.mock_with :rspec do |mocks|
25
+ # mocks.verify_partial_doubles = true
26
+ # end
27
+
28
+ # config.shared_context_metadata_behavior = :apply_to_host_groups
29
+ # end