minitest-holdify 1.1.0 → 1.1.2

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c91100bb90de5695419fdb279520529462f67f37d2e580ce6d61c496cf43cbcb
4
- data.tar.gz: 48b9d63b74bdea68de55c7a2790e8fb1fef59dc1eb956bce96b02d7ef0b141b3
3
+ metadata.gz: 633215ac6e960bb4fe8e63dbf22d1247b9b684da3a2300611882b5a1251ca0ad
4
+ data.tar.gz: a80e8995767b2b3438dc809f494a473e4376da664924ba6ee117aa26ccc2d274
5
5
  SHA512:
6
- metadata.gz: c17c6cfea957cbad41b2f272da130226a1c878546b46559ec80ccb1f939cb8c395c3b91a91e98ada8a1760bf3b661568364f6ca22f2e34ac957961c6011b4a40
7
- data.tar.gz: 89f7c53a3ae05ef7e302efff24398e1579067d246a913c2ca91029185e08af9d8b4dae35da3a6686def351ce6fa89b0b9da1a34d388526dfa1455f5a5950e170
6
+ metadata.gz: '0129f206eddc8ba5683d64e6ac5c4273dd83818a43ef636a5493706816143b6b35c567bcb407aed623c3c761f0a1d954ac23b2c691c6e81b6f1affcdad0d4ac5'
7
+ data.tar.gz: e1980a9bfd4f392c40c13194bb35c6d375f26bcef76a93ac4d1173305da5d3c557e8ecf69e659194ce1515b89b8ae2f423f204db883ff0c092c13db467c9e395
data/lib/holdify/hold.rb CHANGED
@@ -9,50 +9,39 @@ module Holdify
9
9
  @test = test
10
10
  @path, = test.method(test.name).source_location
11
11
  @store = Holdify.stores[@path] ||= Store.new(@path)
12
- @session = Hash.new { |h, k| h[k] = [] } # { lineno => [values] }
13
- @forced = [] # [ "file:lineno" ]
14
- @added = [] # [ "file:lineno" ]
15
- @index = {} # { lineno => index }
16
- @counts = Hash.new(0) # { id => count }
12
+ @session = Hash.new { |h, k| h[k] = [] } # { line => [values] }
13
+ @forced = [] # [ "file:line" ]
14
+ @added = [] # [ "file:line" ]
17
15
  end
18
16
 
19
17
  def call(actual, force: false)
20
18
  location = find_location
21
- lineno = location.lineno
22
- id = @store.id_at(lineno)
23
- raise "Could not find holdify statement at line #{lineno}" unless id
19
+ line = location.lineno
20
+ raise "Could not find holdify statement at line #{line}" unless @store.xxh_at(line)
24
21
 
25
- unless @index.key?(lineno)
26
- @index[lineno] = @counts[id]
27
- @counts[id] += 1
28
- end
29
- index = @index[lineno]
30
-
31
- @session[lineno] << actual
32
- @forced << "#{location.path}:#{lineno}" if force
22
+ @session[line] << actual
23
+ @forced << "#{@path}:#{line}" if force
33
24
 
34
25
  return actual if force || Holdify.reconcile
35
26
 
36
- stored = @store.stored(id, index)
37
- index = @session[lineno].size - 1
38
- return stored[index] if stored && index < stored.size
27
+ # Expected value
28
+ values = @store.get(line)
29
+ index = @session[line].size - 1
30
+ return values[index] if values && index < values.size
39
31
 
40
- @added << "#{location.path}:#{lineno}"
32
+ @added << "#{@path}:#{line}"
41
33
  actual
42
34
  end
43
35
 
44
36
  def save
45
37
  return unless @test.failures.empty?
46
38
 
47
- @added.each { |loc| warn "[holdify] Held new value for #{loc}" } unless Holdify.quiet
48
- @session.each do |lineno, values|
49
- id = @store.id_at(lineno)
50
- index = @index[lineno]
51
- @store.update(lineno, id, values, index)
52
- end
39
+ @added.each { |loc| warn "[holdify] Held new value for #{loc}" } unless Holdify.quiet
40
+ @session.each { |line, values| @store.set(line, values) }
53
41
  @store.save
54
42
  end
55
43
 
44
+ # Find the location in the test that triggered the hold
56
45
  def find_location
57
46
  caller_locations.find do |location|
