inat-get 0.8.0.11 → 0.8.0.13

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 (61) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +6 -0
  3. data/bin/inat-get +1 -1
  4. data/inat-get.gemspec +6 -6
  5. data/lib/extra/enum.rb +4 -0
  6. data/lib/extra/period.rb +15 -0
  7. data/lib/inat/app/application.rb +4 -3
  8. data/lib/inat/app/config/messagelevel.rb +3 -1
  9. data/lib/inat/app/config/shiftage.rb +1 -1
  10. data/lib/inat/app/config/updatemode.rb +1 -1
  11. data/lib/inat/app/config.rb +6 -2
  12. data/lib/inat/app/globals.rb +6 -3
  13. data/lib/inat/app/info.rb +18 -13
  14. data/lib/inat/app/logging.rb +3 -3
  15. data/lib/inat/app/preamble.rb +1 -1
  16. data/lib/inat/app/status.rb +3 -9
  17. data/lib/inat/app/task/context.rb +9 -3
  18. data/lib/inat/app/task/dsl.rb +5 -3
  19. data/lib/inat/app/task.rb +2 -2
  20. data/lib/inat/data/api.rb +210 -181
  21. data/lib/inat/data/db.rb +9 -4
  22. data/lib/inat/data/ddl.rb +24 -5
  23. data/lib/inat/data/entity/comment.rb +8 -4
  24. data/lib/inat/data/entity/flag.rb +7 -3
  25. data/lib/inat/data/entity/identification.rb +9 -4
  26. data/lib/inat/data/entity/observation.rb +27 -14
  27. data/lib/inat/data/entity/observationphoto.rb +7 -3
  28. data/lib/inat/data/entity/observationsound.rb +6 -7
  29. data/lib/inat/data/entity/photo.rb +9 -3
  30. data/lib/inat/data/entity/place.rb +11 -2
  31. data/lib/inat/data/entity/project.rb +16 -10
  32. data/lib/inat/data/entity/projectadmin.rb +4 -4
  33. data/lib/inat/data/entity/projectobservationrule.rb +3 -4
  34. data/lib/inat/data/entity/request.rb +7 -3
  35. data/lib/inat/data/entity/sound.rb +7 -2
  36. data/lib/inat/data/entity/taxon.rb +11 -3
  37. data/lib/inat/data/entity/user.rb +7 -3
  38. data/lib/inat/data/entity/vote.rb +7 -3
  39. data/lib/inat/data/entity.rb +38 -24
  40. data/lib/inat/data/enums/conservationstatus.rb +3 -3
  41. data/lib/inat/data/enums/geoprivacy.rb +3 -1
  42. data/lib/inat/data/enums/iconictaxa.rb +1 -1
  43. data/lib/inat/data/enums/identificationcategory.rb +1 -1
  44. data/lib/inat/data/enums/licensecode.rb +5 -2
  45. data/lib/inat/data/enums/projectadminrole.rb +1 -1
  46. data/lib/inat/data/enums/projecttype.rb +5 -3
  47. data/lib/inat/data/enums/qualitygrade.rb +1 -1
  48. data/lib/inat/data/enums/rank.rb +1 -1
  49. data/lib/inat/data/model.rb +73 -24
  50. data/lib/inat/data/query.rb +14 -9
  51. data/lib/inat/data/sets/dataset.rb +10 -6
  52. data/lib/inat/data/sets/list.rb +8 -3
  53. data/lib/inat/data/sets/listers.rb +10 -4
  54. data/lib/inat/data/sets/wrappers.rb +111 -82
  55. data/lib/inat/data/types/location.rb +17 -8
  56. data/lib/inat/data/types/std.rb +171 -176
  57. data/lib/inat/report/report_dsl.rb +205 -0
  58. data/lib/inat/report/table.rb +11 -3
  59. data/lib/inat/utils/deep.rb +25 -19
  60. metadata +6 -5
  61. data/lib/inat/data/cache.rb +0 -9
@@ -3,12 +3,17 @@
3
3
  require_relative '../app/globals'
4
4
  require_relative 'types/std'
5
5
 
6
- autoload :Entity, 'inat/data/entity'
6
+ module INat::Data
7
+ autoload :Entity, 'inat/data/entity'
8
+ end
9
+
10
+ module INat::Data; end
7
11
 
