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
@@ -0,0 +1,205 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'table'
4
+
5
+ module INat::Report::DSL
6
+
7
+ include INat
8
+ include INat::Report
9
+ include INat::Report::Table::DSL
10
+
11
+ private def class_title object, is_observer: true
12
+ case object
13
+ when Entity::Taxon
14
+ 'Таксон'
15
+ when Entity::Place
16
+ 'Территория'
17
+ when Entity::Project
18
+ 'Проект'
19
+ when Entity::User
20
+ if is_observer
21
+ 'Наблюдатель'
22
+ else
23
+ 'Пользователь'
24
+ end
25
+ when Report::Period
26
+ 'Период'
27
+ else
28
+ 'Объект'
29
+ end
30
+ end
31
+
32
+ def species_table list, observers: false, details: true
33
+ raise ArgumentError, 'Unconsistent flags!' if observers && !details
34
+ object_title = class_title list.first.object
35
+ species = table do
36
+ column '#', width: 3, align: :right, data: :line_no
37
+ column object_title, data: :object
38
+ if details
39
+ column 'Наблюдения', data: :observations
40
+ else
41
+ column 'Наблюдения', data: :observations, width: 6, align: :right
42
+ end
43
+ end
44
+ users = nil
45
+ user_rows = []
46
+ if observers
47
+ @@prefix ||= 0
48
+ @@prefix += 1
49
+ users = table do
50
+ column '#', width: 3, align: :right, data: :line_no, marker: true
51
+ column 'Наблюдатель', data: :user
52
+ column 'Виды', width: 6, align: :right, data: :species
53
+ column 'Наблюдения', width: 6, align: :right, data: :observations
54
+ end
55
+ by_user = list.to_dataset.to_list Listers::USER
56
+ by_user.each do |ds|
57
+ ls = ds.to_list Listers::SPECIES
58
+ user = ds.object
59
+ user_rows << { user: user, anchor: "#{ @@prefix }-user-#{ user.id }", species: ls.count, observations: ds.count }
60
+ end
61
+ user_rows.sort_by! { |row| -row[:species] }
62
+ users << user_rows
63
+ end
64
+ species_rows = []
65
+ list.each do |ds|
66
+ observations = if details
67
+ if observers
68
+ ds.observations.map { |o| "#{ o }<sup><a href=\"\##{ @@prefix }-user-#{ o.user.id }\">#{ user_rows.index { |row| row[:user] == o.user } + 1 }</a></sup>" }
69
+ else
70
+ ds.observations.map(&:to_s)
71
+ end
72
+ else
73
+ [ ds.count.to_s ]
74
+ end
75
+ species_rows << { object: ds.object, observations: observations.join(', ') }
76
+ end
77
+ species << species_rows
78
+ if observers
79
+ [ species, users ]
80
+ else
81
+ species
82
+ end
83
+ end
84
+
85
+ LAST_STYLE = 'font-size:110%;'
86
+ SUMMARY_STYLE = 'font-weight:bold;'
87
+
88
+ def history_table list, news: true, summary: true, base_link: nil, last: nil, extras: false, last_style: LAST_STYLE, summary_style: SUMMARY_STYLE
89
+ object_title = class_title list.first.object, is_observer: false
90
+ history = table do
91
+ column '#', width: 3, align: :right, data: :line_no
92
+ column object_title, data: :object
93
+ column 'Наблюдения', width: 6, align: :right, data: :observations
94
+ column 'Виды', width: 6, align: :right, data: :species
95
+ if news
96
+ column 'Новые', width: 6, align: :right, data: :news
97
+ end
98
+ end
99
+ history_rows = []
100
+ last_object = nil
101
+ last_ds = nil
102
+ last_ls = nil
103
+ delta = nil
104
+ if news || summary
105
+ base_ls = List::zero Listers::SPECIES
106
+ end
107
+ list.each do |ds|
108
+ last_object = ds.object
109
+ last_ds = ds
110
+ last_ls = ds.to_list Listers::SPECIES
111
+ row = { observations: last_ds.count, species: last_ls.count }
112
+ if base_link && last_object.respond_to?(:query_params)
113
+ link = base_link + '&' + last_object.query_params
114
+ row[:object] = "<a href=\"#{ link }\">#{ last_object }</a>"
115
+ else
116
+ row[:object] = last_object
117
+ end
118
+ if news || summary
119
+ delta = last_ls - base_ls
120
+ base_ls += last_ls
121
+ row[:news] = delta.count
122
+ end
123
+ history_rows << row
124
+ end
125
+ if last && last != last_object
126
+ row = { object: last, observations: 0, species: 0 }
127
+ if news
128
+ row[:news] = 0
129
+ delta = List::zero Listers::SPECIES
130
+ last_object = last
131
+ last_ds = DataSet::zero
132
+ last_ls = List::zero Listers::SPECIES
133
+ end
134
+ history_rows << row
135
+ end
136
+ history_rows.last[:style] = last_style if last_style
137
+ if summary
138
+ # Почему base_ls, а не list?
139
+ # 1. list в некоторых случаях может быть не List, а просто массив DataSet с заполненным object.
140
+ # 2. Даже в базовом случае list сформирован не по видам.
141
+ row = { line_no: '', observations: base_ls.observation_count, species: base_ls.count }
142
+ row[:style] = summary_style if summary_style
143
+ history_rows << row
144
+ end
145
+ history << history_rows
146
+ if extras
147
+ [ history, last_ds, delta ]
148
+ else
149
+ history
150
+ end
151
+ end
152
+
153
+ # Вариант history_table с другими предустановками.
154
+ def summary_table list, extras: false, summary_style: SUMMARY_STYLE
155
+ history_table list, news: false, summary: true, base_link: nil, last: nil, extras: extras, last_style: nil, summary_style: summary_style
156
+ end
157
+
158
+ # TODO: разобраться с разными count в разных контекстах.
159
+ def rating_table list, limit: 1, count: 3, details: true, key: :species, summary: false
160
+ object_title = class_title list.first&.object
161
+ rating = table do
162
+ column '#', width: 3, align: :right, data: :line_no
163
+ column object_title, data: :object
164
+ column 'Виды', width: 6, align: :right, data: :species
165
+ if details
166
+ column 'Наблюдения', data: :observations
167
+ else
168
+ column 'Наблюдения', width: 6, align: :right, data: :observations
169
+ end
170
+ end
171
+ rating_rows = []
172
+ list.each do |ds|
173
+ ls = ds.to_list Listers::SPECIES
174
+ row = { object: ds.object, species: ls.count, count: ds.count }
175
+ if details
176
+ row[:observations] = ds.observations.map(&:to_s).join(', ')
177
+ else
178
+ row[:observations] = ds.count
179
+ end
180
+ rating_rows << row
181
+ end
182
+ size = rating_rows.size
183
+ key = :count if key == :observations
184
+ if limit
185
+ rating_rows.filter! { |row| row[key] >= limit }
186
+ end
187
+ rating_rows.sort_by! { |row| -row[key] }
188
+ if count
189
+ rating_rows = rating_rows.take(count)
190
+ end
191
+ if summary
192
+ full_ls = List === list ? list : list.reduce(DataSet::zero, :|).to_list
193
+ rating_rows << { line_no: '', species: full_ls.count, count: full_ls.observation_count }
194
+ end
195
+ rating << rating_rows
196
+ if count
197
+ [ rating, size ]
198
+ else
199
+ rating
200
+ end
201
+ end
202
+
203
+ module_function :species_table, :history_table, :summary_table, :rating_table
204
+
205
+ end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- class Table
3
+ class INat::Report::Table
4
4
 
