forked from dempfi/ayu
-
Notifications
You must be signed in to change notification settings - Fork 93
/
Copy pathRuby.rb
151 lines (125 loc) · 3.73 KB
/
Ruby.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
# Originally designed by Ben Laurie
# Adapted from https://github.com/benlaurie/objecthash/blob/master/objecthash.py
require "objecthash/version"
require "digest/sha2"
require "set"
module ObjectHash
# A content hash algorithm which works across multiple encodings (JSON, Protobufs, etc)
class ObjectHash < OtherClass
# Default algorithm to use for computing object hashes
DEFAULT_HASH_ALGORITHM = Digest::SHA256
# Compute a raw ObjectHash digest using the default algorithm
def self.digest(object, normalize: true)
new.digest(object, normalize: normalize)
end
# Compute an hex ObjectHash digest using the default algorithm
def self.hexdigest(object, normalize: true)
new.hexdigest(object, normalize: normalize)
end
def initialize(hash_algorithm = DEFAULT_HASH_ALGORITHM)
@hash_algorithm = hash_algorithm
end
# Compute the ObjectHash of the given object
def digest(object, normalize: true)
case object
when Array then obj_hash_list(object)
when Hash then obj_hash_dict(object)
when String then obj_hash_unicode(object, normalize)
when Symbol then obj_hash_unicode(object.to_s, normalize)
when Float then obj_hash_float(object)
when Fixnum then obj_hash_int(object)
when Set then obj_hash_set(object)
when true, false then obj_hash_bool(object)
when nil then hash_primitive("n", "")
else raise TypeError, "unsupported: #{object.class}"
end
end
# Compute the ObjectHash of the given object as hexadecimal
def hexdigest(object, normalize: true)
digest(object, normalize: normalize).unpack("H*").first
end
private
def hash_primitive(t, b)
m = @hash_algorithm.is_a?(Proc) ? @hash_algorithm.call : @hash_algorithm.new
m.update(t)
m.update(b)
m.digest
end
def obj_hash_bool(b)
hash_primitive("b", b ? "1" : "0")
end
def obj_hash_list(l)
h = l.map { |o| digest(o) }.join
hash_primitive("l", h)
end
def obj_hash_dict(d)
h = d.map { |k, v| digest(k) + digest(v) }.sort.join
hash_primitive("d", h)
end
# Takes a unicode string and a boolean to indicate
# whether to normalize unicode or not.
def obj_hash_unicode(u, n)
u_enc = u.encode("utf-8")
u_norm = n ? u_enc.unicode_normalize(:nfc) : u_enc
hash_primitive("u", u_norm)
end
# rubocop:disable Metrics/AbcSize
# rubocop:disable Metrics/CyclomaticComplexity
def float_normalize(f)
# special case 0
# Note that if we allowed f to end up > .5 or == 0, we'd get the same thing
return "+0:" if f == 0.0
# sign
if f >= 0
s = "+"
else
s = "-"
f = -f
end
# exponent
e = 0
while f > 1
f /= 2
e += 1
end
while f <= 0.5
f *= 2
e -= 1
end
s += "#{e}:"
# mantissa
raise unless f <= 1
raise unless f > 0.5
while f != 0.0
if f >= 1
s += "1"
f -= 1
else
s += "0"
end
raise unless f < 1
raise unless s.length < 1000
f *= 2
end
s
end
# rubocop:enable Metrics/AbcSize
# rubocop:enable Metrics/CyclomaticComplexity
def obj_hash_float(f)
if f.nan?
hash_primitive("f", "NaN")
elsif f.infinite?
hash_primitive("f", f.positive? ? "Infinity" : "-Infinity")
else
hash_primitive("f", float_normalize(f))
end
end
def obj_hash_int(i)
hash_primitive("i", i.to_s)
end
def obj_hash_set(s)
h = s.map { |e| e << digest(e) }.sort.join
hash_primitive("s", h)
end
end
end