8
- class Model
12
+ class INat::Data::Model
9
13
 
10
- include LogDSL
14
+ include INat::App::Logger::DSL
11
15
 
16
+ # @private
12
17
  class Field
13
18
 
14
19
  attr_reader :model, :name, :type, :id_field
@@ -42,7 +47,10 @@ class Model
42
47
 
43
48
  end
44
49
 
45
- class ScalarField < Model::Field
50
+ # @private
51
+ class ScalarField < Field
52
+
53
+ using INat::Types::Std
46
54
 
47
55
  attr_reader :index, :unique, :primary_key
48
56
 
@@ -51,7 +59,7 @@ class Model
51
59
  end
52
60
 
53
61
  def initialize model, name, type, id_field, required, index, unique, primary_key
54
- if Class === type && Entity > type && id_field == nil
62
+ if Class === type && INat::Data::Entity > type && id_field == nil
55
63
  id_field = "#{ name }_id".intern
56
64
  end
57
65
  super model, name, type, id_field
@@ -74,7 +82,7 @@ class Model
74
82
  md.define_method "#{ ni }=" do |value|
75
83
  prevalue = instance_variable_get "@#{ ni }"
76
84
  if prevalue != value
77
- debug "ASS: #{ self.id }: #{ ni } = #{ prevalue.inspect } <=> #{ value.inspect }" if prevalue != nil && self.class.name == 'Taxon'
85
+ debug "ASS: #{ self.id }: #{ ni } = #{ prevalue.inspect } <=> #{ value.inspect }" if prevalue != nil && self.class.short_name == 'Taxon'
78
86
  instance_variable_set "@#{ ni }", value
79
87
  instance_variable_set "@saved", false
80
88
  end
@@ -92,7 +100,7 @@ class Model
92
100
  md.define_method "#{ nm }=" do |value|
93
101
  prevalue = instance_variable_get "@#{ ni }"
94
102
  if prevalue != value&.id
95
- debug "ASS: #{ self.id }: #{ nm } / #{ ni } = #{ prevalue.inspect } <=> #{ value.inspect }" if prevalue != nil && self.class.name == 'Taxon'
103
+ debug "ASS: #{ self.id }: #{ nm } / #{ ni } = #{ prevalue.inspect } <=> #{ value.inspect }" if prevalue != nil && self.class.short_name == 'Taxon'
96
104
  instance_variable_set "@#{ ni }", value&.id
97
105
  instance_variable_set "@saved", false
98
106
  end
@@ -105,7 +113,7 @@ class Model
105
113
  raise TypeError, "Invalid '#{ nm }' value: #{ value.inspect }!", caller unless tp === value || (value == nil && !rq)
106
114
  prevalue = instance_variable_get "@#{ nm }"
107
115
  if prevalue != value
108
- debug "ASS: #{ self.id }: #{ nm } = #{ prevalue.inspect } <=> #{ value.inspect }" if prevalue != nil && self.class.name == 'Taxon'
116
+ debug "ASS: #{ self.id }: #{ nm } = #{ prevalue.inspect } <=> #{ value.inspect }" if prevalue != nil && self.class.short_name == 'Taxon'
109
117
  instance_variable_set "@#{ nm }", value
110
118
  instance_variable_set "@saved", false
111
119
  end
@@ -207,7 +215,10 @@ class Model
207
215
 
208
216
  end
209
217
 
210
- class ArrayField < Model::Field
218
+ # @private
219
+ class ArrayField < Field
220
+
221
+ using INat::Types::Std
211
222
 
212
223
  attr_reader :back_field
213
224
 
@@ -223,7 +234,7 @@ class Model
223
234
  raise ArgumentError, "Argument 'id_field' is required for name '#{ name }'!", caller[1..]
224
235
  end
225
236
  end
226
- back_field = "#{ model.name.downcase }_id".intern if back_field == nil
237
+ back_field = "#{ model.short_name.downcase }_id".intern if back_field == nil
227
238
  super model, name, type, id_field
228
239
  @owned = owned
229
240
  @back_field = back_field
@@ -248,7 +259,7 @@ class Model
248
259
  value = value&.sort.uniq
249
260
  end
250
261
  if prevalue != value
