inat-get 0.9.0.8 → 0.9.0.10

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 187fbbcea790e41c40ec4d14dd08c52fed9a6c207f5b4f512db425a63361e875
4
- data.tar.gz: ed262c666cf618fa77a2223738583fc7b6ad7817cf1a5f32e94b3fc65fd8a75f
3
+ metadata.gz: 375524ed9be224c8d856b640812d33d449cc0306c7e3ae6f1366b720aa8f5156
4
+ data.tar.gz: d1917c1ef4b018535912c464ce491ac620068389f5c6f7107c24a0a5def577be
5
5
  SHA512:
6
- metadata.gz: 1a7ad7ef87681e1852e4dbb23a39683da38a9c262b5e2bd9b883f3702b6744df5dc610d8014bd5a5ba793b916965ead871267235975c686e44b23d6c3757fb3f
7
- data.tar.gz: 1cb4ac5c3a6c12b9e321189a9207d347f7a962cc3992c48b7a7e39187ec7993672eda415a708d88163c1cc563c012efc38d671e681475f4cbb02a5ba09216c8d
6
+ metadata.gz: b7800a7d299c507d2db2931e5cf7e617bdac8dac27b02b3461363b7487f4dfa5ed62f94fb15d4dd910a8bdeb01a470f1b1f6d345069cf618d8f7f151af414e17
7
+ data.tar.gz: 4b66a9790d97d26197d412f042e253fa2ccd4ed9fddfe4c0251127eafc13b8b9cefa0edd87a254fb42980d6c46543cfbe80a190ed878147cafb96db487c46d4d
data/.yardopts ADDED
@@ -0,0 +1,12 @@
1
+ --title "INatGet"
2
+ --markup markdown
3
+ --readme README.md
4
+ --files DSL.md
5
+ --asset coverage-badge.svg
6
+ --files share/inat-get/docs/*.md
7
+ --protected
8
+ --private
9
+ --no-private
10
+ --load .yardpreamble.rb
11
+ --plugin is-sequel
12
+ lib/**/*.rb
data/.yardpreamble.rb ADDED
@@ -0,0 +1,15 @@
1
+ ENV['SEQUEL_MIGRATIONS_DIR'] = './share/inat-get/db/migrations/'
2
+
3
+ module WF
4
+
5
+ def warn message, category = nil, **kwargs
6
+ case message
7
+ when /literal string will be frozen in the future/, /character class has duplicated range/, /yardpreamble/
8
+ # do nothing
9
+ else
10
+ super message
11
+ end
12
+ end
13
+
14
+ end
15
+ Warning.extend WF
@@ -0,0 +1,6 @@
1
+ <?xml version="1.0"?><svg xmlns="http://www.w3.org/2000/svg" width="100" height="20">
2
+ <rect width="100" height="20" fill="#555"/>
3
+ <rect x="63" width="37" height="20" fill="red"/>
4
+ <text x="8" y="14" fill="#fff" font-family="Verdana" font-size="11">coverage</text>
5
+ <text x="66" y="14" fill="#fff" font-family="Verdana" font-size="11">68%</text>
6
+ </svg>
@@ -69,7 +69,7 @@ module INatGet::App::Maintenance
69
69
  end
70
70
 
71
71
  def db_update config
72
- run_migration config
72
+ run_migrations config
73
73
  exit Errno::NOERROR::Errno
74
74
  end
75
75
 
@@ -79,7 +79,7 @@ module INatGet::App::Maintenance
79
79
  $stderr.puts "❌ \e[1mVersion must be an integer\e[0m"
80
80
  exit Errno::ECANCELED::Errno
81
81
  end
82
- run_migration config, target: target
82
+ run_migrations config, target: target
83
83
  exit Errno::NOERROR::Errno
84
84
  end
85
85
 
@@ -85,7 +85,20 @@ class INatGet::Data::DSL::Dataset
85
85
  def % field
86
86
  # field = field.to_sym
87
87
  values = get_field_values field
88
+ total = if values.respond_to?(:count)
89
+ values.count
90
+ else
91
+ values.size
92
+ end
93
+ Thread::current[:total] ||= 0
94
+ Thread::current[:total] += total
95
+ console.update status: "grouping by #{ field }...", total: Thread::current[:total]
96
+ current = 0
97
+ Thread::current[:current] ||= 0
88
98
  dss = values.map do |value|
99
+ current += 1
100
+ Thread::current[:current] += 1
101
+ console.update status: "grouping by #{ field }...", current: Thread::current[:current]
89
102
  if value.is_a?(INatGet::Data::Model::Taxon)
90
103
  query = Q(self.condition.model, :taxon => value )
91
104
  else
@@ -94,6 +107,12 @@ class INatGet::Data::DSL::Dataset
94
107
  INatGet::Data::DSL::Dataset::new(value, self.condition & query, self.updated?)
95
108
  end
96
109
  INatGet::Data::DSL::List::new(*dss)
