aixm 1.2.1 → 1.3.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.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data/CHANGELOG.md +14 -3
- data/README.md +30 -2
- data/exe/ckmid +1 -7
- data/exe/mkmid +1 -7
- data/lib/aixm/classes.rb +2 -1
- data/lib/aixm/component/address.rb +12 -15
- data/lib/aixm/component/approach_lighting.rb +11 -16
- data/lib/aixm/component/fato.rb +22 -34
- data/lib/aixm/component/frequency.rb +10 -15
- data/lib/aixm/component/geometry/arc.rb +2 -3
- data/lib/aixm/component/geometry/border.rb +6 -10
- data/lib/aixm/component/geometry/circle.rb +4 -4
- data/lib/aixm/component/geometry/point.rb +4 -4
- data/lib/aixm/component/geometry/rhumb_line.rb +4 -4
- data/lib/aixm/component/geometry.rb +4 -4
- data/lib/aixm/component/helipad.rb +13 -20
- data/lib/aixm/component/layer.rb +6 -8
- data/lib/aixm/component/lighting.rb +12 -17
- data/lib/aixm/component/runway.rb +26 -38
- data/lib/aixm/component/service.rb +12 -16
- data/lib/aixm/component/surface.rb +8 -10
- data/lib/aixm/component/timesheet.rb +9 -10
- data/lib/aixm/component/timetable.rb +6 -7
- data/lib/aixm/component/vasis.rb +6 -8
- data/lib/aixm/component/vertical_limit.rb +8 -8
- data/lib/aixm/component.rb +3 -2
- data/lib/aixm/concerns/association.rb +381 -0
- data/lib/aixm/concerns/memoize.rb +107 -0
- data/lib/aixm/concerns/xml_builder.rb +34 -0
- data/lib/aixm/document.rb +52 -21
- data/lib/aixm/feature/airport.rb +44 -47
- data/lib/aixm/feature/airspace.rb +27 -34
- data/lib/aixm/feature/generic.rb +67 -0
- data/lib/aixm/feature/navigational_aid/designated_point.rb +11 -13
- data/lib/aixm/feature/navigational_aid/dme.rb +12 -15
- data/lib/aixm/feature/navigational_aid/marker.rb +12 -15
- data/lib/aixm/feature/navigational_aid/ndb.rb +13 -16
- data/lib/aixm/feature/navigational_aid/tacan.rb +15 -17
- data/lib/aixm/feature/navigational_aid/vor.rb +16 -19
- data/lib/aixm/feature/navigational_aid.rb +7 -7
- data/lib/aixm/feature/obstacle.rb +20 -21
- data/lib/aixm/feature/obstacle_group.rb +19 -20
- data/lib/aixm/feature/organisation.rb +11 -12
- data/lib/aixm/feature/unit.rb +16 -18
- data/lib/aixm/feature.rb +26 -7
- data/lib/aixm/object.rb +1 -1
- data/lib/aixm/refinements.rb +57 -0
- data/lib/aixm/version.rb +1 -1
- data/lib/aixm.rb +4 -3
- data/schemas/ofmx/0.1/OFMX-Snapshot.xsd +6 -1
- data.tar.gz.sig +3 -3
- metadata +7 -19
- metadata.gz.sig +0 -0
- data/lib/aixm/association.rb +0 -378
- data/lib/aixm/memoize.rb +0 -105
@@ -0,0 +1,381 @@
|
|
1
|
+
using AIXM::Refinements
|
2
|
+
|
3
|
+
module AIXM
|
4
|
+
module Concerns
|
5
|
+
|
6
|
+
# Associate features and components with a minimalistic implementation of
|
7
|
+
# +has_many+, +has_one+ and +belongs_to+ associations.
|
8
|
+
#
|
9
|
+
# When adding or assigning an object on the associator (where the +has_many+
|
10
|
+
# or +has_one+ declaration is made), the object is verified and must be an
|
11
|
+
# instance of the declared class or a superclass thereof.
|
12
|
+
#
|
13
|
+
# When assigning an object on the associated (where the +belongs_to+
|
14
|
+
# declaration is made), the object is not verified. However, since the actual
|
15
|
+
# assignment is always delegated to the associator, unacceptable objects will
|
16
|
+
# raise errors.
|
17
|
+
#
|
18
|
+
# @example Simple +has_many+ association
|
19
|
+
# class Blog
|
20
|
+
# has_many :posts # :post has to be a key in AIXM::CLASSES
|
21
|
+
# end
|
22
|
+
# class Post
|
23
|
+
# belongs_to :blog
|
24
|
+
# end
|
25
|
+
# blog, post = Blog.new, Post.new
|
26
|
+
# # --either--
|
27
|
+
# blog.add_post(post) # => Blog
|
28
|
+
# blog.posts.count # => 1
|
29
|
+
# blog.posts.first == post # => true
|
30
|
+
# post.blog == blog # => true
|
31
|
+
# blog.remove_post(post) # => Blog
|
32
|
+
# blog.posts.count # => 0
|
33
|
+
# # --or--
|
34
|
+
# post.blog = blog # => Blog
|
35
|
+
# blog.posts.count # => 1
|
36
|
+
# blog.posts.first == post # => true
|
37
|
+
# post.blog == blog # => true
|
38
|
+
# post.blog = nil # => nil
|
39
|
+
# blog.posts.count # => 0
|
40
|
+
# # --or--
|
41
|
+
# post_2 = Post.new
|
42
|
+
# blog.add_posts([post, post_2])
|
43
|
+
# blog.posts.count # => 2
|
44
|
+
# blog.posts == [post, post_2] # => true
|
45
|
+
# blog.remove_posts([post_2, post])
|
46
|
+
# blog.posts.count # => 0
|
47
|
+
#
|
48
|
+
# @example Simple +has_one+ association
|
49
|
+
# class Blog
|
50
|
+
# has_one :posts # :post has to be a key in AIXM::CLASSES
|
51
|
+
# end
|
52
|
+
# class Post
|
53
|
+
# belongs_to :blog
|
54
|
+
# end
|
55
|
+
# blog, post = Blog.new, Post.new
|
56
|
+
# # --either--
|
57
|
+
# blog.post = post # => Post (standard assignment)
|
58
|
+
# blog.add_post(post) # => Blog (alternative for chaining)
|
59
|
+
# blog.post == post # => true
|
60
|
+
# post.blog == blog # => true
|
61
|
+
# blog.post = nil # => nil
|
62
|
+
# blog.post # => nil
|
63
|
+
# post.blog # => nil
|
64
|
+
# # --or--
|
65
|
+
# post.blog = blog # => Blog (standard assignment)
|
66
|
+
# post.add_blog(blog) # => Post (alternative for chaining)
|
67
|
+
# post.blog == blog # => true
|
68
|
+
# blog.post == post # => true
|
69
|
+
# post.blog = nil # => nil
|
70
|
+
# post.blog # => nil
|
71
|
+
# blog.post # => nil
|
72
|
+
#
|
73
|
+
# @example Association with readonly +belongs_to+ (idem for +has_one+)
|
74
|
+
# class Blog
|
75
|
+
# has_many :posts # :post has to be a key in AIXM::CLASSES
|
76
|
+
# end
|
77
|
+
# class Post
|
78
|
+
# belongs_to :blog, readonly: true
|
79
|
+
# end
|
80
|
+
# blog, post = Blog.new, Post.new
|
81
|
+
# post.blog = blog # => NoMethodError
|
82
|
+
#
|
83
|
+
# @example Association with explicit class (idem for +has_one+)
|
84
|
+
# class Blog
|
85
|
+
# include AIXM::Concerns::Association
|
86
|
+
# has_many :posts, accept: 'Picture'
|
87
|
+
# end
|
88
|
+
# class Picture
|
89
|
+
# include AIXM::Concerns::Association
|
90
|
+
# belongs_to :blog
|
91
|
+
# end
|
92
|
+
# blog, picture = Blog.new, Picture.new
|
93
|
+
# blog.add_post(picture)
|
94
|
+
# blog.posts.first == picture # => true
|
95
|
+
#
|
96
|
+
# @example Polymorphic associator (idem for +has_one+)
|
97
|
+
# class Blog
|
98
|
+
# has_many :posts, as: :postable
|
99
|
+
# end
|
100
|
+
# class Feed
|
101
|
+
# has_many :posts, as: :postable
|
102
|
+
# end
|
103
|
+
# class Post
|
104
|
+
# belongs_to :postable
|
105
|
+
# end
|
106
|
+
# blog, feed, post_1, post_2, post_3 = Blog.new, Feed.new, Post.new, Post.new, Post.new
|
107
|
+
# blog.add_post(post_1)
|
108
|
+
# post_1.postable == blog # => true
|
109
|
+
# feed.add_post(post_2)
|
110
|
+
# post_2.postable == feed # => true
|
111
|
+
# post_3.postable = blog # => NoMethodError
|
112
|
+
#
|
113
|
+
# @example Polymorphic associated (idem for +has_one+)
|
114
|
+
# class Blog
|
115
|
+
# include AIXM::Concerns::Association
|
116
|
+
# has_many :items, accept: ['Post', :picture]
|
117
|
+
# end
|
118
|
+
# class Post
|
119
|
+
# include AIXM::Concerns::Association
|
120
|
+
# belongs_to :blog, as: :item
|
121
|
+
# end
|
122
|
+
# class Picture
|
123
|
+
# include AIXM::Concerns::Association
|
124
|
+
# belongs_to :blog, as: :item
|
125
|
+
# end
|
126
|
+
# blog, post, picture = Blog.new, Post.new, Picture.new
|
127
|
+
# blog.add_item(post)
|
128
|
+
# blog.add_item(picture)
|
129
|
+
# blog.items.count # => 2
|
130
|
+
# blog.items.first == post # => true
|
131
|
+
# blog.items.last == picture # => true
|
132
|
+
# post.blog == blog # => true
|
133
|
+
# picture.blog == blog # => true
|
134
|
+
#
|
135
|
+
# @example Add method which enriches passed associated object (+has_many+ only)
|
136
|
+
# class Blog
|
137
|
+
# has_many :posts do |post, related_to: nil| # this defines the signature of add_post
|
138
|
+
# post.related_to = related_to || @posts.last # executes in the context of the current blog
|
139
|
+
# end
|
140
|
+
# end
|
141
|
+
# class Post
|
142
|
+
# belongs_to :blog
|
143
|
+
# attr_accessor :related_to
|
144
|
+
# end
|
145
|
+
# blog, post_1, post_2, post_3 = Blog.new, Post.new, Post.new, Post.new
|
146
|
+
# blog.add_post(post_1)
|
147
|
+
# post_1.related_to # => nil
|
148
|
+
# blog.add_post(post_2)
|
149
|
+
# post_2.related_to == post_1 # => true
|
150
|
+
# blog.add_post(post_3, related_to: post_1)
|
151
|
+
# post_3.related_to == post_1 # => true
|
152
|
+
#
|
153
|
+
# @example Add method which builds and yields new associated object (+has_many+ only)
|
154
|
+
# class Blog
|
155
|
+
# include AIXM::Concerns::Association
|
156
|
+
# has_many :posts do |post, title:| end
|
157
|
+
# end
|
158
|
+
# class Post
|
159
|
+
# include AIXM::Concerns::Association
|
160
|
+
# belongs_to :blog
|
161
|
+
# attr_accessor :title, :text
|
162
|
+
# def initialize(title:) # same signature as "has_many" block above
|
163
|
+
# @title = title
|
164
|
+
# end
|
165
|
+
# end
|
166
|
+
# blog = Blog.new
|
167
|
+
# blog.add_post(title: "title") do |post| # note that no post instance is passed
|
168
|
+
# post.text = "text"
|
169
|
+
# end
|
170
|
+
# blog.posts.first.title # => "title"
|
171
|
+
# blog.posts.first.text # => "text"
|
172
|
+
module Association
|
173
|
+
module ClassMethods
|
174
|
+
attr_reader :has_many_attributes, :has_one_attributes, :belongs_to_attributes
|
175
|
+
|
176
|
+
def has_many(attribute, as: nil, accept: nil, &association_block)
|
177
|
+
association = attribute.to_s.inflect(:singularize)
|
178
|
+
inversion = as || self.to_s.inflect(:demodulize, :tableize, :singularize)
|
179
|
+
class_names = [accept || association].flatten.map { AIXM::CLASSES[_1.to_sym] || _1 }
|
180
|
+
(@has_many_attributes ||= []) << attribute
|
181
|
+
# features
|
182
|
+
define_method(attribute) do
|
183
|
+
instance_variable_get(:"@#{attribute}") || AIXM::Concerns::Association::Array.new
|
184
|
+
end
|
185
|
+
# add_feature
|
186
|
+
define_method(:"add_#{association}") do |object=nil, **options, &add_block|
|
187
|
+
unless object
|
188
|
+
fail(ArgumentError, "must pass object to add") if class_names.count > 1
|
189
|
+
object = class_names.first.to_class.new(**options)
|
190
|
+
add_block.call(object) if add_block
|
191
|
+
end
|
192
|
+
instance_exec(object, **options, &association_block) if association_block
|
193
|
+
fail(ArgumentError, "#{object.__class__} not allowed") unless class_names.any? { |c| object.is_a?(c.to_class) }
|
194
|
+
instance_eval("@#{attribute} ||= AIXM::Concerns::Association::Array.new")
|
195
|
+
send(attribute).send(:push, object)
|
196
|
+
object.instance_variable_set(:"@#{inversion}", self)
|
197
|
+
self
|
198
|
+
end
|
199
|
+
# add_features
|
200
|
+
define_method(:"add_#{attribute}") do |objects=[], **options, &add_block|
|
201
|
+
objects.each { send(:"add_#{association}", _1, **options, &add_block) }
|
202
|
+
self
|
203
|
+
end
|
204
|
+
# remove_feature
|
205
|
+
define_method(:"remove_#{association}") do |object|
|
206
|
+
send(attribute).send(:delete, object)
|
207
|
+
object.instance_variable_set(:"@#{inversion}", nil)
|
208
|
+
self
|
209
|
+
end
|
210
|
+
# remove_features
|
211
|
+
define_method(:"remove_#{attribute}") do |objects=[]|
|
212
|
+
objects.each { send(:"remove_#{association}", _1) }
|
213
|
+
self
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
def has_one(attribute, as: nil, accept: nil, allow_nil: false)
|
218
|
+
association = attribute.to_s
|
219
|
+
inversion = (as || self.to_s.inflect(:demodulize, :tableize, :singularize)).to_s
|
220
|
+
class_names = [accept || association].flatten.map { AIXM::CLASSES[_1.to_sym] || _1 }
|
221
|
+
class_names << 'NilClass' if allow_nil
|
222
|
+
(@has_one_attributes ||= []) << attribute
|
223
|
+
# feature
|
224
|
+
attr_reader attribute
|
225
|
+
# feature=
|
226
|
+
define_method(:"#{association}=") do |object|
|
227
|
+
fail(ArgumentError, "#{object.__class__} not allowed") unless class_names.any? { |c| object.is_a?(c.to_class) }
|
228
|
+
instance_variable_get(:"@#{attribute}")&.instance_variable_set(:"@#{inversion}", nil)
|
229
|
+
instance_variable_set(:"@#{attribute}", object)
|
230
|
+
object&.instance_variable_set(:"@#{inversion}", self)
|
231
|
+
object
|
232
|
+
end
|
233
|
+
# add_feature
|
234
|
+
define_method(:"add_#{association}") do |object|
|
235
|
+
send("#{association}=", object)
|
236
|
+
self
|
237
|
+
end
|
238
|
+
# remove_feature
|
239
|
+
define_method(:"remove_#{association}") do |_|
|
240
|
+
send(:"#{association}=", nil)
|
241
|
+
self
|
242
|
+
end
|
243
|
+
end
|
244
|
+
|
245
|
+
def belongs_to(attribute, as: nil, readonly: false)
|
246
|
+
association = self.to_s.inflect(:demodulize, :tableize, :singularize)
|
247
|
+
inversion = (as || association).to_s
|
248
|
+
(@belongs_to_attributes ||= []) << attribute
|
249
|
+
# feature
|
250
|
+
attr_reader attribute
|
251
|
+
unless readonly
|
252
|
+
# feature=
|
253
|
+
define_method(:"#{attribute}=") do |object|
|
254
|
+
instance_variable_get(:"@#{attribute}")&.send(:"remove_#{inversion}", self)
|
255
|
+
object&.send(:"add_#{inversion}", self)
|
256
|
+
object
|
257
|
+
end
|
258
|
+
# add_feature
|
259
|
+
define_method(:"add_#{attribute}") do |object|
|
260
|
+
send("#{attribute}=", object)
|
261
|
+
self
|
262
|
+
end
|
263
|
+
end
|
264
|
+
end
|
265
|
+
end
|
266
|
+
|
267
|
+
def self.included(base)
|
268
|
+
base.extend(ClassMethods)
|
269
|
+
end
|
270
|
+
|
271
|
+
class Array < ::Array
|
272
|
+
private :<<, :push, :append, :unshift, :prepend
|
273
|
+
private :delete, :pop, :shift
|
274
|
+
|
275
|
+
# Find objects of the given class and optionally with the given
|
276
|
+
# attribute values on a has_many association.
|
277
|
+
#
|
278
|
+
# The class can either be declared by passing the class itself or by
|
279
|
+
# passing a shortcut symbol as listed in +AIXM::CLASSES+.
|
280
|
+
#
|
281
|
+
# @example
|
282
|
+
# class Blog
|
283
|
+
# include AIXM::Concerns::Association
|
284
|
+
# has_many :items, accept: %i(post picture)
|
285
|
+
# end
|
286
|
+
# class Post
|
287
|
+
# include AIXM::Concerns::Association
|
288
|
+
# belongs_to :blog, as: :item
|
289
|
+
# attr_accessor :title
|
290
|
+
# end
|
291
|
+
# class Picture
|
292
|
+
# include AIXM::Concerns::Association
|
293
|
+
# belongs_to :blog, as: :item
|
294
|
+
# end
|
295
|
+
# blog, post, picture = Blog.new, Post.new, Picture.new
|
296
|
+
# post.title = "title"
|
297
|
+
# blog.add_item(post)
|
298
|
+
# blog.add_item(picture)
|
299
|
+
# blog.items.find_by(:post) == [post] # => true
|
300
|
+
# blog.items.find_by(Post) == [post] # => true
|
301
|
+
# blog.items.find_by(:post, title: "title") == [post] # => true
|
302
|
+
# blog.items.find_by(Object) == [post, picture] # => true
|
303
|
+
#
|
304
|
+
# @param klass [Class, Symbol] class (e.g. AIXM::Feature::Airport,
|
305
|
+
# AIXM::Feature::NavigationalAid::VOR) or shortcut symbol (e.g.
|
306
|
+
# :airport or :vor) as listed in AIXM::CLASSES
|
307
|
+
# @param attributes [Hash] search attributes by their values
|
308
|
+
# @return [AIXM::Concerns::Association::Array]
|
309
|
+
def find_by(klass, attributes={})
|
310
|
+
if klass.is_a? Symbol
|
311
|
+
klass = AIXM::CLASSES[klass]&.to_class || fail(ArgumentError, "unknown class shortcut `#{klass}'")
|
312
|
+
end
|
313
|
+
self.class.new(
|
314
|
+
select do |element|
|
315
|
+
if element.kind_of? klass
|
316
|
+
attributes.all? { |a, v| element.send(a) == v }
|
317
|
+
end
|
318
|
+
end
|
319
|
+
)
|
320
|
+
end
|
321
|
+
|
322
|
+
# Find equal objects on a has_many association.
|
323
|
+
#
|
324
|
+
# This may seem redundant at first, but keep in mind that two instances
|
325
|
+
# of +AIXM::CLASSES+ which implement `#to_uid` are considered equal if
|
326
|
+
# they are instances of the same class and both their UIDs as calculated
|
327
|
+
# by `#to_uid` are equal. Attributes which are not part of the `#to_uid`
|
328
|
+
# calculation are irrelevant!
|
329
|
+
#
|
330
|
+
# @example
|
331
|
+
# class Blog
|
332
|
+
# include AIXM::Concerns::Association
|
333
|
+
# has_many :items, accept: %i(post picture)
|
334
|
+
# end
|
335
|
+
# class Post
|
336
|
+
# include AIXM::Concerns::Association
|
337
|
+
# belongs_to :blog, as: :item
|
338
|
+
# attr_accessor :title
|
339
|
+
# end
|
340
|
+
# blog, post = Blog.new, Post.new
|
341
|
+
# blog.add_item(post)
|
342
|
+
# blog.items.find(post) == [post] # => true
|
343
|
+
#
|
344
|
+
# @param object [Object] instance of class listed in AIXM::CLASSES
|
345
|
+
# @return [AIXM::Concerns::Association::Array]
|
346
|
+
def find(object)
|
347
|
+
klass = object.__class__
|
348
|
+
self.class.new(
|
349
|
+
select do |element|
|
350
|
+
element.kind_of?(klass) && element == object
|
351
|
+
end
|
352
|
+
)
|
353
|
+
end
|
354
|
+
|
355
|
+
# Find equal or identical duplicates on a has_many association.
|
356
|
+
#
|
357
|
+
# @example
|
358
|
+
# class Blog
|
359
|
+
# include AIXM::Concerns::Association
|
360
|
+
# has_many :posts
|
361
|
+
# end
|
362
|
+
# class Post
|
363
|
+
# include AIXM::Concerns::Association
|
364
|
+
# belongs_to :blog
|
365
|
+
# end
|
366
|
+
# blog, post = Blog.new, Post.new
|
367
|
+
# duplicate_post = post.dup
|
368
|
+
# blog.add_posts([post, duplicate_post])
|
369
|
+
# blog.posts.duplicates # => [[post, duplicate_post]]
|
370
|
+
#
|
371
|
+
# @return [Array<Array<AIXM::Feature>>]
|
372
|
+
def duplicates
|
373
|
+
AIXM::Concerns::Memoize.method :to_uid do
|
374
|
+
group_by { _1.to_uid.to_s }.select { |_, a| a.count > 1 }.map(&:last)
|
375
|
+
end
|
376
|
+
end
|
377
|
+
end
|
378
|
+
end
|
379
|
+
|
380
|
+
end
|
381
|
+
end
|
@@ -0,0 +1,107 @@
|
|
1
|
+
module AIXM
|
2
|
+
module Concerns
|
3
|
+
|
4
|
+
# Memoize the return value of a specific method across multiple instances for
|
5
|
+
# the duration of a block.
|
6
|
+
#
|
7
|
+
# The method signature is taken into account, therefore calls of the same
|
8
|
+
# method with different positional and/or keyword arguments are cached
|
9
|
+
# independently. On the other hand, when calling the method with a block,
|
10
|
+
# no memoization is performed at all.
|
11
|
+
#
|
12
|
+
# Nested memoization of the same method is allowed and won't reset the
|
13
|
+
# memoization cache.
|
14
|
+
#
|
15
|
+
# @example
|
16
|
+
# class Either
|
17
|
+
# include AIXM::Concerns::Memoize
|
18
|
+
#
|
19
|
+
# def either(argument=nil, keyword: nil, &block)
|
20
|
+
# $entropy || argument || keyword || (block.call if block)
|
21
|
+
# end
|
22
|
+
# memoize :either
|
23
|
+
# end
|
24
|
+
#
|
25
|
+
# a, b, c = Either.new, Either.new, Either.new
|
26
|
+
#
|
27
|
+
# # No memoization before the block
|
28
|
+
# $entropy = nil
|
29
|
+
# a.either(1) # => 1
|
30
|
+
# b.either(keyword: 2) # => 2
|
31
|
+
# c.either { 3 } # => 3
|
32
|
+
# $entropy = :not_nil
|
33
|
+
# a.either(1) # => :not_nil
|
34
|
+
# b.either(keyword: 2) # => :not_nil
|
35
|
+
# c.either { 3 } # => :not_nil
|
36
|
+
#
|
37
|
+
# # Memoization inside the block
|
38
|
+
# AIXM::Concerns::Memoize.method :either do
|
39
|
+
# $entropy = nil
|
40
|
+
# a.either(1) # => 1
|
41
|
+
# b.either(keyword: 2) # => 2
|
42
|
+
# c.either { 3 } # => 3
|
43
|
+
# $entropy = :not_nil
|
44
|
+
# a.either(1) # => 1 (memoized)
|
45
|
+
# b.either(keyword: 2) # => 2 (memoized)
|
46
|
+
# c.either { 3 } # => :not_nil (cannot be memoized)
|
47
|
+
# end
|
48
|
+
#
|
49
|
+
# # No memoization after the block
|
50
|
+
# $entropy = nil
|
51
|
+
# a.either(1) # => 1
|
52
|
+
# $entropy = :not_nil
|
53
|
+
# a.either(1) # => :not_nil
|
54
|
+
module Memoize
|
55
|
+
module ClassMethods
|
56
|
+
def memoize(method)
|
57
|
+
unmemoized_method = :"unmemoized_#{method}"
|
58
|
+
alias_method unmemoized_method, method
|
59
|
+
define_method method do |*args, **kargs, &block|
|
60
|
+
if block || !AIXM::Concerns::Memoize.cache.has_key?(method)
|
61
|
+
send(unmemoized_method, *args, **kargs, &block)
|
62
|
+
else
|
63
|
+
cache = AIXM::Concerns::Memoize.cache[method]
|
64
|
+
id = object_id.hash ^ args.hash ^ kargs.hash
|
65
|
+
if cache.has_key?(id)
|
66
|
+
cache[id]
|
67
|
+
else
|
68
|
+
cache[id] = send(unmemoized_method, *args, **kargs)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
class << self
|
76
|
+
attr_reader :cache
|
77
|
+
|
78
|
+
def included(base)
|
79
|
+
base.extend(ClassMethods)
|
80
|
+
@cache = {}
|
81
|
+
end
|
82
|
+
|
83
|
+
def method(method, &block) # TODO: [ruby-3.1] use anonymous block "&" on this and next line
|
84
|
+
send(:"call_with#{:out if cached?(method)}_cache", method, &block)
|
85
|
+
end
|
86
|
+
|
87
|
+
private
|
88
|
+
|
89
|
+
def cached?(method)
|
90
|
+
cache.has_key?(method)
|
91
|
+
end
|
92
|
+
|
93
|
+
def call_without_cache(method)
|
94
|
+
yield
|
95
|
+
end
|
96
|
+
|
97
|
+
def call_with_cache(method)
|
98
|
+
cache[method] = {}
|
99
|
+
yield
|
100
|
+
ensure
|
101
|
+
cache.delete(method)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
end
|
107
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module AIXM
|
2
|
+
module Concerns
|
3
|
+
|
4
|
+
# Adds the XML builder wrapped to generate a document fragment.
|
5
|
+
module XMLBuilder
|
6
|
+
include AIXM::Concerns::Memoize
|
7
|
+
|
8
|
+
# Build a XML document fragment.
|
9
|
+
#
|
10
|
+
# @yield [Nokogiri::XML::Builder]
|
11
|
+
# @return [Nokogiri::XML::DocumentFragment]
|
12
|
+
def build_fragment
|
13
|
+
Nokogiri::XML::DocumentFragment.parse('').tap do |document|
|
14
|
+
Nokogiri::XML::Builder.with(document) do |fragment|
|
15
|
+
yield fragment
|
16
|
+
end
|
17
|
+
document.elements.each { _1.add_next_sibling("\n") } # add newline between tags on top level
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
# @return [Nokogiri::XML::DocumentFragment] UID fragment
|
22
|
+
def to_uid(...)
|
23
|
+
build_fragment { add_uid_to(_1, ...) }
|
24
|
+
end
|
25
|
+
memoize :to_uid
|
26
|
+
|
27
|
+
# @return [String] AIXM or OFMX fragment
|
28
|
+
def to_xml(...)
|
29
|
+
build_fragment { add_to(_1, ...) }.to_xml.strip.concat("\n")
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
data/lib/aixm/document.rb
CHANGED
@@ -10,12 +10,13 @@ module AIXM
|
|
10
10
|
# namespace: String (UUID)
|
11
11
|
# created_at: Time or Date or String
|
12
12
|
# effective_at: Time or Date or String
|
13
|
+
# expiration_at: Time or Date or String or nil
|
13
14
|
# )
|
14
15
|
# document.add_feature(AIXM::Feature)
|
15
16
|
#
|
16
17
|
# @see https://gitlab.com/openflightmaps/ofmx/wikis/Snapshot
|
17
18
|
class Document
|
18
|
-
include AIXM::Association
|
19
|
+
include AIXM::Concerns::Association
|
19
20
|
|
20
21
|
NAMESPACE_RE = /\A[a-f\d]{8}-[a-f\d]{4}-[a-f\d]{4}-[a-f\d]{4}-[a-f\d]{12}\z/.freeze
|
21
22
|
|
@@ -36,7 +37,7 @@ module AIXM
|
|
36
37
|
# @param value [String]
|
37
38
|
attr_reader :namespace
|
38
39
|
|
39
|
-
# Creation date and time
|
40
|
+
# Creation date and UTC time
|
40
41
|
#
|
41
42
|
# @overload created_at
|
42
43
|
# @return [Time]
|
@@ -44,7 +45,7 @@ module AIXM
|
|
44
45
|
# @param value [Time] default: {#effective_at} or now
|
45
46
|
attr_reader :created_at
|
46
47
|
|
47
|
-
# Effective after date and time
|
48
|
+
# Effective after date and UTC time
|
48
49
|
#
|
49
50
|
# @overload effective_at
|
50
51
|
# @return [Time]
|
@@ -52,10 +53,19 @@ module AIXM
|
|
52
53
|
# @param value [Time] default: {#created_at} or now
|
53
54
|
attr_reader :effective_at
|
54
55
|
|
56
|
+
# Expiration after date and UTC time
|
57
|
+
#
|
58
|
+
# @overload expiration_at
|
59
|
+
# @return [Time, nil]
|
60
|
+
# @overload expiration_at=(value)
|
61
|
+
# @param value [Time, nil]
|
62
|
+
attr_reader :expiration_at
|
63
|
+
|
55
64
|
# See the {cheat sheet}[AIXM::Document] for examples on how to create
|
56
65
|
# instances of this class.
|
57
|
-
def initialize(namespace: nil, created_at: nil, effective_at: nil)
|
58
|
-
self.namespace
|
66
|
+
def initialize(namespace: nil, created_at: nil, effective_at: nil, expiration_at: nil)
|
67
|
+
self.namespace = namespace
|
68
|
+
self.created_at, self.effective_at, self.expiration_at = created_at, effective_at, expiration_at
|
59
69
|
end
|
60
70
|
|
61
71
|
# @return [String]
|
@@ -69,11 +79,29 @@ module AIXM
|
|
69
79
|
end
|
70
80
|
|
71
81
|
def created_at=(value)
|
72
|
-
@created_at = value&.to_time
|
82
|
+
@created_at = if time = value&.to_time
|
83
|
+
fail(ArgumentError, "must be UTC") unless time.utc_offset.zero?
|
84
|
+
time.round
|
85
|
+
else
|
86
|
+
Time.now.utc.round
|
87
|
+
end
|
73
88
|
end
|
74
89
|
|
75
90
|
def effective_at=(value)
|
76
|
-
@effective_at = value&.to_time
|
91
|
+
@effective_at = if time = value&.to_time
|
92
|
+
fail(ArgumentError, "must be UTC") unless time.utc_offset.zero?
|
93
|
+
time.round
|
94
|
+
else
|
95
|
+
created_at || Time.now.utc.round
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
def expiration_at=(value)
|
100
|
+
@expiration_at = value&.to_time
|
101
|
+
@expiration_at = if time = value&.to_time
|
102
|
+
fail(ArgumentError, "must be UTC") unless time.utc_offset.zero?
|
103
|
+
time.round
|
104
|
+
end
|
77
105
|
end
|
78
106
|
|
79
107
|
# Regions used throughout this document.
|
@@ -128,8 +156,8 @@ module AIXM
|
|
128
156
|
end
|
129
157
|
end
|
130
158
|
|
131
|
-
# @return [
|
132
|
-
def
|
159
|
+
# @return [Nokogiri::XML::Document] Nokogiri AIXM or OFMX document
|
160
|
+
def document
|
133
161
|
meta = {
|
134
162
|
'xmlns:xsi': AIXM.schema(:namespace),
|
135
163
|
version: AIXM.schema(:version),
|
@@ -137,21 +165,24 @@ module AIXM
|
|
137
165
|
namespace: (namespace if AIXM.ofmx?),
|
138
166
|
regions: (regions.join(' '.freeze) if AIXM.ofmx?),
|
139
167
|
created: @created_at.xmlschema,
|
140
|
-
effective: @effective_at.xmlschema
|
168
|
+
effective: @effective_at.xmlschema,
|
169
|
+
expiration: (@expiration_at&.xmlschema if AIXM.ofmx?)
|
141
170
|
}.compact
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
171
|
+
Nokogiri::XML::Builder.new do |builder|
|
172
|
+
builder.send(AIXM.schema(:root), meta) do |root|
|
173
|
+
AIXM::Concerns::Memoize.method :to_uid do
|
174
|
+
features.each { _1.add_to(root) }
|
175
|
+
end
|
176
|
+
if AIXM.ofmx? && AIXM.config.mid
|
177
|
+
AIXM::PayloadHash::Mid.new(builder.doc).insert_mid
|
178
|
+
end
|
147
179
|
end
|
148
|
-
end
|
149
|
-
if AIXM.ofmx? && AIXM.config.mid
|
150
|
-
AIXM::PayloadHash::Mid.new(builder.target!).insert_mid.to_xml
|
151
|
-
else
|
152
|
-
builder.target!
|
153
|
-
end
|
180
|
+
end.doc
|
154
181
|
end
|
155
182
|
|
183
|
+
# @return [String] AIXM or OFMX markup
|
184
|
+
def to_xml
|
185
|
+
document.pretty.to_xml
|
186
|
+
end
|
156
187
|
end
|
157
188
|
end
|