midwire_common 1.1.0 → 2.0.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 +5 -5
- data/.github/mergeable.yml +43 -0
- data/.gitignore +23 -0
- data/.ruby-version +1 -1
- data/CHANGELOG +4 -0
- data/CLAUDE.md +95 -0
- data/Gemfile +2 -0
- data/README.md +8 -5
- data/Rakefile +3 -1
- data/docs/plans/2026-02-13-refinements-implementation.md +1494 -0
- data/docs/plans/2026-02-13-refinements-modernization-design.md +109 -0
- data/lib/midwire_common/all.rb +16 -1
- data/lib/midwire_common/array.rb +35 -45
- data/lib/midwire_common/data_file_cache.rb +6 -5
- data/lib/midwire_common/enumerable.rb +12 -8
- data/lib/midwire_common/file.rb +13 -1
- data/lib/midwire_common/float.rb +10 -3
- data/lib/midwire_common/hash.rb +94 -105
- data/lib/midwire_common/integer.rb +11 -0
- data/lib/midwire_common/number_behavior.rb +4 -2
- data/lib/midwire_common/rake_helper.rb +5 -3
- data/lib/midwire_common/rake_tasks.rb +2 -0
- data/lib/midwire_common/string.rb +76 -108
- data/lib/midwire_common/time.rb +10 -6
- data/lib/midwire_common/time_tool.rb +6 -2
- data/lib/midwire_common/version.rb +3 -1
- data/lib/midwire_common/yaml_setting.rb +4 -3
- data/lib/midwire_common.rb +8 -2
- data/lib/tasks/version.rake +21 -18
- data/midwire_common.gemspec +10 -13
- data/spec/lib/midwire_common/array_spec.rb +23 -23
- data/spec/lib/midwire_common/data_file_cache_spec.rb +14 -14
- data/spec/lib/midwire_common/enumerable_spec.rb +8 -4
- data/spec/lib/midwire_common/file/stat_spec.rb +8 -4
- data/spec/lib/midwire_common/float_spec.rb +7 -3
- data/spec/lib/midwire_common/hash_spec.rb +55 -24
- data/spec/lib/midwire_common/integer_spec.rb +11 -0
- data/spec/lib/midwire_common/rake_helper_spec.rb +6 -3
- data/spec/lib/midwire_common/string_spec.rb +47 -77
- data/spec/lib/midwire_common/time_spec.rb +19 -20
- data/spec/lib/midwire_common/time_tool_spec.rb +4 -2
- data/spec/lib/midwire_common/yaml_setting_spec.rb +8 -5
- data/spec/spec_helper.rb +18 -12
- metadata +29 -99
- data/Guardfile +0 -14
- data/lib/midwire_common/file/stat.rb +0 -11
- data/lib/midwire_common/fixnum.rb +0 -4
- data/spec/lib/midwire_common/fixnum_spec.rb +0 -15
|
@@ -0,0 +1,1494 @@
|
|
|
1
|
+
# midwire_common 2.0.0 Refinements Implementation Plan
|
|
2
|
+
|
|
3
|
+
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
|
|
4
|
+
|
|
5
|
+
**Goal:** Replace all monkey-patching with Ruby refinements, modernize all dependencies, remove stdlib-overlapping methods, and release as version 2.0.0.
|
|
6
|
+
|
|
7
|
+
**Architecture:** Each monkey-patched file becomes a refinement module (`MidwireCommon::XxxExtensions`) using Ruby's `refine` blocks. Consumers opt in per-file with `using`. A composed `MidwireCommon::All` module includes all refinement modules so `using MidwireCommon::All` activates everything at once.
|
|
8
|
+
|
|
9
|
+
**Tech Stack:** Ruby >= 3.2, RSpec ~> 3.12, Rake ~> 13.0
|
|
10
|
+
|
|
11
|
+
**Cross-dependency note:** Some specs depend on methods from other modules. Specifically:
|
|
12
|
+
- `array_spec.rb` uses `String#here_with_pipe` (from StringExtensions)
|
|
13
|
+
- `time_spec.rb` uses `String#numeric?` (from StringExtensions)
|
|
14
|
+
- `version.rake` uses `String#here_with_pipe` (from StringExtensions)
|
|
15
|
+
|
|
16
|
+
These must be handled when converting String first.
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
### Task 1: Update gemspec, version, and remove Guard
|
|
21
|
+
|
|
22
|
+
**Files:**
|
|
23
|
+
- Modify: `midwire_common.gemspec`
|
|
24
|
+
- Modify: `lib/midwire_common/version.rb`
|
|
25
|
+
- Delete: `Guardfile`
|
|
26
|
+
|
|
27
|
+
**Step 1: Update gemspec**
|
|
28
|
+
|
|
29
|
+
Replace the full contents of `midwire_common.gemspec` with:
|
|
30
|
+
|
|
31
|
+
```ruby
|
|
32
|
+
# coding: utf-8
|
|
33
|
+
require File.expand_path('../lib/midwire_common/version', __FILE__)
|
|
34
|
+
|
|
35
|
+
Gem::Specification.new do |spec|
|
|
36
|
+
spec.name = 'midwire_common'
|
|
37
|
+
spec.version = MidwireCommon::VERSION
|
|
38
|
+
spec.authors = ['Chris Blackburn']
|
|
39
|
+
spec.email = ['87a1779b@opayq.com']
|
|
40
|
+
spec.summary = 'Midwire Tech Ruby Library'
|
|
41
|
+
spec.description = 'A useful Ruby library'
|
|
42
|
+
spec.homepage = 'https://bitbucket.org/midwiretech/midwire_common'
|
|
43
|
+
spec.license = 'MIT'
|
|
44
|
+
|
|
45
|
+
spec.files = `git ls-files -z`.split("\x0")
|
|
46
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
|
47
|
+
spec.test_files = spec.files.grep(%r{^spec/})
|
|
48
|
+
spec.require_paths = ['lib']
|
|
49
|
+
|
|
50
|
+
spec.required_ruby_version = '>= 3.2'
|
|
51
|
+
|
|
52
|
+
spec.add_development_dependency 'debug', '~> 1.9'
|
|
53
|
+
spec.add_development_dependency 'rake', '~> 13.0'
|
|
54
|
+
spec.add_development_dependency 'rspec', '~> 3.12'
|
|
55
|
+
spec.add_development_dependency 'rubocop', '~> 1.60'
|
|
56
|
+
spec.add_development_dependency 'simplecov', '~> 0.22'
|
|
57
|
+
end
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
**Step 2: Update version**
|
|
61
|
+
|
|
62
|
+
In `lib/midwire_common/version.rb`, change:
|
|
63
|
+
|
|
64
|
+
```ruby
|
|
65
|
+
original_verbosity = $VERBOSE
|
|
66
|
+
$VERBOSE = nil
|
|
67
|
+
module MidwireCommon
|
|
68
|
+
VERSION = '2.0.0'.freeze
|
|
69
|
+
end
|
|
70
|
+
$VERBOSE = original_verbosity
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
**Step 3: Delete Guardfile**
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
git rm Guardfile
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
**Step 4: Run bundle install**
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
bundle install
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Expected: Success. Gemfile.lock updated with new dependency versions.
|
|
86
|
+
|
|
87
|
+
**Step 5: Commit**
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
git add midwire_common.gemspec lib/midwire_common/version.rb Gemfile.lock
|
|
91
|
+
git commit -m "chore: update dependencies and bump to 2.0.0
|
|
92
|
+
|
|
93
|
+
- Ruby >= 3.2, RSpec 3.12, Rake 13, Rubocop 1.60
|
|
94
|
+
- Remove Guard, pry-nav
|
|
95
|
+
- Add debug gem"
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
---
|
|
99
|
+
|
|
100
|
+
### Task 2: Update spec_helper
|
|
101
|
+
|
|
102
|
+
**Files:**
|
|
103
|
+
- Modify: `spec/spec_helper.rb`
|
|
104
|
+
|
|
105
|
+
**Step 1: Rewrite spec_helper**
|
|
106
|
+
|
|
107
|
+
Replace full contents of `spec/spec_helper.rb`:
|
|
108
|
+
|
|
109
|
+
```ruby
|
|
110
|
+
if ENV['COVERAGE']
|
|
111
|
+
require 'simplecov'
|
|
112
|
+
SimpleCov.start do
|
|
113
|
+
add_filter 'spec/'
|
|
114
|
+
add_filter 'vendor/'
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
require 'debug'
|
|
119
|
+
require File.join(File.dirname(__FILE__), '..', 'lib', 'midwire_common')
|
|
120
|
+
require 'midwire_common/all'
|
|
121
|
+
|
|
122
|
+
PROJECT_ROOT = File.expand_path('..', File.dirname(__FILE__))
|
|
123
|
+
|
|
124
|
+
RSpec.configure do |config|
|
|
125
|
+
include MidwireCommon
|
|
126
|
+
|
|
127
|
+
config.expect_with :rspec do |expectations|
|
|
128
|
+
expectations.syntax = [:should, :expect]
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
config.mock_with :rspec do |mocks|
|
|
132
|
+
mocks.syntax = [:should, :expect]
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
config.order = 'random'
|
|
136
|
+
|
|
137
|
+
def capture(stream)
|
|
138
|
+
begin
|
|
139
|
+
stream = stream.to_s
|
|
140
|
+
eval "$#{stream} = StringIO.new"
|
|
141
|
+
yield
|
|
142
|
+
result = eval("$#{stream}").string
|
|
143
|
+
ensure
|
|
144
|
+
eval("$#{stream} = #{stream.upcase}")
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
result
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
Key changes:
|
|
153
|
+
- `require 'pry'` → `require 'debug'`
|
|
154
|
+
- `config.color = true` removed (RSpec 3 defaults to color on TTY)
|
|
155
|
+
- Added `expectations.syntax = [:should, :expect]` to support both syntaxes during migration
|
|
156
|
+
- Added `mocks.syntax = [:should, :expect]` for same reason
|
|
157
|
+
|
|
158
|
+
**Step 2: Commit**
|
|
159
|
+
|
|
160
|
+
```bash
|
|
161
|
+
git add spec/spec_helper.rb
|
|
162
|
+
git commit -m "chore: update spec_helper for RSpec 3 compatibility"
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
Note: Tests will NOT pass at this point. That's expected — we haven't converted the source files yet.
|
|
166
|
+
|
|
167
|
+
---
|
|
168
|
+
|
|
169
|
+
### Task 3: Convert String to refinements
|
|
170
|
+
|
|
171
|
+
This is the first and most critical conversion because other modules depend on String methods.
|
|
172
|
+
|
|
173
|
+
**Files:**
|
|
174
|
+
- Modify: `lib/midwire_common/string.rb`
|
|
175
|
+
- Modify: `spec/lib/midwire_common/string_spec.rb`
|
|
176
|
+
- Modify: `spec/lib/midwire_common/array_spec.rb` (uses `here_with_pipe`)
|
|
177
|
+
- Modify: `spec/lib/midwire_common/time_spec.rb` (uses `numeric?`)
|
|
178
|
+
- Modify: `lib/tasks/version.rake` (uses `here_with_pipe`)
|
|
179
|
+
|
|
180
|
+
**Step 1: Rewrite string.rb with refinements**
|
|
181
|
+
|
|
182
|
+
Replace full contents of `lib/midwire_common/string.rb`:
|
|
183
|
+
|
|
184
|
+
```ruby
|
|
185
|
+
module MidwireCommon
|
|
186
|
+
module StringExtensions
|
|
187
|
+
refine String.singleton_class do
|
|
188
|
+
def random(count = 6, ranges = [('a'..'z'), ('A'..'Z'), ('0'..'9')])
|
|
189
|
+
coll = ranges.map(&:to_a).flatten
|
|
190
|
+
(0..(count - 1)).map { coll[rand(coll.length)] }.join
|
|
191
|
+
end
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
refine String do
|
|
195
|
+
def left_substr(count)
|
|
196
|
+
slice(0, count)
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
def right_substr(count)
|
|
200
|
+
slice(-count, count)
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
# html = <<-stop.here_with_pipe(delimeter="\n")
|
|
204
|
+
# |<!-- Begin: comment -->
|
|
205
|
+
# |<script type="text/javascript">
|
|
206
|
+
# stop
|
|
207
|
+
def here_with_pipe(delimeter = ' ')
|
|
208
|
+
lines = split("\n")
|
|
209
|
+
lines.map! { |c| c.sub!(/\s*\|/, '') }
|
|
210
|
+
new_string = lines.join(delimeter)
|
|
211
|
+
replace(new_string)
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
def alpha_numeric?
|
|
215
|
+
regex = /^[a-zA-Z0-9]+$/
|
|
216
|
+
(self =~ regex) == 0 ? true : false
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
def email_address?
|
|
220
|
+
email_regex = /\A([\w+\-].?)+@[a-z\d\-]+(\.[a-z]+)*\.[a-z]+\z/i
|
|
221
|
+
(self =~ email_regex) == 0 ? true : false
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
def zipcode?
|
|
225
|
+
self =~ %r{^(\d{5})(-\d{4})?$}x ? true : false
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
def numeric?
|
|
229
|
+
Float(self)
|
|
230
|
+
rescue
|
|
231
|
+
false
|
|
232
|
+
else
|
|
233
|
+
true
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
def format_phone
|
|
237
|
+
gsub!(/[a-z,! \-\(\)\:\;\.\&\$]+/i, '')
|
|
238
|
+
'(' << slice(0..2) << ')' << slice(3..5) << '-' << slice(-4, 4)
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
def sanitize
|
|
242
|
+
gsub(/[^a-z0-9,! \-\(\)\:\;\.\&\$]+/i, '')
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
def sanitize!
|
|
246
|
+
gsub!(/[^a-z0-9,! \-\(\)\:\;\.\&\$]+/i, '')
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
def shorten(maxcount = 30)
|
|
250
|
+
if length >= maxcount
|
|
251
|
+
shortened = self[0, maxcount]
|
|
252
|
+
splitted = shortened.split(/\s/)
|
|
253
|
+
if splitted.length > 1
|
|
254
|
+
words = splitted.length
|
|
255
|
+
splitted[0, words - 1].join(' ') + '...'
|
|
256
|
+
else
|
|
257
|
+
shortened[0, maxcount - 3] + '...'
|
|
258
|
+
end
|
|
259
|
+
else
|
|
260
|
+
self
|
|
261
|
+
end
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
def escape_single_quotes
|
|
265
|
+
gsub(/[']/, '\\\\\'')
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
def escape_double_quotes
|
|
269
|
+
gsub(/["]/, '\\\\\"')
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
def snakerize
|
|
273
|
+
gsub(/::/, '/')
|
|
274
|
+
.gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
|
|
275
|
+
.gsub(/([a-z\d])([A-Z])/, '\1_\2')
|
|
276
|
+
.tr('-', '_')
|
|
277
|
+
.downcase
|
|
278
|
+
end
|
|
279
|
+
|
|
280
|
+
def camelize
|
|
281
|
+
gsub(/\/(.?)/) { '::' + $1.upcase }.gsub(/(^|_)(.)/) { $2.upcase }
|
|
282
|
+
end
|
|
283
|
+
end
|
|
284
|
+
end
|
|
285
|
+
end
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
**Step 2: Rewrite string_spec.rb**
|
|
289
|
+
|
|
290
|
+
Replace full contents of `spec/lib/midwire_common/string_spec.rb`:
|
|
291
|
+
|
|
292
|
+
```ruby
|
|
293
|
+
require 'spec_helper'
|
|
294
|
+
|
|
295
|
+
using MidwireCommon::StringExtensions
|
|
296
|
+
|
|
297
|
+
RSpec.describe String do
|
|
298
|
+
it 'is a String' do
|
|
299
|
+
''.should be_a String
|
|
300
|
+
end
|
|
301
|
+
|
|
302
|
+
it 'generates a random string' do
|
|
303
|
+
String.random.length.should == 6
|
|
304
|
+
end
|
|
305
|
+
|
|
306
|
+
context 'slicing methods' do
|
|
307
|
+
it "'left' returns the leftmost 'n' characters" do
|
|
308
|
+
'My Bogus String'.left_substr(2).should == 'My'
|
|
309
|
+
end
|
|
310
|
+
|
|
311
|
+
it "'right' returns the rightmost 'n' characters " do
|
|
312
|
+
'My Bogus String'.right_substr(2).should == 'ng'
|
|
313
|
+
end
|
|
314
|
+
end
|
|
315
|
+
|
|
316
|
+
context 'formatting and manipulation' do
|
|
317
|
+
it 'here_with_pipe - without linefeeds' do
|
|
318
|
+
html = <<-STOP.here_with_pipe
|
|
319
|
+
|<!-- Begin: comment -->
|
|
320
|
+
|<script type="text/javascript">
|
|
321
|
+
|</script>
|
|
322
|
+
STOP
|
|
323
|
+
html.should == '<!-- Begin: comment --> <script type="text/javascript"> </script>'
|
|
324
|
+
end
|
|
325
|
+
|
|
326
|
+
it 'here_with_pipe - with linefeeds' do
|
|
327
|
+
html = <<-STOP.here_with_pipe("\n")
|
|
328
|
+
|<!-- Begin: comment -->
|
|
329
|
+
|<script type="text/javascript">
|
|
330
|
+
|</script>
|
|
331
|
+
STOP
|
|
332
|
+
html.should == "<!-- Begin: comment -->\n<script type=\"text/javascript\">\n</script>"
|
|
333
|
+
end
|
|
334
|
+
|
|
335
|
+
it 'here_with_pipe - with no space delimeter' do
|
|
336
|
+
html = <<-STOP.here_with_pipe('')
|
|
337
|
+
|<!-- Begin: comment -->
|
|
338
|
+
|<script type="text/javascript">
|
|
339
|
+
|</script>
|
|
340
|
+
STOP
|
|
341
|
+
html.should == '<!-- Begin: comment --><script type="text/javascript"></script>'
|
|
342
|
+
end
|
|
343
|
+
|
|
344
|
+
it 'format_phone returns a formatted phone number string' do
|
|
345
|
+
expect('9132329999'.format_phone).to eq('(913)232-9999')
|
|
346
|
+
expect('913.232.9999'.format_phone).to eq('(913)232-9999')
|
|
347
|
+
expect('913 232 9999'.format_phone).to eq('(913)232-9999')
|
|
348
|
+
expect('913-232-9999'.format_phone).to eq('(913)232-9999')
|
|
349
|
+
end
|
|
350
|
+
|
|
351
|
+
it 'sanitizes itself' do
|
|
352
|
+
expect('|bogus|'.sanitize).to eq('bogus')
|
|
353
|
+
expect('|∫|ß'.sanitize).to eq('')
|
|
354
|
+
expect('ßogus'.sanitize).to eq('ogus')
|
|
355
|
+
expect('<tag>bogus</tag>'.sanitize).to eq('tagbogustag')
|
|
356
|
+
expect('<tag>.bogus.</tag>'.sanitize).to eq('tag.bogus.tag')
|
|
357
|
+
s = '|∫|ß'
|
|
358
|
+
s.sanitize!
|
|
359
|
+
s.should == ''
|
|
360
|
+
end
|
|
361
|
+
|
|
362
|
+
it 'shortens itself with elipses at the end' do
|
|
363
|
+
s = 'this is my very long string which I will eventually shorten with the enhanced String class that we are now testing.'
|
|
364
|
+
short = s.shorten
|
|
365
|
+
expect(short).to eq('this is my very long string...')
|
|
366
|
+
expect(short.length).to eq(30)
|
|
367
|
+
|
|
368
|
+
s = '1234567890123456789012345678901234567890'
|
|
369
|
+
short = s.shorten
|
|
370
|
+
expect(short).to eq('123456789012345678901234567...')
|
|
371
|
+
expect(short.length).to eq(30)
|
|
372
|
+
|
|
373
|
+
s = '12345678901234567890'
|
|
374
|
+
short = s.shorten
|
|
375
|
+
expect(short).to eq('12345678901234567890')
|
|
376
|
+
expect(short.length).to eq(20)
|
|
377
|
+
end
|
|
378
|
+
|
|
379
|
+
context 'quotes' do
|
|
380
|
+
it 'escapes single quotes' do
|
|
381
|
+
"this is a 'test'".escape_single_quotes.should == "this is a \\'test\\'"
|
|
382
|
+
end
|
|
383
|
+
|
|
384
|
+
it 'escapes double quotes' do
|
|
385
|
+
expect('this is a "test"'.escape_double_quotes)
|
|
386
|
+
.to eq('this is a \\\\"test\\\\"')
|
|
387
|
+
end
|
|
388
|
+
end
|
|
389
|
+
end
|
|
390
|
+
|
|
391
|
+
context 'characterization' do
|
|
392
|
+
it 'knows if it is alpha-numeric or not' do
|
|
393
|
+
'abcd-9191'.alpha_numeric?.should eq(false)
|
|
394
|
+
'abcd.9191'.alpha_numeric?.should eq(false)
|
|
395
|
+
'abcd91910'.alpha_numeric?.should eq(true)
|
|
396
|
+
'abcd_9191'.alpha_numeric?.should eq(false)
|
|
397
|
+
'abcd 9191'.alpha_numeric?.should eq(false)
|
|
398
|
+
end
|
|
399
|
+
|
|
400
|
+
it 'knows if it is an email address or not' do
|
|
401
|
+
'abcd_9191'.email_address?.should eq(false)
|
|
402
|
+
'abcd@9191'.email_address?.should eq(false)
|
|
403
|
+
'abcd@9191.info'.email_address?.should eq(true)
|
|
404
|
+
'abcd-asdf@9191.com'.email_address?.should eq(true)
|
|
405
|
+
'abcd_asdf@9191.com'.email_address?.should eq(true)
|
|
406
|
+
'abcd.asdf@9191.com'.email_address?.should eq(true)
|
|
407
|
+
end
|
|
408
|
+
|
|
409
|
+
it 'knows if it is a zipcode or not' do
|
|
410
|
+
'13922-2356'.zipcode?.should eq(true)
|
|
411
|
+
'13922.2343'.zipcode?.should eq(false)
|
|
412
|
+
'13922 2342'.zipcode?.should eq(false)
|
|
413
|
+
'ABSSD'.zipcode?.should eq(false)
|
|
414
|
+
'i3323'.zipcode?.should eq(false)
|
|
415
|
+
'13922'.zipcode?.should eq(true)
|
|
416
|
+
end
|
|
417
|
+
|
|
418
|
+
it 'knows if it is numeric or not' do
|
|
419
|
+
'12341'.numeric?.should eq(true)
|
|
420
|
+
'12341.23'.numeric?.should eq(true)
|
|
421
|
+
'12341.00000000000000023'.numeric?.should eq(true)
|
|
422
|
+
'0.12341'.numeric?.should eq(true)
|
|
423
|
+
'0x2E'.numeric?.should eq(true)
|
|
424
|
+
' 0.12341'.numeric?.should eq(true)
|
|
425
|
+
' 0.12341 '.numeric?.should eq(true)
|
|
426
|
+
'.12341'.numeric?.should eq(true)
|
|
427
|
+
' 12341.'.numeric?.should eq(false)
|
|
428
|
+
' 12341. '.numeric?.should eq(false)
|
|
429
|
+
end
|
|
430
|
+
end
|
|
431
|
+
|
|
432
|
+
context '.snakerize' do
|
|
433
|
+
it 'changes CamelCased string to snake_cased' do
|
|
434
|
+
expect('CamelCased'.snakerize).to eq('camel_cased')
|
|
435
|
+
end
|
|
436
|
+
|
|
437
|
+
it 'handles doulbe-colon' do
|
|
438
|
+
expect('Camel::CasedTwo'.snakerize).to eq('camel/cased_two')
|
|
439
|
+
end
|
|
440
|
+
end
|
|
441
|
+
|
|
442
|
+
context '.camelize' do
|
|
443
|
+
it 'changes snake cased string to camelized' do
|
|
444
|
+
expect('camel_cased'.camelize).to eq('CamelCased')
|
|
445
|
+
end
|
|
446
|
+
|
|
447
|
+
it 'handles slash' do
|
|
448
|
+
expect('camel/cased_two'.camelize).to eq('Camel::CasedTwo')
|
|
449
|
+
end
|
|
450
|
+
end
|
|
451
|
+
end
|
|
452
|
+
```
|
|
453
|
+
|
|
454
|
+
Changes: removed trim method specs, added `using MidwireCommon::StringExtensions` at top.
|
|
455
|
+
|
|
456
|
+
**Step 3: Add `using` to array_spec.rb (cross-dependency)**
|
|
457
|
+
|
|
458
|
+
Add `using MidwireCommon::StringExtensions` after the require line in `spec/lib/midwire_common/array_spec.rb` (for `here_with_pipe` usage on line 37).
|
|
459
|
+
|
|
460
|
+
**Step 4: Add `using` to time_spec.rb (cross-dependency)**
|
|
461
|
+
|
|
462
|
+
Add `using MidwireCommon::StringExtensions` after the require line in `spec/lib/midwire_common/time_spec.rb` (for `numeric?` usage).
|
|
463
|
+
|
|
464
|
+
**Step 5: Update version.rake**
|
|
465
|
+
|
|
466
|
+
At the top of `lib/tasks/version.rake`, after `require 'midwire_common/string'`, add:
|
|
467
|
+
|
|
468
|
+
```ruby
|
|
469
|
+
using MidwireCommon::StringExtensions
|
|
470
|
+
```
|
|
471
|
+
|
|
472
|
+
**Step 6: Run string specs**
|
|
473
|
+
|
|
474
|
+
```bash
|
|
475
|
+
bundle exec rspec spec/lib/midwire_common/string_spec.rb
|
|
476
|
+
```
|
|
477
|
+
|
|
478
|
+
Expected: All pass.
|
|
479
|
+
|
|
480
|
+
**Step 7: Commit**
|
|
481
|
+
|
|
482
|
+
```bash
|
|
483
|
+
git add lib/midwire_common/string.rb spec/lib/midwire_common/string_spec.rb \
|
|
484
|
+
spec/lib/midwire_common/array_spec.rb spec/lib/midwire_common/time_spec.rb \
|
|
485
|
+
lib/tasks/version.rake
|
|
486
|
+
git commit -m "refactor: convert String monkey-patch to refinements
|
|
487
|
+
|
|
488
|
+
Remove trim/left_trim/right_trim (use stdlib strip/lstrip/rstrip).
|
|
489
|
+
Add 'using' to dependent specs and version.rake."
|
|
490
|
+
```
|
|
491
|
+
|
|
492
|
+
---
|
|
493
|
+
|
|
494
|
+
### Task 4: Convert Array to refinements
|
|
495
|
+
|
|
496
|
+
**Files:**
|
|
497
|
+
- Modify: `lib/midwire_common/array.rb`
|
|
498
|
+
- Modify: `spec/lib/midwire_common/array_spec.rb`
|
|
499
|
+
|
|
500
|
+
**Step 1: Rewrite array.rb**
|
|
501
|
+
|
|
502
|
+
Replace full contents of `lib/midwire_common/array.rb`:
|
|
503
|
+
|
|
504
|
+
```ruby
|
|
505
|
+
module MidwireCommon
|
|
506
|
+
module ArrayExtensions
|
|
507
|
+
refine Array do
|
|
508
|
+
def count_occurrences
|
|
509
|
+
hash = Hash.new(0)
|
|
510
|
+
each { |elem| hash[elem] += 1 }
|
|
511
|
+
hash
|
|
512
|
+
end
|
|
513
|
+
|
|
514
|
+
def randomize
|
|
515
|
+
sort_by { rand }
|
|
516
|
+
end
|
|
517
|
+
|
|
518
|
+
def randomize!
|
|
519
|
+
replace(randomize)
|
|
520
|
+
end
|
|
521
|
+
|
|
522
|
+
def sort_case_insensitive
|
|
523
|
+
sort_by(&:downcase)
|
|
524
|
+
end
|
|
525
|
+
|
|
526
|
+
def each_with_first_last(first_code, main_code, last_code)
|
|
527
|
+
each_with_index do |item, ndx|
|
|
528
|
+
case ndx
|
|
529
|
+
when 0 then first_code.call(item)
|
|
530
|
+
when size - 1 then last_code.call(item)
|
|
531
|
+
else main_code.call(item)
|
|
532
|
+
end
|
|
533
|
+
end
|
|
534
|
+
end
|
|
535
|
+
|
|
536
|
+
# Make a string from a multi-dimensional array:
|
|
537
|
+
# 1 dimension:
|
|
538
|
+
# [1,2,3].superjoin(["pre","between","post"])
|
|
539
|
+
#
|
|
540
|
+
# 2 dimensions (html table):
|
|
541
|
+
# [[1,2],[2,3]].superjoin(%w{<table><tr> </tr><tr> </tr></table>}, %w{<td> </td><td> </td>})
|
|
542
|
+
def superjoin(*ldescr)
|
|
543
|
+
dim = ldescr[0]
|
|
544
|
+
rest = ldescr[1..]
|
|
545
|
+
dim[0] + map do |arr|
|
|
546
|
+
(arr.respond_to?(:superjoin) && !rest.empty?) ? arr.superjoin(*rest) : arr.to_s
|
|
547
|
+
end.join(dim[1]) + dim[2]
|
|
548
|
+
end
|
|
549
|
+
end
|
|
550
|
+
end
|
|
551
|
+
end
|
|
552
|
+
```
|
|
553
|
+
|
|
554
|
+
Changes: removed `binsearch`, wrapped in refinement module, `ldescr[1..-1]` → `ldescr[1..]`.
|
|
555
|
+
|
|
556
|
+
**Step 2: Rewrite array_spec.rb**
|
|
557
|
+
|
|
558
|
+
Replace full contents of `spec/lib/midwire_common/array_spec.rb`:
|
|
559
|
+
|
|
560
|
+
```ruby
|
|
561
|
+
require 'spec_helper'
|
|
562
|
+
|
|
563
|
+
using MidwireCommon::StringExtensions
|
|
564
|
+
using MidwireCommon::ArrayExtensions
|
|
565
|
+
|
|
566
|
+
RSpec.describe Array do
|
|
567
|
+
it 'counts occurrences of an element' do
|
|
568
|
+
[1, 2, 2, 3, 3, 3].count_occurrences.should eq(1 => 1, 2 => 2, 3 => 3)
|
|
569
|
+
%w(asdf asdf qwer asdf).count_occurrences.should == {
|
|
570
|
+
'asdf' => 3,
|
|
571
|
+
'qwer' => 1
|
|
572
|
+
}
|
|
573
|
+
end
|
|
574
|
+
|
|
575
|
+
it 'randomizes the order of an array' do
|
|
576
|
+
myarray = [1, 2, 3, 4, 5, 6, 7, 8, 9, '0']
|
|
577
|
+
myarray.randomize.should_not eq(myarray)
|
|
578
|
+
myarray.randomize!
|
|
579
|
+
myarray.should_not == [1, 2, 3, 4, 5, 6, 7, 8, 9, '0']
|
|
580
|
+
end
|
|
581
|
+
|
|
582
|
+
it 'sorts elements case insensitive' do
|
|
583
|
+
myarray = %w(zebra ghost Zebra cat Cat)
|
|
584
|
+
myarray.sort_case_insensitive.should eq(%w(Cat cat ghost Zebra zebra))
|
|
585
|
+
end
|
|
586
|
+
|
|
587
|
+
it 'can process first and last entries differently than others' do
|
|
588
|
+
text = ''
|
|
589
|
+
['KU', 'K-State', 'MU'].each_with_first_last(
|
|
590
|
+
lambda do |team|
|
|
591
|
+
text += "#{team} came first in the NCAA basketball tournament.\n"
|
|
592
|
+
end,
|
|
593
|
+
lambda do |team|
|
|
594
|
+
text += "#{team} did not come first or last in the final four.\n"
|
|
595
|
+
end,
|
|
596
|
+
lambda do |team|
|
|
597
|
+
text += "#{team} came last in the final four this year.\n"
|
|
598
|
+
end
|
|
599
|
+
)
|
|
600
|
+
text.should == <<-string.here_with_pipe("\n")
|
|
601
|
+
|KU came first in the NCAA basketball tournament.
|
|
602
|
+
|K-State did not come first or last in the final four.
|
|
603
|
+
|MU came last in the final four this year.
|
|
604
|
+
|
|
|
605
|
+
string
|
|
606
|
+
end
|
|
607
|
+
|
|
608
|
+
it 'can superjoin elements' do
|
|
609
|
+
[1, 2, 3].superjoin(['->', '+', '<-']).should eq('->1+2+3<-')
|
|
610
|
+
[[1, 2], [2, 3]].superjoin(
|
|
611
|
+
%w(<table><tr> </tr><tr> </tr></table>), %w(<td> </td><td> </td>)
|
|
612
|
+
).should eq(
|
|
613
|
+
'<table><tr><td>1</td><td>2</td></tr><tr><td>2</td><td>3</td></tr></table>'
|
|
614
|
+
)
|
|
615
|
+
end
|
|
616
|
+
end
|
|
617
|
+
```
|
|
618
|
+
|
|
619
|
+
Changes: removed `binsearch` test, added `using` for both String and Array extensions.
|
|
620
|
+
|
|
621
|
+
**Step 3: Run array specs**
|
|
622
|
+
|
|
623
|
+
```bash
|
|
624
|
+
bundle exec rspec spec/lib/midwire_common/array_spec.rb
|
|
625
|
+
```
|
|
626
|
+
|
|
627
|
+
Expected: All pass.
|
|
628
|
+
|
|
629
|
+
**Step 4: Commit**
|
|
630
|
+
|
|
631
|
+
```bash
|
|
632
|
+
git add lib/midwire_common/array.rb spec/lib/midwire_common/array_spec.rb
|
|
633
|
+
git commit -m "refactor: convert Array monkey-patch to refinements
|
|
634
|
+
|
|
635
|
+
Remove binsearch (use stdlib Array#bsearch)."
|
|
636
|
+
```
|
|
637
|
+
|
|
638
|
+
---
|
|
639
|
+
|
|
640
|
+
### Task 5: Convert Hash to refinements and namespace BottomlessHash
|
|
641
|
+
|
|
642
|
+
**Files:**
|
|
643
|
+
- Modify: `lib/midwire_common/hash.rb`
|
|
644
|
+
- Modify: `spec/lib/midwire_common/hash_spec.rb`
|
|
645
|
+
|
|
646
|
+
**Step 1: Rewrite hash.rb**
|
|
647
|
+
|
|
648
|
+
Replace full contents of `lib/midwire_common/hash.rb`:
|
|
649
|
+
|
|
650
|
+
```ruby
|
|
651
|
+
require 'uri'
|
|
652
|
+
|
|
653
|
+
module MidwireCommon
|
|
654
|
+
# By Nick Ostrovsky
|
|
655
|
+
# http://firedev.com/posts/2015/bottomless-ruby-hash/
|
|
656
|
+
class BottomlessHash < Hash
|
|
657
|
+
def initialize
|
|
658
|
+
super(&-> (hash, key) { hash[key] = self.class.new })
|
|
659
|
+
end
|
|
660
|
+
|
|
661
|
+
def self.from_hash(hash)
|
|
662
|
+
new.merge(hash)
|
|
663
|
+
end
|
|
664
|
+
end
|
|
665
|
+
|
|
666
|
+
module HashExtensions
|
|
667
|
+
refine Hash do
|
|
668
|
+
def bottomless
|
|
669
|
+
MidwireCommon::BottomlessHash.from_hash(self)
|
|
670
|
+
end
|
|
671
|
+
|
|
672
|
+
def grep(pattern)
|
|
673
|
+
each_with_object([]) do |kv, res|
|
|
674
|
+
res << kv if kv[0] =~ pattern || kv[1] =~ pattern
|
|
675
|
+
res
|
|
676
|
+
end
|
|
677
|
+
end
|
|
678
|
+
|
|
679
|
+
def diff(other)
|
|
680
|
+
(keys + other.keys).uniq.each_with_object({}) do |key, memo|
|
|
681
|
+
unless self[key] == other[key]
|
|
682
|
+
memo[key] = if self[key].is_a?(Hash) && other[key].is_a?(Hash)
|
|
683
|
+
self[key].diff(other[key])
|
|
684
|
+
else
|
|
685
|
+
[self[key], other[key]]
|
|
686
|
+
end
|
|
687
|
+
end
|
|
688
|
+
memo
|
|
689
|
+
end
|
|
690
|
+
end
|
|
691
|
+
|
|
692
|
+
def apply_diff!(changes, direction = :right)
|
|
693
|
+
path = [[self, changes]]
|
|
694
|
+
pos, local_changes = path.pop
|
|
695
|
+
while local_changes
|
|
696
|
+
local_changes.each_pair do |key, change|
|
|
697
|
+
if change.is_a?(Array)
|
|
698
|
+
pos[key] = (direction == :right) ? change[1] : change[0]
|
|
699
|
+
else
|
|
700
|
+
path.push([pos[key], change])
|
|
701
|
+
end
|
|
702
|
+
end
|
|
703
|
+
pos, local_changes = path.pop
|
|
704
|
+
end
|
|
705
|
+
self
|
|
706
|
+
end
|
|
707
|
+
|
|
708
|
+
def apply_diff(changes, direction = :right)
|
|
709
|
+
cloned = clone
|
|
710
|
+
path = [[cloned, changes]]
|
|
711
|
+
pos, local_changes = path.pop
|
|
712
|
+
while local_changes
|
|
713
|
+
local_changes.each_pair do |key, change|
|
|
714
|
+
if change.is_a?(Array)
|
|
715
|
+
pos[key] = (direction == :right) ? change[1] : change[0]
|
|
716
|
+
else
|
|
717
|
+
pos[key] = pos[key].clone
|
|
718
|
+
path.push([pos[key], change])
|
|
719
|
+
end
|
|
720
|
+
end
|
|
721
|
+
pos, local_changes = path.pop
|
|
722
|
+
end
|
|
723
|
+
cloned
|
|
724
|
+
end
|
|
725
|
+
|
|
726
|
+
# Usage { a: 1, b: 2, c: 3 }.only(:a) -> { a: 1 }
|
|
727
|
+
def only(*keys)
|
|
728
|
+
dup.reject { |key, _v| !keys.flatten.include?(key.to_sym) }
|
|
729
|
+
end
|
|
730
|
+
|
|
731
|
+
# Usage h = { a: 1, b: 2, c: 3 }.pop(:a) -> { a: 1 }
|
|
732
|
+
# ... and now h == { b: 2, c: 3 }
|
|
733
|
+
def pop(*keys)
|
|
734
|
+
ret = reject { |key, _v| !keys.flatten.include?(key.to_sym) }
|
|
735
|
+
reject! { |key, _v| keys.flatten.include?(key.to_sym) }
|
|
736
|
+
ret
|
|
737
|
+
end
|
|
738
|
+
|
|
739
|
+
# Usage { a: 1, b: 2, c: 3 }.to_query_string #=> "a=1&b=2&c=3"
|
|
740
|
+
def to_query_string
|
|
741
|
+
URI.encode_www_form(self)
|
|
742
|
+
end
|
|
743
|
+
|
|
744
|
+
def symbolize_keys!
|
|
745
|
+
keys.each do |key|
|
|
746
|
+
self[(key.to_sym rescue key) || key] = delete(key)
|
|
747
|
+
end
|
|
748
|
+
self
|
|
749
|
+
end
|
|
750
|
+
|
|
751
|
+
def symbolize_keys
|
|
752
|
+
dup.symbolize_keys!
|
|
753
|
+
end
|
|
754
|
+
|
|
755
|
+
def recursively_symbolize_keys!
|
|
756
|
+
symbolize_keys!
|
|
757
|
+
values.each do |value|
|
|
758
|
+
value.recursively_symbolize_keys! if value.is_a?(Hash)
|
|
759
|
+
end
|
|
760
|
+
self
|
|
761
|
+
end
|
|
762
|
+
end
|
|
763
|
+
end
|
|
764
|
+
end
|
|
765
|
+
```
|
|
766
|
+
|
|
767
|
+
Changes: removed `except` (built into Ruby 3.0+), replaced `URI.encode` with `URI.encode_www_form`, namespaced `BottomlessHash` under `MidwireCommon`, wrapped in refinement module.
|
|
768
|
+
|
|
769
|
+
**Step 2: Rewrite hash_spec.rb**
|
|
770
|
+
|
|
771
|
+
Replace full contents of `spec/lib/midwire_common/hash_spec.rb`:
|
|
772
|
+
|
|
773
|
+
```ruby
|
|
774
|
+
require 'spec_helper'
|
|
775
|
+
|
|
776
|
+
using MidwireCommon::HashExtensions
|
|
777
|
+
|
|
778
|
+
RSpec.describe MidwireCommon::BottomlessHash do
|
|
779
|
+
subject { described_class.new }
|
|
780
|
+
|
|
781
|
+
it 'does not raise on missing key' do
|
|
782
|
+
expect do
|
|
783
|
+
subject[:missing][:key]
|
|
784
|
+
end.to_not raise_error
|
|
785
|
+
end
|
|
786
|
+
|
|
787
|
+
it 'returns an empty value on missing key' do
|
|
788
|
+
expect(subject[:missing][:key]).to be_empty
|
|
789
|
+
end
|
|
790
|
+
|
|
791
|
+
it 'stores and returns keys' do
|
|
792
|
+
subject[:existing][:key] = :value
|
|
793
|
+
expect(subject[:existing][:key]).to eq(:value)
|
|
794
|
+
end
|
|
795
|
+
|
|
796
|
+
context '#from_hash' do
|
|
797
|
+
let(:hash) do
|
|
798
|
+
{ existing: { key: { value: :hello } } }
|
|
799
|
+
end
|
|
800
|
+
|
|
801
|
+
subject do
|
|
802
|
+
described_class.from_hash(hash)
|
|
803
|
+
end
|
|
804
|
+
|
|
805
|
+
it 'returns old hash values' do
|
|
806
|
+
expect(subject[:existing][:key][:value]).to eq(:hello)
|
|
807
|
+
end
|
|
808
|
+
|
|
809
|
+
it 'provides a bottomless version' do
|
|
810
|
+
expect(subject[:missing][:key]).to be_empty
|
|
811
|
+
end
|
|
812
|
+
|
|
813
|
+
it 'stores and returns new values' do
|
|
814
|
+
subject[:existing][:key] = :value
|
|
815
|
+
expect(subject[:existing][:key]).to eq(:value)
|
|
816
|
+
end
|
|
817
|
+
|
|
818
|
+
it 'converts nested hashes as well' do
|
|
819
|
+
expect do
|
|
820
|
+
subject[:existing][:key][:missing]
|
|
821
|
+
end.to_not raise_error
|
|
822
|
+
end
|
|
823
|
+
end
|
|
824
|
+
end
|
|
825
|
+
|
|
826
|
+
RSpec.describe Hash do
|
|
827
|
+
it 'greps key/value pairs using a regular expression' do
|
|
828
|
+
h = { a: 'this is a test', 'b' => 'this is not the answer' }
|
|
829
|
+
expect(h.grep(/a test/)).to eq([[:a, 'this is a test']])
|
|
830
|
+
expect(h.grep(/b/)).to eq([['b', 'this is not the answer']])
|
|
831
|
+
expect(
|
|
832
|
+
h.grep(/^this/)
|
|
833
|
+
).to eq([[:a, 'this is a test'], ['b', 'this is not the answer']])
|
|
834
|
+
end
|
|
835
|
+
|
|
836
|
+
it 'returns elements for discretely passed keys' do
|
|
837
|
+
expect({ a: 1, b: 2, c: 3 }.only(:a)).to eq(a: 1)
|
|
838
|
+
end
|
|
839
|
+
|
|
840
|
+
it 'pops an element off of the stack' do
|
|
841
|
+
h = { a: 1, b: 2, c: 3 }
|
|
842
|
+
expect(h.pop(:a)).to eq(a: 1)
|
|
843
|
+
expect(h).to eq(b: 2, c: 3)
|
|
844
|
+
end
|
|
845
|
+
|
|
846
|
+
it 'returns a query string' do
|
|
847
|
+
expect({ a: 1, b: 2, c: 3 }.to_query_string).to eq('a=1&b=2&c=3')
|
|
848
|
+
end
|
|
849
|
+
|
|
850
|
+
it 'symbolizes its keys' do
|
|
851
|
+
h = { 'a' => 1, 'b' => 2, 'c' => 3 }
|
|
852
|
+
expect(h.symbolize_keys).to eq(a: 1, b: 2, c: 3)
|
|
853
|
+
h.symbolize_keys!
|
|
854
|
+
expect(h).to eq(a: 1, b: 2, c: 3)
|
|
855
|
+
end
|
|
856
|
+
|
|
857
|
+
it 'recursively symbolizes its keys' do
|
|
858
|
+
h = {
|
|
859
|
+
'a' => 1,
|
|
860
|
+
'b' => {
|
|
861
|
+
'a' => 1,
|
|
862
|
+
'b' => 2,
|
|
863
|
+
'c' => {
|
|
864
|
+
'a' => 1,
|
|
865
|
+
'b' => 2,
|
|
866
|
+
'c' => 3
|
|
867
|
+
}
|
|
868
|
+
},
|
|
869
|
+
'c' => 3
|
|
870
|
+
}
|
|
871
|
+
expect(h.recursively_symbolize_keys!).to eq(
|
|
872
|
+
a: 1, b: { a: 1, b: 2, c: { a: 1, b: 2, c: 3 } }, c: 3
|
|
873
|
+
)
|
|
874
|
+
end
|
|
875
|
+
|
|
876
|
+
context 'diff methods' do
|
|
877
|
+
let(:h1_keys) { { 'a' => 1, 'b' => 2, 'c' => 3 } }
|
|
878
|
+
let(:h2_keys) { { 'a' => 1, 'b' => 2, 'd' => 3 } }
|
|
879
|
+
|
|
880
|
+
let(:h1_keys_nested) { { 'a' => 1, 'b' => 2, { 'x' => 99 } => 3 } }
|
|
881
|
+
let(:h2_keys_nested) { { 'a' => 1, 'b' => 2, { 'x' => 99 } => 4 } }
|
|
882
|
+
|
|
883
|
+
let(:h1_values) { { 'a' => 1, 'b' => 2, 'c' => 3 } }
|
|
884
|
+
let(:h2_values) { { 'a' => 1, 'b' => 2, 'c' => 4 } }
|
|
885
|
+
|
|
886
|
+
context '.diff' do
|
|
887
|
+
it 'reports different keys' do
|
|
888
|
+
expect(h1_keys.diff(h2_keys)).to eq('c' => [3, nil], 'd' => [nil, 3])
|
|
889
|
+
end
|
|
890
|
+
|
|
891
|
+
it 'reports different values' do
|
|
892
|
+
expect(h1_values.diff(h2_values)).to eq('c' => [3, 4])
|
|
893
|
+
end
|
|
894
|
+
|
|
895
|
+
it 'reports different keys even with nested hashes' do
|
|
896
|
+
expect(h1_keys_nested.diff(h2_keys_nested)).to eq(
|
|
897
|
+
{ 'x' => 99 } => [3, 4]
|
|
898
|
+
)
|
|
899
|
+
end
|
|
900
|
+
end
|
|
901
|
+
|
|
902
|
+
context '.apply_diff!' do
|
|
903
|
+
it 'applies diff to the right by default' do
|
|
904
|
+
diff = h1_values.diff(h2_values)
|
|
905
|
+
result = h1_values.apply_diff!(diff)
|
|
906
|
+
expect(result).to eq('a' => 1, 'b' => 2, 'c' => 4)
|
|
907
|
+
expect(h1_values).to eq('a' => 1, 'b' => 2, 'c' => 4)
|
|
908
|
+
end
|
|
909
|
+
|
|
910
|
+
it 'applies diff to the left' do
|
|
911
|
+
diff = h1_values.diff(h2_values)
|
|
912
|
+
result = h2_values.apply_diff!(diff, :left)
|
|
913
|
+
expect(result).to eq('a' => 1, 'b' => 2, 'c' => 3)
|
|
914
|
+
end
|
|
915
|
+
end
|
|
916
|
+
|
|
917
|
+
context '.apply_diff' do
|
|
918
|
+
it 'returns a new hash with diff applied' do
|
|
919
|
+
diff = h1_values.diff(h2_values)
|
|
920
|
+
result = h1_values.apply_diff(diff)
|
|
921
|
+
expect(result).to eq('a' => 1, 'b' => 2, 'c' => 4)
|
|
922
|
+
expect(h1_values).to eq('a' => 1, 'b' => 2, 'c' => 3)
|
|
923
|
+
end
|
|
924
|
+
|
|
925
|
+
it 'applies diff to the left' do
|
|
926
|
+
diff = h1_values.diff(h2_values)
|
|
927
|
+
result = h2_values.apply_diff(diff, :left)
|
|
928
|
+
expect(result).to eq('a' => 1, 'b' => 2, 'c' => 3)
|
|
929
|
+
expect(h2_values).to eq('a' => 1, 'b' => 2, 'c' => 4)
|
|
930
|
+
end
|
|
931
|
+
end
|
|
932
|
+
end
|
|
933
|
+
end
|
|
934
|
+
```
|
|
935
|
+
|
|
936
|
+
Changes: removed `except` test, added `apply_diff` and `apply_diff!` specs, namespaced `BottomlessHash` reference, added `using`.
|
|
937
|
+
|
|
938
|
+
**Step 3: Run hash specs**
|
|
939
|
+
|
|
940
|
+
```bash
|
|
941
|
+
bundle exec rspec spec/lib/midwire_common/hash_spec.rb
|
|
942
|
+
```
|
|
943
|
+
|
|
944
|
+
Expected: All pass.
|
|
945
|
+
|
|
946
|
+
**Step 4: Commit**
|
|
947
|
+
|
|
948
|
+
```bash
|
|
949
|
+
git add lib/midwire_common/hash.rb spec/lib/midwire_common/hash_spec.rb
|
|
950
|
+
git commit -m "refactor: convert Hash monkey-patch to refinements
|
|
951
|
+
|
|
952
|
+
Remove Hash#except (built into Ruby 3.0+).
|
|
953
|
+
Replace URI.encode with URI.encode_www_form.
|
|
954
|
+
Namespace BottomlessHash under MidwireCommon.
|
|
955
|
+
Add specs for apply_diff and apply_diff!."
|
|
956
|
+
```
|
|
957
|
+
|
|
958
|
+
---
|
|
959
|
+
|
|
960
|
+
### Task 6: Convert Integer (replacing Fixnum) to refinements
|
|
961
|
+
|
|
962
|
+
**Files:**
|
|
963
|
+
- Modify: `lib/midwire_common/fixnum.rb` → becomes `lib/midwire_common/integer.rb`
|
|
964
|
+
- Modify: `spec/lib/midwire_common/fixnum_spec.rb` → becomes `spec/lib/midwire_common/integer_spec.rb`
|
|
965
|
+
|
|
966
|
+
**Step 1: Create integer.rb**
|
|
967
|
+
|
|
968
|
+
Create `lib/midwire_common/integer.rb`:
|
|
969
|
+
|
|
970
|
+
```ruby
|
|
971
|
+
require 'midwire_common'
|
|
972
|
+
|
|
973
|
+
module MidwireCommon
|
|
974
|
+
module IntegerExtensions
|
|
975
|
+
refine Integer do
|
|
976
|
+
include MidwireCommon::NumberBehavior
|
|
977
|
+
end
|
|
978
|
+
end
|
|
979
|
+
end
|
|
980
|
+
```
|
|
981
|
+
|
|
982
|
+
**Step 2: Delete fixnum.rb**
|
|
983
|
+
|
|
984
|
+
```bash
|
|
985
|
+
git rm lib/midwire_common/fixnum.rb
|
|
986
|
+
```
|
|
987
|
+
|
|
988
|
+
**Step 3: Create integer_spec.rb**
|
|
989
|
+
|
|
990
|
+
Create `spec/lib/midwire_common/integer_spec.rb`:
|
|
991
|
+
|
|
992
|
+
```ruby
|
|
993
|
+
require 'spec_helper'
|
|
994
|
+
|
|
995
|
+
using MidwireCommon::IntegerExtensions
|
|
996
|
+
|
|
997
|
+
RSpec.describe Integer do
|
|
998
|
+
it 'can format itself with commas' do
|
|
999
|
+
8_729_928_827.commify.should == '8,729,928,827'
|
|
1000
|
+
end
|
|
1001
|
+
end
|
|
1002
|
+
```
|
|
1003
|
+
|
|
1004
|
+
Note: removed `odd?`/`even?` tests — those are built into Integer.
|
|
1005
|
+
|
|
1006
|
+
**Step 4: Delete fixnum_spec.rb**
|
|
1007
|
+
|
|
1008
|
+
```bash
|
|
1009
|
+
git rm spec/lib/midwire_common/fixnum_spec.rb
|
|
1010
|
+
```
|
|
1011
|
+
|
|
1012
|
+
**Step 5: Run integer specs**
|
|
1013
|
+
|
|
1014
|
+
```bash
|
|
1015
|
+
bundle exec rspec spec/lib/midwire_common/integer_spec.rb
|
|
1016
|
+
```
|
|
1017
|
+
|
|
1018
|
+
Expected: All pass.
|
|
1019
|
+
|
|
1020
|
+
**Step 6: Commit**
|
|
1021
|
+
|
|
1022
|
+
```bash
|
|
1023
|
+
git add lib/midwire_common/integer.rb spec/lib/midwire_common/integer_spec.rb
|
|
1024
|
+
git commit -m "refactor: replace Fixnum with Integer refinements
|
|
1025
|
+
|
|
1026
|
+
Fixnum was unified into Integer in Ruby 2.4.
|
|
1027
|
+
Remove odd?/even? tests (built-in)."
|
|
1028
|
+
```
|
|
1029
|
+
|
|
1030
|
+
---
|
|
1031
|
+
|
|
1032
|
+
### Task 7: Convert Float to refinements
|
|
1033
|
+
|
|
1034
|
+
**Files:**
|
|
1035
|
+
- Modify: `lib/midwire_common/float.rb`
|
|
1036
|
+
- Modify: `spec/lib/midwire_common/float_spec.rb`
|
|
1037
|
+
|
|
1038
|
+
**Step 1: Rewrite float.rb**
|
|
1039
|
+
|
|
1040
|
+
Replace full contents of `lib/midwire_common/float.rb`:
|
|
1041
|
+
|
|
1042
|
+
```ruby
|
|
1043
|
+
require 'midwire_common'
|
|
1044
|
+
|
|
1045
|
+
module MidwireCommon
|
|
1046
|
+
module FloatExtensions
|
|
1047
|
+
refine Float do
|
|
1048
|
+
include MidwireCommon::NumberBehavior
|
|
1049
|
+
end
|
|
1050
|
+
end
|
|
1051
|
+
end
|
|
1052
|
+
```
|
|
1053
|
+
|
|
1054
|
+
**Step 2: Rewrite float_spec.rb**
|
|
1055
|
+
|
|
1056
|
+
Replace full contents of `spec/lib/midwire_common/float_spec.rb`:
|
|
1057
|
+
|
|
1058
|
+
```ruby
|
|
1059
|
+
require 'spec_helper'
|
|
1060
|
+
|
|
1061
|
+
using MidwireCommon::FloatExtensions
|
|
1062
|
+
|
|
1063
|
+
RSpec.describe Float do
|
|
1064
|
+
it 'can format itself with commas' do
|
|
1065
|
+
8_729_928_827.0.commify.should eq('8,729,928,827.0')
|
|
1066
|
+
8_729_928_827.20332002.commify.should eq('8,729,928,827.20332')
|
|
1067
|
+
end
|
|
1068
|
+
end
|
|
1069
|
+
```
|
|
1070
|
+
|
|
1071
|
+
**Step 3: Run float specs**
|
|
1072
|
+
|
|
1073
|
+
```bash
|
|
1074
|
+
bundle exec rspec spec/lib/midwire_common/float_spec.rb
|
|
1075
|
+
```
|
|
1076
|
+
|
|
1077
|
+
Expected: All pass.
|
|
1078
|
+
|
|
1079
|
+
**Step 4: Commit**
|
|
1080
|
+
|
|
1081
|
+
```bash
|
|
1082
|
+
git add lib/midwire_common/float.rb spec/lib/midwire_common/float_spec.rb
|
|
1083
|
+
git commit -m "refactor: convert Float monkey-patch to refinements"
|
|
1084
|
+
```
|
|
1085
|
+
|
|
1086
|
+
---
|
|
1087
|
+
|
|
1088
|
+
### Task 8: Convert Enumerable to refinements
|
|
1089
|
+
|
|
1090
|
+
**Files:**
|
|
1091
|
+
- Modify: `lib/midwire_common/enumerable.rb`
|
|
1092
|
+
- Modify: `spec/lib/midwire_common/enumerable_spec.rb`
|
|
1093
|
+
|
|
1094
|
+
**Step 1: Rewrite enumerable.rb**
|
|
1095
|
+
|
|
1096
|
+
Replace full contents of `lib/midwire_common/enumerable.rb`:
|
|
1097
|
+
|
|
1098
|
+
```ruby
|
|
1099
|
+
module MidwireCommon
|
|
1100
|
+
module EnumerableExtensions
|
|
1101
|
+
refine Enumerable do
|
|
1102
|
+
def sort_by_frequency
|
|
1103
|
+
histogram = each_with_object(Hash.new(0)) do |elem, hash|
|
|
1104
|
+
hash[elem] += 1
|
|
1105
|
+
hash
|
|
1106
|
+
end
|
|
1107
|
+
sort_by { |elem| [histogram[elem] * -1, elem] }
|
|
1108
|
+
end
|
|
1109
|
+
end
|
|
1110
|
+
end
|
|
1111
|
+
end
|
|
1112
|
+
```
|
|
1113
|
+
|
|
1114
|
+
**Step 2: Rewrite enumerable_spec.rb**
|
|
1115
|
+
|
|
1116
|
+
Replace full contents of `spec/lib/midwire_common/enumerable_spec.rb`:
|
|
1117
|
+
|
|
1118
|
+
```ruby
|
|
1119
|
+
require 'spec_helper'
|
|
1120
|
+
|
|
1121
|
+
using MidwireCommon::EnumerableExtensions
|
|
1122
|
+
|
|
1123
|
+
RSpec.describe Enumerable do
|
|
1124
|
+
it 'can sort by frequency of occurrences' do
|
|
1125
|
+
[1, 2, 3, 3, 3, 3, 2].sort_by_frequency.should eq([3, 3, 3, 3, 2, 2, 1])
|
|
1126
|
+
%w(a b c d e f a f f b f a).sort_by_frequency
|
|
1127
|
+
.should eq(%w(f f f f a a a b b c d e))
|
|
1128
|
+
end
|
|
1129
|
+
end
|
|
1130
|
+
```
|
|
1131
|
+
|
|
1132
|
+
**Step 3: Run enumerable specs**
|
|
1133
|
+
|
|
1134
|
+
```bash
|
|
1135
|
+
bundle exec rspec spec/lib/midwire_common/enumerable_spec.rb
|
|
1136
|
+
```
|
|
1137
|
+
|
|
1138
|
+
Expected: All pass.
|
|
1139
|
+
|
|
1140
|
+
**Step 4: Commit**
|
|
1141
|
+
|
|
1142
|
+
```bash
|
|
1143
|
+
git add lib/midwire_common/enumerable.rb spec/lib/midwire_common/enumerable_spec.rb
|
|
1144
|
+
git commit -m "refactor: convert Enumerable monkey-patch to refinements"
|
|
1145
|
+
```
|
|
1146
|
+
|
|
1147
|
+
---
|
|
1148
|
+
|
|
1149
|
+
### Task 9: Convert Time to refinements
|
|
1150
|
+
|
|
1151
|
+
**Files:**
|
|
1152
|
+
- Modify: `lib/midwire_common/time.rb`
|
|
1153
|
+
- Modify: `spec/lib/midwire_common/time_spec.rb`
|
|
1154
|
+
|
|
1155
|
+
**Step 1: Rewrite time.rb**
|
|
1156
|
+
|
|
1157
|
+
Replace full contents of `lib/midwire_common/time.rb`:
|
|
1158
|
+
|
|
1159
|
+
```ruby
|
|
1160
|
+
module MidwireCommon
|
|
1161
|
+
module TimeExtensions
|
|
1162
|
+
refine Time.singleton_class do
|
|
1163
|
+
def timestamp(resolution = 3)
|
|
1164
|
+
return Time.now.strftime('%Y%m%d%H%M%S') if resolution < 1
|
|
1165
|
+
Time.now.strftime("%Y%m%d%H%M%S.%#{resolution}N")
|
|
1166
|
+
end
|
|
1167
|
+
end
|
|
1168
|
+
end
|
|
1169
|
+
end
|
|
1170
|
+
```
|
|
1171
|
+
|
|
1172
|
+
**Step 2: Rewrite time_spec.rb**
|
|
1173
|
+
|
|
1174
|
+
Replace full contents of `spec/lib/midwire_common/time_spec.rb`:
|
|
1175
|
+
|
|
1176
|
+
```ruby
|
|
1177
|
+
require 'spec_helper'
|
|
1178
|
+
|
|
1179
|
+
using MidwireCommon::StringExtensions
|
|
1180
|
+
using MidwireCommon::TimeExtensions
|
|
1181
|
+
|
|
1182
|
+
RSpec.describe Time do
|
|
1183
|
+
context 'generates a timestamp' do
|
|
1184
|
+
it 'appropriate for a filename' do
|
|
1185
|
+
ts = Time.timestamp
|
|
1186
|
+
expect(ts.length).to eq(18)
|
|
1187
|
+
expect(ts.numeric?).to eq(true)
|
|
1188
|
+
end
|
|
1189
|
+
|
|
1190
|
+
it 'with the only seconds' do
|
|
1191
|
+
ts = Time.timestamp(0)
|
|
1192
|
+
expect(ts.length).to eq(14)
|
|
1193
|
+
expect(ts.numeric?).to eq(true)
|
|
1194
|
+
end
|
|
1195
|
+
|
|
1196
|
+
it 'defaults to nanosecond resolution' do
|
|
1197
|
+
ts = Time.timestamp
|
|
1198
|
+
expect(ts.length).to eq(18)
|
|
1199
|
+
expect(ts.numeric?).to eq(true)
|
|
1200
|
+
end
|
|
1201
|
+
|
|
1202
|
+
it 'with the millisecond resolution' do
|
|
1203
|
+
ts = Time.timestamp(3)
|
|
1204
|
+
expect(ts.length).to eq(18)
|
|
1205
|
+
expect(ts.numeric?).to eq(true)
|
|
1206
|
+
end
|
|
1207
|
+
|
|
1208
|
+
it 'with the microsecond resolution' do
|
|
1209
|
+
ts = Time.timestamp(6)
|
|
1210
|
+
expect(ts.length).to eq(21)
|
|
1211
|
+
expect(ts.numeric?).to eq(true)
|
|
1212
|
+
end
|
|
1213
|
+
|
|
1214
|
+
it 'with the nanosecond resolution' do
|
|
1215
|
+
ts = Time.timestamp(9)
|
|
1216
|
+
expect(ts.length).to eq(24)
|
|
1217
|
+
expect(ts.numeric?).to eq(true)
|
|
1218
|
+
end
|
|
1219
|
+
|
|
1220
|
+
it 'with the picosecond resolution' do
|
|
1221
|
+
ts = Time.timestamp(12)
|
|
1222
|
+
expect(ts.length).to eq(27)
|
|
1223
|
+
expect(ts.numeric?).to eq(true)
|
|
1224
|
+
end
|
|
1225
|
+
end
|
|
1226
|
+
end
|
|
1227
|
+
```
|
|
1228
|
+
|
|
1229
|
+
Note: needs `using MidwireCommon::StringExtensions` because tests call `numeric?` on the timestamp strings.
|
|
1230
|
+
|
|
1231
|
+
**Step 3: Run time specs**
|
|
1232
|
+
|
|
1233
|
+
```bash
|
|
1234
|
+
bundle exec rspec spec/lib/midwire_common/time_spec.rb
|
|
1235
|
+
```
|
|
1236
|
+
|
|
1237
|
+
Expected: All pass.
|
|
1238
|
+
|
|
1239
|
+
**Step 4: Commit**
|
|
1240
|
+
|
|
1241
|
+
```bash
|
|
1242
|
+
git add lib/midwire_common/time.rb spec/lib/midwire_common/time_spec.rb
|
|
1243
|
+
git commit -m "refactor: convert Time monkey-patch to refinements"
|
|
1244
|
+
```
|
|
1245
|
+
|
|
1246
|
+
---
|
|
1247
|
+
|
|
1248
|
+
### Task 10: Convert File to refinements
|
|
1249
|
+
|
|
1250
|
+
**Files:**
|
|
1251
|
+
- Modify: `lib/midwire_common/file.rb`
|
|
1252
|
+
- Delete: `lib/midwire_common/file/stat.rb` (merge into file.rb)
|
|
1253
|
+
- Modify: `spec/lib/midwire_common/file/stat_spec.rb`
|
|
1254
|
+
|
|
1255
|
+
**Step 1: Rewrite file.rb (consolidating file/stat.rb)**
|
|
1256
|
+
|
|
1257
|
+
Replace full contents of `lib/midwire_common/file.rb`:
|
|
1258
|
+
|
|
1259
|
+
```ruby
|
|
1260
|
+
module MidwireCommon
|
|
1261
|
+
module FileExtensions
|
|
1262
|
+
refine File::Stat.singleton_class do
|
|
1263
|
+
def device_name(file)
|
|
1264
|
+
Dir['/dev/*'].inject({}) do |hash, node|
|
|
1265
|
+
hash.update(File.stat(node).rdev => node)
|
|
1266
|
+
end.values_at(File.stat(file).dev).first || nil
|
|
1267
|
+
end
|
|
1268
|
+
end
|
|
1269
|
+
end
|
|
1270
|
+
end
|
|
1271
|
+
```
|
|
1272
|
+
|
|
1273
|
+
**Step 2: Delete file/stat.rb**
|
|
1274
|
+
|
|
1275
|
+
```bash
|
|
1276
|
+
git rm lib/midwire_common/file/stat.rb
|
|
1277
|
+
rmdir lib/midwire_common/file 2>/dev/null || true
|
|
1278
|
+
```
|
|
1279
|
+
|
|
1280
|
+
**Step 3: Rewrite stat_spec.rb**
|
|
1281
|
+
|
|
1282
|
+
Replace full contents of `spec/lib/midwire_common/file/stat_spec.rb`:
|
|
1283
|
+
|
|
1284
|
+
```ruby
|
|
1285
|
+
require 'spec_helper'
|
|
1286
|
+
|
|
1287
|
+
using MidwireCommon::FileExtensions
|
|
1288
|
+
|
|
1289
|
+
RSpec.describe File::Stat do
|
|
1290
|
+
it 'knows on which device it resides' do
|
|
1291
|
+
dev = File::Stat.device_name('/tmp')
|
|
1292
|
+
dev.should_not be_nil
|
|
1293
|
+
end
|
|
1294
|
+
end
|
|
1295
|
+
```
|
|
1296
|
+
|
|
1297
|
+
**Step 4: Run file specs**
|
|
1298
|
+
|
|
1299
|
+
```bash
|
|
1300
|
+
bundle exec rspec spec/lib/midwire_common/file/stat_spec.rb
|
|
1301
|
+
```
|
|
1302
|
+
|
|
1303
|
+
Expected: All pass.
|
|
1304
|
+
|
|
1305
|
+
**Step 5: Commit**
|
|
1306
|
+
|
|
1307
|
+
```bash
|
|
1308
|
+
git add lib/midwire_common/file.rb spec/lib/midwire_common/file/stat_spec.rb
|
|
1309
|
+
git commit -m "refactor: convert File::Stat monkey-patch to refinements
|
|
1310
|
+
|
|
1311
|
+
Consolidate file/stat.rb into file.rb."
|
|
1312
|
+
```
|
|
1313
|
+
|
|
1314
|
+
---
|
|
1315
|
+
|
|
1316
|
+
### Task 11: Update all.rb and midwire_common.rb
|
|
1317
|
+
|
|
1318
|
+
**Files:**
|
|
1319
|
+
- Modify: `lib/midwire_common/all.rb`
|
|
1320
|
+
- Modify: `lib/midwire_common.rb`
|
|
1321
|
+
|
|
1322
|
+
**Step 1: Rewrite all.rb**
|
|
1323
|
+
|
|
1324
|
+
Replace full contents of `lib/midwire_common/all.rb`:
|
|
1325
|
+
|
|
1326
|
+
```ruby
|
|
1327
|
+
require 'midwire_common/array'
|
|
1328
|
+
require 'midwire_common/enumerable'
|
|
1329
|
+
require 'midwire_common/data_file_cache'
|
|
1330
|
+
require 'midwire_common/file'
|
|
1331
|
+
require 'midwire_common/integer'
|
|
1332
|
+
require 'midwire_common/float'
|
|
1333
|
+
require 'midwire_common/hash'
|
|
1334
|
+
require 'midwire_common/string'
|
|
1335
|
+
require 'midwire_common/time'
|
|
1336
|
+
require 'midwire_common/time_tool'
|
|
1337
|
+
require 'midwire_common/yaml_setting'
|
|
1338
|
+
|
|
1339
|
+
module MidwireCommon
|
|
1340
|
+
module All
|
|
1341
|
+
include MidwireCommon::StringExtensions
|
|
1342
|
+
include MidwireCommon::ArrayExtensions
|
|
1343
|
+
include MidwireCommon::HashExtensions
|
|
1344
|
+
include MidwireCommon::IntegerExtensions
|
|
1345
|
+
include MidwireCommon::FloatExtensions
|
|
1346
|
+
include MidwireCommon::EnumerableExtensions
|
|
1347
|
+
include MidwireCommon::TimeExtensions
|
|
1348
|
+
include MidwireCommon::FileExtensions
|
|
1349
|
+
end
|
|
1350
|
+
end
|
|
1351
|
+
```
|
|
1352
|
+
|
|
1353
|
+
Key change: `fixnum` → `integer`. Adds `MidwireCommon::All` module composing all refinement modules so consumers can `using MidwireCommon::All`.
|
|
1354
|
+
|
|
1355
|
+
**Step 2: Update midwire_common.rb**
|
|
1356
|
+
|
|
1357
|
+
Replace full contents of `lib/midwire_common.rb`:
|
|
1358
|
+
|
|
1359
|
+
```ruby
|
|
1360
|
+
require 'pathname'
|
|
1361
|
+
require 'midwire_common/version'
|
|
1362
|
+
|
|
1363
|
+
module MidwireCommon
|
|
1364
|
+
class << self
|
|
1365
|
+
def root
|
|
1366
|
+
Pathname.new(File.dirname(__FILE__)).parent
|
|
1367
|
+
end
|
|
1368
|
+
end
|
|
1369
|
+
|
|
1370
|
+
autoload :BottomlessHash, 'midwire_common/hash'
|
|
1371
|
+
autoload :DataFileCache, 'midwire_common/data_file_cache'
|
|
1372
|
+
autoload :NumberBehavior, 'midwire_common/number_behavior'
|
|
1373
|
+
autoload :RakeHelper, 'midwire_common/rake_helper'
|
|
1374
|
+
autoload :TimeTool, 'midwire_common/time_tool'
|
|
1375
|
+
autoload :YamlSetting, 'midwire_common/yaml_setting'
|
|
1376
|
+
end
|
|
1377
|
+
```
|
|
1378
|
+
|
|
1379
|
+
Added autoloads for all utility classes so they're accessible without requiring `all.rb`.
|
|
1380
|
+
|
|
1381
|
+
**Step 3: Run full test suite**
|
|
1382
|
+
|
|
1383
|
+
```bash
|
|
1384
|
+
bundle exec rspec
|
|
1385
|
+
```
|
|
1386
|
+
|
|
1387
|
+
Expected: All tests pass.
|
|
1388
|
+
|
|
1389
|
+
**Step 4: Commit**
|
|
1390
|
+
|
|
1391
|
+
```bash
|
|
1392
|
+
git add lib/midwire_common/all.rb lib/midwire_common.rb
|
|
1393
|
+
git commit -m "refactor: update all.rb with composed refinements module
|
|
1394
|
+
|
|
1395
|
+
Add MidwireCommon::All for 'using MidwireCommon::All'.
|
|
1396
|
+
Add autoloads for utility classes."
|
|
1397
|
+
```
|
|
1398
|
+
|
|
1399
|
+
---
|
|
1400
|
+
|
|
1401
|
+
### Task 12: Update README and CLAUDE.md
|
|
1402
|
+
|
|
1403
|
+
**Files:**
|
|
1404
|
+
- Modify: `README.md`
|
|
1405
|
+
- Modify: `CLAUDE.md`
|
|
1406
|
+
|
|
1407
|
+
**Step 1: Update README.md**
|
|
1408
|
+
|
|
1409
|
+
Update the version string and usage section in `README.md` to reflect the new refinements API:
|
|
1410
|
+
|
|
1411
|
+
- Change `**Version: 1.1.1**` to `**Version: 2.0.0**`
|
|
1412
|
+
- Update the Usage section to show `using` instead of `require`:
|
|
1413
|
+
|
|
1414
|
+
```markdown
|
|
1415
|
+
## Usage
|
|
1416
|
+
|
|
1417
|
+
### Ruby Class Extensions
|
|
1418
|
+
|
|
1419
|
+
Require the extensions, then activate them with `using`:
|
|
1420
|
+
|
|
1421
|
+
require 'midwire_common/all'
|
|
1422
|
+
using MidwireCommon::All
|
|
1423
|
+
|
|
1424
|
+
... or include individual refinement modules:
|
|
1425
|
+
|
|
1426
|
+
require 'midwire_common/string'
|
|
1427
|
+
using MidwireCommon::StringExtensions
|
|
1428
|
+
```
|
|
1429
|
+
|
|
1430
|
+
- Update the `required_ruby_version` note if present
|
|
1431
|
+
|
|
1432
|
+
**Step 2: Update CLAUDE.md**
|
|
1433
|
+
|
|
1434
|
+
Update loading section to document the `using` pattern and the `MidwireCommon::All` module.
|
|
1435
|
+
|
|
1436
|
+
**Step 3: Commit**
|
|
1437
|
+
|
|
1438
|
+
```bash
|
|
1439
|
+
git add README.md CLAUDE.md
|
|
1440
|
+
git commit -m "docs: update README and CLAUDE.md for 2.0.0 refinements API"
|
|
1441
|
+
```
|
|
1442
|
+
|
|
1443
|
+
---
|
|
1444
|
+
|
|
1445
|
+
### Task 13: Final verification
|
|
1446
|
+
|
|
1447
|
+
**Step 1: Run full test suite**
|
|
1448
|
+
|
|
1449
|
+
```bash
|
|
1450
|
+
bundle exec rspec
|
|
1451
|
+
```
|
|
1452
|
+
|
|
1453
|
+
Expected: All tests pass, zero failures.
|
|
1454
|
+
|
|
1455
|
+
**Step 2: Run tests with coverage**
|
|
1456
|
+
|
|
1457
|
+
```bash
|
|
1458
|
+
COVERAGE=1 bundle exec rspec
|
|
1459
|
+
```
|
|
1460
|
+
|
|
1461
|
+
Review the coverage report for any gaps.
|
|
1462
|
+
|
|
1463
|
+
**Step 3: Verify the gem builds**
|
|
1464
|
+
|
|
1465
|
+
```bash
|
|
1466
|
+
gem build midwire_common.gemspec
|
|
1467
|
+
```
|
|
1468
|
+
|
|
1469
|
+
Expected: `midwire_common-2.0.0.gem` built successfully.
|
|
1470
|
+
|
|
1471
|
+
**Step 4: Clean up gem file**
|
|
1472
|
+
|
|
1473
|
+
```bash
|
|
1474
|
+
rm midwire_common-2.0.0.gem
|
|
1475
|
+
```
|
|
1476
|
+
|
|
1477
|
+
---
|
|
1478
|
+
|
|
1479
|
+
## Task Dependency Graph
|
|
1480
|
+
|
|
1481
|
+
```
|
|
1482
|
+
Task 1 (infrastructure) ──→ Task 2 (spec_helper) ──→ Task 3 (String) ──→ Task 4 (Array)
|
|
1483
|
+
│ │
|
|
1484
|
+
├→ Task 5 (Hash) │
|
|
1485
|
+
├→ Task 6 (Integer) │
|
|
1486
|
+
├→ Task 7 (Float) │
|
|
1487
|
+
├→ Task 8 (Enum) │
|
|
1488
|
+
├→ Task 9 (Time) │
|
|
1489
|
+
└→ Task 10 (File) │
|
|
1490
|
+
│
|
|
1491
|
+
Tasks 4-10 all complete ──→ Task 11 (all.rb) ──→ Task 12 (docs) ──→ Task 13 (verify)
|
|
1492
|
+
```
|
|
1493
|
+
|
|
1494
|
+
Tasks 4-10 can be executed in parallel after Task 3 completes (they don't depend on each other). Task 11 depends on all of them completing.
|