inat-get 0.8.0.11

Sign up to get free protection for your applications and to get access to all the features.
Files changed (65) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +674 -0
  3. data/README.md +16 -0
  4. data/Rakefile +4 -0
  5. data/bin/inat-get +59 -0
  6. data/docs/logo.png +0 -0
  7. data/inat-get.gemspec +33 -0
  8. data/lib/extra/enum.rb +184 -0
  9. data/lib/extra/period.rb +252 -0
  10. data/lib/extra/uuid.rb +90 -0
  11. data/lib/inat/app/application.rb +50 -0
  12. data/lib/inat/app/config/messagelevel.rb +22 -0
  13. data/lib/inat/app/config/shiftage.rb +24 -0
  14. data/lib/inat/app/config/updatemode.rb +20 -0
  15. data/lib/inat/app/config.rb +296 -0
  16. data/lib/inat/app/globals.rb +80 -0
  17. data/lib/inat/app/info.rb +21 -0
  18. data/lib/inat/app/logging.rb +35 -0
  19. data/lib/inat/app/preamble.rb +27 -0
  20. data/lib/inat/app/status.rb +74 -0
  21. data/lib/inat/app/task/context.rb +47 -0
  22. data/lib/inat/app/task/dsl.rb +24 -0
  23. data/lib/inat/app/task.rb +75 -0
  24. data/lib/inat/data/api.rb +218 -0
  25. data/lib/inat/data/cache.rb +9 -0
  26. data/lib/inat/data/db.rb +87 -0
  27. data/lib/inat/data/ddl.rb +18 -0
  28. data/lib/inat/data/entity/comment.rb +29 -0
  29. data/lib/inat/data/entity/flag.rb +22 -0
  30. data/lib/inat/data/entity/identification.rb +45 -0
  31. data/lib/inat/data/entity/observation.rb +172 -0
  32. data/lib/inat/data/entity/observationphoto.rb +25 -0
  33. data/lib/inat/data/entity/observationsound.rb +26 -0
  34. data/lib/inat/data/entity/photo.rb +31 -0
  35. data/lib/inat/data/entity/place.rb +57 -0
  36. data/lib/inat/data/entity/project.rb +94 -0
  37. data/lib/inat/data/entity/projectadmin.rb +21 -0
  38. data/lib/inat/data/entity/projectobservationrule.rb +50 -0
  39. data/lib/inat/data/entity/request.rb +58 -0
  40. data/lib/inat/data/entity/sound.rb +27 -0
  41. data/lib/inat/data/entity/taxon.rb +94 -0
  42. data/lib/inat/data/entity/user.rb +67 -0
  43. data/lib/inat/data/entity/vote.rb +22 -0
  44. data/lib/inat/data/entity.rb +291 -0
  45. data/lib/inat/data/enums/conservationstatus.rb +30 -0
  46. data/lib/inat/data/enums/geoprivacy.rb +14 -0
  47. data/lib/inat/data/enums/iconictaxa.rb +23 -0
  48. data/lib/inat/data/enums/identificationcategory.rb +13 -0
  49. data/lib/inat/data/enums/licensecode.rb +16 -0
  50. data/lib/inat/data/enums/projectadminrole.rb +11 -0
  51. data/lib/inat/data/enums/projecttype.rb +37 -0
  52. data/lib/inat/data/enums/qualitygrade.rb +12 -0
  53. data/lib/inat/data/enums/rank.rb +60 -0
  54. data/lib/inat/data/model.rb +551 -0
  55. data/lib/inat/data/query.rb +1145 -0
  56. data/lib/inat/data/sets/dataset.rb +104 -0
  57. data/lib/inat/data/sets/list.rb +190 -0
  58. data/lib/inat/data/sets/listers.rb +15 -0
  59. data/lib/inat/data/sets/wrappers.rb +137 -0
  60. data/lib/inat/data/types/extras.rb +88 -0
  61. data/lib/inat/data/types/location.rb +89 -0
  62. data/lib/inat/data/types/std.rb +293 -0
  63. data/lib/inat/report/table.rb +135 -0
  64. data/lib/inat/utils/deep.rb +30 -0
  65. metadata +137 -0
