pray-cli 1.7.0 → 1.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0b96847ff63e2123258b7dcedd5c074392941b6654f20d02767b0a221ecbeb8e
4
- data.tar.gz: 256f98e161876f0145790fde9a581eda5747267823e3e2bdf30beeabfbdc477b
3
+ metadata.gz: 7712d0c9a8ea370b50c90c7212ab7d15ea2c8ea90a304365194f9c34242b72dc
4
+ data.tar.gz: 0a5a5de36c49409e1e9bb8d689a1fdd7284eaa79b472eee210f19144835812a4
5
5
  SHA512:
6
- metadata.gz: 4d7d234f3c63b528eaec20fd9d0e1eba462df4128c691b56daa7b6eb3e943f5ed56259c59335e91eb12501385cda68b1ccbce4179bf62f0aab896bbcc4e4edf8
7
- data.tar.gz: 989b8e9f72b95740f4328e5ae584b60c467d33c3a36a2cc8388ca567ab014adf6449264d22ac96610b6f66feffc3c6d96d779f39c5119d8dc7e7a780efbf1a0c
6
+ metadata.gz: 6750b00e4f4cc372849209ec4e23ae222cee7ef49134afe7dd2f2fbd19d2efcfd1bed5a143ae829b17c68b596d2320e1a8095be36c01c335c894f07c4415bcc0
7
+ data.tar.gz: 173d7e11c528fec7a3d816aedb511952e8189e2ba8fd061245c0cf13b3a00a9f658df255be54c0e44b75326c47435f88933d867bea0db4ffa99cc0456089effa
data/lib/pray/verify.rb CHANGED
@@ -20,7 +20,7 @@ module Pray
20
20
  module_function
21
21
 
22
22
  def inspect_project(project, lockfile)
23
- collect_verification_report(project, lockfile).first
23
+ collect_verification_report(project, lockfile)[0]
24
24
  end
25
25
 
26
26
  def verify_project(project, lockfile, strict: false)
@@ -35,21 +35,21 @@ module Pray
35
35
  end
36
36
 
37
37
  def drift_project(project, lockfile)
38
- report, rendered_targets = collect_verification_report(project, lockfile)
39
- Render.render_project(project).each do |target|
40
- normalized_fresh = Hashing.normalize_line_endings(target.content)
41
- on_disk = rendered_targets[target.path]
38
+ report, rendered_targets, fresh_targets = collect_verification_report(project, lockfile)
39
+ fresh_targets.each do |path, fresh_content|
40
+ normalized_fresh = Hashing.normalize_line_endings(fresh_content)
41
+ on_disk = rendered_targets[path]
42
42
  on_disk = Hashing.normalize_line_endings(on_disk) if on_disk
43
43
  unless on_disk == normalized_fresh
44
44
  report.findings << VerificationFinding.new(
45
45
  kind: "renderer_drift",
46
- message: "#{target.path} differs from fresh render"
46
+ message: "#{path} differs from fresh render"
47
47
  )
48
48
  end
49
- unless lockfile_targets(lockfile).include?(target.path)
49
+ unless lockfile_targets(lockfile).include?(path)
50
50
  report.findings << VerificationFinding.new(
51
51
  kind: "renderer_drift",
52
- message: "#{target.path} is not tracked in lockfile"
52
+ message: "#{path} is not tracked in lockfile"
53
53
  )
54
54
  end
55
55
  end
@@ -64,6 +64,7 @@ module Pray
64
64
  def collect_verification_report(project, lockfile)
65
65
  report = VerificationReport.new
66
66
  rendered_targets = {}
67
+ fresh_targets = Render.render_project(project).to_h { |target| [target.path, target.content] }
67
68
 
68
69
  if project.manifest_hash != lockfile.manifest_hash
69
70
  report.findings << VerificationFinding.new(
@@ -125,21 +126,30 @@ module Pray
125
126
  message: "`#{target_path}` is missing managed marker `#{span.id}` for `#{span.package}::#{span.export}`. Run `pray install` to restore the managed span."
126
127
  )
127
128
  else
