inat-get 0.8.0.11 → 0.8.0.13

Sign up to get free protection for your applications and to get access to all the features.
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