kronk 1.5.4 → 1.6.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.
@@ -7,6 +7,17 @@ class Kronk
7
7
 
8
8
  class AsciiFormat
9
9
 
10
+
11
+ def self.head left, right
12
+ ["--- #{left}", "+++ #{right}"]
13
+ end
14
+
15
+
16
+ def self.context left, right, info=nil
17
+ "@@ -#{left} +#{right} @@ #{info}"
18
+ end
19
+
20
+
10
21
  def self.lines line_nums, col_width
11
22
  out =
12
23
  [*line_nums].map do |lnum|
@@ -17,6 +17,18 @@ class Kronk
17
17
  end
18
18
 
19
19
 
20
+ def self.head left, right
21
+ ensure_color
22
+ ["\033[1;33m--- #{left}", "+++ #{right}\033[0m"]
23
+ end
24
+
25
+
26
+ def self.context left, right, info=nil
27
+ ensure_color
28
+ "\033[1;35m@@ -#{left} +#{right} @@\033[0m #{info}"
29
+ end
30
+
31
+
20
32
  def self.lines line_nums, col_width
21
33
  ensure_color
22
34
 
@@ -31,13 +43,13 @@ class Kronk
31
43
 
32
44
  def self.deleted str
33
45
  ensure_color
34
- "\033[31m- #{str}\033[0m"
46
+ "\033[1;31m- #{str}\033[0m"
35
47
  end
36
48
 
37
49
 
38
50
  def self.added str
39
51
  ensure_color
40
- "\033[32m+ #{str}\033[0m"
52
+ "\033[1;32m+ #{str}\033[0m"
41
53
  end
42
54
 
43
55
 
@@ -0,0 +1,155 @@
1
+ class Kronk::Diff
2
+
3
+ class Output
4
+
5
+ ##
6
+ # Returns a formatter from a symbol or string. Returns nil if not found.
7
+
8
+ def self.formatter name
9
+ return unless name
10
+
11
+ return name if Class === name
12
+ return AsciiFormat if name == :ascii_diff
13
+ return ColorFormat if name == :color_diff
14
+ Kronk.find_const name rescue name
15
+ end
16
+
17
+
18
+ def self.attr_rm_cache *attrs # :nodoc:
19
+ self.send :attr_reader, *attrs
20
+
21
+ attrs.each do |attr|
22
+ define_method "#{attr}=" do |value|
23
+ if send(attr) != value
24
+ instance_variable_set("@#{attr}", value)
25
+ instance_variable_set("@cached", nil)
26
+ end
27
+ end
28
+ end
29
+ end
30
+
31
+
32
+ attr_rm_cache :labels, :show_lines, :join_ch, :context, :format, :diff_ary
33
+
34
+
35
+ def initialize diff, opts={}
36
+ @output = []
37
+ @cached = nil
38
+ @diff = diff
39
+
40
+ @format =
41
+ self.class.formatter(opts[:format] || Kronk.config[:diff_format]) ||
42
+ AsciiFormat
43
+
44
+ @context = Kronk.config[:context]
45
+ @context = opts[:context] if opts[:context] || opts[:context] == false
46
+
47
+ @join_ch = opts[:join_char] || "\n"
48
+
49
+ @labels = Array(opts[:labels])
50
+ @labels[0] ||= "left"
51
+ @labels[1] ||= "right"
52
+
53
+ @show_lines = opts[:show_lines] || Kronk.config[:show_lines]
54
+ @record = false
55
+
56
+ lines1 = diff.str1.lines.count
57
+ lines2 = diff.str2.lines.count
58
+ @cwidth = (lines1 > lines2 ? lines1 : lines2).to_s.length
59
+ end
60
+
61
+
62
+ def record? i, line1, line2
63
+ if @context
64
+ clen = @context + 1
65
+ next_diff = @diff_ary[i,clen].to_a.find{|da| Array === da}
66
+ end
67
+
68
+ if !clen || next_diff
69
+ @record || [@output.length, line1+1, line2+1, 0, 0]
70
+
71
+ elsif @record && clen && !next_diff
72
+ scheck = @output.length - (clen - 1)
73
+ subary = @output[scheck..-1].to_a
74
+
75
+ if i == @diff_ary.length || !subary.find{|da| Array === da}
76
+ start = @record[0]
77
+ cleft = "#{@record[1]},#{@record[3]}"
78
+ cright = "#{@record[2]},#{@record[4]}"
79
+
80
+ str = @output[start]
81
+ str = str[0][0] if Array === str
82
+ info = str.meta[0] if str.respond_to?(:meta)
83
+
84
+ @output[start,0] = @format.context cleft, cright, info
85
+ false
86
+
87
+ else
88
+ @record
89
+ end
90
+
91
+ else
92
+ @record
93
+ end
94
+ end
95
+
96
+
97
+ def render force=false
98
+ self.diff_ary = @diff.diff_array
99
+
100
+ return @cached if !force && @cached
101
+ @output << @format.head(*@labels)
102
+
103
+ line1 = line2 = 0
104
+
105
+ 0.upto(@diff_ary.length) do |i|
106
+ item = @diff_ary[i]
107
+ @record = record? i, line1, line2
108
+
109
+ case item
110
+ when String
111
+ line1 = line1.next
112
+ line2 = line2.next
113
+ @output << make_line(item, line1, line2) if @record
114
+
115
+ when Array
116
+ sides = [[],[]]
117
+
118
+ item[0].each do |ditem|
119
+ line1 = line1.next
120
+ sides[0] << make_line(ditem, line1, nil) if @record
121
+ end
122
+
123
+ item[1].each do |ditem|
124
+ line2 = line2.next
125
+ sides[0] << make_line(ditem, nil, line2) if @record
126
+ end
127
+
128
+ @output << sides if @record
129
+ end
130
+ end
131
+
132
+ @cached = @output.flatten.join(@join_ch)
133
+ end
134
+
135
+ alias to_s render
136
+
137
+
138
+ def make_line item, line1, line2
139
+ action = if line1 && !line2
140
+ :deleted
141
+ elsif !line1 && line2
142
+ :added
143
+ else
144
+ :common
145
+ end
146
+
147
+ lines = @format.lines [line1, line2], @cwidth if @show_lines
148
+ line = "#{lines}#{@format.send action, item}"
149
+ line = Kronk::DataString.new line, item.meta[0] if item.respond_to? :meta
150
+ @record[3] += 1 if line1
151
+ @record[4] += 1 if line2
152
+ line
153
+ end
154
+ end
155
+ end
data/lib/kronk/path.rb CHANGED
@@ -38,9 +38,6 @@ class Kronk
38
38
  # Used as path instruction to go up one path level.