128
- open_line, close_line, checksum = marker
129
+ _open_line, _close_line, checksum = marker
129
130
  if checksum != span.ideal_checksum
130
131
  report.findings << VerificationFinding.new(
131
132
  kind: "custom_implementation",
132
133
  message: "`#{target_path}` marker `#{span.id}` (`#{span.package}::#{span.export}`) was edited. Restore the managed block or run `pray install` to regenerate it."
133
134
  )
134
135
  end
135
- if open_line != span.open_line || close_line != span.close_line
136
- report.findings << VerificationFinding.new(
137
- kind: "position_drift",
138
- message: "`#{target_path}` marker `#{span.id}` (`#{span.package}::#{span.export}`) moved to different lines. Run `pray install` to restore expected positions."
139
- )
140
- end
141
136
  end
142
137
  end
138
+ fresh_lines = fresh_targets[target_path]&.lines(chomp: true)
139
+ summary = VerifyPosition.summarize_position_drift(
140
+ target_path,
141
+ spans,
142
+ markers,
143
+ lines,
144
+ fresh_lines,
145
+ project.local_files
146
+ )
147
+ if summary
148
+ report.findings << VerificationFinding.new(
149
+ kind: "position_drift",
150
+ message: VerifyPosition.format_position_drift_message(summary)
151
+ )
152
+ end
143
153
  find_orphan_marker_findings(spans, markers, target_path).each do |finding|
144
154
  report.findings << finding
145
155
  end
@@ -155,7 +165,7 @@ module Pray
155
165
  )
156
166
  end
157
167
 
158
- [report, rendered_targets]
168
+ [report, rendered_targets, fresh_targets]
159
169
  end
160
170
 
161
171
  def find_orphan_marker_findings(spans, markers, target_path)
