kronk 1.2.5 → 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -19,22 +19,6 @@ class Kronk
19
19
  end
20
20
 
21
21
 
22
- ##
23
- # Retrieves the data at the given path array.
24
-
25
- def data_at_path path
26
- self.class.data_at_path @data, path
27
- end
28
-
29
-
30
- ##
31
- # Checks if data is available at the given path.
32
-
33
- def data_at_path? path
34
- self.class.data_at_path? @data, path
35
- end
36
-
37
-
38
22
  ##
39
23
  # Modify the data object by passing inclusive or exclusive data paths.
40
24
  # Supports the following options:
@@ -85,6 +69,10 @@ class Kronk
85
69
  new_curr_data[key] = curr_data[key]
86
70
  break
87
71
 
72
+ elsif path.length == 1 && affect_parent
73
+ new_data = curr_data
74
+ break
75
+
88
76
  else
89
77
  new_curr_data[key] ||= curr_data[key].class.new
90
78
  new_curr_data = new_curr_data[key]
@@ -106,6 +94,8 @@ class Kronk
106
94
  find_data data_path do |obj, k, path|
107
95
 
108
96
  if affect_parent && data_at_path?(path)
97
+ @data = @data.class.new and return if path.length == 1
98
+
109
99
  parent_data = data_at_path path[0..-3]
110
100
  del_method = Array === parent_data ? :delete_at : :delete
111
101
 
@@ -140,23 +130,16 @@ class Kronk
140
130
  # # Returns an Array of grand-children key/value pairs
141
131
  # # where the value is 'invalid' or blank
142
132
 
143
- def find_data data_path, curr_path=nil, &block
144
- self.class.find_data @data, data_path, curr_path, &block
145
- end
146
-
147
-
148
- ##
149
- # See DataSet#find_data
150
-
151
- def self.find_data data, data_path, curr_path=nil, &block
133
+ def find_data data_path, curr_path=nil, data=nil, &block
152
134
  curr_path ||= []
135
+ data ||= @data
153
136
 
154
137
  key, value, rec, data_path = parse_data_path data_path
155
138
 
156
139
  yield_data_points data, key, value, rec, curr_path do |d, k, p|
157
140
 
158
141
  if data_path
159
- find_data d[k], data_path, p, &block
142
+ find_data data_path, p, d[k], &block
160
143
  else
161
144
  yield d, k, p
162
145
  end
@@ -167,8 +150,8 @@ class Kronk
167
150
  ##
168
151
  # Checks if data is available at the given path.
169
152
 
170
- def self.data_at_path? data, path
171
- data_at_path(data, path)
153
+ def data_at_path? path
154
+ data_at_path path
172
155
  true
173
156
 
174
157
  rescue NoMethodError, TypeError
@@ -179,8 +162,8 @@ class Kronk
179
162
  ##
180
163
  # Retrieve the data at the given path array location.
181
164
 
182
- def self.data_at_path data, path
183
- curr = data
165
+ def data_at_path path
166
+ curr = @data
184
167
  path.each do |p|
185
168
  raise TypeError, "Expected instance of Array or Hash" unless
186
169
  Array === curr || Hash === curr
@@ -198,7 +181,7 @@ class Kronk
198
181
  # - Recursive matching
199
182
  # - New data path value
200
183
 
201
- def self.parse_data_path data_path
184
+ def parse_data_path data_path
202
185
  data_path = data_path.dup
203
186
  key = nil
204
187
  value = nil
@@ -236,17 +219,22 @@ class Kronk
236
219
 
237
220
 
238
221
  ##
239
- # Decide whether to make path item a regex or not.
222
+ # Decide whether to make path item a regex, range, array, or string.
240
223
 
241
- def self.parse_path_item str
224
+ def parse_path_item str
242
225
  if str =~ /(^|[^\\])([\*\?\|])/
243
226
  str.gsub!(/(^|[^\\])(\*|\?)/, '\1.\2')
