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.
- data/.autotest +23 -0
- data/History.txt +6 -0
- data/Manifest.txt +14 -0
- data/README.txt +118 -0
- data/Rakefile +20 -0
- data/bin/kronk +13 -0
- data/lib/kronk.rb +404 -0
- data/lib/kronk/data_set.rb +230 -0
- data/lib/kronk/diff.rb +210 -0
- data/lib/kronk/plist_parser.rb +15 -0
- data/lib/kronk/request.rb +191 -0
- data/lib/kronk/response.rb +226 -0
- data/lib/kronk/xml_parser.rb +108 -0
- data/test/test_data_set.rb +410 -0
- data/test/test_diff.rb +427 -0
- data/test/test_helper.rb +37 -0
- data/test/test_kronk.rb +192 -0
- data/test/test_request.rb +225 -0
- data/test/test_response.rb +258 -0
- data/test/test_xml_parser.rb +101 -0
- metadata +225 -0
@@ -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
|