inat-get 0.8.0.11

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