58
47
  next unless location.path == @path
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'yaml'
4
+ require 'fileutils'
5
+
6
+ module Holdify
7
+ # Manages the persistence and alignment of stored values
8
+ class Ledger
9
+ def initialize(path, source)
10
+ @path = "#{path}#{CONFIG[:ext]}"
11
+ @source = source
12
+ File.delete(@path) if Holdify.reconcile && File.exist?(@path)
13
+ @data = load_and_align
14
+ end
15
+
16
+ def get(line) = @data[line]
17
+
18
+ def set(line, values) = (@data[line] = values)
19
+
20
+ def save
21
+ return FileUtils.rm_f(@path) if @data.empty?
22
+
23
+ output = {}
24
+ @data.keys.sort.each do |line|
25
+ sha = @source.xxh_at(line)
26
+ next unless sha
27
+
28
+ output["L#{line} #{sha}"] = @data[line]
29
+ end
30
+
31
+ content = YAML.dump(output, line_width: 78) # Ensure 80 columns (including pretty gutter)
32
+ return if File.exist?(@path) && File.read(@path) == content
33
+
34
+ File.write(@path, content)
35
+ end
36
+
37
+ private
38
+
39
+ def load_and_align
40
+ {}.tap do |aligned|
41
+ raw_data = (File.exist?(@path) && YAML.unsafe_load_file(@path)) || {}
42
+ raw_data.group_by { |k, _| k.split.last }.each do |sha, entries|
43
+ target_lines = @source.lines_with(sha)
44
+ next if target_lines.empty?
45
+
46
+ # Old data
47
+ candidates = entries.map { |key, values| { line: key[/\d+/].to_i, values: values } }
48
+ exact, moved = candidates.partition { |c| target_lines.include?(c[:line]) }
49
+ moved.sort_by! { |c| c[:line] }
50
+
51
+ # New aligned data
52
+ target_lines.each do |line|
53
+ match = exact.find { |c| c[:line] == line } || moved.shift
54
+ aligned[line] = match[:values] if match
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
@@ -55,9 +55,9 @@ module Holdify
55
55
  exp_buf << text
56
56
  act_buf << text
57
57
  when '-'
58
- exp_buf << RED_FG << text << RESET
58
+ exp_buf << "#{RED_FG}#{text}#{RESET}"
59
59
  when '+'
60
- act_buf << GREEN_FG << text << RESET
60
+ act_buf << "#{GREEN_FG}#{text}#{RESET}"
61
61
  when '~'
62
62
  flush_buffers(output, exp_buf, act_buf)
63
63
  exp_buf.clear
@@ -81,7 +81,7 @@ module Holdify
81
81
 
82
82
  def create_tempfile(obj)
83
83
  Tempfile.new(%w[holdify .yaml]).tap do |file|
84
- file.write(YAML.dump(obj, line_width: 78)) # Ensure 80 columns (including gutter)
84
+ file.write(YAML.dump(obj, line_width: 78)) # Ensure 80 columns (including pretty gutter)
85
85
  file.flush
86
86
  end
87
87
  end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'xxhash'
4
+
5
+ module Holdify
6
+ # Represents the current state of the source file
7
+ class Source
8
+ def initialize(path) = (@line_sha, @sha_lines = parse(path))
9
+
10
+ def xxh_at(line) = @line_sha[line]
11
+
12
+ def lines_with(sha) = @sha_lines[sha]
13
+
14
+ private
15
+
16
+ def parse(path)
17
+ line_sha = {}
18
+ sha_lines = Hash.new { |h, k| h[k] = [] }
19
+
20
+ File.foreach(path).with_index(1) do |text, line|
21
+ content = text.strip
22
+ next if content.empty?
23
+
24
+ sha = XXhash.xxh32(content).to_s
25
+ line_sha[line] = sha
26
+ sha_lines[sha] << line
27
+ end
28
+
29
+ [line_sha, sha_lines]
30
+ end
31
+ end
32
+ end
data/lib/holdify/store.rb CHANGED
@@ -1,68 +1,20 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'yaml'
4
- require 'fileutils'
5
- require 'digest/sha1'
3
+ require 'forwardable'
4
+ require_relative 'source'
5
+ require_relative 'ledger'
6
6
 
7
7
  module Holdify
8
- # A simple Hash-based store that syncs with a static source map
8
+ # Interface to the Source (code) and the Ledger (data)
9
9
  class Store
10
- def initialize(source_path)
11
- @path = "#{source_path}#{CONFIG[:ext]}"
12
- File.delete(@path) if Holdify.reconcile && File.exist?(@path)
10
+ extend Forwardable
13
11
 