110
+ ensure
111
+ if current < total
112
+ Thread::current[:total] -= total - current
113
+ console.update total: Thread::current[:total]
114
+ end
115
+ console.update status: 'grouped'
97
116
  end
98
117
 
99
118
  # @return [Dataset]
@@ -112,13 +131,29 @@ class INatGet::Data::DSL::Dataset
112
131
  # @yield Block
113
132
  # @yieldparam [Sequel::Model] obj
114
133
  # @return [void]
115
- def each &block
134
+ def each
116
135
  return to_enum(__method__) unless block_given?
117
136
  connect!
137
+ total = @dataset.count
138
+ Thread::current[:total] ||= 0
139
+ Thread::current[:total] += total
140
+ console.update status: 'processing...', total: Thread::current[:total]
141
+ current = 0
142
+ Thread::current[:current] ||= 0
118
143
  @dataset.each do |item|
119
144
  check_shutdown!
120
- block.call item
145
+ current += 1
146
+ Thread::current[:current] += 1
147
+ console.update status: 'processing...', current: Thread::current[:current]
148
+ yield item
149
+ end
150
+ ensure
151
+ # Цикл может быть прерван досрочно, тогда нам нужно уменьшить total, чтобы результат сходился
152
+ if current < total
153
+ Thread::current[:total] -= total - current
154
+ console.update total: Thread::current[:total]
121
155
  end
156
+ console.update status: 'processed'
122
157
  end
123
158
 
124
159
  # @return [Integer]
@@ -171,7 +206,7 @@ class INatGet::Data::DSL::Dataset
171
206
  ranks = INatGet::Data::Enum::Rank::select { |r| r.level == field.level }
172
207
  subquery = taxon_id_at_ranks(*ranks)
173
208
  ids = query.distinct.select_map(subquery.as(:taxon_at_rank)).compact
174
- INatGet::Data::Model::Taxon.where(id: ids).all
209
+ INatGet::Data::Model::Taxon.where(id: ids)
175
210
  elsif model.associations.include?(field)
176
211
  reflection = model.association_reflection(field)
177
212
  target = reflection.associated_class
@@ -179,14 +214,14 @@ class INatGet::Data::DSL::Dataset
179
214
  case reflection[:type]
180
215
  when :many_to_one
181
216
  ids = query.distinct.select_map(reflection[:key])
182
- target.where(id: ids).all
217
+ target.where(id: ids)
183
218
  when :one_to_many, :many_to_many
184
219
  # Для всех типов "много" используем ассоциативный датасет
185
220
  # association_join делает join на основе метаданных связи
186
221
  ids = query.association_join(field)
187
222
  .distinct
188
223
  .select_map(reflection.qualified_right_key)
189
- target.where(id: ids).all
224
+ target.where(id: ids)
190
225
  end
191
226
  else
192
227
  # field может быть как символом :column, так и Sequel.function(:month, :created_at)
@@ -41,6 +41,17 @@ module INatGet::Data::DSL
41
41
  true
42
42
  end
43
43
 
44
+ # @private
45
+ class DummyConsole
46
+ def update(**kwargs)
47
+ # do nothing
48
+ end
49
+ end
50
+
51
+ def console
52
+ @console ||= Thread::current[:console] || DummyConsole::new
53
+ end
54
+
44
55
  # @endgroup
45
56
 
46
57
  # @group Date Utils
@@ -48,6 +48,7 @@ class INatGet::Data::DSL::List
48
48
 
49
49
  # @return [self]
50
50
  def add! other
51
+ other = [ other ] if other.is_a?(INatGet::Data::DSL::Dataset)
51
52
  other.each do |ds|
52
53
  if @datasets.has_key?(ds.key)
53
54
  @datasets[ds.key] += ds
@@ -135,12 +136,30 @@ class INatGet::Data::DSL::List
135
136
  # @yieldparam [Dataset] ds
136
137
  def each
137
138
  return to_enum(__method__) unless block_given?
138
- if @sorter
139
- @datasets.values.sort_by(&@sorter).each { |ds| yield ds }
139
+ sorted = if @sorter
140
+ @datasets.values.sort_by(&@sorter)
140
141
  else
141
- @datasets.each { |_, ds| yield ds }
142
+ @datasets.values
143
+ end
144
+ total = sorted.size
145
+ Thread::current[:total] ||= 0
146
+ Thread::current[:total] += total
147
+ console.update status: 'listing...', total: Thread::current[:total]
148
+ current = 0
149
+ Thread::current[:current] ||= 0
150
+ sorted.each do |ds|
151
+ current += 1
152
+ Thread::current[:current] += 1
153
+ console.update status: 'listing...', current: Thread::current[:current]
154
+ yield ds
142
155
  end
143
156
  self
157
+ ensure
158
+ if current < total
159
+ Thread::current[:total] -= total - current
160
+ console.update total: Thread::current[:total]
161
+ end
162
+ console.update status: 'listed'
144
163
  end
145
164
 
146
165
  # @return [Integer]