@@ -0,0 +1,135 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pray
4
+ module VerifyPosition
5
+ module_function
6
+
7
+ PositionDriftSummary = Struct.new(
8
+ :target_path,
9
+ :marker_count,
10
+ :uniform_delta,
11
+ :first_id,
12
+ :lock_open,
13
+ :lock_close,
14
+ :file_open,
15
+ :file_close,
16
+ :cause,
17
+ keyword_init: true
18
+ )
19
+
20
+ def summarize_position_drift(target_path, spans, markers, on_disk_lines, fresh_lines, local_files)
21
+ drifted = []
22
+ spans.each do |span|
23
+ marker = markers[span.id]
24
+ next if marker.nil?
25
+
26
+ open_line, close_line, checksum = marker
27
+ next if checksum != span.ideal_checksum
28
+ next if open_line == span.open_line && close_line == span.close_line
29
+
30
+ drifted << {
31
+ id: span.id,
32
+ lock_open: span.open_line,
33
+ lock_close: span.close_line,
34
+ file_open: open_line,
35
+ file_close: close_line
36
+ }
37
+ end
38
+ return nil if drifted.empty?
39
+
40
+ drifted.sort_by! { |marker| marker[:file_open] }
41
+ first = drifted.first
42
+ deltas = drifted.map { |marker| marker[:file_open] - marker[:lock_open] }
43
+ uniform_delta = (deltas.all? { |delta| delta == deltas.first }) ? deltas.first : nil
44
+ cause = if fresh_lines
45
+ unmarked_drift_cause(target_path, on_disk_lines, fresh_lines, local_files)
46
+ end
47
+
48
+ PositionDriftSummary.new(
49
+ target_path: target_path,
50
+ marker_count: drifted.length,
51
+ uniform_delta: uniform_delta,
52
+ first_id: first[:id],
53
+ lock_open: first[:lock_open],
54
+ lock_close: first[:lock_close],
55
+ file_open: first[:file_open],
56
+ file_close: first[:file_close],
57
+ cause: cause
58
+ )
59
+ end
60
+
61
+ def format_position_drift_message(summary)
62
+ shift = if summary.uniform_delta && !summary.uniform_delta.zero?
63
+ format(" (%+d lines)", summary.uniform_delta)
64
+ else
65
+ ""
66
+ end
67
+ marker_word = (summary.marker_count == 1) ? "marker" : "markers"
68
+ parts = [
69
+ "`#{summary.target_path}` position drift#{shift} across #{summary.marker_count} #{marker_word}",
70
+ "first marker `#{summary.first_id}` lock #{summary.lock_open}:#{summary.lock_close}, file #{summary.file_open}:#{summary.file_close}"
71
+ ]
72
+ parts << "cause: #{summary.cause}" if summary.cause
73
+ parts << "Align unmarked text with compose sources, or run `pray install` to refresh lock positions."
74
+ parts.join("; ")
75
+ end
76
+
77
+ def unmarked_drift_cause(target_path, on_disk_lines, fresh_lines, local_files)
78
+ disk_preamble = preamble_lines(on_disk_lines)
79
+ fresh_preamble = preamble_lines(fresh_lines)
80
+ diff = first_line_diff(disk_preamble, fresh_preamble)
81
+ return nil unless diff
82
+
83
+ index, disk_line, fresh_line = diff
84
+ target_line = index + 1
85
+ if (local = locate_line_in_locals(local_files, fresh_line))
86
+ return "`#{target_path}:#{target_line}` unmarked text differs from `#{local[0]}:#{local[1]}`"
87
+ end
88
+ if (local = locate_line_in_locals(local_files, disk_line))
89
+ return "`#{target_path}:#{target_line}` unmarked text differs from `#{local[0]}:#{local[1]}`"
90
+ end
91
+
92
+ "`#{target_path}:#{target_line}` unmarked text differs from fresh composition"
93
+ end
94
+
95
+ def preamble_lines(lines)
96
+ preamble = []
97
+ lines.each do |line|
98
+ break if managed_marker?(line)
99
+
100
+ preamble << line
101
+ end
102
+ preamble
103
+ end
104
+
105
+ def managed_marker?(line)
106
+ trimmed = line.strip
107
+ return false unless trimmed.start_with?("<!-- pray:") && trimmed.end_with?(" -->")
108
+
109
+ id = trimmed.delete_prefix("<!-- pray:").delete_suffix(" -->")
110
+ id != "0 ignore-comments" && id.match?(/\A[a-z0-9]+\z/)
111
+ end
112
+
113
+ def first_line_diff(left, right)
114
+ shared = [left.length, right.length].min
115
+ shared.times do |index|
116
+ return [index, left[index], right[index]] if left[index] != right[index]
117
+ end
118
+ return nil if left.length == right.length
119
+
120
+ index = shared
121
+ [index, left[index] || "", right[index] || ""]
122
+ end
123
+
124
+ def locate_line_in_locals(local_files, line)
125
+ return nil if line.nil? || line.empty?
126
+
127
+ local_files.each do |local|
128
+ local.content.each_line(chomp: true).with_index do |candidate, index|
129
+ return [local.manifest_path, index + 1] if candidate == line
130
+ end
131
+ end
132
+ nil
133
+ end
134
+ end
135
+ end
data/lib/pray/version.rb CHANGED
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Pray
4
- VERSION = "1.7.0"
5
- GENERATED_BY = "pray 1.7.0"
4
+ VERSION = "1.8.1"
5
+ GENERATED_BY = "pray 1.8.1"
6
6
  end
data/lib/pray.rb CHANGED
@@ -22,6 +22,7 @@ require_relative "pray/resolve_context"
22
22
  require_relative "pray/invocation"
23
23
  require_relative "pray/resolve"
24
24
  require_relative "pray/render"
25
+ require_relative "pray/verify_position"
25
26
  require_relative "pray/verify"
26
27
  require_relative "pray/registry"
27
28
  require_relative "pray/archive"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pray-cli
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.7.0
4
+ version: 1.8.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrei Makarov
@@ -155,6 +155,7 @@ files:
155
155
  - lib/pray/trust_feed.rb
156
156
  - lib/pray/trust_ops.rb
157
157
  - lib/pray/verify.rb
158
+ - lib/pray/verify_position.rb
158
159
  - lib/pray/version.rb
159
160
  - pray-cli.gemspec
160
161
  homepage: https://pray.kisko.dev