14
- @source = {} # { lineno => id }
15
- File.foreach(source_path).with_index(1) do |line, lineno|
16
- content = line.strip
17
- @source[lineno] = Digest::SHA1.hexdigest(content) unless content.empty?
18
- end
19
-
20
- @index = Hash.new { |h, k| h[k] = [] } # { id => ["L123 id", "L124 id"] }
21
- @data = (File.exist?(@path) && YAML.unsafe_load_file(@path)) || {} # { key => [values] }
22
-
23
- organize_data
24
- end
25
-
26
- def id_at(lineno) = @source[lineno]
27
- def stored(id, index) = @data[@index[id][index]]
28
-
29
- # Overwrite the entry for a given ID with a new list of values
30
- def update(lineno, id, values, index)
31
- new_key = "L#{lineno} #{id}"
32
- old_key = @index[id][index]
33
- @data.delete(old_key) if old_key && old_key != new_key
34
- @data[new_key] = values
35
- @index[id][index] = new_key
36
- end
37
-
38
- def save
39
- return FileUtils.rm_f(@path) if @data.empty?
40
-
41
- sorted = @data.sort_by { |k, _| k[/\d+/].to_i }.to_h
42
- content = YAML.dump(sorted, line_width: 78)
43
- return if File.exist?(@path) && File.read(@path) == content
44
-
45
- File.write(@path, content)
12
+ def initialize(path)
13
+ @source = Source.new(path)
14
+ @ledger = Ledger.new(path, @source)
46
15
  end
47
16
 
48
- private
49
-
50
- def organize_data
51
- source_counts = @source.values.tally
52
- sorted_keys = @data.keys.sort_by { |k| k[/\d+/].to_i }
53
-
54
- sorted_keys.each do |key|
55
- id = key.split.last
56
- next unless source_counts[id]
57
-
58
- if @index[id].size < source_counts[id]
59
- @index[id] << key
60
- else
61
- @data.delete(key)
62
- end
63
- end
64
-
65
- @data.keep_if { |key, _| source_counts[key.split.last] }
66
- end
17
+ def_delegator :@source, :xxh_at
18
+ def_delegators :@ledger, :get, :set, :save
67
19
  end
68
20
  end
data/lib/holdify.rb CHANGED
@@ -3,9 +3,9 @@
3
3
  require_relative 'holdify/store'
4
4
  require_relative 'holdify/hold'
5
5
 
6
- # Add description
6
+ # The container module
7
7
  module Holdify
8
- VERSION = '1.1.0'
8
+ VERSION = '1.1.2'
9
9
  CONFIG = { ext: '.yaml' }.freeze
10
10
 
11
11
  class << self
@@ -11,12 +11,14 @@ module Minitest
11
11
  Holdify.reconcile = true
12
12
  Holdify.quiet = true
13
13
  end
14
- opts.on '--holdify-quiet', 'Skip the warning on storing a new value' do
15
- Holdify.quiet = true
16
- end
14
+
17
15
  opts.on '--holdify-pretty', 'Format the stored values with pretty print' do
18
16
  Holdify.pretty = true
19
17
  end
18
+
19
+ opts.on '--holdify-quiet', 'Skip the warning on storing a new value' do
20
+ Holdify.quiet = true
21
+ end
20
22
  end
21
23
 
22
24
  # Reopen the minitest class
@@ -68,10 +70,10 @@ module Minitest
68
70
  expected
69
71
  end
70
72
 
71
- # Temporarily used to store the actual value, useful for reconciliation of expected changed values
73
+ # Force store the current value
72
74
  def assert_hold!(*, **) = assert_hold(*, **, force: true)
73
75
 
74
- # Temporarily used for development feedback to print to STDERR the actual value
76
+ # Print to STDERR the actual value
75
77
  def assert_hold?(*, **) = assert_hold(*, **, inspect: true)
76
78
  end
77
79
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: minitest-holdify
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.0
4
+ version: 1.1.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Domizio Demichelis
@@ -23,6 +23,20 @@ dependencies:
23
23
  - - ">="
24
24
  - !ruby/object:Gem::Version
25
25
  version: 5.0.0
26
+ - !ruby/object:Gem::Dependency
27
+ name: xxhash
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: 0.7.0
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: 0.7.0
26
40
  description: Stop maintaining large expected values in your test/fixture files! Hold
27
41
  them automatically. Update them effortlessly.
28
42
  email:
@@ -34,7 +48,9 @@ files:
34
48
  - LICENSE.txt
35
49
  - lib/holdify.rb
36
50
  - lib/holdify/hold.rb
51
+ - lib/holdify/ledger.rb
37
52
  - lib/holdify/pretty.rb
53
+ - lib/holdify/source.rb
38
54
  - lib/holdify/store.rb
39
55
  - lib/minitest-holdify.rb
40
56
  - lib/minitest/holdify_plugin.rb