@@ -48,6 +48,8 @@ module INatGet::Data
48
48
 
49
49
  def updater() = self.manager&.updater
50
50
 
51
+ def endpoint() = self.manager&.endpoint
52
+
51
53
  end
52
54
 
53
55
  end
@@ -23,7 +23,7 @@ class INatGet::Data::Model::Place < INatGet::Data::Model
23
23
  def <=> other
24
24
  return nil unless other.is_a?(INatGet::Data::Model::Place)
25
25
  return 0 if self.id == other.id
26
- self.slug <=> other.slug
26
+ (self.display_name || self.name || self.slug) <=> (other.display_name || other.name || other.slug)
27
27
  end
28
28
 
29
29
  end
@@ -96,7 +96,7 @@ class INatGet::Data::Model::Project < INatGet::Data::Model
96
96
  def <=> other
97
97
  return nil unless other.is_a?(INatGet::Data::Model::Project)
98
98
  return 0 if self.id == other.id
99
- self.slug <=> other.slug
99
+ (self.title || self.slug) <=> (other.title || other.slug)
100
100
  end
101
101
 
102
102
  end
@@ -83,11 +83,30 @@ class INatGet::Data::Updater
83
83
  # @private
84
84
  def update_by_ids! *ids
85
85
  return [] if ids.empty?
86
- interval = parse_duration(@config.dig(:caching, :refs, self.manager.endpoint) || @config.dig(:caching, :refs, :default))
86
+ interval = parse_duration(@config.dig(:caching, :refs, self.manager&.endpoint) || @config.dig(:caching, :refs, :default))
87
87
  if interval
88
88
  point = (Time::now.to_time - interval).to_time
89
- # TODO: учесть slugs и uuids
90
- fresh = self.model.where(id: ids, cached: (point .. )).select_map(:id)
89
+ ints = uuids = slugs = []
90
+ if self.manager&.uuid? || self.manager&.sid
91
+ ints, other = ids.compact.partition { |i| i.is_a?(Integer) }
92
+ if !self.manager&.uuid?
93
+ slugs = other
94
+ elsif !self.manager&.sid
95
+ uuids = other
96
+ else
97
+ uuids, slugs = other.partition { |i| i.is_a?(String) && i =~ INatGet::Data::Helper::UUID_PATTERN }
98
+ end
99
+ else
100
+ ints = ids
101
+ end
102
+ condition = { id: ints }
103
+ # condition = Sequel.|(condition, { id: ints }) unless ints.empty?
104
+ condition = Sequel.|(condition, { uuid: uuids }) unless uuids.empty?
105
+ condition = Sequel.|(condition, { self.manager&.sid => slugs }) unless slugs.empty?
106
+ condition = Sequel.&(condition, { cached: (point .. ) })
107
+ fresh = self.model.where(condition).select_map(:id)
108
+ fresh += self.model.where(condition).select_map(:uuid) unless uuids.empty?
109
+ fresh += self.model.where(condition).select_map(self.manager&.sid) unless slugs.empty?
91
110
  ids -= fresh
92
111
  end
93
112
  ids.each_slice(self.slice_size) do |slice|
data/lib/inat-get/info.rb CHANGED
@@ -5,7 +5,7 @@ module INatGet; end
5
5
  module INatGet::Info
6
6
 
7
7
  NAME = 'inat-get'
8
- VERSION = '0.9.0.8'
8
+ VERSION = '0.9.0.10'
9
9
  VERSION_ALIAS = 'Carduelis carduelis'
10
10
 
11
11
  AUTHOR = 'Ivan Shikhalev'
@@ -0,0 +1,13 @@
1
+ # frozen_string_literals: true
2
+
3
+ Sequel.migration do
4
+
5
+ change do
6
+
7
+ alter_table :places do
8
+ set_column_allow_null :slug, true
9
+ end
10
+
11
+ end
12
+
13
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: inat-get
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.0.8
4
+ version: 0.9.0.10
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ivan Shikhalev
@@ -240,9 +240,12 @@ executables:
240
240
  extensions: []
241
241
  extra_rdoc_files: []
242
242
  files:
243
+ - ".yardopts"
244
+ - ".yardpreamble.rb"
243
245
  - LICENSE
244
246
  - README.md
245
247
  - bin/inat-get
248
+ - coverage-badge.svg
246
249
  - lib/inat-get.rb
247
250
  - lib/inat-get/app.rb
248
251
  - lib/inat-get/app/application.rb
@@ -373,6 +376,7 @@ files:
373
376
  - lib/inat-get/utils/json.rb
374
377
  - lib/inat-get/utils/simple_singular.rb
375
378
  - share/inat-get/db/migrations/001_start_database.rb
379
+ - share/inat-get/db/migrations/002_nullable_slug.rb
376
380
  - share/inat-get/demo/01_user_stat.rb
377
381
  - share/inat-get/demo/02_underfound.rb
378
382
  - share/inat-get/demo/03_newcomers.rb