244
- str = /^(#{str})$/
227
+ /^(#{str})$/i
228
+
229
+ elsif str =~ %r{^(\-?\d+)(\.{2,3})(\-?\d+)$}
230
+ Range.new $1.to_i, $3.to_i, ($2 == "...")
231
+
232
+ elsif str =~ %r{^(\-?\d+),(\-?\d+)$}
233
+ Range.new $1.to_i, ($1.to_i + $2.to_i), true
234
+
245
235
  else
246
- str.gsub! "\\", ""
236
+ str.gsub "\\", ""
247
237
  end
248
-
249
- str
250
238
  end
251
239
 
252
240
 
@@ -254,7 +242,7 @@ class Kronk
254
242
  # Yield data object and key, if a specific key or value matches
255
243
  # the given data.
256
244
 
257
- def self.yield_data_points data, mkey, mvalue=nil,
245
+ def yield_data_points data, mkey, mvalue=nil,
258
246
  recursive=false, path=nil, &block
259
247
 
260
248
  return unless Hash === data || Array === data
@@ -266,8 +254,6 @@ class Kronk
266
254
  found = match_data_item(mkey, key) &&
267
255
  match_data_item(mvalue, value)
268
256
 
269
- #puts "Found: #{data.inspect} #{mkey.inspect} -> #{key.inspect}" if found
270
-
271
257
  yield data, key, curr_path if found
272
258
  yield_data_points data[key], mkey, mvalue, true, curr_path, &block if
273
259
  recursive
@@ -278,15 +264,23 @@ class Kronk
278
264
  ##
279
265
  # Check if data key or value is a match for nested data searches.
280
266
 
281
- def self.match_data_item item1, item2
267
+ def match_data_item item1, item2
282
268
  return if !item1.nil? && (Array === item2 || Hash === item2)
283
269
 
284
- if Regexp === item1
270
+ if item1.class == item2.class
271
+ item1 == item2
272
+
273
+ elsif Regexp === item1
285
274
  item2.to_s =~ item1
275
+
276
+ elsif Range === item1
277
+ item1.include? item2.to_i
278
+
286
279
  elsif item1.nil?
287
280
  true
281
+
288
282
  else
289
- item2.to_s == item1.to_s
283
+ item2.to_s.downcase == item1.to_s.downcase
290
284
  end
291
285
  end
292
286
 
@@ -294,7 +288,7 @@ class Kronk
294
288
  ##
295
289
  # Universal iterator for Hash and Array objects.
296
290
 
297
- def self.each_data_item data, &block
291
+ def each_data_item data, &block
298
292
  case data
299
293
 
300
294
  when Hash
@@ -1,87 +1,11 @@
1
1
  class Kronk
2
2
 
3
-
4
3
  ##
5
4
  # Creates simple diffs as formatted strings or arrays, from two strings or
6
5
  # data objects.
7
6
 
8
7
  class Diff
9
8
 
10
- ##
11
- # Format diff with ascii
12
-
13
- class AsciiFormat
14
-
15
- def self.lines line_nums, col_width
16
- out =
17
- [*line_nums].map do |lnum|
18
- lnum.to_s.rjust col_width
19
- end.join "|"
20
-
21
- "#{out} "
22
- end
23
-
24
-
25
- def self.deleted str
26
- "- #{str}"
27
- end
28
-
29
-
30
- def self.added str
31
- "+ #{str}"
32
- end
33
-
34
-
35
- def self.common str
36
- " #{str}"
37
- end
38
- end
39
-
40
-
41
- ##
42
- # Format diff with ascii
43
-
44
- class ColorFormat
45
-
46
- def self.require_win_color
47
- begin
48
- require 'Win32/Console/ANSI'
49
- rescue LoadError
50
- puts "Warning: You must gem install win32console to use color"
51
- end
52
- end
53
-
54
-
55
- def self.lines line_nums, col_width
56
- require_win_color if Kronk.windows?
57
-
58
- out =
59
- [*line_nums].map do |lnum|
60
- lnum.to_s.rjust col_width
61
- end.join "\033[32m"
62
-
63
- "\033[7;31m#{out}\033[0m "
64
- end
65
-
66
-
67
- def self.deleted str
68
- require_win_color if Kronk.windows?
69
- "\033[31m#{str}\033[0m"
70
- end
71
-
72
-
73
- def self.added str
74
- require_win_color if Kronk.windows?
75
- "\033[32m#{str}\033[0m"
76
- end
77
-
78
-
79
- def self.common str
80
- str
81
- end
82
- end
83
-
84
-
85
9
  ##
86
10
  # Creates a new diff from two data objects.
87
11
 
@@ -184,69 +108,127 @@ class Kronk
184
108
 
185
109
  def create_diff
186
110
  diff_ary = []
187
- sub_diff = nil
188
111
 
189
112
  arr1 = @str1.split @char
190
113
  arr2 = @str2.split @char
191
114
 
192
- until arr1.empty? && arr2.empty?
193
- item1, item2 = arr1.shift, arr2.shift
115
+ common_list = find_common arr1, arr2
194
116
 
195
- if item1 == item2
196
- if sub_diff
197
- diff_ary << sub_diff
198
- sub_diff = nil
199
- end
117
+ return [[arr1, arr2]] if common_list.empty?
200
118
 
201
- diff_ary << item1
202
- next
203
- end
119
+ last_i1 = 0
120
+ last_i2 = 0
204
121
 
205
- match1 = arr1.index item2
206
- match2 = arr2.index item1
122
+ common_list.each do |c|
123
+ next unless c
207
124
 
125
+ left = arr1[last_i1...c[1]]
126
+ right = arr2[last_i2...c[2]]
208
127
 
209
- if use_left?(match1, match2)
210
- diff_ary.concat diff_match(item1, match1, arr1, 0, sub_diff)
211
- sub_diff = nil
128
+ # add diffs
129
+ diff_ary << [left, right] unless left.empty? && right.empty?
212
130
 
213
- elsif match2
214
- diff_ary.concat diff_match(item2, match2, arr2, 1, sub_diff)
215
- sub_diff = nil
131
+ # add common
132
+ diff_ary.concat arr1[c[1], c[0]]
216
133
 
217
- elsif !item1.nil? || !item2.nil?
218
- sub_diff ||= [[],[]]
219
- sub_diff[0] << item1 if item1
220
- sub_diff[1] << item2 if item2
221
- end
134
+ last_i1 = c[1] + c[0]
135
+ last_i2 = c[2] + c[0]
222
136
  end
223
137
 
224
- diff_ary << sub_diff if sub_diff
138
+ left = arr1[last_i1..-1]
139
+ right = arr2[last_i2..-1]
140
+
141
+ diff_ary << [left, right] unless left.empty? && right.empty?
225
142
 
226
143
  diff_ary
227
144
  end
228
145
 
229
146
 
230
147
  ##
231
- # Check if the index on the left should be used.
148
+ # Recursively finds common sequences between two arrays and returns
149
+ # them in the order they occur as an array of arrays:
150
+ # find_common arr1, arr2
151
+ # #=> [[size, arr1_index, arr2_index], [size, arr1_index, arr2_index],...]
152
+
153
+ def find_common arr1, arr2
154
+ used1 = []
155
+ used2 = []
156
+
157
+ common = []
232
158
 
233
- def use_left? index1, index2
234
- index1 && (index2.nil? || index1 < index2)
159
+ common_sequences(arr1, arr2) do |seq|
160
+ next if used1[seq[1]] || used2[seq[2]]
161
+
162
+ next if used1[seq[1], seq[0]].to_a.index(true) ||
163
+ used2[seq[2], seq[0]].to_a.index(true)
164
+
165
+ next if used1[seq[1]+seq[0]..-1].to_a.nitems !=
166
+ used2[seq[2]+seq[0]..-1].to_a.nitems
167
+
168
+
169
+ used1.fill(true, seq[1], seq[0])
170
+ used2.fill(true, seq[2], seq[0])
171
+
172
+ common[seq[1]] = seq
173
+ end
174
+
175
+ common
235
176
  end
236
177
 
237
178
 
238
179
  ##
239
- # Create a diff from a match.
180
+ # Returns all common sequences between to arrays ordered by sequence length
181
+ # according to the following format:
182
+ # [[[len1, ix, iy], [len1, ix, iy]],[[len2, ix, iy]]]
183
+ # # e.g.
184
+ # [nil,[[1,2,3],[1,2,5]],nil,[[3,4,5],[3,6,9]]
185
+
186
+ def common_sequences arr1, arr2, &block
187
+ sequences = []
188
+
189
+ arr2_map = {}
190
+ arr2.each_with_index do |line, j|
191
+ arr2_map[line] ||= []
192
+ arr2_map[line] << j
193
+ end
194
+
195
+ arr1.each_with_index do |line, i|
196
+ next unless arr2_map[line]
240
197
 
241
- def diff_match item, match, arr, side, sub_diff
242
- sub_diff ||= [[],[]]
198
+ arr2_map[line].each do |j|
199
+ line1 = line
200
+ line2 = arr2[j]
243
201
 
244
- index = match - 1
245
- added = [item]
246
- added.concat arr.slice!(0..index) if index >= 0
202
+ k = i
203
+ start_j = j
247
204
 
248
- sub_diff[side].concat(added)
249
- [sub_diff, arr.shift]
205
+ while line1 && line1 == line2 && k < arr1.length
206
+ k += 1
207
+ j += 1
208
+
209
+ line1 = arr1[k]
210
+ line2 = arr2[j]
211
+ end
212
+
213
+ len = j - start_j
214
+
215
+ sequences[len] ||= []
216
+ sequences[len] << [len, i, start_j]
217
+ end
218
+ end
219
+
220
+ yield_sequences sequences, &block if block_given?
221
+
222
+ sequences
223
+ end
224
+
225
+
226
+ def yield_sequences sequences, dist=0, &block
227
+ while sequences.length > dist
228
+ item = sequences.pop
229
+ next unless item
230
+ item.each &block
231
+ end
250
232
  end
251
233
 
252
234
 
@@ -329,3 +311,14 @@ class Kronk
329
311
  end
330
312
  end
331
313
  end
314
+
315
+
316
+ # For Ruby 1.9
317
+
318
+ unless [].respond_to? :nitems
319
+ class Array
320
+ def nitems
321
+ self.compact.length
322
+ end
323
+ end
324
+ end
@@ -0,0 +1,35 @@
1
+ class Kronk
2
+
3
+ class Diff
4
+
5
+ ##
6
+ # Format diff with ascii
7
+
8
+ class AsciiFormat
9
+
10
+ def self.lines line_nums, col_width
11
+ out =
12
+ [*line_nums].map do |lnum|
13
+ lnum.to_s.rjust col_width
14
+ end.join "|"
15
+
16
+ "#{out} "
17
+ end
18
+
19
+
20
+ def self.deleted str
21
+ "- #{str}"
22
+ end
23
+
24
+
25
+ def self.added str
26
+ "+ #{str}"
27
+ end
28
+
29
+
30
+ def self.common str
31
+ " #{str}"
32
+ end
33
+ end
34
+ end
35
+ end