251
- debug "ASS: #{ self.id }: #{ ni } = #{ prevalue.inspect } <=> #{ value.inspect } :: #{ caller[..2] }" if prevalue != nil && self.class.name == 'Taxon'
262
+ debug "ASS: #{ self.id }: #{ ni } = #{ prevalue.inspect } <=> #{ value.inspect } :: #{ caller[..2] }" if prevalue != nil && self.class.short_name == 'Taxon'
252
263
  instance_variable_set "@#{ ni }", value
253
264
  instance_variable_set "@saved", false
254
265
  end
@@ -274,7 +285,7 @@ class Model
274
285
  end
275
286
  prevalue = instance_variable_get("@#{ nm }")
276
287
  if prevalue&.sort != value&.sort
277
- debug "ASS: #{ self.id }: #{ nm } = #{ prevalue.inspect } <=> #{ value.inspect } :: #{ caller[..2] }" if prevalue != nil && self.class.name == 'Taxon'
288
+ debug "ASS: #{ self.id }: #{ nm } = #{ prevalue.inspect } <=> #{ value.inspect } :: #{ caller[..2] }" if prevalue != nil && self.class.short_name == 'Taxon'
278
289
  instance_variable_set "@#{ nm }", value
279
290
  instance_variable_set "@saved", false
280
291
  end
@@ -292,13 +303,16 @@ class Model
292
303
 
293
304
  end
294
305
 
295
- class ManyToManyField < Model::ArrayField
306
+ # @private
307
+ class ManyToManyField < ArrayField
308
+
309
+ using INat::Types::Std
296
310
 
297
311
  attr_reader :table_name, :link_field, :index
298
312
 
299
313
  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
314
+ table_name = "#{ model.short_name.downcase }_#{ name }".intern if table_name == nil
315
+ link_field = "#{ type.short_name.downcase }_id".intern if link_field == nil
302
316
  super model, name, type, id_field, owned, back_field
303
317
  @table_name = table_name
304
318
  @link_field = link_field
@@ -327,7 +341,8 @@ class Model
327
341
 
328
342
  end
329
343
 
330
- class OneToManyField < Model::ArrayField
344
+ # @private
345
+ class OneToManyField < ArrayField
331
346
 
332
347
  def kind
333
348
  :backs
@@ -335,7 +350,8 @@ class Model
335
350
 
336
351
  end
337
352
 
338
- class SpecialField < Model::Field
353
+ # @private
354
+ class SpecialField < Field
339
355
 
340
356
  def initialize model, name, type, &block
341
357
  raise ArgumentError, "Block is required!", caller[1..] unless block_given?
@@ -359,7 +375,8 @@ class Model
359
375
 
360
376
  end
361
377
 
362
- class IgnoreField < Model::SpecialField
378
+ # @private
379
+ class IgnoreField < SpecialField
363
380
 
364
381
  def initialize model, name
365
382
  super model, name, Object do
@@ -433,7 +450,15 @@ class Model
433
450
  result.freeze
434
451
  end
435
452
 
436
- private def field name, type: nil, id_field: nil, required: false, index: false, unique: false, primary_key: false
453
+ # Defines a new field
454
+ # @param [Symbol] name
455
+ # @param [Class] type
456
+ # @return [void]
457
+ # @!macro [attach] field
458
+ # @api public
459
+ # @!attribute [rw]
460
+ # @return [$2] the +$1+ field
461
+ def field name, type: nil, id_field: nil, required: false, index: false, unique: false, primary_key: false
437
462
  raise TypeError, "Field name must be a Symbol!", caller unless Symbol === name
438
463
  raise TypeError, "Field type must be a Module!", caller unless Module === type
439
464
  raise TypeError, "Argument 'id_field' must be a Symbol!", caller unless Symbol === id_field || id_field == nil
@@ -446,9 +471,17 @@ class Model
446
471
  @fields[name].implement
447
472
  end
448
473
 
449
- private def links name, item_type: nil, ids_field: nil, owned: true, table_name: nil, back_field: nil, link_field: nil, index: false
474
+ # Defines a new many-to-many field
475
+ # @param [Symbol] name
476
+ # @param [Class] item_type
477
+ # @return [void]
478
+ # @!macro [attach] links
479
+ # @api public
480
+ # @!attribute [rw]
481
+ # @return [Array<$2>] the +$1+ field
482
+ def links name, item_type: nil, ids_field: nil, owned: true, table_name: nil, back_field: nil, link_field: nil, index: false
450
483
  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
