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
data/README.md ADDED
@@ -0,0 +1,16 @@
1
+ <img src="docs/logo.png" align="right" style="float:right;width:57%;">
2
+
3
+ # INat::Get
4
+
5
+ A toolset for fetching and processing data from **[iNaturalist.org][inat]**.
6
+
7
+ The author of this software is not associated or affiliated with the iNaturalist.
8
+ Only the [public API][api] is used in accordance with [the conditions][cond].
9
+
10
+ [inat]: https://www.inaturalist.org/
11
+ [api]: https://api.inaturalist.org/v1/docs/
12
+ [cond]: https://api.inaturalist.org/v1/docs/#terms-of-use
13
+
14
+ ## Current status
15
+
16
+ Early alpha version.
data/Rakefile ADDED
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ task default: %i[]
data/bin/inat-get ADDED
@@ -0,0 +1,59 @@
1
+ #!/usr/bin/ruby -w
2
+
3
+ require 'date'
4
+ require 'pp'
5
+
6
+ # require 'extra/enum'
7
+
8
+ require 'inat/app/application'
9
+ require 'inat/app/globals'
10
+ require 'inat/data/entity/observation'
11
+ require 'inat/data/entity/request'
12
+ require 'inat/data/db'
13
+ require 'inat/data/query'
14
+
15
+ app = Application.new
16
+ app.run
17
+ # PP::pp Globals.config, $>, 64
18
+
19
+ # puts
20
+ # puts DDL.DDL
21
+
22
+ # PP::pp DB::instance, $>, 64
23
+
24
+ # data = API::query(:observations, user_login: 'shikhalev', month: 11)
25
+
26
+ # PP::pp data.map { |d| Observation::parse(d) }.size, $>, 64
27
+
28
+ # https://www.inaturalist.org/projects/174222
29
+ # https://www.inaturalist.org/places/193592
30
+ # https://www.inaturalist.org/places/193881 (Ачитский район)
31
+ # https://www.inaturalist.org/places/194104 (Богдановичский район)
32
+ # https://www.inaturalist.org/places/194414 (Качканарский район)
33
+ # https://www.inaturalist.org/places/194023 (Берёзовский район)
34
+ # https://api.inaturalist.org/v1/observations?project_id=176067&updated_since=2023-10-31T19:36:29+05:00&per_page=200&order_by=id&order=asc&locale=ru&preferred_place_id=11829
35
+
36
+ # W, [2023-11-02T01:54:41.818021 #8118] WARN -- ‹main›: Some Taxon IDs were not fetched: 48460, 1, 47120, 245097, 47119, 47118, 120474, 342614, 319384, 67599, 495875, 153683, 153680, 367182, 48893, 1252003, 60920!
37
+ # W, [2023-11-02T03:49:58.509031 #8118] WARN -- ‹main›: Some Taxon IDs were not fetched: 1, 47118, 47119, 47120, 48460, 48893, 60920, 67599, 120474, 153680, 153683, 245097, 319384, 342614, 367182, 495875, 1252003!
38
+
39
+ # query = Query::new project_id: 180212
40
+ # PP::pp query.observations.size, $>, 64
41
+
42
+ # require 'inat/data/types/apiquery'
43
+
44
+ # pp Time::new.to_date.to_s
45
+
46
+ # a = ApiQuery::new ololo: 1, lalala: UUID.generate
47
+
48
+ # PP::pp a, $>, 64
49
+ # puts a.to_query
50
+
51
+ # Request::create ''
52
+
53
+ # pp URI::parse('https://api.inaturalist.org/v1/observations?project_id=176067&updated_since=2023-10-31T19:36:29+05:00&per_page=200&order_by=id&order=asc&locale=ru&preferred_place_id=11829')
54
+
55
+ # d = Date::parse '2023-11-01'
56
+ # t = d.to_time
57
+ # i = t.to_i
58
+
59
+ # pp [ d, t, i ]
data/docs/logo.png ADDED
Binary file
data/inat-get.gemspec ADDED
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'lib/inat/app/info'
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "inat-get"
7
+ spec.version = AppInfo::VERSION
8
+ spec.authors = [ AppInfo::AUTHOR ]
9
+ spec.email = [ AppInfo::EMAIL ]
10
+
11
+ spec.summary = "Client for iNaturalist API."
12
+ # spec.description = "TODO: Write a longer description or delete this line."
13
+ spec.homepage = AppInfo::HOMEPAGE
14
+ spec.license = "GPL-3.0"
15
+ spec.required_ruby_version = ">= 3.1.0"
16
+
17
+ spec.metadata["homepage_uri"] = spec.homepage
18
+ spec.metadata["source_code_uri"] = AppInfo::SOURCE_URL
19
+
20
+ spec.files = Dir.chdir(__dir__) do
21
+ `git ls-files -z`.split("\x0").reject do |f|
22
+ (File.expand_path(f) == __FILE__) ||
23
+ f.start_with?(*%w[test/ spec/ features/ samples/ .git .circleci appveyor Gemfile])
24
+ end
25
+ end
26
+ spec.bindir = "bin"
27
+ spec.executables = [ 'inat-get' ]
28
+ spec.require_paths = [ "lib" ]
29
+
30
+ spec.add_dependency "sqlite3", "~> 1.6.6"
31
+ spec.add_dependency "tzinfo", "~> 2.0.6"
32
+
33
+ end
data/lib/extra/enum.rb ADDED
@@ -0,0 +1,184 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Enum
4
+
5
+ class << self
6
+
7
+ def inherited sub
8
+ if self != ::Enum
9
+ raise TypeError, "Enum classes are closed!", caller
10
+ end
11
+ end
12
+
13
+ private :new
14
+
15
+ private def item name, order = nil, description: nil, data: nil
16
+ raise TypeError, "Name of enum value must be a Symbol!", caller unless Symbol === name
17
+ raise TypeError, "Order of enum value must be a Integer!", caller unless nil === order || Integer === order
18
+ raise TypeError, "Description of enum value must be a String!", caller unless nil === description || String === description
19
+ @values ||= []
20
+ @by_name ||= {}
21
+ @by_order ||= {}
22
+ order ||= (@values.map(&:order).max || 0) + 1
23
+ value = new name, order, description, data
24
+ value.freeze
25
+ @values << value
26
+ @by_name[name] = value
27
+ @by_order[order] = value
28
+ const_name = if name[0] != name[0].upcase
29
+ name.upcase
30
+ else
31
+ name
32
+ end
33
+ const_name = const_name.to_s.gsub '-', '_'
34
+ self.const_set const_name, value
35
+ return value
36
+ end
37
+
38
+ private def items *names, **names_with_order
39
+ names.each do |name|
40
+ item name
41
+ end
42
+ names_with_order.each do |name, order|
43
+ item name, order
44
+ end
45
+ return values
46
+ end
47
+
48
+ private def item_alias **params
49
+ @aliases ||= {}
50
+ params.each do |name, value|
51
+ raise TypeError, "Alias name must be a Symbol!", caller unless Symbol === name
52
+ value = self[value] if Symbol === value
53
+ raise TypeError, "Alias value must be a Symbol or #{ self.name }!", caller unless self === value
54
+ @aliases[name] = value
55
+ @by_name[name] = value
56
+ const_name = if name[0] != name[0].upcase
57
+ name.upcase
58
+ else
59
+ name
60
+ end
61
+ const_name = const_name.to_s.gsub '-', '_'
62
+ self.const_set const_name, value
63
+ end
64
+ return aliases
65
+ end
66
+
67
+ def values
68
+ @values ||= []
69
+ @values.sort_by(&:order).dup.freeze
70
+ end
71
+
72
+ def aliases
73
+ @aliases ||= {}
74
+ @aliases.dup.freeze
75
+ end
76
+
77
+ def to_a
78
+ values
79
+ end
80
+
81
+ def to_h
82
+ @by_name.dup.freeze
83
+ end
84
+
85
+ include Enumerable
86
+
87
+ def each
88
+ if block_given?
89
+ @values.sort_by(&:order).each do |item|
90
+ yield item
91
+ end
92
+ else
93
+ to_enum :each
94
+ end
95
+ end
96
+
97
+ private def get name_or_order
98
+ return nil if name == nil
99
+ result = @by_name[name_or_order] || @by_order[name_or_order]
100
+ raise ArgumentError, "Invalid name or order: #{ name_or_order.inspect }!", caller if result == nil
101
+ return result
102
+ end
103
+
104
+ def [] name_or_order
105
+ return name_or_order if self === name_or_order
106
+ case name_or_order
107
+ when Symbol, Integer
108
+ @by_name[name_or_order] || @by_order[name_or_order]
109
+ when Range
110
+ Range::new get(name_or_order.begin), get(name_or_order.end), name_or_order.exclude_end?
111
+ else
112
+ raise TypeError, "Invalid key: #{ name_or_order.inspect }!", caller
113
+ end
114
+ end
115
+
116
+ def parse src, case_sensitive: true
117
+ return src if self === src
118
+ raise TypeError, "Source must be a String!", caller unless String === src
119
+ src = src.strip
120
+ prefix = self.name + '::'
121
+ src = src[prefix.length ..] if src.start_with?(prefix)
122
+ return nil if src.empty?
123
+ key = if case_sensitive
124
+ @by_name.keys.find { |v| v.to_s == src }
125
+ else
126
+ src = src.downcase
127
+ @by_name.keys.find { |v| v.to_s.downcase == src }
128
+ end
129
+ raise NameError, "Invalid name: #{ src.inspect }!", caller if key.nil?
130
+ @by_name[key]
131
+ end
132
+
133
+ end
134
+
135
+ attr_reader :name, :order, :description, :data
136
+
137
+ def initialize name, order, description, data
138
+ @name = name
139
+ @order = order
140
+ @description = description
141
+ @data = data
142
+ end
143
+
144
+ def intern
145
+ @name
146
+ end
147
+
148
+ def to_s
149
+ @name.to_s
150
+ end
151
+
152
+ def to_i
153
+ @order
154
+ end
155
+
156
+ def inspect
157
+ const_name = @name
158
+ add = ''
159
+ if @name[0] != @name[0].upcase
160
+ const_name = "#{@name.upcase}"
161
+ add = " = #{ @name.inspect }"
162
+ end
163
+ add += " @data=#{ @data.inspect }" if @data
164
+ add += " @description=#{ @description.inspect }" if @description
165
+ const_name = const_name.to_s.gsub '-', '_'
166
+ "\#<#{ self.class.name }::#{ const_name }#{ add }>"
167
+ end
168
+
169
+ include Comparable
170
+
171
+ def <=> other
172
+ return nil if !(self.class === other)
173
+ @order <=> other.order
174
+ end
175
+
176
+ def succ
177
+ self.class.values.select { |v| v.order > @order }.first
178
+ end
179
+
180
+ def pred
181
+ self.class.values.select { |v| v.order < @order }.last
182
+ end
183
+
184
+ end
@@ -0,0 +1,252 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'time'
4
+ require 'date'
5
+
6
+ class Period
7
+
8
+ attr_reader :value
9
+
10
+ def initialize value
11
+ @value = value
12
+ end
13
+
14
+ class << self
15
+
16
+ private :new
17
+
18
+ def parse src
19
+ return nil if src == nil
20
+ return src if Period === src
21
+ raise TypeError, "Source (#{ src.inspect }) must be a String!", caller unless String === src
22
+ src = src.strip
23
+ value = 0
24
+ case src
25
+ when /^\d+$/
26
+ value = src.to_i
27
+ when /^(\d+[wW])?(\d+[dD])?(\d+[hH])?(\d+[mM])?(\d+[sS])?$/
28
+ weeks = /(\d+)[wW]/.match(src)
29
+ value += weeks[1].to_i * 7 * 24 * 60 * 60 if weeks
30
+ days = /(\d+)[dD]/.match(src)
31
+ value += days[1].to_i * 24 * 60 * 60 if days
32
+ hours = /(\d+)[hH]/.match(src)
33
+ value += hours[1].to_i * 60 * 60 if hours
34
+ minutes = /(\d+)[mM]/.match(src)
35
+ value += minutes[1].to_i * 60 if minutes
36
+ seconds = /(\d+)[sS]/.match(src)
37
+ value += seconds[1].to_i if seconds
38
+ else
39
+ raise ArgumentError, "Invalid source: #{ src }!", caller
40
+ end
41
+ new(value).freeze
42
+ end
43
+
44
+ def make weeks: 0, days: 0, hours: 0, minutes: 0, seconds: 0
45
+ raise TypeError, "Parameter 'weeks' must be an Integer!", caller unless Integer === weeks
46
+ raise TypeError, "Parameter 'days' must be an Integer!", caller unless Integer === days
47
+ raise TypeError, "Parameter 'hours' must be an Integer!", caller unless Integer === hours
48
+ raise TypeError, "Parameter 'minutes' must be an Integer!", caller unless Integer === minutes
49
+ raise TypeError, "Parameter 'seconds' must be an Integer!", caller unless Integer === seconds
50
+ value = seconds
51
+ value += minutes * 60
52
+ value += hours * 60 * 60
53
+ value += days * 24 * 60 * 60
54
+ value += weeks * 7 * 24 * 60 * 60
55
+ new(value).freeze
56
+ end
57
+
58
+ def diff first, second
59
+ first = first.to_time if Date === first
60
+ second = second.to_time if Date === second
61
+ raise TypeError, "Parameter 'first' must be a Time or Date!", caller unless Time === first
62
+ raise TypeError, "Parameter 'second' must be a Time or Date!", caller unless Time === second
63
+ value = first.to_i - second.to_i
64
+ new(value).freeze
65
+ end
66
+
67
+ end
68
+
69
+ WEEK = make weeks: 1
70
+ DAY = make days: 1
71
+ HOUR = make hours: 1
72
+ MINUTE = make minutes: 1
73
+ SECOND = make seconds: 1
74
+ ZERO = make seconds: 0
75
+
76
+ def weeks
77
+ g = sgn
78
+ w = @value.abs / (7 * 24 * 60 * 60)
79
+ g * w
80
+ end
81
+
82
+ def days all: true
83
+ g = sgn
84
+ d = @value.abs / (24 * 60 * 60)
85
+ all && g * d || g * (d % 7)
86
+ end
87
+
88
+ def hours all: false
89
+ g = sgn
90
+ h = @value.abs / (60 * 60)
91
+ all && g * h || g * (h % 24)
92
+ end
93
+
94
+ def minutes all: false
95
+ g = sgn
96
+ m = @value.abs / 60
97
+ all && g * m || g * (m % 60)
98
+ end
99
+
100
+ def seconds all: false
101
+ g = sgn
102
+ s = @value.abs
103
+ all && g * s || g * (s % 60)
104
+ end
105
+
106
+ def sgn
107
+ if @value == 0
108
+ 0
109
+ elsif @value > 0
110
+ 1
111
+ else
112
+ -1
113
+ end
114
+ end
115
+
116
+ def abs
117
+ self.class.make seconds: @value.abs
118
+ end
119
+
120
+ def to_i
121
+ @value
122
+ end
123
+
124
+ def to_s with_weeks: true
125
+ return '0' if @value == 0
126
+ return "-#{abs.to_s}" if @value < 0
127
+ w = with_weeks && weeks || 0
128
+ d = days all: !with_weeks
129
+ h = hours
130
+ m = minutes
131
+ s = seconds
132
+ result = ''
133
+ result += "#{ w }w" if w != 0
134
+ result += "#{ d }d" if d != 0
135
+ result += "#{ h }h" if h != 0
136
+ result += "#{ m }m" if m != 0
137
+ result += "#{ s }s" if s != 0
138
+ result
139
+ end
140
+
141
+ def to_hs
142
+ return "-#{abs.to_hs}" if @value < 0
143
+ h = hours all: true
144
+ m = minutes
145
+ s = seconds
146
+ format "%d:%02d:%02d", h, m, s
147
+ end
148
+
149
+ def inspect
150
+ "\#<Period: #{to_s}>"
151
+ end
152
+
153
+ include Comparable
154
+
155
+ def <=> other
156
+ return nil if other.nil?
157
+ @value <=> other.value
158
+ end
159
+
160
+ def +@
161
+ self
162
+ end
163
+
164
+ def -@
165
+ self.class.make(seconds: -@value)
166
+ end
167
+
168
+ def + other
169
+ raise TypeError, "Second argument must be a Period!", caller unless Period === other
170
+ self.class.make(seconds: @value + other.value)
171
+ end
172
+
173
+ def - other
174
+ raise TypeError, "Second argument must be a Period!", caller unless Period === other
175
+ self.class.make(seconds: @value - other.value)
176
+ end
177
+
178
+ def * num
179
+ raise TypeError, "Second argument must be a number!", caller unless Numeric === num
180
+ self.class.make(seconds: (@value * num).to_i)
181
+ end
182
+
183
+ def / num
184
+ raise TypeError, "Second argument must be a number!", caller unless Numeric === num
185
+ self.class.make(seconds: (@value / num).to_i)
186
+ end
187
+
188
+ class Num
189
+
190
+ attr_reader :value
191
+
192
+ def initialize num
193
+ @value = num
194
+ end
195
+
196
+ def * period
197
+ period * @value
198
+ end
199
+
200
+ end
201
+
202
+ def coerce num
203
+ case num
204
+ when Integer, Float, Rational
205
+ [ Period::Num::new(num), self ]
206
+ else
207
+ raise TypeError, "Cannot coerce Period to a #{ num.class }!", caller
208
+ end
209
+ end
210
+
211
+ end
212
+
213
+ class Time
214
+
215
+ alias :preperiod_plus :+
216
+ alias :preperiod_minus :-
217
+
218
+ def + arg
219
+ return self + arg.value if Period === arg
220
+ preperiod_plus arg
221
+ end
222
+
223
+ def - arg
224
+ return self + (-arg.value) if Period === arg
225
+ preperiod_minus arg
226
+ end
227
+
228
+ end
229
+
230
+ class Numeric
231
+
232
+ def weeks
233
+ Period::make(weeks: 1) * self
234
+ end
235
+
236
+ def days
237
+ Period::make(days: 1) * self
238
+ end
239
+
240
+ def hours
241
+ Period::make(hours: 1) * self
242
+ end
243
+
244
+ def minutes
245
+ Period::make(minutes: 1) * self
246
+ end
247
+
248
+ def seconds
249
+ Period::make(seconds: 1) * self
250
+ end
251
+
252
+ end
data/lib/extra/uuid.rb ADDED
@@ -0,0 +1,90 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'securerandom'
4
+
5
+ class UUID
6
+
7
+ BASE = 16
8
+ BYTES = 16
9
+ DIGITS = 32
10
+
11
+ attr_reader :value
12
+
13
+ def initialize value
14
+ @value = value
15
+ end
16
+
17
+ class << self
18
+
19
+ private :new
20
+
21
+ def parse src
22
+ return nil if src == nil
23
+ raise TypeError, "Source must be a String!", caller unless String === src
24
+ src = src.strip
25
+ raise ArgumentError, "Invalid UUID source: #{ src.inspect }!", caller unless /^\h{8}\-\h{4}\-\h{4}\-\h{4}\-\h{12}$/ === src || /^\h{32}$/ === src
26
+ value = src.gsub('-', '').to_i(BASE)
27
+ new(value).freeze
28
+ end
29
+
30
+ def generate count = 1, prefix: nil, prefix_length: nil
31
+ if prefix == nil
32
+ if prefix_length != nil
33
+ raise ArgumentError, "Prefix length must be Integer < #{ BYTES }!", caller unless Integer === prefix_length && prefix_length < BYTES
34
+ prefix = SecureRandom.hex prefix_length
35
+ else
36
+ prefix = ''
37
+ prefix_length = 0
38
+ end
39
+ else
40
+ raise TypeError, "Prefix must be a String!", caller unless String === prefix
41
+ prefix = prefix.gsub('-', '')
42
+ prefix_length = prefix.length / 2
43
+ end
44
+ result = count.times.map { parse(prefix + SecureRandom.hex(BYTES - prefix_length)) }
45
+ if count == 1
46
+ result[0]
47
+ else
48
+ result
49
+ end
50
+ end
51
+
52
+ def make *args
53
+ raise ArgumentError, "Method receives 1..5 arguments!", caller unless (1..5) === args.size
54
+ raise ArgumentError, "Arguments must be Integers!", caller unless args.all? { |a| Integer === a }
55
+ last = args.pop
56
+ value = 0
57
+ value += args[0] << 12 * 8 if args.size > 0
58
+ value += args[1] << 10 * 8 if args.size > 1
59
+ value += args[2] << 8 * 8 if args.size > 2
60
+ value += args[3] << 6 * 8 if args.size > 3
61
+ value += last
62
+ new(value).freeze
63
+ end
64
+
65
+ end
66
+
67
+ ZERO = make 0
68
+
69
+ def to_i
70
+ @value
71
+ end
72
+
73
+ def to_s
74
+ raw = @value.to_s(BASE)
75
+ raw = '0' * (DIGITS - raw.length) + raw if raw.length < DIGITS
76
+ "#{ raw[0..7] }-#{ raw[8..11] }-#{ raw[12..15] }-#{ raw[16..19] }-#{ raw[20..31] }"
77
+ end
78
+
79
+ def inspect
80
+ to_s
81
+ end
82
+
83
+ include Comparable
84
+
85
+ def <=> other
86
+ return nil unless UUID === other
87
+ @value <=> other.value
88
+ end
89
+
90
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'config'
4
+ require_relative 'logging'
5
+ require_relative 'preamble'
6
+ require_relative 'globals'
7
+ require_relative 'task'
8
+
9
+ class Application
10
+
11
+ include AppPreamble
12
+
13
+ def logger
14
+ G.logger
15
+ end
16
+
17
+ def initialize
18
+ setup!
19
+ G.config = @config.freeze
20
+ G.logger = DualLogger::new self
21
+ end
22
+
23
+ private def tasks!
24
+ @tasks = Queue::new
25
+ @files.each do |file|
26
+ @tasks.push Task::new(self, file)
27
+ end
28
+ end
29
+
30
+ private def start!
31
+ @threads = []
32
+ count = [ @tasks.size, G.config[:threads][:tasks] ].min
33
+ count.times do
34
+ @threads << Thread.start do
35
+ while !@tasks.empty?
36
+ task = @tasks.pop
37
+ task.execute
38
+ end
39
+ end
40
+ end
41
+ @threads.each { |th| th.join }
42
+ end
43
+
44
+ def run
45
+ preamble!
46
+ tasks!
47
+ start!
48
+ end
49
+
50
+ end