emery 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 8dc449ad3ba135140479a6711b016e63bfc30f9fd6dcf6dba8ff78ee71208a6a
4
+ data.tar.gz: 9c2f86bde90521cfa5adb78eda8ceef5504c2fbcf44de558cf2450c550cea96e
5
+ SHA512:
6
+ metadata.gz: e9695fe9eeed44ad9d3bc33e1dc92ae4de5a31e024c7fa9ba1fa34dbd3df554cf5cd73e9634dbaec1246ca80b8ac536fd1020b129ef26d9973512c34ca77b920
7
+ data.tar.gz: 8f7b8961c1c39cf2a354163725bd1fc761e620627ebbb73cb1dd91c7d5746bdc6fd937ccc17fa2bf70ef7af826dd0eb60f09102a4b27ab7f6cf1343633686ecf
data/lib/emery.rb ADDED
@@ -0,0 +1,6 @@
1
+ require 'emery/tod'
2
+ require 'emery/type'
3
+ require 'emery/jsoner'
4
+ require 'emery/enum'
5
+ require 'emery/dataclass'
6
+ require 'emery/client'
@@ -0,0 +1,86 @@
1
+ module Emery
2
+ module DataClass
3
+ def initialize(params)
4
+ self.class.json_attributes.each do |attr, attr_type|
5
+ attr_value = params[attr]
6
+ self.instance_variable_set("@#{attr}", T.check_var(attr, attr_type, attr_value))
7
+ end
8
+ end
9
+
10
+ def ==(other)
11
+ begin
12
+ T.check(self.class, other)
13
+ self.class.json_attributes.keys.each do |attr|
14
+ if self.instance_variable_get("@#{attr}") != other.instance_variable_get("@#{attr}")
15
+ return false
16
+ end
17
+ end
18
+ return true
19
+ rescue
20
+ return false
21
+ end
22
+ end
23
+
24
+ def copy(params)
25
+ params.each do |attr, attr_value|
26
+ if !self.class.json_attributes.key?(attr)
27
+ raise TypeError.new("Non existing attribute #{attr}")
28
+ end
29
+ end
30
+ new_params =
31
+ self.class.json_attributes.map do |attr, attr_type|
32
+ attr_value =
33
+ if params.key?(attr)
34
+ params[attr]
35
+ else
36
+ self.instance_variable_get("@#{attr}")
37
+ end
38
+ [attr, attr_value]
39
+ end.to_h
40
+ return self.class.new(new_params)
41
+ end
42
+
43
+ def self.included(base)
44
+ base.extend ClassMethods
45
+ end
46
+
47
+ module ClassMethods
48
+ def json_attributes
49
+ @json_attributes
50
+ end
51
+
52
+ def val(name, type)
53
+ if @json_attributes == nil
54
+ @json_attributes = {}
55
+ end
56
+ @json_attributes[name] = type
57
+ attr_reader name
58
+ end
59
+
60
+ def var(name, type)
61
+ if @json_attributes == nil
62
+ @json_attributes = {}
63
+ end
64
+ @json_attributes[name] = type
65
+ attr_accessor name
66
+ end
67
+
68
+ def jsoner_deserialize(json_value)
69
+ T.check(T.hash(String, NilableUntyped), json_value)
70
+ parameters = @json_attributes.map do |attr, attr_type|
71
+ attr_value = json_value[attr.to_s]
72
+ [attr, Jsoner.deserialize(attr_type, attr_value)]
73
+ end
74
+ return self.new parameters.to_h
75
+ end
76
+
77
+ def jsoner_serialize(value)
78
+ T.check(self, value)
79
+ attrs = @json_attributes.map do |attr, attr_type|
80
+ [attr, Jsoner.serialize(attr_type, value.send(attr))]
81
+ end
82
+ return attrs.to_h
83
+ end
84
+ end
85
+ end
86
+ end
data/lib/emery/enum.rb ADDED
@@ -0,0 +1,101 @@
1
+ module Emery
2
+ module Enum
3
+ attr_reader :key, :value
4
+
5
+ def initialize(key, value)
6
+ @key = key
7
+ @value = value
8
+ end
9
+
10
+ def self.included(base)
11
+ base.extend Enumerable
12
+ base.extend ClassMethods
13
+
14
+ base.private_class_method(:new)
15
+ end
16
+
17
+ module ClassMethods
18
+ def check(value)
19
+ T.check_not_nil(self, value)
20
+ if !value?(value)
21
+ raise TypeError.new("Value '#{value.inspect.to_s}' is not a member of enum #{self}")
22
+ end
23
+ end
24
+
25
+ def jsoner_deserialize(json_value)
26
+ T.check(self, json_value)
27
+ end
28
+
29
+ def jsoner_serialize(value)
30
+ T.check(self, value)
31
+ end
32
+
33
+ def define(key, value)
34
+ @_enum_hash ||= {}
35
+ @_enums_by_value ||= {}
36
+
37
+ if @_enum_hash.key?(key) then
38
+ raise TypeError.new("Duplicate key: #{key}")
39
+ end
40
+
41
+ if @_enums_by_value.key?(value) then
42
+ raise TypeError.new("Duplicate value: #{value}")
43
+ end
44
+
45
+ new_instance = new(key, value)
46
+ @_enum_hash[key] = new_instance
47
+ @_enums_by_value[value] = new_instance
48
+
49
+ if key.to_s == key.to_s.upcase
50
+ const_set key, value
51
+ else
52
+ define_singleton_method(key) { value }
53
+ end
54
+ end
55
+
56
+ def each(&block)
57
+ @_enum_hash.each(&block)
58
+ end
59
+
60
+ def parse(k)
61
+ k = k.to_s.upcase
62
+ each do |key, enum|
63
+ return enum.value if key.to_s.upcase == k
64
+ end
65
+ nil
66
+ end
67
+
68
+ def key?(k)
69
+ @_enum_hash.key?(k)
70
+ end
71
+
72
+ def value(k)
73
+ enum = @_enum_hash[k]
74
+ enum.value if enum
75
+ end
76
+
77
+ def value?(v)
78
+ @_enums_by_value.key?(v)
79
+ end
80
+
81
+ def key(v)
82
+ enum = @_enums_by_value[v]
83
+ enum.key if enum
84
+ end
85
+
86
+ def keys
87
+ @_enum_hash.values.map(&:key)
88
+ end
89
+
90
+ def values
91
+ @_enum_hash.values.map(&:value)
92
+ end
93
+
94
+ def to_h
95
+ Hash[@_enum_hash.map do |key, enum|
96
+ [key, enum.value]
97
+ end]
98
+ end
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,188 @@
1
+ require "json"
2
+ require "date"
3
+
4
+ require "emery/type"
5
+
6
+ module Emery
7
+ class JsonerError < StandardError
8
+ end
9
+
10
+ module Jsoner
11
+ T::StringFormatted.class_eval do
12
+ def jsoner_deserialize(json_value)
13
+ T.check(self, json_value)
14
+ end
15
+ def jsoner_serialize(value)
16
+ T.check(self, value)
17
+ end
18
+ end
19
+
20
+ T::ArrayType.class_eval do
21
+ def jsoner_deserialize(json_value)
22
+ T.check_not_nil(self, json_value)
23
+ if !json_value.is_a?(Array)
24
+ raise JsonerError.new("JSON value type #{json_value.class} is not Array")
25
+ end
26
+ json_value.map { |item_json_value| Jsoner.deserialize(self.item_type, item_json_value) }
27
+ end
28
+ def jsoner_serialize(value)
29
+ if !value.is_a?(Array)
30
+ raise JsonerError.new("Value type #{json_value.class} is not Array")
31
+ end
32
+ value.map { |item| Jsoner.serialize(self.item_type, item) }
33
+ end
34
+ end
35
+
36
+ T::HashType.class_eval do
37
+ def jsoner_deserialize(json_value)
38
+ T.check_not_nil(self, json_value)
39
+ if self.key_type != String
40
+ raise JsonerError.new("Hash key type #{self.key_type} is not supported for JSON (de)serialization - key should be String")
41
+ end
42
+ if !json_value.is_a?(Hash)
43
+ raise JsonerError.new("JSON value type #{json_value.class} is not Hash")
44
+ end
45
+ json_value.map do |key, value|
46
+ [T.check(self.key_type, key), Jsoner.deserialize(self.value_type, value)]
47
+ end.to_h
48
+ end
49
+ def jsoner_serialize(value)
50
+ if self.key_type != String
51
+ raise JsonerError.new("Hash key type #{self.key_type} is not supported for JSON (de)serialization - key should be String")
52
+ end
53
+ if !value.is_a?(Hash)
54
+ raise JsonerError.new("Value type #{value.class} is not Hash")
55
+ end
56
+ value.map do |key, value|
57
+ [T.check(self.key_type, key), Jsoner.serialize(self.value_type, value)]
58
+ end.to_h
59
+ end
60
+ end
61
+
62
+ T::AnyType.class_eval do
63
+ def jsoner_deserialize(json_value)
64
+ types.each do |type|
65
+ begin
66
+ return Jsoner.deserialize(type, json_value)
67
+ rescue TypeError
68
+ end
69
+ end
70
+ raise JsonerError.new("Value '#{json_value.inspect.to_s}' can not be deserialized as any of #{@types.map { |t| t.to_s}.join(', ')}")
71
+ end
72
+ def jsoner_serialize(value)
73
+ T.check(self, value)
74
+ type = types.find {|t| T.instance_of?(t, value) }
75
+ Jsoner.serialize(type, value)
76
+ end
77
+ end
78
+
79
+ T::Nilable.class_eval do
80
+ def jsoner_deserialize(json_value)
81
+ if json_value != nil
82
+ Jsoner.deserialize(self.type, json_value)
83
+ else
84
+ nil
85
+ end
86
+ end
87
+ def jsoner_serialize(value)
88
+ if value != nil
89
+ Jsoner.serialize(self.type, value)
90
+ else
91
+ nil
92
+ end
93
+ end
94
+ end
95
+
96
+ module FloatSerializer
97
+ def self.jsoner_deserialize(json_value)
98
+ T.check(T.any(Float, Integer), json_value)
99
+ json_value.to_f
100
+ end
101
+ def self.jsoner_serialize(value)
102
+ T.check(Float, value)
103
+ end
104
+ end
105
+
106
+ module DateTimeSerializer
107
+ def self.jsoner_deserialize(json_value)
108
+ T.check(String, json_value)
109
+ begin
110
+ DateTime.strptime(json_value, '%Y-%m-%dT%H:%M:%S')
111
+ rescue
112
+ raise JsonerError.new("Failed to parse DateTime from '#{json_value.inspect.to_s}' format %Y-%m-%dT%H:%M:%S is required")
113
+ end
114
+ end
115
+ def self.jsoner_serialize(value)
116
+ T.check(DateTime, value)
117
+ value.strftime('%Y-%m-%dT%H:%M:%S')
118
+ end
119
+ end
120
+
121
+ module DateSerializer
122
+ def self.jsoner_deserialize(json_value)
123
+ T.check(String, json_value)
124
+ begin
125
+ Date.strptime(json_value, '%Y-%m-%d')
126
+ rescue
127
+ raise JsonerError.new("Failed to parse Date from '#{json_value.inspect.to_s}' format %Y-%m-%d is required")
128
+ end
129
+ end
130
+ def self.jsoner_serialize(value)
131
+ T.check(Date, value)
132
+ value.strftime('%Y-%m-%d')
133
+ end
134
+ end
135
+
136
+ @@serializers = {
137
+ Float => FloatSerializer,
138
+ Date => DateSerializer,
139
+ DateTime => DateTimeSerializer
140
+ }
141
+ def self.add_serializer(type, serializer)
142
+ @@serializers[type] = serializer
143
+ end
144
+
145
+ def Jsoner.from_json(type, json)
146
+ data = JSON.parse(json)
147
+ return deserialize(type, data)
148
+ end
149
+
150
+ def Jsoner.deserialize(type, json_value)
151
+ begin
152
+ if type.methods.include? :jsoner_deserialize
153
+ return type.jsoner_deserialize(json_value)
154
+ elsif @@serializers.include? type
155
+ return @@serializers[type].jsoner_deserialize(json_value)
156
+ else
157
+ if ![String, Float, Integer, TrueClass, FalseClass, NilClass].include? type
158
+ raise JsonerError.new("Type #{type} is not supported in Jsoner deserialization")
159
+ end
160
+ return T.check(type, json_value)
161
+ end
162
+ rescue StandardError => error
163
+ raise JsonerError.new(error.message)
164
+ end
165
+ end
166
+
167
+ def Jsoner.to_json(type, value)
168
+ JSON.dump(serialize(type, value))
169
+ end
170
+
171
+ def Jsoner.serialize(type, value)
172
+ begin
173
+ if type.methods.include? :jsoner_serialize
174
+ return type.jsoner_serialize(value)
175
+ elsif @@serializers.include? type
176
+ return @@serializers[type].jsoner_serialize(value)
177
+ else
178
+ if ![String, Float, Integer, TrueClass, FalseClass, NilClass].include? type
179
+ raise JsonerError.new("Type #{type} is not supported in Jsoner serialization")
180
+ end
181
+ return T.check(type, value)
182
+ end
183
+ rescue StandardError => error
184
+ raise JsonerError.new(error.message)
185
+ end
186
+ end
187
+ end
188
+ end
data/lib/emery/tod.rb ADDED
@@ -0,0 +1,262 @@
1
+ =begin
2
+
3
+ source: https://github.com/jackc/tod
4
+
5
+ Copyright (c) 2010-2015 Jack Christensen
6
+
7
+ Permission is hereby granted, free of charge, to any person obtaining
8
+ a copy of this software and associated documentation files (the
9
+ "Software"), to deal in the Software without restriction, including
10
+ without limitation the rights to use, copy, modify, merge, publish,
11
+ distribute, sublicense, and/or sell copies of the Software, and to
12
+ permit persons to whom the Software is furnished to do so, subject to
13
+ the following conditions:
14
+
15
+ The above copyright notice and this permission notice shall be
16
+ included in all copies or substantial portions of the Software.
17
+
18
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
19
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
20
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
21
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
22
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
23
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
24
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
25
+
26
+ =end
27
+
28
+ module Emery
29
+ class TimeOfDay
30
+ include Comparable
31
+
32
+ def self.jsoner_deserialize(json_value)
33
+ TimeOfDay.parse(T.check(String, json_value))
34
+ end
35
+ def self.jsoner_serialize(value)
36
+ T.check(TimeOfDay, value).to_s
37
+ end
38
+
39
+ attr_reader :hour, :minute, :second, :second_of_day
40
+ alias_method :min, :minute
41
+ alias_method :sec, :second
42
+ alias_method :to_i, :second_of_day
43
+
44
+ PARSE_24H_REGEX = /
45
+ \A
46
+ ([01]?\d|2[0-4])
47
+ :?
48
+ ([0-5]\d)?
49
+ :?
50
+ ([0-5]\d)?
51
+ \z
52
+ /x
53
+
54
+ PARSE_12H_REGEX = /
55
+ \A
56
+ (0?\d|1[0-2])
57
+ :?
58
+ ([0-5]\d)?
59
+ :?
60
+ ([0-5]\d)?
61
+ \s*
62
+ ([ap])
63
+ \.?
64
+ \s*
65
+ m?
66
+ \.?
67
+ \z
68
+ /x
69
+
70
+ WORDS = {
71
+ "noon" => "12pm".freeze,
72
+ "midnight" => "12am".freeze
73
+ }
74
+
75
+ NUM_SECONDS_IN_DAY = 86400
76
+ NUM_SECONDS_IN_HOUR = 3600
77
+ NUM_SECONDS_IN_MINUTE = 60
78
+
79
+ FORMATS = {
80
+ short: "%-l:%M %P".freeze,
81
+ medium: "%-l:%M:%S %P".freeze,
82
+ time: "%H:%M".freeze
83
+ }
84
+
85
+ def initialize(h, m=0, s=0)
86
+ @hour = Integer(h)
87
+ @minute = Integer(m)
88
+ @second = Integer(s)
89
+
90
+ raise ArgumentError, "hour must be between 0 and 24" unless (0..24).include?(@hour)
91
+ if @hour == 24 && (@minute != 0 || @second != 0)
92
+ raise ArgumentError, "hour can only be 24 when minute and second are 0"
93
+ end
94
+ raise ArgumentError, "minute must be between 0 and 59" unless (0..59).include?(@minute)
95
+ raise ArgumentError, "second must be between 0 and 59" unless (0..59).include?(@second)
96
+
97
+ @second_of_day = @hour * 60 * 60 + @minute * 60 + @second
98
+
99
+ freeze # TimeOfDay instances are value objects
100
+ end
101
+
102
+ def <=>(other)
103
+ return unless other.respond_to?(:second_of_day)
104
+ @second_of_day <=> other.second_of_day
105
+ end
106
+
107
+ # Rounding to the given nearest number of seconds
108
+ def round(round_sec = 1)
109
+ down = self - (self.to_i % round_sec)
110
+ up = down + round_sec
111
+
112
+ difference_down = self - down
113
+ difference_up = up - self
114
+
115
+ if (difference_down < difference_up)
116
+ return down
117
+ else
118
+ return up
119
+ end
120
+ end
121
+
122
+ # Formats identically to Time#strftime
123
+ def strftime(format_string)
124
+ # Special case 2400 because strftime will load TimeOfDay into Time which
125
+ # will convert 24 to 0
126
+ format_string = format_string.gsub(/%H|%k/, '24') if @hour == 24
127
+ Time.local(2000,1,1, @hour, @minute, @second).strftime(format_string)
128
+ end
129
+
130
+ def to_formatted_s(format = :default)
131
+ if formatter = FORMATS[format]
132
+ if formatter.respond_to?(:call)
133
+ formatter.call(self).to_s
134
+ else
135
+ strftime(formatter)
136
+ end
137
+ else
138
+ strftime "%H:%M:%S"
139
+ end
140
+ end
141
+ alias_method :to_s, :to_formatted_s
142
+
143
+ def value_for_database
144
+ to_s
145
+ end
146
+
147
+ # Return a new TimeOfDay num_seconds greater than self. It will wrap around
148
+ # at midnight.
149
+ def +(num_seconds)
150
+ TimeOfDay.from_second_of_day @second_of_day + num_seconds
151
+ end
152
+
153
+ # Return a new TimeOfDay num_seconds less than self. It will wrap around
154
+ # at midnight.
155
+ def -(other)
156
+ if other.instance_of?(TimeOfDay)
157
+ TimeOfDay.from_second_of_day @second_of_day - other.second_of_day
158
+ else
159
+ TimeOfDay.from_second_of_day @second_of_day - other
160
+ end
161
+ end
162
+
163
+ # Returns a Time instance on date using self as the time of day
164
+ # Optional time_zone will build time in that zone
165
+ def on(date, time_zone=Tod::TimeOfDay.time_zone)
166
+ time_zone.local date.year, date.month, date.day, @hour, @minute, @second
167
+ end
168
+
169
+ # Build a new TimeOfDay instance from second_of_day
170
+ #
171
+ # TimeOfDay.from_second_of_day(3600) == TimeOfDay.new(1) # => true
172
+ def self.from_second_of_day(second_of_day)
173
+ second_of_day = Integer(second_of_day)
174
+ return new 24 if second_of_day == NUM_SECONDS_IN_DAY
175
+ remaining_seconds = second_of_day % NUM_SECONDS_IN_DAY
176
+ hour = remaining_seconds / NUM_SECONDS_IN_HOUR
177
+ remaining_seconds -= hour * NUM_SECONDS_IN_HOUR
178
+ minute = remaining_seconds / NUM_SECONDS_IN_MINUTE
179
+ remaining_seconds -= minute * NUM_SECONDS_IN_MINUTE
180
+ new hour, minute, remaining_seconds
181
+ end
182
+ class << self
183
+ alias :from_i :from_second_of_day
184
+ end
185
+
186
+ # Build a TimeOfDay instance from string
187
+ #
188
+ # Strings only need to contain an hour. Minutes, seconds, AM or PM, and colons
189
+ # are all optional.
190
+ # TimeOfDay.parse "8" # => 08:00:00
191
+ # TimeOfDay.parse "8am" # => 08:00:00
192
+ # TimeOfDay.parse "8pm" # => 20:00:00
193
+ # TimeOfDay.parse "8p" # => 20:00:00
194
+ # TimeOfDay.parse "9:30" # => 09:30:00
195
+ # TimeOfDay.parse "15:30" # => 15:30:00
196
+ # TimeOfDay.parse "3:30pm" # => 15:30:00
197
+ # TimeOfDay.parse "1230" # => 12:30:00
198
+ # TimeOfDay.parse "3:25:58" # => 03:25:58
199
+ # TimeOfDay.parse "515p" # => 17:15:00
200
+ # TimeOfDay.parse "151253" # => 15:12:53
201
+ # You can give a block, that is called with the input if the string is not parsable.
202
+ # If no block is given an ArgumentError is raised if try_parse returns nil.
203
+ def self.parse(tod_string)
204
+ try_parse(tod_string) || (block_given? ? yield(tod_string) : raise(ArgumentError, "Invalid time of day string"))
205
+ end
206
+
207
+ # Same as parse(), but return nil if not parsable (instead of raising an error)
208
+ # TimeOfDay.try_parse "8am" # => 08:00:00
209
+ # TimeOfDay.try_parse "" # => nil
210
+ # TimeOfDay.try_parse "abc" # => nil
211
+ def self.try_parse(tod_string)
212
+ tod_string = tod_string.to_s
213
+ tod_string = tod_string.strip
214
+ tod_string = tod_string.downcase
215
+ tod_string = WORDS[tod_string] || tod_string
216
+ if PARSE_24H_REGEX =~ tod_string || PARSE_12H_REGEX =~ tod_string
217
+ hour, minute, second, a_or_p = $1.to_i, $2.to_i, $3.to_i, $4
218
+ if hour == 12 && a_or_p == "a"
219
+ hour = 0
220
+ elsif hour < 12 && a_or_p == "p"
221
+ hour += 12
222
+ end
223
+
224
+ new hour, minute, second
225
+ else
226
+ nil
227
+ end
228
+ end
229
+
230
+ # Determine if a string is parsable into a TimeOfDay instance
231
+ # TimeOfDay.parsable? "8am" # => true
232
+ # TimeOfDay.parsable? "abc" # => false
233
+ def self.parsable?(tod_string)
234
+ !!try_parse(tod_string)
235
+ end
236
+
237
+ # If ActiveSupport TimeZone is available and set use current time zone else return Time
238
+ def self.time_zone
239
+ (Time.respond_to?(:zone) && Time.zone) || Time
240
+ end
241
+
242
+ def self.dump(time_of_day)
243
+ time_of_day =
244
+ if time_of_day.is_a? Hash
245
+ # rails multiparam attribute
246
+ # get hour, minute and second and construct new TimeOfDay object
247
+ ::Tod::TimeOfDay.new(time_of_day[4], time_of_day[5], time_of_day[6])
248
+ else
249
+ # return nil, if input is not parsable
250
+ Tod::TimeOfDay(time_of_day){}
251
+ end
252
+ time_of_day.to_s if time_of_day
253
+ end
254
+
255
+ def self.load(time)
256
+ if time && !time.to_s.empty?
257
+ return ::Tod::TimeOfDay.new(24) if time.respond_to?(:day) && time.day == 2 && time.hour == 0 && time.min == 0 && time.sec == 0
258
+ ::Tod::TimeOfDay(time)
259
+ end
260
+ end
261
+ end
262
+ end