484
+ raise TypeError, "Item type must be an Entity subclass!", caller unless Class === item_type && INat::Entity > item_type
452
485
  raise TypeError, "Argument 'ids_field' must be a Symbol!", caller unless Symbol === ids_field || ids_field == nil
453
486
  raise TypeError, "Argument 'table_name' must be a Symbol!", caller unless Symbol === table_name || table_name == nil
454
487
  raise TypeError, "Argument 'back_field' must be a Symbol!", caller unless Symbol === back_field || back_field == nil
@@ -460,9 +493,17 @@ class Model
460
493
  @fields[name].implement
461
494
  end
462
495
 
463
- private def backs name, item_type: nil, ids_field: nil, owned: true, back_field: nil
496
+ # Defines a new one-to-many field
497
+ # @param [Symbol] name
498
+ # @param [Class] item_type
499
+ # @return [void]
500
+ # @!macro [attach] backs
501
+ # @api public
502
+ # @!attribute [rw]
503
+ # @return [Array<$2>] the +$1+ field
504
+ def backs name, item_type: nil, ids_field: nil, owned: true, back_field: nil
464
505
  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
506
+ raise TypeError, "Item type must be an Entity subclass!", caller unless Class === item_type && INat::Entity > item_type
466
507
  raise TypeError, "Argument 'ids_field' must be a Symbol!", caller unless Symbol === ids_field || ids_field == nil
467
508
  raise TypeError, "Argument 'back_field' must be a Symbol!", caller unless Symbol === back_field || back_field == nil
468
509
  raise TypeError, "Argument 'owned' must be a Boolean!", caller unless Boolean === owned
@@ -471,7 +512,15 @@ class Model
471
512
  @fields[name].implement
472
513
  end
473
514
 
474
- private def block name, type: nil, &block
515
+ # Defines a new write-only field
516
+ # @param [Symbol] name
517
+ # @param [Class] type
518
+ # @return [void]
519
+ # @!macro [attach] block
520
+ # @api public
521
+ # @!attribute [w]
522
+ # @return [$2] the +$1+ field
523
+ def block name, type: nil, &block
475
524
  raise TypeError, "Field name must be a Symbol!", caller unless Symbol === name
476
525
  raise TypeError, "Field type must be a Module!", caller unless Module === type
477
526
  raise ArgumentError, "Block is required!", caller unless block_given?
@@ -14,9 +14,14 @@ require_relative 'types/std'
14
14
  require_relative 'types/location'
15
15
  require_relative 'types/extras'
16
16
 
17
- class Query
17
+ class INat::Query
18
18
 
19
- include LogDSL
19
+ using INat::Types::Std
20
+
21
+ include INat
22
+ include INat::App
23
+ include INat::App::Logger::DSL
24
+ include INat::Data::Types
20
25
 
21
26
  private def parse_accuracy value
22
27
  case value
@@ -1077,17 +1082,17 @@ class Query
1077
1082
  if mode != UpdateMode::MINIMAL
1078
1083
  actual_time = Time::new - Period::parse(G.config[:data][:update_period])
1079
1084
  end
1080
- actuals = Request::from_db_rows(DB.execute("SELECT * FROM requests WHERE time >= ?", actual_time.to_db)).select { |rq| self.in?(rq.query) }
1085
+ actuals = Entity::Request::from_db_rows(DB.execute("SELECT * FROM requests WHERE time >= ?", actual_time.to_db)).select { |rq| self.in?(rq.query) }
1081
1086
  end
1082
1087
  if actuals.empty? || mode == UpdateMode::FORCE
1083
1088
  # 2. Ищем чего бы обновить
1084
- request = Request::from_db_rows(DB.execute("SELECT * FROM requests WHERE query = ?", api_query)).first
1089
+ request = Entity::Request::from_db_rows(DB.execute("SELECT * FROM requests WHERE query = ?", api_query)).first
1085
1090
  updated_since = nil
1086
1091
  if request == nil
1087
1092
  query_string = api_query
1088
1093
  project_id = @api_params[:project_id]