5
5
  attr_reader :columns
6
6
 
@@ -41,6 +41,10 @@ class Table
41
41
  @rows
42
42
  end
43
43
 
44
+ def empty?
45
+ @rows.empty?
46
+ end
47
+
44
48
  def << data
45
49
  if Array === data
46
50
  rows(*data)
@@ -124,12 +128,16 @@ class Table
124
128
 
125
129
  end
126
130
 
127
- module TableDSL
131
+ module INat::Report::Table::DSL
128
132
 
129
- private def table &block
133
+ include INat::Report
134
+
135
+ def table &block
130
136
  tbl = Table::new
131
137
  tbl.instance_eval(&block) if block_given?
132
138
  tbl
133
139
  end
134
140
 
141
+ module_function :table
142
+
135
143
  end
@@ -1,30 +1,36 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- class Hash
4
-
5
- def deep_merge! other
6
- other.each do |key, value|
7
- if has_key?(key) && self[key].respond_to?(:deep_merge!)
8
- self[key].deep_merge! value
9
- else
10
- self[key] = value
3
+ # TODO: убрать, все равно конфиг надо парсить нормально
4
+
5
+ module DeepMerge
6
+
7
+ refine Hash do
8
+
9
+ def deep_merge! other
10
+ other.each do |key, value|
11
+ if has_key?(key) && self[key].respond_to?(:deep_merge!)
12
+ self[key].deep_merge! value
13
+ else
14
+ self[key] = value
15
+ end
11
16
  end