39
39
  module PARENT; end
40
40
 
41
- # Used as path item value to match any key or value.
42
- module ANY_VALUE; end
43
-
44
41
  # Mapping of letters to Regexp options.
45
42
  REGEX_OPTS = {
46
43
  "i" => Regexp::IGNORECASE,
@@ -49,19 +46,12 @@ class Kronk
49
46
  "x" => Regexp::EXTENDED
50
47
  }
51
48
 
52
- # Shortcut characters that require modification before being turned into
53
- # a matcher.
54
- SUFF_CHARS = Regexp.escape "*?"
55
-
56
- # All special path characters.
57
- PATH_CHARS = Regexp.escape("()|") << SUFF_CHARS
58
-
59
- # Path chars that get regexp escaped.
60
- RESC_CHARS = "*?()|/"
61
-
62
49
  # The path item delimiter character "/"
63
50
  DCH = "/"
64
51
 
52
+ # The replacement character "%" for path mapping
53
+ RCH = "%"
54
+
65
55
  # The path character to assign value "="
66
56
  VCH = "="
67
57
 
@@ -80,26 +70,14 @@ class Kronk
80
70
  # The key string that indicates recursive lookup.
81
71
  RECUR_KEY = "**"
82
72
 
83
- # Matcher for Range path item.
84
- RANGE_MATCHER = %r{^(\-?\d+)(\.{2,3})(\-?\d+)$}
85
-
86
- # Matcher for index,length path item.
87
- ILEN_MATCHER = %r{^(\-?\d+),(\-?\d+)$}
88
-
89
- # Matcher allowing any value to be matched.
90
- ANYVAL_MATCHER = /^(\?*\*+\?*)*$/
91
-
92
- # Matcher to assert if any unescaped special chars are in a path item.
93
- PATH_CHAR_MATCHER = /(^|[^#{RECH}])([#{PATH_CHARS}])/
94
-
95
73
 
96
74
  ##
97
75
  # Instantiate a Path object with a String data path.
98
76
  # Path.new "/path/**/to/*=bar/../../**/last"
99
77
 
100
- def initialize path_str, regex_opts=nil
78
+ def initialize path_str, regex_opts=nil, &block
101
79
  path_str = path_str.dup
102
- @path = self.class.parse_path_str path_str, regex_opts
80
+ @path = self.class.parse_path_str path_str, regex_opts, &block
103
81
  end
104
82
 
105
83
 
@@ -129,11 +107,10 @@ class Kronk
129
107
  def find_in data
130
108
  matches = {[] => data}
131
109
 
132
- @path.each_with_index do |(mkey, mvalue, recur), i|
133
- args = [matches, data, mkey, mvalue, recur]
110
+ @path.each_with_index do |matcher, i|
134
111
  last_item = i == @path.length - 1
135
112
 
136
- self.class.assign_find(*args) do |sdata, key, spath|
113
+ self.class.assign_find(matches, data, matcher) do |sdata, key, spath|
137
114
  yield sdata, key, spath if last_item && block_given?
138
115
  end
139
116
  end
@@ -142,6 +119,36 @@ class Kronk
142
119
  end
143
120
 
144
121
 
122
+ ##
123
+ # Returns a path-keyed data hash. Be careful of mixed key types in hashes
124
+ # as Symbols and Strings both use #to_s.
125
+
126
+ def self.pathed data, escape=true
127
+ new_data = {}
128
+
129
+ find "**", data do |subdata, key, path|
130
+ next if Array === subdata[key] || Hash === subdata[key]
131
+ path_str = "#{DCH}#{join(path, escape)}"
132
+ new_data[path_str] = subdata[key]
133
+ end
134
+
135
+ new_data
136
+ end
137
+
138
+
139
+ ##
140
+ # Joins an Array into a path String.
141
+
142
+ def self.join path_arr, escape=true
143
+ path_esc = "*?()|/."
144
+ path_arr.map! do |k|
145
+ k.to_s.gsub(/([#{path_esc}])/){|m| "\\#{m}"}
146
+ end if escape
147
+
148
+ path_arr.join(DCH)
149
+ end
150
+
151
+
145
152
  ##
146
153
  # Fully streamed version of:
147
154
  # Path.new(str_path).find_in data
@@ -151,8 +158,8 @@ class Kronk
151
158
  def self.find path_str, data, regex_opts=nil, &block
152
159
  matches = {[] => data}
153
160
 
154
- parse_path_str path_str, regex_opts do |mkey, mvalue, recur, last_item|
155
- assign_find matches, data, mkey, mvalue, recur do |sdata, key, spath|
161
+ parse_path_str path_str, regex_opts do |matcher, last_item|
162
+ assign_find matches, data, matcher do |sdata, key, spath|
156
163
  yield sdata, key, spath if last_item && block_given?
157
164
  end
158
165
  end
@@ -164,11 +171,11 @@ class Kronk
164
171
  ##
165
172
  # Common find functionality that assigns to the matches hash.
166
173
 
167
- def self.assign_find matches, data, mkey, mvalue, recur
174
+ def self.assign_find matches, data, matcher
168
175
  matches.keys.each do |path|
169
176
  pdata = matches.delete path
170
177
 
171
- if mkey == PARENT
178
+ if matcher.key == PARENT
172
179
  path = path[0..-2]
173
180
  subdata = data_at_path path[0..-2], data
174
181
 
@@ -180,7 +187,7 @@ class Kronk
180
187
  next
181
188
  end
182
189
 
183
- find_match pdata, mkey, mvalue, recur, path do |sdata, key, spath|
190
+ matcher.find_in pdata, path do |sdata, key, spath|
184
191
  yield sdata, key, spath if block_given?
185
192
  matches[spath] = sdata[key]
186
193
  end
@@ -206,118 +213,6 @@ class Kronk
206
213
  end
207
214
 
208
215
 
209
- ##
210
- # Universal iterator for Hash and Array like objects.
211
- # The data argument must either respond to both :each_with_index
212
- # and :length, or respond to :each yielding a key/value pair.
213
-
214
- def self.each_data_item data, &block
215
- if data.respond_to?(:has_key?) && data.respond_to?(:each)
216
- data.each(&block)
217
-
218
- elsif data.respond_to?(:each_with_index) && data.respond_to?(:length)
219
- # We need to iterate through the array this way
220
- # in case items in it get deleted.
221
- (data.length - 1).downto(0) do |i|
222
- block.call i, data[i]
223
- end
224
- end
225
- end
226
-
227
-
228
- ##
229
- # Finds data with the given key and value matcher, optionally recursive.
230
- # Yields data, key and path Array when block is given.
231
- # Returns an Array of path arrays.
232
-
233
- def self.find_match data, mkey, mvalue=ANY_VALUE,
234
- recur=false, path=nil, &block
235
-
236
- return [] unless Array === data || Hash === data
237
-
238
- paths = []
239
- path ||= []
240
-
241
- each_data_item data do |key, value|
242
- c_path = path.dup << key
243
-
244
- if match_data_item(mkey, key) && match_data_item(mvalue, value)
245
- yield data, key, c_path if block_given?
246
- paths << c_path
247
- end
248
-
249
- paths.concat \
250
- find_match(data[key], mkey, mvalue, true, c_path, &block) if recur
251
- end
252
-
253
- paths
254
- end
255
-
256
-
257
- ##
258
- # Check if data key or value is a match for nested data searches.
259
-
260
- def self.match_data_item item1, item2
261
- return if ANY_VALUE != item1 && (Array === item2 || Hash === item2)
262
-
263
- if item1.class == item2.class
264
- item1 == item2
265
-
266
- elsif Regexp === item1
267
- item2.to_s =~ item1
268
-
269
- elsif Range === item1
270
- item1.include? item2.to_i
271
-
272
- elsif ANY_VALUE == item1
273
- true
274
-
275
- else
276
- item2.to_s.downcase == item1.to_s.downcase
277
- end
278
- end
279
-
280
-
281
- ##
282
- # Decide whether to make path item matcher a regex, range, array, or string.
283
-
284
- def self.parse_path_item str, regex_opts=nil
285
- case str
286
- when nil, ANYVAL_MATCHER
287
- ANY_VALUE
288
-
289
- when RANGE_MATCHER
290
- Range.new $1.to_i, $3.to_i, ($2 == "...")
291
-
292
- when ILEN_MATCHER
293
- Range.new $1.to_i, ($1.to_i + $2.to_i), true
294
-
295
- else
296
- if String === str && (regex_opts || str =~ PATH_CHAR_MATCHER)
297
-
298
- # Remove extra suffix characters
299
- str.gsub! %r{(^|[^#{RECH}])(\*+\?+|\?+\*+)}, '\1*'
300
- str.gsub! %r{(^|[^#{RECH}])\*+}, '\1*'
301
-
302
- str = Regexp.escape str
303
-
304
- # Remove escaping from special path characters
305
- str.gsub! %r{#{RECH}([#{PATH_CHARS}])}, '\1'
306
- str.gsub! %r{#{RECH}([#{RESC_CHARS}])}, '\1'
307
- str.gsub! %r{(^|[^#{RECH}])([#{SUFF_CHARS}])}, '\1.\2'
308
-
309
- Regexp.new "\\A(#{str})\\Z", regex_opts
310
-
311
- elsif String === str
312
- str.gsub %r{#{RECH}([^#{RECH}]|$)}, '\1'
313
-
314
- else
315
- str
316
- end
317
- end
318
- end
319
-
320
-
321
216
  ##
322
217
  # Parses a path String into an Array of arrays containing
323
218
  # matchers for key, value, and any special modifiers
@@ -388,13 +283,13 @@ class Kronk
388
283
  end
389
284
 
390
285
  unless key =~ /^\.?$/ && !value
391
- parsed << [ parse_path_item(key, regex_opts),
392
- parse_path_item(value, regex_opts),
393
- recur ]
394
-
395
- yield_args = (parsed.last.dup << path.empty?)
286
+ matcher = Matcher.new :key => key,
287
+ :value => value,
288
+ :recursive => recur,
289
+ :regex_opts => regex_opts
396
290
 
397
- yield(*yield_args) if block_given?
291
+ parsed << matcher
292
+ yield matcher, path.empty? if block_given?
398
293
  end
399
294
 
400
295
  key = ""