1089
1094
  project_id = nil unless Integer === project_id
1090
- request = Request::create query_string, project_id
1095
+ request = Entity::Request::create query_string, project_id
1091
1096
  request.save
1092
1097
  else
1093
1098
  updated_since = request.time if mode != UpdateMode::RELOAD
@@ -1104,7 +1109,7 @@ class Query
1104
1109
  tt = nil
1105
1110
  cc = 0
1106
1111
  current_time = Time::new
1107
- API::query(:observations, **params) do |json, total|
1112
+ INat::API::query(:observations, **params) do |json, total|
1108
1113
  tt ||= total
1109
1114
  cc += 1
1110
1115
  pc = cc * 100 / tt
@@ -1113,7 +1118,7 @@ class Query
1113
1118
  pe = Period::make seconds: te
1114
1119
  pt = Period::make seconds: (Time::new - current_time).to_i
1115
1120
  Status::status nil, "Query \##{ @int_key } : R\##{ request.id } : parsed #{ format("%d of %d : %3d%% : time %s remain %s", cc, tt, pc, pt.to_hs, pe.to_hs) }"
1116
- obs = Observation::parse json
1121
+ obs = Entity::Observation::parse json
1117
1122
  obs.save
1118
1123
  DB.execute "INSERT OR REPLACE INTO request_observations (request_id, observation_id) VALUES (?, ?);", request.id, obs.id
1119
1124
  end
@@ -1123,12 +1128,12 @@ class Query
1123
1128
  # TODO: разобраться с удалением устаревшего
1124
1129
  # NEED: разобраться с частичной загрузкой — большие проекты грузятся недопустимо долго
1125
1130
  # возможно, стоит запараллелить обработку
1126
- request = Request::read(request.id).first
1131
+ request = Entity::Request::read(request.id).first
1127
1132
  end
1128
1133
  end
1129
1134
  # TODO: разобраться, где тупня
1130
1135
  sql, sql_args = db_where
1131
- result = Observation::from_db_rows(DB.execute("SELECT * FROM observations o#{ sql.empty? && '' || ' WHERE ' }#{ sql };", *sql_args))
1136
+ result = Entity::Observation::from_db_rows(DB.execute("SELECT * FROM observations o#{ sql.empty? && '' || ' WHERE ' }#{ sql };", *sql_args))
1132
1137
  if !@r_match.empty?
1133
1138
  result = result.filter { |o| self.match?(o) }
1134
1139
  end
@@ -3,10 +3,13 @@
3
3
  require_relative 'listers'
4
4
  require_relative 'list'
5
5
 
6
- class DataSet
6
+ class INat::Report::DataSet
7
+
8
+ include INat
9
+ include INat::Report
7
10
 
8
11
  attr_reader :time
9
- attr_reader :object
12
+ attr_accessor :object # TODO: переделать select так, чтобы не было необходимости во внешнем присваивании
10
13
  attr_reader :observations
11
14
 
12
15
  def initialize object, observations, time: Time::new
@@ -74,7 +77,8 @@ class DataSet
74
77
  alias :=== :include?
75
78
 
76
79
  def << observation
77
- raise TypeError, "Argument must be an Observation (#{ observation.inspect })!" unless Observation === observation
80
+ raise TypeError, "Argument must be an Observation (#{ observation.inspect })!" unless Entity::Observation === observation
81
+ # TODO: добавить массивы и прочее
78
82
  if !self.include?(observation)
79
83
  @observations << observation
80
84
  @by_id[observation.id] = observation
@@ -84,17 +88,17 @@ class DataSet
84
88
 
85
89
  def | other
86
90
  obj = @object == other.object ? @object : nil
87
- DataSet::new obj, @observations + other.observations, time: Time::new
91
+ INat::Report::DataSet::new obj, @observations + other.observations, time: Time::new
88
92
  end
89
93
 
90
94
  def & other
91
95
  obj = @object == other.object ? @object : nil
92
- DataSet::new obj, @observations.select { |o| other.include?(o) }, time: Time::new
96
+ INat::Report::DataSet::new obj, @observations.select { |o| other.include?(o) }, time: Time::new
93
97
  end
94
98
 
95
99
  def - other
96
100
  obj = @object == other.object ? @object : nil