17
+ self
12
18
  end
13
- self
14
- end
15
19
 
16
- end
20
+ end
17
21
 
18
- class Array
22
+ refine Array do
19
23
 
20
- def deep_merge! other
21
- other.each do |value|
22
- next if self.include?(value)
23
- next if String === value && self.include?(value.intern)
24
- next if Symbol === value && self.include?(value.to_s)
25
- self << value
24
+ def deep_merge! other
25
+ other.each do |value|
26
+ next if self.include?(value)
27
+ next if String === value && self.include?(value.intern)
28
+ next if Symbol === value && self.include?(value.to_s)
29
+ self << value
30
+ end
31
+ self
26
32
  end
27
- self
33
+
28
34
  end
29
35
 
30
36
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: inat-get
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.0.11
4
+ version: 0.8.0.13
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ivan Shikhalev
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-11-22 00:00:00.000000000 Z
11
+ date: 2024-01-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: sqlite3
@@ -46,6 +46,7 @@ executables:
46
46
  extensions: []
47
47
  extra_rdoc_files: []
48
48
  files:
49
+ - ".yardopts"
49
50
  - LICENSE
50
51
  - README.md
51
52
  - Rakefile
@@ -69,7 +70,6 @@ files:
69
70
  - lib/inat/app/task/context.rb
70
71
  - lib/inat/app/task/dsl.rb
71
72
  - lib/inat/data/api.rb
72
- - lib/inat/data/cache.rb
73
73
  - lib/inat/data/db.rb
74
74
  - lib/inat/data/ddl.rb
75
75
  - lib/inat/data/entity.rb
@@ -107,11 +107,12 @@ files:
107
107
  - lib/inat/data/types/extras.rb
108
108
  - lib/inat/data/types/location.rb
109
109
  - lib/inat/data/types/std.rb
110
+ - lib/inat/report/report_dsl.rb
110
111
  - lib/inat/report/table.rb
111
112
  - lib/inat/utils/deep.rb
112
113
  homepage: https://github.com/shikhalev/inat-get
113
114
  licenses:
114
- - GPL-3.0
115
+ - GPL-3.0-or-later
115
116
  metadata:
116
117
  homepage_uri: https://github.com/shikhalev/inat-get
117
118
  source_code_uri: https://github.com/shikhalev/inat-get
@@ -130,7 +131,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
130
131
  - !ruby/object:Gem::Version
131
132
  version: '0'
132
133
  requirements: []
133
- rubygems_version: 3.4.19
134
+ rubygems_version: 3.5.3
134
135
  signing_key:
135
136
  specification_version: 4
136
137
  summary: Client for iNaturalist API.
@@ -1,9 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # class Cache
4
-
5
- # class << self
6
-
7
- # end
8
-
9
- # end