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
@@ -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