97
- DataSet::new obj, @observations.select { |o| !other.include?(o) }, time: Time::new
101
+ INat::Report::DataSet::new obj, @observations.select { |o| !other.include?(o) }, time: Time::new
98
102
  end
99
103
 
100
104
  def to_a
@@ -1,8 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- autoload :DataSet, 'inat/data/sets/dataset'
3
+ module INat::Report
4
+ autoload :DataSet, 'inat/data/sets/dataset'
5
+ end
6
+
7
+ class INat::Report::List
4
8
 
5
- class List
9
+ include INat
10
+ include INat::Report
6
11
 
7
12
  attr_reader :lister, :sorter
8
13
 
@@ -77,7 +82,7 @@ class List
77
82
 
78
83
  def << some
79
84
  case some
80
- when Observation
85
+ when Entity::Observation
81
86
  key = @lister.call some
82
87
  if key != nil
83
88
  @data[key] ||= DataSet::new key, [], time: @time
@@ -2,14 +2,20 @@
2
2
 
3
3
  require_relative 'wrappers'
4
4
 
5
- module Listers
5
+ # TODO: возможно, загнать внутрь List?
6
+
7
+ module INat::Report::Listers
8
+
9
+ include INat::Report
10
+ include INat::Data::Types
6
11
 
7
12
  SPECIES = lambda { |o| o.normalized_taxon(Rank::COMPLEX .. Rank::HYBRID) }
8
13
  GENUS = lambda { |o| o.normalized_taxon(Rank::GENUS) }
9
14
  FAMILY = lambda { |o| o.normalized_taxon(Rank::FAMILY) }
10
- YEAR = lambda { |o| Year[o.observed_on] }
11
- MONTH = lambda { |o| Month[o.observed_on] }
12
- DAY = lambda { |o| Day[o.observed_on] }
15
+ YEAR = lambda { |o| Period::Year[o.observed_on] }
16
+ MONTH = lambda { |o| Period::Month[o.observed_on] }
17
+ DAY = lambda { |o| Period::Day[o.observed_on] }
18
+ WINTER = lambda { |o| Period::Winter[o.observed_on] }
13
19
  USER = lambda { |o| o.user }
14
20
 
15
21
  end
@@ -2,136 +2,165 @@
2
2
 
3
3
  require 'date'
4
4
 
5
- class Year
5
+ module INat::Report; end
6
+
7
+ # TODO: переделать в модуль namespace и Base-класс отдельно
8
+
9
+ class INat::Report::Period
6
10
 
7
11
  class << self
8
12
 
9
13
  private :new
10
14
 
11
- def [] source
12
- return nil if source == nil
13
- return source if Year === source
14
- year = case source
15
- when Date, Time
16
- source.year
15
+ def [] src
16
+ return nil if src == nil
17
+ return src if self === src
18
+ value = case src
19
+ when Date
20
+ self.date_to_value src
21
+ when Time
22
+ self.date_to_value src.to_date
17
23
  when String
18
- date = Date::parse source
19
- date.year
24
+ date = Date::parse src
25
+ self.date_to_value date
20
26
  when Integer
21
- source
27
+ src
22
28
  else
23
- raise TypeError, "Invalid year key: #{ source.inspect }!", caller
29
+ raise TypeError, "Invalid date: #{ src.inspect }", caller
24
30
  end
25
- @years ||= {}
26
- @years[year] ||= new year
27
- @years[year]
31
+ return nil if value == nil
32
+ @values ||= {}
33
+ @values[value] ||= new value
34
+ @values[value]
28
35
  end
29
36
 
30
37
  end
31
38
 
32
- attr_reader :year
39
+ attr_reader :value
33
40
 
34
- def initialize year
35
- @year = year
41
+ def initialize value
42
+ @value = value
36
43
  end
37
44
 
38
45
  include Comparable
39
46
 
40
47
  def <=> other
41
- return nil unless Year === other
42
- @year <=> other.year
48
+ return nil unless self.class === other
49
+ @value <=> other.value
43
50
  end
44
51
 
45
- def - num
46
- self.class[@year - num]
47
- end
52
+ class Year < INat::Report::Period
48
53
 
49
- def to_s
50
- "<i class=\"glyphicon glyphicon-calendar\"></i>  #{ @year }"
51
- end
54
+ class << self
52
55
 
