inat-get 0.8.0.11
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/LICENSE +674 -0
- data/README.md +16 -0
- data/Rakefile +4 -0
- data/bin/inat-get +59 -0
- data/docs/logo.png +0 -0
- data/inat-get.gemspec +33 -0
- data/lib/extra/enum.rb +184 -0
- data/lib/extra/period.rb +252 -0
- data/lib/extra/uuid.rb +90 -0
- data/lib/inat/app/application.rb +50 -0
- data/lib/inat/app/config/messagelevel.rb +22 -0
- data/lib/inat/app/config/shiftage.rb +24 -0
- data/lib/inat/app/config/updatemode.rb +20 -0
- data/lib/inat/app/config.rb +296 -0
- data/lib/inat/app/globals.rb +80 -0
- data/lib/inat/app/info.rb +21 -0
- data/lib/inat/app/logging.rb +35 -0
- data/lib/inat/app/preamble.rb +27 -0
- data/lib/inat/app/status.rb +74 -0
- data/lib/inat/app/task/context.rb +47 -0
- data/lib/inat/app/task/dsl.rb +24 -0
- data/lib/inat/app/task.rb +75 -0
- data/lib/inat/data/api.rb +218 -0
- data/lib/inat/data/cache.rb +9 -0
- data/lib/inat/data/db.rb +87 -0
- data/lib/inat/data/ddl.rb +18 -0
- data/lib/inat/data/entity/comment.rb +29 -0
- data/lib/inat/data/entity/flag.rb +22 -0
- data/lib/inat/data/entity/identification.rb +45 -0
- data/lib/inat/data/entity/observation.rb +172 -0
- data/lib/inat/data/entity/observationphoto.rb +25 -0
- data/lib/inat/data/entity/observationsound.rb +26 -0
- data/lib/inat/data/entity/photo.rb +31 -0
- data/lib/inat/data/entity/place.rb +57 -0
- data/lib/inat/data/entity/project.rb +94 -0
- data/lib/inat/data/entity/projectadmin.rb +21 -0
- data/lib/inat/data/entity/projectobservationrule.rb +50 -0
- data/lib/inat/data/entity/request.rb +58 -0
- data/lib/inat/data/entity/sound.rb +27 -0
- data/lib/inat/data/entity/taxon.rb +94 -0
- data/lib/inat/data/entity/user.rb +67 -0
- data/lib/inat/data/entity/vote.rb +22 -0
- data/lib/inat/data/entity.rb +291 -0
- data/lib/inat/data/enums/conservationstatus.rb +30 -0
- data/lib/inat/data/enums/geoprivacy.rb +14 -0
- data/lib/inat/data/enums/iconictaxa.rb +23 -0
- data/lib/inat/data/enums/identificationcategory.rb +13 -0
- data/lib/inat/data/enums/licensecode.rb +16 -0
- data/lib/inat/data/enums/projectadminrole.rb +11 -0
- data/lib/inat/data/enums/projecttype.rb +37 -0
- data/lib/inat/data/enums/qualitygrade.rb +12 -0
- data/lib/inat/data/enums/rank.rb +60 -0
- data/lib/inat/data/model.rb +551 -0
- data/lib/inat/data/query.rb +1145 -0
- data/lib/inat/data/sets/dataset.rb +104 -0
- data/lib/inat/data/sets/list.rb +190 -0
- data/lib/inat/data/sets/listers.rb +15 -0
- data/lib/inat/data/sets/wrappers.rb +137 -0
- data/lib/inat/data/types/extras.rb +88 -0
- data/lib/inat/data/types/location.rb +89 -0
- data/lib/inat/data/types/std.rb +293 -0
- data/lib/inat/report/table.rb +135 -0
- data/lib/inat/utils/deep.rb +30 -0
- metadata +137 -0
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'extra/enum'
|
4
|
+
|
5
|
+
class MessageLevel < Enum
|
6
|
+
|
7
|
+
item :TRACE, -1
|
8
|
+
item :DEBUG, data: 0
|
9
|
+
item :INFO, data: 1
|
10
|
+
item :WARNING, data: 2
|
11
|
+
item :ERROR, data: 3
|
12
|
+
item :FATAL, data: 4
|
13
|
+
item :UNKNOWN, data: 5
|
14
|
+
|
15
|
+
item_alias :WARN => :WARNING
|
16
|
+
|
17
|
+
def severity
|
18
|
+
self.data
|
19
|
+
end
|
20
|
+
|
21
|
+
freeze
|
22
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ShiftAge
|
4
|
+
|
5
|
+
class << self
|
6
|
+
|
7
|
+
def parse source
|
8
|
+
source = source.to_i if /\d+/ === source
|
9
|
+
source = source.to_s.downcase.intern if Symbol === source || String === source
|
10
|
+
case source
|
11
|
+
when nil
|
12
|
+
0
|
13
|
+
when Integer
|
14
|
+
source
|
15
|
+
when :daily, :weekly, :monthly
|
16
|
+
source
|
17
|
+
else
|
18
|
+
raise TypeError, "Invalid shift age value: #{source}!"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'extra/enum'
|
4
|
+
|
5
|
+
class UpdateMode < Enum
|
6
|
+
|
7
|
+
items :UPDATE,
|
8
|
+
:FORCE,
|
9
|
+
:RELOAD,
|
10
|
+
:MINIMAL,
|
11
|
+
:OFFLINE
|
12
|
+
|
13
|
+
item_alias :DEFAULT => :UPDATE,
|
14
|
+
:FORCE_UPDATE => :FORCE,
|
15
|
+
:FORCE_RELOAD => :RELOAD,
|
16
|
+
:SKIP_EXISTING => :MINIMAL,
|
17
|
+
:NO_UPDATE => :OFFLINE
|
18
|
+
|
19
|
+
freeze
|
20
|
+
end
|
@@ -0,0 +1,296 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'yaml'
|
4
|
+
require 'optparse'
|
5
|
+
|
6
|
+
require_relative '../utils/deep'
|
7
|
+
require_relative '../../extra/period'
|
8
|
+
require_relative 'info'
|
9
|
+
require_relative 'config/messagelevel'
|
10
|
+
require_relative 'config/updatemode'
|
11
|
+
require_relative 'config/shiftage'
|
12
|
+
|
13
|
+
class Application
|
14
|
+
|
15
|
+
include AppInfo
|
16
|
+
|
17
|
+
API_DEFAULT = 'https://api.inaturalist.org/v1/'
|
18
|
+
DEFAULT_LOG = "./#{ NAME }.log"
|
19
|
+
|
20
|
+
DEFAULTS = {
|
21
|
+
threads: {
|
22
|
+
enable: true,
|
23
|
+
tasks: 3,
|
24
|
+
},
|
25
|
+
data: {
|
26
|
+
cache: true,
|
27
|
+
directory: File.expand_path("~/.cache/#{ NAME }/"),
|
28
|
+
update: UpdateMode::UPDATE,
|
29
|
+
update_period: Period::DAY,
|
30
|
+
outdate_period: Period::WEEK,
|
31
|
+
},
|
32
|
+
verbose: MessageLevel::ERROR,
|
33
|
+
log: {
|
34
|
+
enable: false,
|
35
|
+
file: DEFAULT_LOG,
|
36
|
+
level: MessageLevel::WARNING,
|
37
|
+
shift: {
|
38
|
+
age: nil,
|
39
|
+
size: nil,
|
40
|
+
},
|
41
|
+
},
|
42
|
+
api: {
|
43
|
+
root: API_DEFAULT,
|
44
|
+
locale: nil,
|
45
|
+
preferred_place_id: nil,
|
46
|
+
open_timeout: nil,
|
47
|
+
read_timeout: nil,
|
48
|
+
},
|
49
|
+
preamble: [],
|
50
|
+
}
|
51
|
+
|
52
|
+
CONFIG_FILE = File.expand_path "~/.config/#{ NAME }.yml"
|
53
|
+
|
54
|
+
private def parse_files files
|
55
|
+
files.map do |file|
|
56
|
+
if file.start_with?('@')
|
57
|
+
list = file[1..]
|
58
|
+
File.readlines(list, chomp: true).filter { |l| !l.empty? }
|
59
|
+
else
|
60
|
+
file
|
61
|
+
end
|
62
|
+
end.flatten
|
63
|
+
end
|
64
|
+
|
65
|
+
private def parse_args!
|
66
|
+
options = {}
|
67
|
+
opts = OptionParser::new USAGE do |o|
|
68
|
+
|
69
|
+
o.accept UpdateMode do |mode|
|
70
|
+
UpdateMode::parse mode, case_sensitive: false
|
71
|
+
end
|
72
|
+
|
73
|
+
o.accept MessageLevel do |level|
|
74
|
+
MessageLevel::parse level, case_sensitive: false
|
75
|
+
end
|
76
|
+
|
77
|
+
o.accept ShiftAge do |shift_age|
|
78
|
+
ShiftAge::parse shift_age
|
79
|
+
end
|
80
|
+
|
81
|
+
o.accept Period do |period|
|
82
|
+
Period::parse period
|
83
|
+
end
|
84
|
+
|
85
|
+
o.separator ''
|
86
|
+
o.separator "\e[1mInformation:\e[0m"
|
87
|
+
|
88
|
+
o.on '-h', '--help', 'Show help and exit.' do
|
89
|
+
puts ABOUT
|
90
|
+
puts
|
91
|
+
puts opts.help
|
92
|
+
exit 0
|
93
|
+
end
|
94
|
+
|
95
|
+
o.on '-?', '--usage', 'Show short help and exit.' do
|
96
|
+
puts opts.help
|
97
|
+
exit 0
|
98
|
+
end
|
99
|
+
|
100
|
+
o.on '--about', 'Show program info and exit.' do
|
101
|
+
puts ABOUT
|
102
|
+
exit 0
|
103
|
+
end
|
104
|
+
|
105
|
+
o.on '--version', 'Show version and exit.' do
|
106
|
+
puts VERSION
|
107
|
+
exit 0
|
108
|
+
end
|
109
|
+
|
110
|
+
o.separator ''
|
111
|
+
o.separator "\e[1mConfiguration:\e[0m"
|
112
|
+
|
113
|
+
o.on '-c', '--config FILE', String, "Configuration file instead standard (~/.config/#{ NAME }.yml)." do |value|
|
114
|
+
options[:config_file] = value
|
115
|
+
end
|
116
|
+
|
117
|
+
o.separator ''
|
118
|
+
o.separator "\e[1mFlow Control:\e[0m"
|
119
|
+
|
120
|
+
o.on '-1', '--no-threads', 'Disable threads.' do
|
121
|
+
options[:threads] ||= {}
|
122
|
+
options[:threads][:enable] = false
|
123
|
+
end
|
124
|
+
|
125
|
+
o.on '-t', '--tasks NUMBER', Integer, 'Limit number of concurrent tasks.' do |value|
|
126
|
+
options[:threads] ||= {}
|
127
|
+
options[:threads][:tasks] = value
|
128
|
+
end
|
129
|
+
|
130
|
+
o.separator ''
|
131
|
+
o.separator "\e[1mCache Control:\e[0m"
|
132
|
+
|
133
|
+
o.on '-u', '--update MODE', UpdateMode, "Update mode:", " 'default' (or 'update')",
|
134
|
+
" 'force-update' (or 'force')",
|
135
|
+
" 'force-reload' (or 'reload')",
|
136
|
+
" 'skip-existing' (or 'minimal')",
|
137
|
+
" 'no-update' (or 'offline')" do |value|
|
138
|
+
options[:data] ||= {}
|
139
|
+
options[:data][:update] = value
|
140
|
+
end
|
141
|
+
|
142
|
+
o.on '-f', '--force-update', 'Force update.' do
|
143
|
+
options[:data] ||= {}
|
144
|
+
options[:data][:update] = UpdateMode::FORCE
|
145
|
+
end
|
146
|
+
|
147
|
+
o.on '-F', '--force-reload', '--reload', 'Force reload.' do
|
148
|
+
options[:data] ||= {}
|
149
|
+
options[:data][:update] = UpdateMode::RELOAD
|
150
|
+
end
|
151
|
+
|
152
|
+
o.on '--skip-existing', 'Skip updating if exist.' do
|
153
|
+
options[:data] ||= {}
|
154
|
+
options[:data][:update] = UpdateMode::MINIMAL
|
155
|
+
end
|
156
|
+
|
157
|
+
o.on '-n', '--offline', '--no-update', 'No updating.' do
|
158
|
+
options[:data] ||= {}
|
159
|
+
options[:data][:update] = UpdateMode::OFFLINE
|
160
|
+
end
|
161
|
+
|
162
|
+
o.separator ''
|
163
|
+
|
164
|
+
o.on '--update-period PERIOD', Period, 'Update period.' do |value|
|
165
|
+
options[:data] ||= {}
|
166
|
+
options[:data][:update_period] = value
|
167
|
+
end
|
168
|
+
|
169
|
+
o.on '--obsolete-period PERIOD', Period, 'Obsolete period.' do |value|
|
170
|
+
options[:data] ||= {}
|
171
|
+
options[:data][:obsolete_period] = value
|
172
|
+
end
|
173
|
+
|
174
|
+
o.on '--cache-directory PATH', String, 'Cache directory path.' do |value|
|
175
|
+
options[:data] ||= {}
|
176
|
+
options[:data][:directory] = value
|
177
|
+
end
|
178
|
+
|
179
|
+
o.separator ''
|
180
|
+
|
181
|
+
o.on '--clean-requests', 'Clean outdated requests in data cache.' do
|
182
|
+
options[:preamble] << :clean_requests
|
183
|
+
end
|
184
|
+
|
185
|
+
o.on '--clean-observations', 'Clean observations without request in data cache.' do
|
186
|
+
options[:preamble] << :clean_observations
|
187
|
+
end
|
188
|
+
|
189
|
+
o.on '--clean-orphans', 'Clean orphan objects in data cache.' do
|
190
|
+
options[:preamble] << :clean_orphans
|
191
|
+
end
|
192
|
+
|
193
|
+
o.on '--clean-all', 'Clean data cache.' do
|
194
|
+
options[:preamble] << :clean_all
|
195
|
+
end
|
196
|
+
|
197
|
+
o.separator ''
|
198
|
+
o.separator "\e[1mAPI Parameters:\e[0m"
|
199
|
+
|
200
|
+
o.on '-A', '--api-root URI', String, 'API root' do |value|
|
201
|
+
options[:api] ||= {}
|
202
|
+
options[:api][:root] = value
|
203
|
+
end
|
204
|
+
|
205
|
+
o.on '-L', '--locale LOCALE', String, 'Set the locale.' do |value|
|
206
|
+
value = nil if value.downcase == 'none'
|
207
|
+
options[:api] ||= {}
|
208
|
+
options[:api][:locale] = value
|
209
|
+
end
|
210
|
+
|
211
|
+
o.on '-P', '--preferred-place ID', Integer, 'Preferred place for API.' do |value|
|
212
|
+
value = nil if value == 0
|
213
|
+
options[:api] ||= {}
|
214
|
+
options[:api][:preferred_place_id] = value
|
215
|
+
end
|
216
|
+
|
217
|
+
o.separator ''
|
218
|
+
o.separator "\e[1mLogging Control:\e[0m"
|
219
|
+
|
220
|
+
o.on '--verbose-level LEVEL', MessageLevel, 'Set the logging level for $STDERR.', " 'fatal'",
|
221
|
+
" 'error'",
|
222
|
+
" 'warn' (or 'warning')",
|
223
|
+
" 'info'",
|
224
|
+
" 'debug'" do |value|
|
225
|
+
options[:verbose] = value
|
226
|
+
end
|
227
|
+
|
228
|
+
o.on '-v', '--verbose', 'Set verbose level to INFO.' do
|
229
|
+
options[:verbose] = MessageLevel::INFO
|
230
|
+
end
|
231
|
+
|
232
|
+
o.on '--log-level LEVEL', MessageLevel, 'Set the logging level for file log' do |value|
|
233
|
+
options[:log] ||= {}
|
234
|
+
options[:log][:level] = value
|
235
|
+
end
|
236
|
+
|
237
|
+
o.on '--log-file FILE', String, 'File name for log.' do |value|
|
238
|
+
value = DEFAULT_LOG if /^default$/i === value
|
239
|
+
options[:log] ||= {}
|
240
|
+
options[:log][:file] = value
|
241
|
+
end
|
242
|
+
|
243
|
+
o.on '-l', '--log [FILE]', String, "Enable logging to file. If file is not specified './#{ NAME }.log' used.",
|
244
|
+
" 'none', 'no' or 'false' to disabling." do |value|
|
245
|
+
options[:log] ||= {}
|
246
|
+
case value
|
247
|
+
when nil, /^(true|yes|default)$/i
|
248
|
+
options[:log][:enable] = true
|
249
|
+
options[:log][:file] = DEFAULT_LOG
|
250
|
+
when /^(false|no|none)$/i
|
251
|
+
options[:log][:enable] = false
|
252
|
+
options[:log][:file] = DEFAULT_LOG
|
253
|
+
else
|
254
|
+
options[:log][:enable] = true
|
255
|
+
options[:log][:file] = value.to_s
|
256
|
+
end
|
257
|
+
end
|
258
|
+
|
259
|
+
o.on '--log-shift-age AGE', ShiftAge, 'Log file shift age.' do |value|
|
260
|
+
value = nil if value == 0
|
261
|
+
options[:log] ||= {}
|
262
|
+
options[:log][:shift] ||= {}
|
263
|
+
options[:log][:shift][:age] = value
|
264
|
+
end
|
265
|
+
|
266
|
+
o.on '--log-shift-size SIZE', Integer, 'Log file shift size.' do |value|
|
267
|
+
value = nil if value == 0
|
268
|
+
options[:log] ||= {}
|
269
|
+
options[:log][:shift] ||= {}
|
270
|
+
options[:log][:shift][:size] = value
|
271
|
+
end
|
272
|
+
|
273
|
+
o.separator ''
|
274
|
+
|
275
|
+
o.separator "\e[1mFile Arguments:\e[0m"
|
276
|
+
o.separator "\t‹task[, ...]›\t\t One or more names of task files.\n" +
|
277
|
+
"\t\t\t\t If name has not extension try to read '‹task›' than '‹task›.inat' than '‹task›.rb'."
|
278
|
+
|
279
|
+
end
|
280
|
+
files = parse_files(opts.parse(ARGV))
|
281
|
+
[ options, files ]
|
282
|
+
end
|
283
|
+
|
284
|
+
private def setup!
|
285
|
+
@config = DEFAULTS
|
286
|
+
options, @files = parse_args!
|
287
|
+
config_file = options[:config_file] || CONFIG_FILE
|
288
|
+
@config.deep_merge! YAML.load_file(config_file, symbolize_names: true, freeze: true) if File.exist?(config_file)
|
289
|
+
@config.deep_merge! options
|
290
|
+
@config[:verbose] = MessageLevel::parse @config[:verbose]
|
291
|
+
@config[:log][:level] = MessageLevel::parse @config[:log][:level]
|
292
|
+
end
|
293
|
+
|
294
|
+
attr_reader :config
|
295
|
+
|
296
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Globals
|
4
|
+
|
5
|
+
class << self
|
6
|
+
|
7
|
+
def logger
|
8
|
+
@@logger
|
9
|
+
end
|
10
|
+
|
11
|
+
def logger= value
|
12
|
+
@@logger = value
|
13
|
+
end
|
14
|
+
|
15
|
+
def current_task
|
16
|
+
Thread.current.thread_variable_get 'current_task'
|
17
|
+
end
|
18
|
+
|
19
|
+
def current_task= value
|
20
|
+
Thread.current.thread_variable_set 'current_task', value
|
21
|
+
end
|
22
|
+
|
23
|
+
def config
|
24
|
+
@@config
|
25
|
+
end
|
26
|
+
|
27
|
+
def config= value
|
28
|
+
@@config = value
|
29
|
+
end
|
30
|
+
|
31
|
+
def status
|
32
|
+
@@status
|
33
|
+
end
|
34
|
+
|
35
|
+
def status= value
|
36
|
+
@@status = value
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
|
43
|
+
G = Globals
|
44
|
+
|
45
|
+
module LogDSL
|
46
|
+
|
47
|
+
def included mod
|
48
|
+
mod.extend LogDSL
|
49
|
+
end
|
50
|
+
|
51
|
+
def log message, level: MessageLevel::INFO
|
52
|
+
G.status.wrap do
|
53
|
+
G.logger.log(G.current_task&.name || '‹main›', level, message)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def error message, exception: nil
|
58
|
+
log message, level: MessageLevel::ERROR
|
59
|
+
raise exception, message if Class === exception && Exception >= exception
|
60
|
+
end
|
61
|
+
|
62
|
+
def warning message
|
63
|
+
log message, level: MessageLevel::WARNING
|
64
|
+
end
|
65
|
+
|
66
|
+
def info message
|
67
|
+
log message, level: MessageLevel::INFO
|
68
|
+
end
|
69
|
+
|
70
|
+
def debug message
|
71
|
+
log message, level: MessageLevel::DEBUG
|
72
|
+
end
|
73
|
+
|
74
|
+
def echo message, level: MessageLevel::UNKNOWN
|
75
|
+
log message, level: level
|
76
|
+
end
|
77
|
+
|
78
|
+
module_function :log, :error, :warning, :info, :debug, :echo
|
79
|
+
|
80
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module AppInfo
|
4
|
+
|
5
|
+
AUTHOR = 'Ivan Shikhalev'
|
6
|
+
EMAIL = 'shkikhalev@gmail.com'
|
7
|
+
|
8
|
+
VERSION = '0.8.0.11'
|
9
|
+
HOMEPAGE = 'https://github.com/shikhalev/inat-get'
|
10
|
+
SOURCE_URL = 'https://github.com/shikhalev/inat-get'
|
11
|
+
|
12
|
+
EXE = File.basename $0
|
13
|
+
NAME = File.basename $0, '.rb'
|
14
|
+
|
15
|
+
USAGE = "\e[1mUsage $\e[0m #{EXE} [options] ‹task[, ...]›"
|
16
|
+
ABOUT = "\e[1mINat::Get v#{VERSION}\e[0m — A toolset for fetching and processing data from \e[4miNaturalist.org\e[0m.\n" +
|
17
|
+
"\n" +
|
18
|
+
"\e[1mSource:\e[0m \e[4m#{HOMEPAGE}\e[0m\n" +
|
19
|
+
"\e[1mAuthor:\e[0m #{AUTHOR} ‹\e[4m#{EMAIL}\e[0m›"
|
20
|
+
|
21
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'logger'
|
4
|
+
|
5
|
+
class DualLogger
|
6
|
+
|
7
|
+
def config
|
8
|
+
@application.config
|
9
|
+
end
|
10
|
+
|
11
|
+
def initialize application
|
12
|
+
@application = application
|
13
|
+
v_para = {
|
14
|
+
level: config[:verbose].severity
|
15
|
+
}
|
16
|
+
@verb = Logger::new STDERR, **v_para
|
17
|
+
if config[:log][:enable]
|
18
|
+
f_para = {
|
19
|
+
level: config[:log][:level].severity,
|
20
|
+
}
|
21
|
+
f_para[:shift_age] = config[:log][:shift][:age] if config[:log][:shift][:age]
|
22
|
+
f_para[:shift_level] = config[:log][:shift][:level] if config[:log][:shift][:level]
|
23
|
+
@file = Logger::new config[:log][:file], **f_para
|
24
|
+
else
|
25
|
+
@file = nil
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def log task, level, message
|
30
|
+
task = task.name if task.respond_to?(:name)
|
31
|
+
@verb.log(level.severity, message, task)
|
32
|
+
@file.log(level.severity, message, task) if @file
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module AppPreamble
|
4
|
+
|
5
|
+
private def do_clean_requests
|
6
|
+
# NEED: implement
|
7
|
+
end
|
8
|
+
|
9
|
+
private def do_clean_observations
|
10
|
+
# NEED: implement
|
11
|
+
end
|
12
|
+
|
13
|
+
private def do_clean_orphans
|
14
|
+
# NEED: implement
|
15
|
+
end
|
16
|
+
|
17
|
+
private def do_clean_all
|
18
|
+
# NEED: implement
|
19
|
+
end
|
20
|
+
|
21
|
+
private def preamble!
|
22
|
+
config[:preamble].each do |name|
|
23
|
+
self.send "do_#{name}"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'globals'
|
4
|
+
|
5
|
+
# TODO: переделать на отдельный поток и очередь статусов. Ввести отдельно ключ, как идентификатор запроса, например.
|
6
|
+
|
7
|
+
module Status
|
8
|
+
|
9
|
+
class << self
|
10
|
+
|
11
|
+
private def ellipsis src, len
|
12
|
+
return '' unless String === src
|
13
|
+
if src.length <= len
|
14
|
+
src
|
15
|
+
else
|
16
|
+
src[.. len - 4] + '...'
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
# private def get_num
|
21
|
+
# @num ||= 0
|
22
|
+
# @num += 1
|
23
|
+
# @num
|
24
|
+
# end
|
25
|
+
|
26
|
+
def init
|
27
|
+
@lines = {}
|
28
|
+
@mutex = Mutex::new
|
29
|
+
@shift = 0
|
30
|
+
end
|
31
|
+
|
32
|
+
def up
|
33
|
+
$stderr.print "\e[#{ @shift }A\r" if @shift > 0
|
34
|
+
end
|
35
|
+
|
36
|
+
def out
|
37
|
+
$stderr.print "\e[0K\n"
|
38
|
+
@lines.each do |key, value|
|
39
|
+
$stderr.print "#{ value }\e[0K\n"
|
40
|
+
end
|
41
|
+
$stderr.print "\e[0K\n"
|
42
|
+
@shift = @lines.size + 2
|
43
|
+
end
|
44
|
+
|
45
|
+
def wrap
|
46
|
+
@mutex.synchronize do
|
47
|
+
up
|
48
|
+
yield
|
49
|
+
out
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def status key, value
|
54
|
+
task = ellipsis G.current_task&.name, 16
|
55
|
+
key = task if key == nil
|
56
|
+
line = format("%16s | %s", key.to_s, value)
|
57
|
+
@mutex.synchronize do
|
58
|
+
@lines ||= {}
|
59
|
+
@lines[key] = line
|
60
|
+
up
|
61
|
+
out
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
66
|
+
|
67
|
+
private def status str
|
68
|
+
self.class.status str
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
72
|
+
|
73
|
+
Status::init
|
74
|
+
G.status = Status
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'dsl'
|
4
|
+
require_relative '../../report/table'
|
5
|
+
|
6
|
+
class Task::Context
|
7
|
+
|
8
|
+
include Task::DSL
|
9
|
+
include TableDSL
|
10
|
+
|
11
|
+
attr_accessor :name
|
12
|
+
|
13
|
+
def config
|
14
|
+
@task.config
|
15
|
+
end
|
16
|
+
|
17
|
+
def logger
|
18
|
+
@task.logger
|
19
|
+
end
|
20
|
+
|
21
|
+
def done?
|
22
|
+
@done
|
23
|
+
end
|
24
|
+
|
25
|
+
def initialize task, name, path
|
26
|
+
@task = task
|
27
|
+
@name = name
|
28
|
+
@done = false
|
29
|
+
source_code = File.readlines(path).map { |line| " #{line}" }.join('')
|
30
|
+
full_code = "define_singleton_method :execute do\n" +
|
31
|
+
" begin\n" +
|
32
|
+
" #{source_code}\n" +
|
33
|
+
" Status::status nil, 'DONE'\n" +
|
34
|
+
" rescue Exception => e\n" +
|
35
|
+
" error \"\#{e.inspect}\"\n" +
|
36
|
+
" error \"\#{e.backtrace.join(\"\\n\\t\")}\"\n" +
|
37
|
+
" Status::status nil, 'ERROR : ' + e.inspect\n" +
|
38
|
+
" rescue\n" +
|
39
|
+
" error 'Unknown error!'\n" +
|
40
|
+
" Status::status nil, 'ERROR'\n" +
|
41
|
+
" end\n" +
|
42
|
+
" @done = true\n" +
|
43
|
+
"end"
|
44
|
+
self.instance_eval full_code
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../globals'
|
4
|
+
require_relative '../config/messagelevel'
|
5
|
+
|
6
|
+
require_relative '../../data/entity/observation'
|
7
|
+
require_relative '../../data/query'
|
8
|
+
require_relative '../../data/sets/dataset'
|
9
|
+
require_relative '../../data/sets/list'
|
10
|
+
|
11
|
+
class Task; end
|
12
|
+
|
13
|
+
module Task::DSL
|
14
|
+
|
15
|
+
include LogDSL
|
16
|
+
|
17
|
+
def select **params
|
18
|
+
query = Query::new(**params)
|
19
|
+
DataSet::new nil, query.observations
|
20
|
+
end
|
21
|
+
|
22
|
+
module_function :select
|
23
|
+
|
24
|
+
end
|