@@ -0,0 +1,551 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../app/globals'
4
+ require_relative 'types/std'
5
+
6
+ autoload :Entity, 'inat/data/entity'
7
+
8
+ class Model
9
+
10
+ include LogDSL
11
+
12
+ class Field
13
+
14
+ attr_reader :model, :name, :type, :id_field
15
+
16
+ def required?
17
+ false
18
+ end
19
+
20
+ def initialize model, name, type, id_field
21
+ @model = model
22
+ @name = name
23
+ @type = type
24
+ @id_field = id_field
25
+ end
26
+
27
+ def DDL
28
+ [ [], [] ]
29
+ end
30
+
31
+ def from_db row
32
+ [ nil, nil ]
33
+ end
34
+
35
+ def to_db value
36
+ [ nil, nil ]
37
+ end
38
+
39
+ def kind
40
+ nil
41
+ end
42
+
43
+ end
44
+
45
+ class ScalarField < Model::Field
46
+
47
+ attr_reader :index, :unique, :primary_key
48
+
49
+ def required?
50
+ @required
51
+ end
52
+
53
+ def initialize model, name, type, id_field, required, index, unique, primary_key
54
+ if Class === type && Entity > type && id_field == nil
55
+ id_field = "#{ name }_id".intern
56
+ end
57
+ super model, name, type, id_field
58
+ @required = required
59
+ @index = index
60
+ @unique = unique
61
+ @primary_key = primary_key
62
+ end
63
+
64
+ def implement
65
+ nm = @name
66
+ md = @model
67
+ rq = @required
68
+ ni = @id_field
69
+ tp = @type
70
+ if ni
71
+ md.define_method "#{ ni }" do
72
+ instance_variable_get("@#{ ni }")
73
+ end
74
+ md.define_method "#{ ni }=" do |value|
75
+ prevalue = instance_variable_get "@#{ ni }"
76
+ if prevalue != value
77
+ debug "ASS: #{ self.id }: #{ ni } = #{ prevalue.inspect } <=> #{ value.inspect }" if prevalue != nil && self.class.name == 'Taxon'
78
+ instance_variable_set "@#{ ni }", value
79
+ instance_variable_set "@saved", false
80
+ end
81
+ end
82
+ md.define_method "#{ nm }" do
83
+ v = instance_variable_get("@#{ ni }")
84
+ return nil if v == nil
85
+ r = tp.fetch v
86
+ if r.size == 0
87
+ nil
88
+ else
89
+ r.first
90
+ end
91
+ end
92
+ md.define_method "#{ nm }=" do |value|
93
+ prevalue = instance_variable_get "@#{ ni }"
94
+ if prevalue != value&.id
95
+ debug "ASS: #{ self.id }: #{ nm } / #{ ni } = #{ prevalue.inspect } <=> #{ value.inspect }" if prevalue != nil && self.class.name == 'Taxon'
96
+ instance_variable_set "@#{ ni }", value&.id
97
+ instance_variable_set "@saved", false
98
+ end
99
+ end
100
+ else
101
+ md.define_method "#{ nm }" do
102
+ instance_variable_get "@#{ nm }"
103
+ end
104
+ md.define_method "#{ nm }=" do |value|
105
+ raise TypeError, "Invalid '#{ nm }' value: #{ value.inspect }!", caller unless tp === value || (value == nil && !rq)
106
+ prevalue = instance_variable_get "@#{ nm }"
107
+ if prevalue != value
108
+ debug "ASS: #{ self.id }: #{ nm } = #{ prevalue.inspect } <=> #{ value.inspect }" if prevalue != nil && self.class.name == 'Taxon'
109
+ instance_variable_set "@#{ nm }", value
110
+ instance_variable_set "@saved", false
111
+ end
112
+ end
113
+ end
114
+ end
115
+
116
+ def read?
117
+ true
118
+ end
119
+
120
+ def write?
121
+ true
122
+ end
123
+
124
+ def DDL
125
+ inner = ''
126
+ outer = []
127
+ ddl_name = @id_field || @name
128
+ type_ddl = @type.ddl
129
+ case type_ddl
130
+ when String, Symbol
131
+ inner = " #{ ddl_name } #{ type_ddl }"
132
+ if @primary_key
133
+ inner += ' NOT NULL PRIMARY KEY'
134
+ elsif @unique
135
+ outer << "CREATE UNIQUE INDEX IF NOT EXISTS uq_#{ @model.table }_#{ ddl_name } ON #{ @model.table } (#{ ddl_name });"
136
+ elsif @index
137
+ outer << "CREATE INDEX IF NOT EXISTS ix_#{ @model.table }_#{ ddl_name } ON #{ @model.table } (#{ ddl_name });"
138
+ end
139
+ when Hash
140
+ inner = []
141
+ names = []
142
+ type_ddl.each do |k, v|
143
+ inner << " #{ ddl_name }_#{ k } #{ v }"
144
+ names << "#{ ddl_name }_#{ k }"
145
+ end
146
+ if @unique
147
+ outer << "CREATE UNIQUE INDEX IF NOT EXISTS uq_#{ @model.table }_#{ ddl_name } ON #{ @model.table } (#{ names.join(',') });"
148
+ elsif @index
149
+ outer << "CREATE INDEX IF NOT EXISTS ix_#{ @model.table }_#{ ddl_name } ON #{ @model.table } (#{ names.join(',') });"
150
+ end
151
+ else
152
+ raise TypeError, "Invalid type DDL: #{ type_ddl.inspect }", caller
153
+ end
154
+ [ inner, outer ]
155
+ end
156
+
157
+ def from_db row
158
+ ddl_name = @id_field || @name
159
+ type_ddl = @type.ddl
160
+ value = nil
161
+ case type_ddl
162
+ when String, Symbol
163
+ value = row[ddl_name.to_s]
164
+ value = @type.from_db value unless @id_field || @type === value
165
+ when Hash
166
+ value = {}
167
+ type_ddl.each do |k, v|
168
+ value[k] = row["#{ ddl_name }_#{k}"]
169
+ end
170
+ value = @type.from_db value
171
+ else
172
+ raise TypeError, "Invalid type DDL: #{ type_ddl.inspect }!", caller
173
+ end
174
+ [ ddl_name, value ]
175
+ end
176
+
177
+ def to_db value
178
+ ddl_name = @id_field || @name
179
+ type_ddl = @type.ddl
180
+ case type_ddl
181
+ when String, Symbol
182
+ [ ddl_name, value.to_db ]
183
+ when Hash
184
+ keys = []
185
+ values = []
186
+ if value != nil
187
+ hash = value.to_db
188
+ hash.each do |k, v|
189
+ keys << "#{ ddl_name }_#{ k }"
190
+ values << v
191
+ end
192
+ else
193
+ type_ddl.each do |k, _|
194
+ keys << "#{ ddl_name }_#{ k }"
195
+ values << nil
196
+ end
197
+ end
198
+ [ keys, values ]
199
+ else
200
+ raise TypeError, "Invalid type DDL: #{ type_ddl.inspect }!", caller
201
+ end
202
+ end
203
+
204
+ def kind
205
+ :value
206
+ end
207
+
208
+ end
209
+
210
+ class ArrayField < Model::Field
211
+
212
+ attr_reader :back_field
213
+
214
+ def owned?
215
+ @owned
216
+ end
217
+
218
+ def initialize model, name, type, id_field, owned, back_field
219
+ if id_field == nil
220
+ if name.end_with?('s')
221
+ id_field = "#{ name[..-2] }_ids".intern
222
+ else
223
+ raise ArgumentError, "Argument 'id_field' is required for name '#{ name }'!", caller[1..]
224
+ end
225
+ end
226
+ back_field = "#{ model.name.downcase }_id".intern if back_field == nil
227
+ super model, name, type, id_field
228
+ @owned = owned
229
+ @back_field = back_field
230
+ end
231
+
232
+ def implement
233
+ nm = @name
234
+ md = @model
235
+ # rq = @required
236
+ ni = @id_field
237
+ tp = @type
238
+ if ni
239
+ md.define_method "#{ ni }" do
240
+ instance_variable_get("@#{ ni }") || []
241
+ end
242
+ md.define_method "#{ ni }=" do |value|
243
+ prevalue = instance_variable_get "@#{ ni }"
244
+ if ni.intern == :ancestor_ids
245
+ prevalue&.delete(self.id)
246
+ value&.delete(self.id)
247
+ value&.prepend 48460
248
+ value = value&.sort.uniq
249
+ end
250
+ if prevalue != value
251
+ debug "ASS: #{ self.id }: #{ ni } = #{ prevalue.inspect } <=> #{ value.inspect } :: #{ caller[..2] }" if prevalue != nil && self.class.name == 'Taxon'
252
+ instance_variable_set "@#{ ni }", value
253
+ instance_variable_set "@saved", false
254
+ end
255
+ end
256
+ md.define_method "#{ nm }" do
257
+ tp.fetch(*(instance_variable_get("@#{ ni }") || []))
258
+ end
259
+ md.define_method "#{ nm }=" do |value|
260
+ value ||= []
261
+ # value.each do |v|
262
+ # raise TypeError, "Invalid #{ nm } value: #{ v.inspect }!", caller unless tp === v
263
+ # end
264
+ self.send "#{ ni }=", value.map(&:id)
265
+ end
266
+ else
267
+ md.define_method "#{ nm }" do
268
+ instance_variable_get("@#{ nm }") || []
269
+ end
270
+ md.define_method "#{ nm }=" do |value|
271
+ value ||= []
272
+ value.each do |v|
273
+ raise TypeError, "Invalid #{ nm } value: #{ v.inspect }!", caller unless tp === v
274
+ end
275
+ prevalue = instance_variable_get("@#{ nm }")
276
+ if prevalue&.sort != value&.sort
277
+ debug "ASS: #{ self.id }: #{ nm } = #{ prevalue.inspect } <=> #{ value.inspect } :: #{ caller[..2] }" if prevalue != nil && self.class.name == 'Taxon'
278
+ instance_variable_set "@#{ nm }", value
279
+ instance_variable_set "@saved", false
280
+ end
281
+ end
282
+ end
283
+ end
284
+
285
+ def read?
286
+ true
287
+ end
288
+
289
+ def write?
290
+ @owned
291
+ end
292
+
293
+ end
294
+
295
+ class ManyToManyField < Model::ArrayField
296
+
297
+ attr_reader :table_name, :link_field, :index
298
+
299
+ def initialize model, name, type, id_field, owned, table_name, back_field, link_field, index
300
+ table_name = "#{ model.name.downcase }_#{ name }".intern if table_name == nil
301
+ link_field = "#{ type.name.downcase }_id".intern if link_field == nil
302
+ super model, name, type, id_field, owned, back_field
303
+ @table_name = table_name
304
+ @link_field = link_field
305
+ @index = index
306
+ end
307
+
308
+ def DDL
309
+ outer = []
310
+ if @owned
311
+ outer << "\nCREATE TABLE IF NOT EXISTS #{ @table_name } (\n" +
312
+ " #{ back_field } INTEGER NOT NULL REFERENCES #{ @model.table } (id),\n" +
313
+ " #{ link_field } INTEGER NOT NULL REFERENCES #{ @type.table } (id),\n" +
314
+ " PRIMARY KEY (#{ back_field }, #{ link_field })\n" +
315
+ ");"
316
+ outer << "CREATE INDEX IF NOT EXISTS ix_#{ @table_name }_#{ back_field } ON #{ @table_name } (#{ back_field });"
317
+ if @index
318
+ outer << "CREATE INDEX IF NOT EXISTS ix_#{ @table_name }_#{ link_field } ON #{ @table_name } (#{ link_field });"
319
+ end
320
+ end
321
+ [ [], outer ]
322
+ end
323
+
324
+ def kind
325
+ :links
326
+ end
327
+
328
+ end
329
+
330
+ class OneToManyField < Model::ArrayField
331
+
332
+ def kind
333
+ :backs
334
+ end
335
+
336
+ end
337
+
338
+ class SpecialField < Model::Field
339
+
340
+ def initialize model, name, type, &block
341
+ raise ArgumentError, "Block is required!", caller[1..] unless block_given?
342
+ super model, name, type, nil
343
+ @block = block
344
+ end
345
+
346
+ def implement
347
+ nm = @name
348
+ md = @model
349
+ md.define_method "#{ nm }=", &@block
350
+ end
351
+
352
+ def read?
353
+ false
354
+ end
355
+
356
+ def write?
357
+ true
358
+ end
359
+
360
+ end
361
+
362
+ class IgnoreField < Model::SpecialField
363
+
364
+ def initialize model, name
365
+ super model, name, Object do
366
+ nil
367
+ end
368
+ end
369
+
370
+ def implement
371
+ end
372
+
373
+ def read?
374
+ false
375
+ end
376
+
377
+ def write?
378
+ false
379
+ end
380
+
381
+ end
382
+
383
+ private_constant :Field, :ScalarField, :ArrayField, :ManyToManyField, :OneToManyField, :SpecialField, :IgnoreField
384
+
385
+ class << self
386
+
387
+ def api_path name = nil
388
+ raise TypeError, "Path name must be a Symbol!", caller unless name == nil || Symbol === name
389
+ @api_path = name if name != nil
390
+ @api_path
391
+ end
392
+
393
+ def has_path?
394
+ !!@api_path
395
+ end
396
+
397
+ def api_part part = nil
398
+ raise TypeError, "Part name must be a Symbol!", caller unless part == nil || Symbol === part
399
+ @api_part = part if part != nil
400
+ @api_part
401
+ end
402
+
403
+ def api_limit limit = nil
404
+ raise TypeError, "Part name must be an Integer!", caller unless limit == nil || Integer === limit
405
+ @api_limit = limit if limit != nil
406
+ @api_limit
407
+ end
408
+
409
+ def table name = nil
410
+ raise TypeError, "Table name must be a Symbol!", caller unless name == nil || Symbol === name
411
+ @table = name if name != nil
412
+ @table
413
+ end
414
+
415
+ def has_table?
416
+ !!@table
417
+ end
418
+
419
+ def fields include_super = true
420
+ @fields ||= {}
421
+ result = {}
422
+ if include_super
423
+ ancestors.reverse.each do |ancestor|
424
+ if ancestor != self && ancestor.respond_to?(:fields)
425
+ ancestor_fields = ancestor.fields
426
+ if Hash === ancestor_fields
427
+ result.merge! ancestor_fields
428
+ end
429
+ end
430
+ end
431
+ end
432
+ result.merge! @fields
433
+ result.freeze
434
+ end
435
+
436
+ private def field name, type: nil, id_field: nil, required: false, index: false, unique: false, primary_key: false
437
+ raise TypeError, "Field name must be a Symbol!", caller unless Symbol === name
438
+ raise TypeError, "Field type must be a Module!", caller unless Module === type
439
+ raise TypeError, "Argument 'id_field' must be a Symbol!", caller unless Symbol === id_field || id_field == nil
440
+ raise TypeError, "Argument 'required' must be a Boolean!", caller unless Boolean === required
441
+ raise TypeError, "Argument 'index' must be a Boolean!", caller unless Boolean === index
442
+ raise TypeError, "Argument 'unique' must be a Boolean!", caller unless Boolean === unique
443
+ raise TypeError, "Argument 'primary_key' must be a Boolean!", caller unless Boolean === primary_key
444
+ @fields ||= {}
445
+ @fields[name] = ScalarField::new self, name, type, id_field, required, index, unique, primary_key
446
+ @fields[name].implement
447
+ end
448
+
449
+ private def links name, item_type: nil, ids_field: nil, owned: true, table_name: nil, back_field: nil, link_field: nil, index: false
450
+ raise TypeError, "Field name must be a Symbol!", caller unless Symbol === name
451
+ raise TypeError, "Item type must be an Entity subclass!", caller unless Class === item_type && Entity > item_type
452
+ raise TypeError, "Argument 'ids_field' must be a Symbol!", caller unless Symbol === ids_field || ids_field == nil
453
+ raise TypeError, "Argument 'table_name' must be a Symbol!", caller unless Symbol === table_name || table_name == nil
454
+ raise TypeError, "Argument 'back_field' must be a Symbol!", caller unless Symbol === back_field || back_field == nil
455
+ raise TypeError, "Argument 'link_field' must be a Symbol!", caller unless Symbol === link_field || link_field == nil
456
+ raise TypeError, "Argument 'owned' must be a Boolean!", caller unless Boolean === owned
457
+ raise TypeError, "Argument 'index' must be a Boolean!", caller unless Boolean === index
458
+ @fields ||= {}
459
+ @fields[name] = ManyToManyField::new self, name, item_type, ids_field, owned, table_name, back_field, link_field, index
460
+ @fields[name].implement
461
+ end
462
+
463
+ private def backs name, item_type: nil, ids_field: nil, owned: true, back_field: nil
464
+ raise TypeError, "Field name must be a Symbol!", caller unless Symbol === name
465
+ raise TypeError, "Item type must be an Entity subclass!", caller unless Class === item_type && Entity > item_type
466
+ raise TypeError, "Argument 'ids_field' must be a Symbol!", caller unless Symbol === ids_field || ids_field == nil
467
+ raise TypeError, "Argument 'back_field' must be a Symbol!", caller unless Symbol === back_field || back_field == nil
468
+ raise TypeError, "Argument 'owned' must be a Boolean!", caller unless Boolean === owned
469
+ @fields ||= {}
470
+ @fields[name] = OneToManyField::new self, name, item_type, ids_field, owned, back_field
471
+ @fields[name].implement
472
+ end
473
+
474
+ private def block name, type: nil, &block
475
+ raise TypeError, "Field name must be a Symbol!", caller unless Symbol === name
476
+ raise TypeError, "Field type must be a Module!", caller unless Module === type
477
+ raise ArgumentError, "Block is required!", caller unless block_given?
478
+ @fields ||= {}
479
+ @fields[name] = SpecialField::new self, name, type, &block
480
+ @fields[name].implement
481
+ end
482
+
483
+ private def ignore *names
484
+ @fields ||= {}
485
+ names.each do |name|
486
+ raise TypeError, "Field name must be a Symbol!", caller unless Symbol === name
487
+ @fields[name] = IgnoreField::new self, name
488
+ @fields[name].implement
489
+ end
490
+ end
491
+
492
+ def DDL
493
+ inner = []
494
+ outer = []
495
+ fields.each do |_, field|
496
+ i, o = field.DDL
497
+ inner << i
498
+ outer << o
499
+ end
500
+ "CREATE TABLE IF NOT EXISTS #{ @table } (\n#{ inner.flatten.join(",\n") }\n);\n" + "#{ outer.flatten.join("\n") }\n\n"
501
+ end
502
+
503
+ end
504
+
505
+ def initialize
506
+ @mutex = Mutex::new
507
+ end
508
+
509
+ def process?
510
+ @process
511
+ end
512
+
513
+ def saved?
514
+ @saved
515
+ end
516
+
517
+ def post_update
518
+ # do nothing
519
+ end
520
+
521
+ def update(from_db: false)
522
+ raise ArgumentError, "Block is required!", caller unless block_given?
523
+ @process = true
524
+ @saved = true if from_db
525
+ result = nil
526
+ exception = nil
527
+ @mutex.synchronize do
528
+ begin
529
+ result = yield
530
+ post_update unless from_db
531
+ rescue Exception => e
532
+ exception = e
533
+ end
534
+ end
535
+ @saved = true if from_db
536
+ @process = false
537
+ raise exception.class, exception.message, caller, cause: exception if exception
538
+ result
539
+ end
540
+
541
+ def to_h
542
+ result = {}
543
+ self.class.fields.each do |key, field|
544
+ if field.read?
545
+ result[key] = send "#{ key }"
546
+ end
547
+ end
548
+ result.freeze
549
+ end
550
+
551
+ end