rspec-interactive 0.1.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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 9cc41d7480a29852c93f700e92b70314e0b6e8fc70f73089ca419eae750f32bc
4
+ data.tar.gz: b7b3357dbf44d8dc21beef28a684950f148eac98aa40b4de53e2eb7faa9eb2e9
5
+ SHA512:
6
+ metadata.gz: f6413fa767a8c4dd85e6f63129d35b9b341a264ae82183696d5c160f63cada92fe96b17972913246bea85b9fb5217b19d0bfd72c4dc1a0a3152a38861cb011de
7
+ data.tar.gz: 9a42fc226690f04ea027d5cf42403164cbd0cd7f3dae80a9eec4383ef1d3c5f36d40e4f9291432366f449e0651b19b91d1b54c88b6b455cd906b73ae2bad2c08
data/.gitignore ADDED
@@ -0,0 +1 @@
1
+ rspec-interactive-*.gem
data/Gemfile ADDED
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gemspec
6
+
7
+ gem 'rspec-core'
data/Gemfile.lock ADDED
@@ -0,0 +1,29 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ rspec-console (0.1.0)
5
+ listen
6
+
7
+ GEM
8
+ remote: https://rubygems.org/
9
+ specs:
10
+ ffi (1.15.0)
11
+ listen (3.5.1)
12
+ rb-fsevent (~> 0.10, >= 0.10.3)
13
+ rb-inotify (~> 0.9, >= 0.9.10)
14
+ rb-fsevent (0.11.0)
15
+ rb-inotify (0.10.1)
16
+ ffi (~> 1.0)
17
+ rspec-core (3.10.1)
18
+ rspec-support (~> 3.10.0)
19
+ rspec-support (3.10.2)
20
+
21
+ PLATFORMS
22
+ x86_64-darwin-20
23
+
24
+ DEPENDENCIES
25
+ rspec-console!
26
+ rspec-core
27
+
28
+ BUNDLED WITH
29
+ 2.2.17
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2021 TODO: Write your name
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ task default: %i[]
data/bin/console ADDED
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "bundler/setup"
5
+ require "repo"
6
+
7
+ # You can add fixtures and/or initialization code here to make experimenting
8
+ # with your gem easier. You can also use a different console, if you like.
9
+
10
+ # (If you use this, don't forget to add pry to your Gemfile!)
11
+ # require "pry"
12
+ # Pry.start
13
+
14
+ require "irb"
15
+ IRB.start(__FILE__)
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'rspec-interactive.rb'
4
+
5
+ RSpecInteractive::Console.new(ARGV).start()
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
data/lib/repo.rb ADDED
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "repo/version"
4
+
5
+ module Repo
6
+ class Error < StandardError; end
7
+ # Your code goes here...
8
+ end
@@ -0,0 +1,205 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'json'
4
+ require 'listen'
5
+ require 'readline'
6
+ require 'rspec/core'
7
+ require 'shellwords'
8
+
9
+ require_relative 'rspec-interactive/runner.rb'
10
+
11
+ module RSpecInteractive
12
+ class Console
13
+
14
+ HISTORY_FILE = '.rspec_interactive_history'.freeze
15
+ CONFIG_FILE = '.rspec_interactive_config'.freeze
16
+ MAX_HISTORY_ITEMS = 100
17
+ COMMANDS = ['help', 'rspec']
18
+
19
+ def initialize(args)
20
+ if args.size > 1
21
+ STDERR.puts "expected 0 or 1 argument, got: #{args.join(', ')}"
22
+ exit!(1)
23
+ end
24
+
25
+ @stty_save = %x`stty -g`.chomp
26
+ @mutex = Mutex.new
27
+ @runner = nil
28
+ load_config(args[0])
29
+ end
30
+
31
+ def start()
32
+ start_file_watcher
33
+ load_history
34
+ configure_auto_complete
35
+ trap_interrupt
36
+ start_console
37
+ end
38
+
39
+ private
40
+
41
+ def load_config(name = nil)
42
+ @config = get_config(name)
43
+ load @config["init_script"]
44
+ end
45
+
46
+ def get_config(name = nil)
47
+ if !File.exists? CONFIG_FILE
48
+ STDERR.puts "file not found: #{CONFIG_FILE}"
49
+ exit!(1)
50
+ end
51
+
52
+ configs = JSON.parse(File.read(CONFIG_FILE))["configs"]
53
+ if configs.empty?
54
+ STDERR.puts "no configs found in: #{CONFIG_FILE}"
55
+ exit!(1)
56
+ end
57
+
58
+ # If a specific config was specified, use it.
59
+ if name
60
+ config = configs.find { |e| e["name"] == name }
61
+ return config if config
62
+ STDERR.puts "invalid config: #{name}"
63
+ exit!(1)
64
+ end
65
+
66
+ # If there is only one, use it.
67
+ if configs.size == 1
68
+ return configs[0]
69
+ end
70
+
71
+ # Ask the user which to use.
72
+ loop do
73
+ names = configs.map { |e| e["name"] }
74
+ names[0] = "#{names[0]} (default)"
75
+ print "Multiple simultaneous configs not yet supported. Please choose a config. #{names.join(', ')}: "
76
+ answer = STDIN.gets.chomp
77
+ if answer.strip.empty?
78
+ return configs[0]
79
+ end
80
+ config = configs.find { |e| e["name"] == answer }
81
+ return config if config
82
+ STDERR.puts "invalid config: #{answer}"
83
+ end
84
+ end
85
+
86
+ def trap_interrupt
87
+ trap('INT') do
88
+ @mutex.synchronize do
89
+ if @runner
90
+ @runner.quit
91
+ else
92
+ puts
93
+ system "stty", @stty_save
94
+ exit!(0)
95
+ end
96
+ end
97
+ end
98
+ end
99
+
100
+ def start_file_watcher
101
+ # Only polling seems to work in Docker.
102
+ listener = Listen.to(*@config["watch_dirs"], only: /\.rb$/, force_polling: true) do |modified, added, removed|
103
+ (added + modified).each { |filename| load filename }
104
+ end
105
+ end
106
+
107
+ def load_history
108
+ if File.exists? HISTORY_FILE
109
+ lines = File.readlines(HISTORY_FILE)
110
+ lines.each do |line|
111
+ Readline::HISTORY << line.strip
112
+ end
113
+ end
114
+ end
115
+
116
+ def configure_auto_complete
117
+ Readline.completion_append_character = ""
118
+ end
119
+
120
+ def start_console
121
+ loop do
122
+ buffer = Readline.readline('> ', true)&.strip
123
+
124
+ # Exit on ctrl-D.
125
+ if !buffer
126
+ puts
127
+ system "stty", @stty_save
128
+ exit!(0)
129
+ end
130
+
131
+ # Ignore blank lines.
132
+ if buffer.empty?
133
+ Readline::HISTORY.pop
134
+ next
135
+ end
136
+
137
+ # Write history to file.
138
+ if Readline::HISTORY.size > 0
139
+ file = File.open(HISTORY_FILE, 'w')
140
+ lines = Readline::HISTORY.to_a
141
+ lines[-[MAX_HISTORY_ITEMS, lines.size].min..-1].each do |line|
142
+ file.write(line.strip + "\n")
143
+ end
144
+ file.close
145
+ end
146
+
147
+ # Handle quoting, etc.
148
+ args = Shellwords.shellsplit(buffer)
149
+ next if args.empty?
150
+
151
+ command = args[0].strip
152
+ if COMMANDS.include?(command)
153
+ send command.to_sym, args[1..-1]
154
+ else
155
+ STDERR.puts "command not found: #{args[0]}"
156
+ end
157
+ end
158
+ end
159
+
160
+ def help(args)
161
+ if !args.empty?
162
+ STDERR.puts "invalid argument(s): #{args}"
163
+ return
164
+ end
165
+
166
+ print "commands:\n\n"
167
+ print "help - print this message\n"
168
+ print "rspec - execute the specified spec file(s), wildcards allowed\n"
169
+ end
170
+
171
+ def rspec(args)
172
+ if args.empty?
173
+ STDERR.puts "you must specify one or more spec files"
174
+ return
175
+ end
176
+
177
+ # Allow wildcards.
178
+ filenames = args.flat_map { |filename| Dir.glob(filename) }
179
+
180
+ # Store formatters, if any, set by the init script. They will be cleared by RSpec below.
181
+ formatters = RSpec.configuration.formatters || []
182
+
183
+ # Initialize the runner. Also accessed by the signal handler above.
184
+ # RSpecInteractive::Runner sets RSpec.world.wants_to_quit to false. The signal
185
+ # handler sets it to true.
186
+ @mutex.synchronize { @runner = RSpecInteractive::Runner.new(filenames) }
187
+
188
+ # Run the specs.
189
+ @runner.run
190
+
191
+ # Clear the runner.
192
+ @mutex.synchronize { @runner = nil }
193
+
194
+ # Clear data from previous run.
195
+ RSpec.clear_examples
196
+
197
+ # Formatters get cleared by clear_examples. I don't understand why but the actual run
198
+ # also modifies the list of formatters. Reset them to whatever the init script set.
199
+ if !RSpec.configuration.formatters.empty?
200
+ raise "internal error. expected formatters to be cleared."
201
+ end
202
+ formatters.each { |f| RSpec.configuration.add_formatter(f) }
203
+ end
204
+ end
205
+ end
@@ -0,0 +1,46 @@
1
+ require 'rspec/core'
2
+
3
+ module RSpecInteractive
4
+ class Runner
5
+ def initialize(args)
6
+ RSpec.world.wants_to_quit = false
7
+ @options = RSpec::Core::ConfigurationOptions.new(args)
8
+ end
9
+
10
+ def run()
11
+ begin
12
+ @options.configure(RSpec.configuration)
13
+ return if RSpec.world.wants_to_quit
14
+
15
+ RSpec.configuration.load_spec_files
16
+ ensure
17
+ RSpec.world.announce_filters
18
+ end
19
+
20
+ return RSpec.configuration.reporter.exit_early(RSpec.configuration.failure_exit_code) if RSpec.world.wants_to_quit
21
+
22
+ example_groups = RSpec.world.ordered_example_groups
23
+ examples_count = RSpec.world.example_count(example_groups)
24
+
25
+ success = RSpec.configuration.reporter.report(examples_count) do |reporter|
26
+ RSpec.configuration.with_suite_hooks do
27
+ if examples_count == 0 && RSpec.configuration.fail_if_no_examples
28
+ return RSpec.configuration.failure_exit_code
29
+ end
30
+
31
+ result = example_groups.map do |example_group|
32
+ example_group.run(reporter)
33
+ end
34
+
35
+ result.all?
36
+ end
37
+ end
38
+
39
+ success && !RSpec.world.non_example_failure ? 0 : RSpec.configuration.failure_exit_code
40
+ end
41
+
42
+ def quit
43
+ RSpec.world.wants_to_quit = true
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RSpecInteractive
4
+ VERSION = "0.1.0"
5
+ end
Binary file
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "lib/rspec-interactive/version"
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "rspec-interactive"
7
+ spec.version = RSpecInteractive::VERSION
8
+ spec.authors = ["Nick Dower"]
9
+ spec.email = ["nicholasdower@gmail.com"]
10
+
11
+ spec.summary = "An interactive console for running specs."
12
+ spec.description = "An interactive console for running specs."
13
+ spec.homepage = "https://github.com/nicholasdower/rspec-interactive"
14
+ spec.license = "MIT"
15
+ spec.required_ruby_version = Gem::Requirement.new(">= 2.4.0")
16
+
17
+ spec.metadata["homepage_uri"] = spec.homepage
18
+ spec.metadata["source_code_uri"] = "https://github.com/nicholasdower/rspec-interactive"
19
+ spec.metadata["changelog_uri"] = "https://github.com/nicholasdower/rspec-interactive"
20
+
21
+ spec.files = Dir.chdir(File.expand_path(__dir__)) do
22
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{\A(?:test|spec|features)/}) }
23
+ end
24
+ spec.bindir = 'bin'
25
+ spec.executables << 'rspec-interactive'
26
+ spec.require_paths = ["lib"]
27
+
28
+ spec.add_dependency "listen"
29
+ end
metadata ADDED
@@ -0,0 +1,75 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rspec-interactive
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Nick Dower
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2021-05-09 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: listen
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ description: An interactive console for running specs.
28
+ email:
29
+ - nicholasdower@gmail.com
30
+ executables:
31
+ - rspec-interactive
32
+ extensions: []
33
+ extra_rdoc_files: []
34
+ files:
35
+ - ".gitignore"
36
+ - Gemfile
37
+ - Gemfile.lock
38
+ - LICENSE.txt
39
+ - Rakefile
40
+ - bin/console
41
+ - bin/rspec-interactive
42
+ - bin/setup
43
+ - lib/repo.rb
44
+ - lib/rspec-interactive.rb
45
+ - lib/rspec-interactive/runner.rb
46
+ - lib/rspec-interactive/version.rb
47
+ - rspec-console-0.1.0.gem
48
+ - rspec-interactive.gemspec
49
+ homepage: https://github.com/nicholasdower/rspec-interactive
50
+ licenses:
51
+ - MIT
52
+ metadata:
53
+ homepage_uri: https://github.com/nicholasdower/rspec-interactive
54
+ source_code_uri: https://github.com/nicholasdower/rspec-interactive
55
+ changelog_uri: https://github.com/nicholasdower/rspec-interactive
56
+ post_install_message:
57
+ rdoc_options: []
58
+ require_paths:
59
+ - lib
60
+ required_ruby_version: !ruby/object:Gem::Requirement
61
+ requirements:
62
+ - - ">="
63
+ - !ruby/object:Gem::Version
64
+ version: 2.4.0
65
+ required_rubygems_version: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - ">="
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
70
+ requirements: []
71
+ rubygems_version: 3.2.3
72
+ signing_key:
73
+ specification_version: 4
74
+ summary: An interactive console for running specs.
75
+ test_files: []