-
Notifications
You must be signed in to change notification settings - Fork 28
/
hana.rb
155 lines (122 loc) · 3.06 KB
/
hana.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
module Hana
VERSION = '1.0.1'
class Pointer
include Enumerable
def initialize path
@path = Pointer.parse path
end
def each
@path.each { |x| yield x }
end
def to_a; @path.dup; end
def eval object
Pointer.eval @path, object
end
def self.eval list, object
list.inject(object) { |o, part| o[(Array === o ? part.to_i : part)] }
end
def self.parse path
return [''] if path == '/'
path.sub(/^\//, '').split(/(?<!\^)\//).map { |part|
part.gsub!(/\^([\/^])/, '\1')
part.gsub!(/~1/, '/')
part.gsub!(/~0/, '~')
part
}
end
def self.parse2 path
path.sub(/^\//, '').split(/(?<!\^)\//)
end
end
class Patch
class Exception < StandardError
end
class FailedTestException < Exception
attr_accessor :path, :value
def initialize path, value
super "expected #{value} at #{path}"
@path = path
@value = value
end
end
class OutOfBoundsException < Exception
end
class ObjectOperationOnArrayException < Exception
end
def initialize is
@is = is
end
VALID = Hash[%w{ add move test replace remove copy }.map { |x| [x,x]}] # :nodoc:
def apply doc
@is.each_with_object(doc) { |ins, d|
send VALID.fetch(ins['op'].strip) { |k|
raise Exception, "bad method `#{k}`"
}, ins, d
}
end
private
def copy ins, doc
raise NotImplementedError
end
def add ins, doc
list = Pointer.parse ins['path']
key = list.pop
dest = Pointer.eval list, doc
obj = ins['value']
add_op dest, key, obj
end
def move ins, doc
from = Pointer.parse ins['from']
to = Pointer.parse ins['path']
from_key = from.pop
key = to.pop
src = Pointer.eval(from, doc)
if Array === src
obj = src.delete_at from_key.to_i
else
obj = src.delete from_key
end
dest = Pointer.eval(to, doc)
add_op dest, key, obj
end
def test ins, doc
expected = Pointer.new(ins['path']).eval doc
unless expected == ins['value']
raise FailedTestException.new(ins['value'], ins['path'])
end
end
def replace ins, doc
list = Pointer.parse ins['path']
key = list.pop
obj = Pointer.eval list, doc
if Array === obj
obj[key.to_i] = ins['value']
else
obj[key] = ins['value']
end
end
def remove ins, doc
list = Pointer.parse ins['path']
key = list.pop
obj = Pointer.eval list, doc
if Array === obj
obj.delete_at key.to_i
else
obj.delete key
end
end
def check_index obj, key
raise ObjectOperationOnArrayException unless key =~ /\A-?\d+\Z/
idx = key.to_i
raise OutOfBoundsException if idx > obj.length || idx < 0
idx
end
def add_op dest, key, obj
if Array === dest
dest.insert check_index(dest, key), obj
else
dest[key] = obj
end
end
end
end