kronk 1.0.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.
@@ -0,0 +1,230 @@
1
+ class Kronk
2
+
3
+ ##
4
+ # Wraps a complex data structure to provide a search-driven interface.
5
+
6
+ class DataSet
7
+
8
+ attr_accessor :data
9
+
10
+ def initialize data
11
+ @data = data
12
+ end
13
+
14
+
15
+ ##
16
+ # Keep only specific data points from the data structure.
17
+
18
+ def collect_data_points data_paths
19
+ new_data = @data.class.new
20
+
21
+ [*data_paths].each do |data_path|
22
+ find_data data_path do |obj, k, path|
23
+
24
+ curr_data = @data
25
+ new_curr_data = new_data
26
+
27
+ path.each_with_index do |key, i|
28
+
29
+ if i == path.length - 1
30
+ new_curr_data[key] = curr_data[key]
31
+ else
32
+ new_curr_data[key] ||= curr_data[key].class.new
33
+ new_curr_data = new_curr_data[key]
34
+ curr_data = curr_data[key]
35
+ end
36
+ end
37
+ end
38
+ end
39
+
40
+ @data = new_data
41
+ end
42
+
43
+
44
+ ##
45
+ # Remove specific data points from the data structure.
46
+
47
+ def delete_data_points data_paths
48
+ [*data_paths].each do |data_path|
49
+ find_data data_path do |obj, k, p|
50
+ case obj
51
+ when Hash then obj.delete k
52
+ when Array then obj.delete_at k
53
+ end
54
+ end
55
+ end
56
+
57
+ @data
58
+ end
59
+
60
+
61
+ ##
62
+ # Find specific data points from a nested hash or array data structure.
63
+ # If a block is given, will pass it any matched parent data object,
64
+ # key, and full path.
65
+ #
66
+ # Data points must be an Array or String with a glob-like format.
67
+ # Special characters are: / * = | \ and are interpreted as follows:
68
+ # :key/ - walk down tree by one level from key
69
+ # :*/key - walk down tree from any parent with key as a child
70
+ # :key1|key2 - return elements with key value of key1 or key2
71
+ # :key=val - return elements where key has a value of val
72
+ # :key\* - return root-level element with key "key*"
73
+ #
74
+ # Other examples:
75
+ # find_data data, root/**=invalid|
76
+ # # Returns an Array of grand-children key/value pairs
77
+ # # where the value is 'invalid' or blank
78
+
79
+ def find_data data_path, curr_path=nil, &block
80
+ self.class.find_data @data, data_path, curr_path, &block
81
+ end
82
+
83
+
84
+ ##
85
+ # See DataSet#find_data
86
+
87
+ def self.find_data data, data_path, curr_path=nil, &block
88
+ curr_path ||= []
89
+
90
+ key, value, rec, data_path = parse_data_path data_path
91
+
92
+ yield_data_points data, key, value, rec, curr_path do |d, k, p|
93
+
94
+ if data_path
95
+ find_data d[k], data_path, p, &block
96
+ else
97
+ yield d, k, p
98
+ end
99
+ end
100
+ end
101
+
102
+
103
+ ##
104
+ # Parses a given data point and returns an array with the following:
105
+ # - Key to match
106
+ # - Value to match
107
+ # - Recursive matching
108
+ # - New data path value
109
+
110
+ def self.parse_data_path data_path
111
+ data_path = data_path.dup
112
+ key = nil
113
+ value = nil
114
+ recursive = false
115
+
116
+ until key && key != "**" || value || data_path.empty? do
117
+ value = data_path.slice!(%r{((.*?[^\\])+?/)})
118
+ (value ||= data_path).sub!(/\/$/, '')
119
+
120
+ data_path = nil if value == data_path
121
+
122
+ key = value.slice! %r{((.*?[^\\])+?=)}
123
+ key, value = value, nil if key.nil?
124
+ key.sub!(/\=$/, '')
125
+
126
+ value = parse_path_item value if value
127
+
128
+ if key =~ /^\*{2,}$/
129
+ if data_path && !value
130
+ key, value, rec, data_path = parse_data_path(data_path)
131
+ else
132
+ key = /.*/
133
+ end
134
+
135
+ recursive = true
136
+ else
137
+ key = parse_path_item key
138
+ end
139
+ end
140
+
141
+ data_path = nil if data_path && data_path.empty?
142
+
143
+ [key, value, recursive, data_path]
144
+ end
145
+
146
+
147
+ ##
148
+ # Decide whether to make path item a regex or not.
149
+
150
+ def self.parse_path_item str
151
+ if str =~ /(^|[^\\])([\*\?\|])/
152
+ str.gsub!(/(^|[^\\])(\*|\?)/, '\1.\2')
153
+ str = /^(#{str})$/
154
+ else
155
+ str.gsub! "\\", ""
156
+ end
157
+
158
+ str
159
+ end
160
+
161
+
162
+ ##
163
+ # Yield data object and key, if a specific key or value matches
164
+ # the given data.
165
+
166
+ def self.yield_data_points data, mkey, mvalue=nil,
167
+ recursive=false, path=nil, &block
168
+
169
+ return unless Hash === data || Array === data
170
+ path ||= []
171
+
172
+ each_data_item data do |key, value|
173
+ curr_path = path.dup << key
174
+
175
+ found = match_data_item(mkey, key) &&
176
+ match_data_item(mvalue, value)
177
+
178
+ yield data, key, curr_path if found
179
+ yield_data_points data[key], mkey, mvalue, true, curr_path, &block if
180
+ recursive
181
+ end
182
+ end
183
+
184
+
185
+ ##
186
+ # Check if data key or value is a match for nested data searches.
187
+
188
+ def self.match_data_item item1, item2
189
+ return if !item1.nil? && (Array === item2 || Hash === item2)
190
+
191
+ if Regexp === item1
192
+ item2.to_s =~ item1
193
+ elsif item1.nil?
194
+ true
195
+ else
196
+ item2.to_s == item1.to_s
197
+ end
198
+ end
199
+
200
+
201
+ ##
202
+ # Universal iterator for Hash and Array objects.
203
+
204
+ def self.each_data_item data, &block
205
+ case data
206
+
207
+ when Hash
208
+ data.each(&block)
209
+
210
+ when Array
211
+ i = 0
212
+
213
+ # We need to iterate through the array this way
214
+ # in case items in it get deleted.
215
+
216
+ while i < data.length do
217
+ index = i
218
+ old_length = data.length
219
+
220
+ block.call index, data[index]
221
+
222
+ adj = old_length - data.length
223
+ adj = 0 if adj < 0
224
+
225
+ i = i.next - adj
226
+ end
227
+ end
228
+ end
229
+ end
230
+ end
data/lib/kronk/diff.rb ADDED
@@ -0,0 +1,210 @@
1
+ class Kronk
2
+
3
+
4
+ ##
5
+ # Creates simple diffs as formatted strings or arrays, from two strings or
6
+ # data objects.
7
+
8
+ class Diff
9
+
10
+
11
+ ##
12
+ # Creates a new diff from two data objects.
13
+
14
+ def self.new_from_data data1, data2, options={}
15
+ new ordered_data_string(data1, options[:struct]),
16
+ ordered_data_string(data2, options[:struct])
17
+ end
18
+
19
+
20
+ ##
21
+ # Returns a data string that is diff-able, meaning sorted by
22
+ # Hash keys when available.
23
+
24
+ def self.ordered_data_string data, struct_only=false, indent=0
25
+ case data
26
+
27
+ when Hash
28
+ output = "{\n"
29
+
30
+ key_width = 0
31
+ data.keys.each do |k|
32
+ key_width = k.inspect.length if k.inspect.length > key_width
33
+ end
34
+
35
+ data_values =
36
+ data.map do |key, value|
37
+ pad = " " * indent
38
+ subdata = ordered_data_string value, struct_only, indent + 1
39
+ "#{pad}#{key.inspect} => #{subdata}"
40
+ end
41
+
42
+ output << data_values.sort.join(",\n") << "\n"
43
+ output << "#{" " * indent}}"
44
+
45
+ when Array
46
+ output = "[\n"
47
+
48
+ data_values =
49
+ data.map do |value|
50
+ pad = " " * indent
51
+ "#{pad}#{ordered_data_string value, struct_only, indent + 1}"
52
+ end
53
+
54
+ output << data_values.join(",\n") << "\n"
55
+ output << "#{" " * indent}]"
56
+
57
+ else
58
+ return data.inspect unless struct_only
59
+ return "Boolean" if data == true || data == false
60
+ data.class
61
+ end
62
+ end
63
+
64
+
65
+ attr_accessor :str1, :str2, :char, :format
66
+
67
+ def initialize str1, str2, char=/\r?\n/
68
+ @str1 = str1
69
+ @str2 = str2
70
+ @char = char
71
+ @diff_ary = nil
72
+ @format = Kronk.config[:diff_format] || :ascii_diff
73
+ end
74
+
75
+
76
+ ##
77
+ # Returns a diff array with the following format:
78
+ # str1 = "match1\nmatch2\nstr1 val"
79
+ # str1 = "match1\nin str2\nmore str2\nmatch2\nstr2 val"
80
+ #
81
+ # Diff.new(str1, str2).create_diff
82
+ # ["match 1",
83
+ # [[], ["in str2", "more str2"]],
84
+ # "match 2",
85
+ # [["str1 val"], ["str2 val"]]]
86
+
87
+ def create_diff
88
+ diff_ary = []
89
+ sub_diff = nil
90
+
91
+ arr1 = @str1.split @char
92
+ arr2 = @str2.split @char
93
+
94
+ until arr1.empty? && arr2.empty?
95
+ item1, item2 = arr1.shift, arr2.shift
96
+
97
+ if item1 == item2
98
+ if sub_diff
99
+ diff_ary << sub_diff
100
+ sub_diff = nil
101
+ end
102
+
103
+ diff_ary << item1
104
+ next
105
+ end
106
+
107
+ match1 = arr1.index item2
108
+ match2 = arr2.index item1
109
+
110
+ if match1
111
+ diff_ary.concat diff_match(item1, match1, arr1, 0, sub_diff)
112
+ sub_diff = nil
113
+
114
+ elsif match2
115
+ diff_ary.concat diff_match(item2, match2, arr2, 1, sub_diff)
116
+ sub_diff = nil
117
+
118
+ elsif !item1.nil? || !item2.nil?
119
+ sub_diff ||= [[],[]]
120
+ sub_diff[0] << item1 if item1
121
+ sub_diff[1] << item2 if item2
122
+ end
123
+ end
124
+
125
+ diff_ary << sub_diff if sub_diff
126
+
127
+ diff_ary
128
+ end
129
+
130
+
131
+ ##
132
+ # Create a diff from a match.
133
+
134
+ def diff_match item, match, arr, side, sub_diff
135
+ sub_diff ||= [[],[]]
136
+
137
+ index = match - 1
138
+ added = [item]
139
+ added.concat arr.slice!(0..index) if index >= 0
140
+
141
+ sub_diff[side].concat(added)
142
+ [sub_diff, arr.shift]
143
+ end
144
+
145
+
146
+ ##
147
+ # Returns a formatted output as a string.
148
+ # Custom formats may be achieved by passing a block.
149
+
150
+ def formatted format=@format, join_char="\n", &block
151
+ block ||= method format
152
+
153
+ diff_array.map do |item|
154
+ block.call item.dup
155
+ end.flatten.join join_char
156
+ end
157
+
158
+
159
+ ##
160
+ # Formats a single diff element to the default diff format.
161
+
162
+ def ascii_diff item
163
+ case item
164
+ when String
165
+ " #{item}"
166
+ when Array
167
+ item[0] = item[0].map{|str| "- #{str}"}
168
+ item[1] = item[1].map{|str| "+ #{str}"}
169
+ item
170
+ else
171
+ " #{item.inspect}"
172
+ end
173
+ end
174
+
175
+
176
+ ##
177
+ # Formats a single diff element with colors.
178
+
179
+ def color_diff item
180
+ case item
181
+ when String
182
+ item
183
+ when Array
184
+ item[0] = item[0].map{|str| "\033[31m#{str}\033[0m"}
185
+ item[1] = item[1].map{|str| "\033[32m#{str}\033[0m"}
186
+ item
187
+ else
188
+ item.inspect
189
+ end
190
+ end
191
+
192
+
193
+ ##
194
+ # Returns the cached diff array when available, otherwise creates it.
195
+
196
+ def diff_array
197
+ @diff_ary ||= create_diff
198
+ end
199
+
200
+ alias to_a diff_array
201
+
202
+
203
+ ##
204
+ # Returns a diff string with the default format.
205
+
206
+ def to_s
207
+ formatted
208
+ end
209
+ end
210
+ end
@@ -0,0 +1,15 @@
1
+ class Kronk
2
+
3
+ ##
4
+ # Simple plist wrapper to add :parse method.
5
+
6
+ class PlistParser
7
+
8
+ ##
9
+ # Alias for Plist.parse_xml
10
+
11
+ def self.parse plist
12
+ Plist.parse_xml plist
13
+ end
14
+ end
15
+ end