53
- end
56
+ protected def date_to_value date
57
+ date.year
58
+ end
54
59
 
55
- class Month
60
+ end
56
61
 
57
- class << self
62
+ alias :year :value
58
63
 
59
- private :new
64
+ def - num
65
+ self.class[@value - num]
66
+ end
60
67
 
61
- def [] source
62
- return nil if source == nil
63
- return source if Month === source
64
- month = case source
65
- when Date, Time
66
- source.month
67
- when String
68
- date = Date::parse source
69
- date.month
70
- when Integer
71
- source
72
- else
73
- raise TypeError, "Invalid month key: #{ source.inspect }!", caller
74
- end
75
- @months ||= {}
76
- @months[month] ||= new month
77
- @months[month]
68
+ def to_s
69
+ "<i class=\"glyphicon glyphicon-calendar\"></i>  #{ @value } год"
70
+ end
71
+
72
+ def query_params
73
+ "year=#{ @value }"
78
74
  end
79
75
 
80
76
  end
81
77
 
82
- attr_reader :month
78
+ class Month < INat::Report::Period
83
79
 
84
- def initialize month
85
- @month = month
86
- end
80
+ class << self
87
81
 
88
- include Comparable
82
+ protected def date_to_value date
83
+ date.month
84
+ end
89
85
 
90
- def <=> other
91
- return nil unless Month === other
92
- @month <=> other.month
93
- end
86
+ end
94
87
 
95
- end
88
+ alias :month :value
89
+
90
+ NAMES = {
91
+ 1 => 'Январь',
92
+ 2 => 'Февраль',
93
+ 3 => 'Март',
94
+ 4 => 'Апрель',
95
+ 5 => 'Май',
96
+ 6 => 'Июнь',
97
+ 7 => 'Июль',
98
+ 8 => 'Август',
99
+ 9 => 'Сентябрь',
100
+ 10 => 'Октябрь',
101
+ 11 => 'Ноябрь',
102
+ 12 => 'Декабрь'
103
+ }
104
+
105
+ def to_s
106
+ "<i class=\"glyphicon glyphicon-calendar\"></i>  #{ NAMES[@value] }"
107
+ end
96
108
 
97
- class Day
109
+ def query_params
110
+ "month=#{ @value }"
111
+ end
98
112
 
99
- class << self
113
+ end
100
114
 
101
- attr_reader :day
115
+ class Day < INat::Report::Period
102
116
 
103
- def [] source
104
- return nil if source == nil
105
- return source if Day === source
106
- day = case source
107
- when Date, Time
108
- source.day
109
- when String
110
- date = Date::parse source
117
+ class << self
118
+
119
+ protected def date_to_value date
111
120
  date.day
112
- when Integer
113
- source
114
- else
115
- raise TypeError, "Invalid day key: #{ source.inspect }!", caller
116
121
  end
117
- @days ||= {}
118
- @days[day] ||= new day
119
- @days[day]
122
+
120
123
  end
121
124
 
122
- end
125
+ alias :day :value
126
+
127
+ def to_s
128
+ "<i class=\"glyphicon glyphicon-calendar\"></i>  #{ @value }"
129
+ end
123
130
 
124
- attr_reader :day
131
+ def query_params
132
+ "day=#{ @value }"
133
+ end
125
134
 
126
- def initialize day
127
- @day = day
128
135
  end
129
136
 
130
- include Comparable
137
+ class Winter < INat::Report::Period
138
+
139
+ class << self
140
+
141
+ protected def date_to_value date
142
+ month = date.month
143
+ if month <= 4
144
+ date.year
145
+ elsif month >= 10
146
+ date.year + 1
147
+ else
148
+ nil
149
+ end
150
+ end
151
+
152
+ end
153
+
154
+ alias :winter :value
155
+
156
+ def to_s
157
+ "<i class=\"glyphicon glyphicon-calendar\"></i>  Зима #{ @value - 1 }–#{ @value }"
158
+ end
159
+
160
+ def query_params
161
+ "d1=#{ @value - 1 }-10-01&d2=#{ @value }-04-30"
162
+ end
131
163
 
132
- def <=> other
133
- return nil unless Day === other
134
- @day <=> other.day
135
164
  end
136
165
 
137
166
  end