kronk 1.5.4 → 1.6.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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 = ""