warp-dir 1.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.
Files changed (61) hide show
  1. checksums.yaml +7 -0
  2. data/.atom-build.json +22 -0
  3. data/.codeclimate.yml +22 -0
  4. data/.gitignore +40 -0
  5. data/.idea/encodings.xml +6 -0
  6. data/.idea/misc.xml +14 -0
  7. data/.idea/modules.xml +8 -0
  8. data/.idea/runConfigurations/All_Specs.xml +33 -0
  9. data/.idea/vcs.xml +6 -0
  10. data/.idea/warp-dir.iml +224 -0
  11. data/.rspec +4 -0
  12. data/.rubocop.yml +1156 -0
  13. data/.travis.yml +13 -0
  14. data/Gemfile +4 -0
  15. data/Guardfile +14 -0
  16. data/LICENSE +22 -0
  17. data/README.md +114 -0
  18. data/ROADMAP.md +96 -0
  19. data/Rakefile +24 -0
  20. data/bin/console +11 -0
  21. data/bin/setup +8 -0
  22. data/bin/warp-dir +13 -0
  23. data/bin/warp-dir.bash +25 -0
  24. data/lib/warp.rb +4 -0
  25. data/lib/warp/dir.rb +65 -0
  26. data/lib/warp/dir/app/cli.rb +162 -0
  27. data/lib/warp/dir/app/response.rb +133 -0
  28. data/lib/warp/dir/command.rb +120 -0
  29. data/lib/warp/dir/command/add.rb +16 -0
  30. data/lib/warp/dir/command/help.rb +80 -0
  31. data/lib/warp/dir/command/install.rb +78 -0
  32. data/lib/warp/dir/command/list.rb +13 -0
  33. data/lib/warp/dir/command/ls.rb +31 -0
  34. data/lib/warp/dir/command/remove.rb +16 -0
  35. data/lib/warp/dir/command/warp.rb +24 -0
  36. data/lib/warp/dir/commander.rb +71 -0
  37. data/lib/warp/dir/config.rb +87 -0
  38. data/lib/warp/dir/errors.rb +60 -0
  39. data/lib/warp/dir/formatter.rb +77 -0
  40. data/lib/warp/dir/point.rb +53 -0
  41. data/lib/warp/dir/serializer.rb +14 -0
  42. data/lib/warp/dir/serializer/base.rb +43 -0
  43. data/lib/warp/dir/serializer/dotfile.rb +36 -0
  44. data/lib/warp/dir/store.rb +129 -0
  45. data/lib/warp/dir/version.rb +6 -0
  46. data/spec/fixtures/warprc +2 -0
  47. data/spec/spec_helper.rb +71 -0
  48. data/spec/support/cli_expectations.rb +118 -0
  49. data/spec/warp/dir/app/cli_spec.rb +225 -0
  50. data/spec/warp/dir/app/response_spec.rb +131 -0
  51. data/spec/warp/dir/command_spec.rb +62 -0
  52. data/spec/warp/dir/commands/add_spec.rb +40 -0
  53. data/spec/warp/dir/commands/install_spec.rb +20 -0
  54. data/spec/warp/dir/commands/list_spec.rb +37 -0
  55. data/spec/warp/dir/config_spec.rb +45 -0
  56. data/spec/warp/dir/errors_spec.rb +16 -0
  57. data/spec/warp/dir/formatter_spec.rb +38 -0
  58. data/spec/warp/dir/point_spec.rb +35 -0
  59. data/spec/warp/dir/store_spec.rb +105 -0
  60. data/warp-dir.gemspec +56 -0
  61. metadata +228 -0
