inat-get 0.8.0.11

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.
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