@@ -0,0 +1,43 @@
1
+ module Warp
2
+ module Dir
3
+ module Serializer
4
+ class Base
5
+ attr_accessor :store
6
+
7
+ def initialize store
8
+ self.store = store
9
+ end
10
+
11
+ def config
12
+ self.store.config
13
+ end
14
+
15
+ def self.inherited subclass
16
+ Warp::Dir::SERIALIZERS[subclass.name] = subclass
17
+ end
18
+
19
+ #
20
+ # restore method should read the values from somewhere (i.e. database?)
21
+ # and perform the following operation:
22
+ #
23
+ # for each [ shortcut, path ] do
24
+ # self.store.add(shortcut, path)
25
+ # end
26
+
27
+ def restore!
28
+ raise NotImplementedError.new('Abstract Method')
29
+ end
30
+
31
+ #
32
+ # save shortcuts to the persistence layer
33
+ #
34
+ # store.points.each_pair |shortcut, path| do
35
+ # save(shortcut, path)
36
+ # end
37
+ def persist!
38
+ raise NotImplementedError.new('Abstract Method')
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,36 @@
1
+ require_relative '../errors'
2
+ require_relative '../../dir'
3
+ module Warp
4
+ module Dir
5
+ module Serializer
6
+ class Dotfile < Base
7
+
8
+ def restore!
9
+ File.open(Warp::Dir.absolute(config.warprc), "r") do |f|
10
+ f.each_line do |line|
11
+ line = line.chomp
12
+ next if line.blank?
13
+ name, path = line.split(/:/)
14
+ if name.nil? || path.nil?
15
+ raise Warp::Dir::Errors::StoreFormatError.new("File may be corrupt - #{config.warprc}:#{line}", line)
16
+ end
17
+ store.add point_name: name, point_path: path
18
+ end
19
+ end
20
+ end
21
+
22
+ def persist!
23
+ File.open(Warp::Dir.absolute(config.warprc), 'w') do |file|
24
+ buffer = ''
25
+ store.points.each do |point|
26
+ buffer << "#{point.name}:#{point.relative_path}\n"
27
+ end
28
+ file.write(buffer)
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
35
+
36
+
@@ -0,0 +1,129 @@
1
+ require_relative 'point'
2
+ require_relative 'errors'
3
+ require_relative 'serializer'
4
+ require 'forwardable'
5
+
6
+ module Warp
7
+ module Dir
8
+
9
+ # We want to keep around only one store, so we follow the Singleton patter.
10
+ # Due to us wanting to pass parameters to the singleton class's #new method,
11
+ # using standard Singleton becomes more hassle than it's worth.
12
+ class Store
13
+ extend Forwardable
14
+
15
+ def_delegators :@points_collection, :size, :clear, :each, :map
16
+ def_delegators :@config, :warprc, :shell
17
+
18
+ attr_reader :config, :serializer, :points_collection
19
+
20
+ def initialize(config, serializer_class = Warp::Dir::Serializer.default)
21
+ @config = config
22
+ serializer_class ||= Warp::Dir::Serializer.default
23
+ @serializer = serializer_class.new(self)
24
+ restore!
25
+ end
26
+
27
+ def restore!
28
+ @points_collection = Set.new
29
+ self.serializer.restore!
30
+ end
31
+
32
+ def [](name)
33
+ find_point(name)
34
+ end
35
+
36
+ def first
37
+ points_collection.to_a.sort.first
38
+ end
39
+
40
+ def last
41
+ points_collection.to_a.sort.last
42
+ end
43
+
44
+ def <<(value)
45
+ raise ArgumentError.new("#{value} is not a Point") unless value.is_a?(Point)
46
+ self.add(point: value)
47
+ end
48
+
49
+ def remove(point_name: nil)
50
+ point = point_name.is_a?(Warp::Dir::Point) ? point_name : self[point_name]
51
+ self.points_collection.delete(point) if point
52
+ save!
53
+ end
54
+
55
+ def points
56
+ points_collection.to_a
57
+ end
58
+
59
+ def find_point(name_or_point)
60
+ return if name_or_point.nil?
61
+ result = if name_or_point.is_a?(Warp::Dir::Point)
62
+ self.find_point(name_or_point.name)
63
+ else
64
+ matching_set = self.points_collection.classify { |p| p.name.to_sym }[name_or_point.to_sym]
65
+ (matching_set && !matching_set.empty?) ? matching_set.first : nil
66
+ end
67
+ raise ::Warp::Dir::Errors::PointNotFound.new(name_or_point) unless result
68
+ result
69
+ end
70
+
71
+ def save!
72
+ serializer.persist!
73
+ end
74
+
75
+ # a version of add that save right after.
76
+ def insert(*args)
77
+ add(*args)
78
+ save!
79
+ end
80
+
81
+ # add to memory representation only
82
+ def add(point: nil,
83
+ point_name: nil,
84
+ point_path: nil,
85
+ overwrite: false)
86
+ unless point
87
+ if !(point_name && point_path)
88
+ raise ArgumentError.new('invalid arguments')
89
+ end
90
+ point = Warp::Dir::Point.new(point_name, point_path)
91
+ end
92
+
93
+ # Three use-cases here.
94
+ # if we found this WarpPoint by name, and it's path is different from the incoming...
95
+ existing = begin
96
+ self[point]
97
+ rescue Warp::Dir::Errors::PointNotFound
98
+ nil
99
+ end
100
+
101
+ if existing.eql?(point) # found, but it's identical
102
+ if config.debug
103
+ puts "Point being added #{point} is identical to existing #{existing}, ignore."
104
+ end
105
+ return
106
+ elsif existing # found, but it's different
107
+ if overwrite # replace it
108
+ if config.debug
109
+ puts "Point being added #{point} is replacing the existing #{existing}."
110
+ end
111
+ replace(point, existing)
112
+ else # reject it
113
+ if config.debug
114
+ puts "Point being added #{point} already exists, but no overwrite was set"
115
+ end
116
+ raise Warp::Dir::Errors::PointAlreadyExists.new(point)
117
+ end
118
+ else # no lookup found
119
+ self.points_collection << point # add it
120
+ end
121
+ end
122
+
123
+ def replace(point, existing_point)
124
+ remove(point_name: existing_point)
125
+ insert(point: point)
126
+ end
127
+ end
128
+ end
129
+ end
@@ -0,0 +1,6 @@
1
+ module Warp
2
+ module Dir
3
+ VERSION = '1.1.0'
4
+ end
5
+ end
6
+
@@ -0,0 +1,2 @@
1
+ tmp:/tmp
2
+ log:/var/log
@@ -0,0 +1,71 @@
1
+ require 'codeclimate-test-reporter'
2
+ require 'warp/dir'
3
+ require 'rspec/core'
4
+
5
+ CodeClimate::TestReporter.start
6
+
7
+ module Warp
8
+ module Dir
9
+ module App
10
+ class Response
11
+ class << self
12
+ attr_accessor :exit_disabled
13
+
14
+ def enable_exit!
15
+ self.exit_disabled = false
16
+ end
17
+
18
+ def disable_exit!
19
+ self.exit_disabled = true
20
+ end
21
+
22
+ def exit_disabled?
23
+ self.exit_disabled
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
30
+
31
+ RSpec.configure do |config|
32
+ config.before do
33
+ Warp::Dir::App::Response.disable_exit!
34
+ end
35
+ end
36
+
37
+ RSpec.shared_context :fake_serializer do
38
+ let(:file) { @file ||= ::Tempfile.new('warp-dir') }
39
+ let(:config) { Warp::Dir::Config.new(config: file.path) }
40
+ let(:serializer) {
41
+ @initialized_store ||= FakeSerializer ||= Class.new(Warp::Dir::Serializer::Base) do
42
+ def persist!;
43
+ end
44
+
45
+ def restore!;
46
+ end
47
+ end
48
+ }
49
+
50
+ after do
51
+ file.close
52
+ file.unlink
53
+ end
54
+ end
55
+
56
+ RSpec.shared_context :fixture_file do
57
+ let(:fixture_file) { 'spec/fixtures/warprc'}
58
+ let(:config_path) { '/tmp/warprc' }
59
+ let(:file) {
60
+ FileUtils.cp(fixture_file, config_path)
61
+ File.new(config_path)
62
+ }
63
+ let(:config) { Warp::Dir::Config.new(config: file.path) }
64
+ end
65
+
66
+ RSpec.shared_context :initialized_store do
67
+ let(:store) { Warp::Dir::Store.new(config) }
68
+ let(:wp_path) { ENV['HOME'] + '/workspace/tinker-mania' }
69
+ let(:wp_name) { 'harro' }
70
+ let(:point) { Warp::Dir::Point.new(wp_name, wp_path) }
71
+ end
@@ -0,0 +1,118 @@
1
+ require 'rspec/expectations'
2
+ require 'warp/dir/app/cli'
3
+ require 'rspec/expectations'
4
+
5
+ module Warp
6
+ module Dir
7
+ module CLIHelper
8
+
9
+ def run_command!(arguments)
10
+ argv = arguments.is_a?(Array) ? arguments : arguments.split(' ')
11
+ cli = Warp::Dir::App::CLI.new(argv)
12
+ cli.run
13
+ end
14
+
15
+ def validate!(arguments, yield_before_validation: false)
16
+ argv = arguments.is_a?(Array) ? arguments : arguments.split(' ')
17
+ cli = Warp::Dir::App::CLI.new(argv)
18
+ if yield_before_validation
19
+ yield(cli) if block_given?
20
+ end
21
+ cli.validate
22
+ unless yield_before_validation
23
+ yield(cli) if block_given?
24
+ end
25
+ cli.run
26
+ end
27
+
28
+ def output_matches(output, expected)
29
+ if expected.is_a?(Regexp)
30
+ expected.match(output)
31
+ elsif expected.is_a?(String)
32
+ output.include?(expected)
33
+ else
34
+ nil
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
40
+
41
+ RSpec::Matchers.define :output do |*expectations|
42
+ include Warp::Dir::CLIHelper
43
+ match do |actual|
44
+ @response = nil
45
+ @command = "wd #{actual.is_a?(Array) ? actual.join(' ') : actual}"
46
+ expectations.all? do |expected|
47
+ @response = run_command!(actual)
48
+ @response.messages.any? { |m| output_matches(m, expected) }
49
+ end
50
+ end
51
+ failure_message do |actual|
52
+ "#{@command} was supposed to produce something matching or containing:\nexpected: '#{expected}',\n actual: #{@response.messages}"
53
+ end
54
+ match_when_negated do |actual|
55
+ @response = nil
56
+ @command = "wd #{actual.is_a?(Array) ? actual.join(' ') : actual}"
57
+ expectations.none? do |expected|
58
+ @response = run_command!(actual)
59
+ @response.messages.any? { |m| output_matches(m, expected) }
60
+ end
61
+ end
62
+ failure_message_when_negated do |actual|
63
+ "expected #{actual} not to contain #{expected}, got #{@response}"
64
+ end
65
+ end
66
+
67
+ RSpec::Matchers.define :validate do |expected|
68
+ include Warp::Dir::CLIHelper
69
+ match do |actual|
70
+ if expected == true || expected == false
71
+ yield_before_validation = expected
72
+ end
73
+ expected = block_arg
74
+ if expected.is_a?(Proc)
75
+ begin
76
+ @response = validate!(actual, yield_before_validation: yield_before_validation) do |cli|
77
+ expected.call(cli)
78
+ end
79
+ rescue Exception => e
80
+ STDERR.puts(e.inspect)
81
+ STDERR.puts(e.backtrace.join("\n"))
82
+ raise
83
+ end
84
+ else
85
+ raise TypeError.new('Expected must be a block')
86
+ end
87
+ end
88
+ failure_message do |actual|
89
+ "expected #{actual} to validate that the block evaluates to true"
90
+ end
91
+
92
+ end
93
+
94
+ RSpec::Matchers.define :exit_with do |expected|
95
+ include Warp::Dir::CLIHelper
96
+
97
+ match do |actual|
98
+ response = run_command!(actual)
99
+ response.code == expected
100
+ end
101
+ match_when_negated do |actual|
102
+ response = run_command!(actual)
103
+ response.code != expected
104
+ end
105
+ end
106
+
107
+ # RSpec::Matchers.define :eval_to_true_after_validate do |expected|
108
+ # match do |actual|
109
+ # expected_type = expected.is_a?(Symbol) ?
110
+ # Warp::Dir::App::Response::RETURN_TYPE[expected_type] :
111
+ # expected
112
+ # response = run_and_yield(expected)
113
+ # response.type == expected_type
114
+ # end
115
+ # failure_message_for_should_not do |actual|
116
+ # "expected #{expected} to produce return type #{}"
117
+ # end
118
+ # end
@@ -0,0 +1,225 @@
1
+ require 'spec_helper'
2
+ require 'support/cli_expectations'
3
+ require 'warp/dir'
4
+ require 'warp/dir/config'
5
+ require 'warp/dir/app/cli'
6
+ require 'pp'
7
+ require 'fileutils'
8
+
9
+ RSpec.describe Warp::Dir::App::CLI do
10
+ include_context :fixture_file
11
+ include_context :initialized_store
12
+
13
+ let(:config_args) { ['--config', config.warprc] }
14
+ let(:warprc) { config_args.join(' ') }
15
+
16
+ describe 'when parsing argument list' do
17
+ let(:cli) { Warp::Dir::App::CLI.new(argv) }
18
+ before do
19
+ cli.config = config
20
+ end
21
+
22
+ describe 'with suffix flags' do
23
+ subject { cli.send(:extract_suffix_flags, argv) }
24
+ describe 'and with at leats two arguments' do
25
+ let(:argv) { 'command argument --flag1 --flag2 -- --suffix1 --suffix2 suffix-argument'.split(' ')}
26
+ it 'extracts them well' do
27
+ should eql(%w(--suffix1 --suffix2 suffix-argument))
28
+ end
29
+ end
30
+ end
31
+
32
+ describe 'with only one argument' do
33
+ let(:result) { cli.send(:shift_non_flag_commands) }
34
+
35
+ describe "that's a list command" do
36
+ let(:argv) { %w(list --verbose) }
37
+
38
+ it 'should assign the command' do
39
+ expect(cli.argv).to eql(%w(list --verbose))
40
+ expect(result[:command]).to eql(:list)
41
+ expect(cli.argv).to eql(['--verbose'])
42
+ end
43
+ end
44
+
45
+ describe "that's a warp point" do
46
+ let(:argv) { %w(awesome-point) }
47
+
48
+ it 'should default to the :warp command' do
49
+ expect(result[:command]).to eql(:warp)
50
+ expect(result[:point]).to eql(:'awesome-point')
51
+ expect(cli.argv).to be_empty
52
+ end
53
+ end
54
+ end
55
+
56
+ describe 'with two command args' do
57
+ let(:argv) { %w(add mypoint) }
58
+ let(:result) { cli.send(:shift_non_flag_commands) }
59
+
60
+ it 'should interpret as a command and a point' do
61
+ expect(result[:command]).to eql(:add)
62
+ expect(result[:point]).to eql(:mypoint)
63
+ expect(cli.argv).to be_empty
64
+ end
65
+ end
66
+ end
67
+
68
+ describe 'when parsing flags' do
69
+ describe 'and found --help' do
70
+ let(:argv) { ['--help', *config_args] }
71
+ it 'should print the help message' do
72
+ expect(argv).to output(/<point>/, /Usage:/)
73
+ expect(argv).not_to output(/^cd /)
74
+ end
75
+ it 'should exit with zero status' do
76
+ expect(argv).to exit_with(0)
77
+ end
78
+ end
79
+
80
+ describe 'and a flag is no found' do
81
+ let(:argv) { [ '--boo mee --moo', *config_args ] }
82
+ it 'should report invalid option' do
83
+ expect(argv).to output( /unknown option/)
84
+ end
85
+ end
86
+
87
+ describe 'when an exception error occurs' do
88
+ let(:argv) { [ %w(boo dkk --debug), *config_args ].flatten }
89
+ context 'and --debug is given' do
90
+ it 'should print backtrace' do
91
+ expect(argv.join(' ')).to eql('boo dkk --debug --config /tmp/warprc')
92
+ expect(STDERR).to receive(:puts).twice
93
+ expect(argv).to validate(false) { |cli|
94
+ expect(cli.config.debug).to be_truthy
95
+ }
96
+ end
97
+ end
98
+ end
99
+ end
100
+
101
+ describe 'when running command' do
102
+ describe 'without a parameter' do
103
+ describe 'such as list' do
104
+ let(:argv) { ['list', *config_args] }
105
+
106
+ it 'should return listing of all points' do
107
+ expect("list #{warprc}").to output %r{log -> /var/log}
108
+ expect("list #{warprc}").to output %r{tmp -> /tmp}
109
+ end
110
+
111
+ it 'should exit with zero status' do
112
+ expect("list #{warprc}").to exit_with(0)
113
+ end
114
+ end
115
+ end
116
+
117
+ describe 'with a point arg, such as ' do
118
+ let(:wp_name) { store.last.name }
119
+ let(:wp_path) { store.last.path }
120
+ let(:warp_args) { "#{wp_name} #{warprc}" }
121
+
122
+ describe 'warp <point>' do
123
+ it "should return response with a 'cd' to a warp point" do
124
+ warp_point = wp_name
125
+ expect(warp_args).to validate { |cli|
126
+ expect(cli.config.point).to eql(warp_point)
127
+ expect(cli.config.command).to eql(:warp)
128
+ expect(cli.store[warp_point]).to eql(Warp::Dir::Point.new(wp_name, wp_path))
129
+ }
130
+ expect(warp_args).to output("cd #{wp_path}")
131
+ end
132
+ end
133
+
134
+ describe 'remove <point>' do
135
+ let(:warp_args) { "remove #{wp_name} #{warprc}" }
136
+
137
+ it 'should show that point is removed ' do
138
+ expect(warp_args).to output(/has been removed/)
139
+ end
140
+ it 'should change warp point count ' do
141
+ expect(store.size).to eq(2)
142
+ expect {
143
+ expect(warp_args).to validate { |cli|
144
+ expect(cli.config.point).to eql(point.name)
145
+ expect(cli.config.command).to eql(:remove)
146
+ }
147
+ store.restore!
148
+ }.to change(store, :size).by(-1)
149
+ expect(store.size).to eq(1)
150
+ end
151
+ end
152
+
153
+ describe 'add <point>' do
154
+ context 'when point exists' do
155
+ context 'without --force flag' do
156
+ let(:warp_args) { "add #{wp_name} #{warprc}" }
157
+
158
+ it 'should show error without' do
159
+ expect(warp_args).to output(/already exists/)
160
+ expect(warp_args).to exit_with(1)
161
+ end
162
+ end
163
+ context 'with --force' do
164
+ let(:warp_args) { "add #{wp_name} #{warprc} --force" }
165
+ it 'should overwrite existing point' do
166
+
167
+ expect(Warp::Dir.pwd).to_not eql(wp_path)
168
+
169
+ existing_point = store[wp_name]
170
+ expect(existing_point).to be_kind_of(Warp::Dir::Point)
171
+ expect(existing_point.path).to eql(wp_path)
172
+
173
+ expect {
174
+ response = expect(warp_args).to validate { |cli|
175
+ expect(cli.config.point).to eql(point.name)
176
+ expect(cli.config.command).to eql(:add)
177
+ expect(cli.store[point.name]).to_not be_nil
178
+ }
179
+ expect(response.type).to eql(Warp::Dir::App::Response::INFO), response.message
180
+ store.restore!
181
+ updated_point = store[wp_name]
182
+ expect(updated_point.relative_path).to eql(Warp::Dir::pwd)
183
+ }.to_not change(store, :size)
184
+
185
+ end
186
+ end
187
+ end
188
+ end
189
+
190
+ describe 'ls <point>' do
191
+ context 'no flags' do
192
+ let(:warp_args) { "ls #{wp_name} #{warprc}" }
193
+ it 'should default to -al' do
194
+ expect(warp_args).to output(%r{total \d+\n}, %r{ warprc\n})
195
+ end
196
+ end
197
+ context '-- -l' do
198
+ let(:warp_args) { "ls #{wp_name} #{warprc} -- -l" }
199
+ it 'should extract flags' do
200
+ expect(warp_args).to validate { |cli|
201
+ expect(cli.flags).to eql(['-l'])
202
+ }
203
+ end
204
+ it 'should ls folder in long format' do
205
+ expect(warp_args).to output(%r{total \d+\n}, %r{ warprc\n})
206
+ end
207
+ end
208
+ context '-- -1' do
209
+ let(:warp_args) { "ls #{wp_name} #{warprc} -- -1" }
210
+ it 'should not list directory in long format' do
211
+ expect(warp_args).not_to output(%r{total \d+\n}, %r{ warprc\n})
212
+ end
213
+ end
214
+ context '-- -elf' do
215
+ let(:warp_args) { "ls #{wp_name} #{warprc} -- -alF" }
216
+ [%r{total \d+\n}, %r{ warprc\n}].each do |reg|
217
+ it "should list directory and match #{reg}" do
218
+ expect(warp_args).to output(reg)
219
+ end
220
+ end
221
+ end
222
+ end
223
+ end
224
+ end
225
+ end