scout-gear 10.7.4 → 10.7.6

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 (57) hide show
  1. checksums.yaml +4 -4
  2. data/.gitmodules +0 -4
  3. data/.vimproject +9 -14
  4. data/Rakefile +1 -4
  5. data/VERSION +1 -1
  6. data/lib/scout/association/index.rb +3 -3
  7. data/lib/scout/entity/identifiers.rb +5 -2
  8. data/lib/scout/entity/property.rb +1 -1
  9. data/lib/scout/knowledge_base/description.rb +108 -0
  10. data/lib/scout/knowledge_base/entity.rb +6 -1
  11. data/lib/scout/knowledge_base/registry.rb +43 -15
  12. data/lib/scout/knowledge_base.rb +3 -2
  13. data/lib/scout/tsv/change_id/translate.rb +9 -2
  14. data/lib/scout/tsv/open.rb +10 -0
  15. data/lib/scout/tsv/parser.rb +14 -3
  16. data/lib/scout/workflow/deployment/orchestrator.rb +9 -1
  17. data/lib/scout/workflow/deployment/queue.rb +26 -0
  18. data/lib/scout/workflow/entity.rb +99 -0
  19. data/lib/scout/workflow/export.rb +72 -0
  20. data/lib/scout/workflow/persist.rb +6 -0
  21. data/lib/scout/workflow/step/file.rb +3 -3
  22. data/lib/scout/workflow/step/info.rb +6 -0
  23. data/lib/scout/workflow/step/inputs.rb +11 -1
  24. data/lib/scout/workflow/step/provenance.rb +1 -2
  25. data/lib/scout/workflow/step/status.rb +1 -0
  26. data/lib/scout/workflow/step.rb +5 -3
  27. data/lib/scout/workflow/task/inputs.rb +2 -1
  28. data/lib/scout/workflow/task.rb +2 -1
  29. data/lib/scout/workflow.rb +11 -3
  30. data/lib/scout-gear.rb +5 -1
  31. data/scout-gear.gemspec +14 -18
  32. data/scout_commands/kb/config +3 -0
  33. data/scout_commands/kb/list +1 -0
  34. data/scout_commands/kb/query +2 -1
  35. data/scout_commands/kb/register +3 -1
  36. data/scout_commands/kb/show +4 -2
  37. data/scout_commands/workflow/cmd +116 -0
  38. data/scout_commands/workflow/process +82 -0
  39. data/scout_commands/workflow/task +15 -3
  40. data/test/data/person/README.md +17 -0
  41. data/test/scout/knowledge_base/test_description.rb +59 -0
  42. data/test/scout/workflow/task/test_dependencies.rb +7 -7
  43. data/test/scout/workflow/test_definition.rb +2 -2
  44. data/test/scout/workflow/test_entity.rb +58 -0
  45. data/test/scout/workflow/test_step.rb +1 -1
  46. metadata +14 -57
  47. data/lib/scout/offsite/exceptions.rb +0 -9
  48. data/lib/scout/offsite/ssh.rb +0 -175
  49. data/lib/scout/offsite/step.rb +0 -100
  50. data/lib/scout/offsite/sync.rb +0 -55
  51. data/lib/scout/offsite.rb +0 -3
  52. data/scout_commands/offsite +0 -30
  53. data/test/scout/offsite/test_ssh.rb +0 -15
  54. data/test/scout/offsite/test_step.rb +0 -32
  55. data/test/scout/offsite/test_sync.rb +0 -36
  56. data/test/scout/offsite/test_task.rb +0 -0
  57. data/test/scout/test_offsite.rb +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 92262edf62404f5935bf749d3ab71d2ce39ffc7292a1b3d860fd5dc712501fcb
4
- data.tar.gz: 47f69a80c0a135d3a9b3b42558e7b91e766b7781ff2360cd8344ef9c0f7ac8b9
3
+ metadata.gz: f346d283c182fbb5a090329100ad4bd32a50e7e84c058c156014ca1fd2e64772
4
+ data.tar.gz: d4457afe22a81e426232ee94ddc798921fc69dfcf35a628c8ecafa39fb3a271b
5
5
  SHA512:
6
- metadata.gz: 3d532c6a4ac2bd3d0b5584761531b0364f381c317a9864b85b22c7a23bd61dbf2b31d1655f5fc7085c893c843f1b3f36a1b23fffae4c4b9dc2253352f3f5febf
7
- data.tar.gz: 50e36c694239f0d74528b9156435b8769d16ae172dd87029ee741c0002b8ef3606f5088fc2c8f881d413b78f1f00f66134f06fc14129da3015648546130809c6
6
+ metadata.gz: 27ac7e8599c5caabad5d63282c2f316404bce25d82dc0cb302acbb249c1ddb76eff171044b0f9c766e05ee582f399f4ba27076c6514bb3abab16857f11974846
7
+ data.tar.gz: 475df8c7b163a30dd09d608119633443c8bfc9eec1f6bcc657e3f2116bf23a678e18f3224f8da399c6923ac222f5cc1b953dd4035f44514bff90d1ee2b1ffcb7
data/.gitmodules CHANGED
@@ -1,4 +0,0 @@
1
- [submodule "modules/rbbt-util"]
2
- path = modules/rbbt-util
3
- url = https://github.com/mikisvaz/rbbt-util.git
4
- branch = scout
data/.vimproject CHANGED
@@ -8,7 +8,9 @@ scout-gear=/$PWD filter="*.rb *.yaml" {
8
8
  workflow.rb
9
9
  workflow=workflow{
10
10
  exceptions.rb
11
+ export.rb
11
12
  definition.rb
13
+ persist.rb
12
14
  documentation.rb
13
15
  usage.rb
14
16
  util.rb
@@ -35,9 +37,11 @@ scout-gear=/$PWD filter="*.rb *.yaml" {
35
37
  }
36
38
  deployment.rb
37
39
  deployment=deployment{
40
+ queue.rb
38
41
  trace.rb
39
42
  orchestrator.rb
40
43
  }
44
+ entity.rb
41
45
  }
42
46
  work_queue.rb
43
47
  work_queue=work_queue{
@@ -121,12 +125,7 @@ scout-gear=/$PWD filter="*.rb *.yaml" {
121
125
  traverse.rb
122
126
  enrichment.rb
123
127
  list.rb
124
- }
125
- offsite.rb
126
- offsite=offsite{
127
- ssh.rb
128
- sync.rb
129
- step.rb
128
+ description.rb
130
129
  }
131
130
  semaphore.rb
132
131
  }
@@ -144,7 +143,6 @@ scout-gear=/$PWD filter="*.rb *.yaml" {
144
143
  doc
145
144
  update
146
145
  template
147
- offsite
148
146
  kb=kb{
149
147
  config
150
148
  entities
@@ -162,6 +160,9 @@ scout-gear=/$PWD filter="*.rb *.yaml" {
162
160
  install
163
161
  trace
164
162
  prov
163
+ queue
164
+ process
165
+ cmd
165
166
  }
166
167
  batch=batch{
167
168
  list
@@ -177,6 +178,7 @@ scout-gear=/$PWD filter="*.rb *.yaml" {
177
178
  test_scout.rb
178
179
  data=data filter="*"{
179
180
  person=person{
181
+ README.md
180
182
  brothers
181
183
  identifiers
182
184
  marriages
@@ -186,7 +188,6 @@ scout-gear=/$PWD filter="*.rb *.yaml" {
186
188
  scout=scout{
187
189
  test_association.rb
188
190
  test_entity.rb
189
- test_offsite.rb
190
191
  test_semaphore.rb
191
192
  test_tsv.rb
192
193
  test_workflow.rb
@@ -248,12 +249,6 @@ scout-gear=/$PWD filter="*.rb *.yaml" {
248
249
  test_tokyocabinet.rb
249
250
  }
250
251
  }
251
- offsite=offsite{
252
- test_ssh.rb
253
- test_step.rb
254
- test_sync.rb
255
- test_task.rb
256
- }
257
252
  }
258
253
  }
259
254
  share=share{
data/Rakefile CHANGED
@@ -12,7 +12,7 @@ Juwelier::Tasks.new do |gem|
12
12
  gem.homepage = "http://github.com/mikisvaz/scout-gear"
13
13
  gem.license = "MIT"
14
14
  gem.summary = %Q{basic gear for scouts}
15
- gem.description = %Q{Temporary files, logs, path, resources, persistence, workflows, TSV, etc.}
15
+ gem.description = %Q{Scout gear: workflow, TSVs, persistence, entities, associations, and knowledge_bases.}
16
16
  gem.email = "mikisvaz@gmail.com"
17
17
  gem.authors = ["Miguel Vazquez"]
18
18
 
@@ -26,10 +26,7 @@ Juwelier::Tasks.new do |gem|
26
26
  gem.add_runtime_dependency 'RubyInline'
27
27
  #gem.add_runtime_dependency 'tokyocabinet'
28
28
 
29
- gem.add_development_dependency "rdoc", "~> 3.12"
30
- gem.add_development_dependency "bundler", "~> 1.0"
31
29
  gem.add_development_dependency "juwelier", "~> 2.1.0"
32
- gem.add_development_dependency "simplecov", ">= 0"
33
30
  end
34
31
  Juwelier::RubygemsDotOrgTasks.new
35
32
 
data/VERSION CHANGED
@@ -1 +1 @@
1
- 10.7.4
1
+ 10.7.6
@@ -1,7 +1,7 @@
1
1
  require 'scout/annotation'
2
2
  module Association
3
3
 
4
- def self.index(file, source: nil, target: nil, source_format: nil, target_format: nil, format: nil, **kwargs)
4
+ def self.index(file, source: nil, target: nil, source_format: nil, target_format: nil, format: nil, database: nil, **kwargs)
5
5
  IndiferentHash.setup(kwargs)
6
6
  source = kwargs.delete :source if kwargs.include?(:source)
7
7
  target = kwargs.delete :target if kwargs.include?(:target)
@@ -14,7 +14,7 @@ module Association
14
14
  index = Persist.tsv(file, kwargs, engine: "BDB", persist_options: index_persist_options) do |data|
15
15
  recycle, undirected = IndiferentHash.process_options kwargs, :recycle, :undirected
16
16
 
17
- database = Association.open(file, source: source, target: target, source_format: source_format, target_format: target_format, **kwargs.merge(persist_prefix: "Association::Database"))
17
+ database ||= Association.open(file, source: source, target: target, source_format: source_format, target_format: target_format, **kwargs.merge(persist_prefix: "Association::Database"))
18
18
 
19
19
  source_field = database.key_field
20
20
  target_field, *fields = database.fields
@@ -141,7 +141,7 @@ module Association
141
141
  new.extend Index
142
142
  new
143
143
  else
144
- FileUtils.mkdir_p File.dirname(reverse_filename) unless File.exist?(File.dirname(reverse_filename))
144
+ Open.mkdir File.dirname(reverse_filename) unless Open.exist?(File.dirname(reverse_filename))
145
145
 
146
146
  new = Persist.open_tokyocabinet(reverse_filename, true, serializer, TokyoCabinet::BDB)
147
147
 
@@ -64,13 +64,16 @@ module Entity
64
64
  Persist.memory("Entity index #{identity_type}: #{format} (from #{source || "All"})", :persist => true, :format => format, :source => source) do
65
65
  source ||= self.respond_to?(:format)? self.format : nil
66
66
 
67
+ saved_exception = nil
67
68
  begin
68
69
  index = TSV.translation_index(identifier_files, source, format, :persist => true)
69
70
  raise "No index from #{ Misc.fingerprint source } to #{ Misc.fingerprint format }: #{Misc.fingerprint identifier_files}" if index.nil?
70
71
  index.unnamed = true
71
72
  index
72
73
  rescue
73
- raise $! if source.nil?
74
+ raise saved_exception || $! if source.nil?
75
+ Log.debug "Retrying identifier index without specifying source"
76
+ saved_exception = $!
74
77
  source = nil
75
78
  retry
76
79
  end
@@ -93,7 +96,7 @@ module Entity
93
96
 
94
97
  name = default if name.nil?
95
98
 
96
- self.send(:include, Entity::Identified) unless Entity::Identified === self
99
+ self.send(:include, Entity::Identified) unless self.include?(Entity::Identified)
97
100
 
98
101
  self.format = all_fields
99
102
  @formats ||= []
@@ -148,7 +148,7 @@ module Entity
148
148
 
149
149
  def persist(name, type = :marshal, options = {})
150
150
  options = IndiferentHash.add_defaults options, persist: true,
151
- dir: File.join(Entity.entity_property_cache, self.to_s, name.to_s)
151
+ dir: Entity.entity_property_cache[self.to_s][name.to_s]
152
152
  @persisted_methods ||= {}
153
153
  @persisted_methods[name] = [type, options]
154
154
  end
@@ -0,0 +1,108 @@
1
+ class KnowledgeBase
2
+ def self.doc_parse_up_to(str, pattern, keep = false)
3
+ pre, _pat, _post = str.partition pattern
4
+ if _pat
5
+ [pre, (keep ? _pat << _post : _post)]
6
+ else
7
+ _post
8
+ end
9
+ end
10
+
11
+ def self.doc_parse_chunks(str, pattern)
12
+ parts = str.split(pattern)
13
+ return {} if parts.length < 2
14
+ databases = Hash[*parts[1..-1].collect{|v| v.strip }]
15
+ databases.delete_if{|t,d| d.empty?}
16
+ databases.transform_keys!(&:downcase)
17
+ databases
18
+ end
19
+
20
+ def self.parse_knowledge_base_doc(doc)
21
+ description, db_description = doc_parse_up_to doc, /^#/, true
22
+ databases = doc_parse_chunks db_description, /^# (.*)/
23
+ IndiferentHash.setup({:description => description.strip, :databases => databases})
24
+ end
25
+
26
+ def documentation_markdown
27
+ return "" if @libdir.nil?
28
+ file = @libdir['README.md'].find unless file.exists?
29
+ if file.exists?
30
+ file.read
31
+ else
32
+ ""
33
+ end
34
+ end
35
+
36
+ def database_description_file(name)
37
+ dir[name.to_s + '.md']
38
+ end
39
+
40
+ def knowledge_base_description_file(name)
41
+ file = dir['README.md']
42
+ return file if file.exists?
43
+
44
+ file, options = registry[name]
45
+ file = Path.setup(file.dup) unless file.nil? or Path === file
46
+ source_readme = file.dirname['README.md'] if file
47
+ return source_readme if source_readme && source_readme.exists?
48
+ end
49
+
50
+ def description(name)
51
+ return registered_options(name)[:description] if registered_options(name)[:description]
52
+
53
+ return database_description_file(name).read if database_description_file(name).exist?
54
+
55
+ if knowledge_base_description_file(name)
56
+ KnowledgeBase.parse_knowledge_base_doc(knowledge_base_description_file(name).read)[:databases][name.to_s.downcase]
57
+ end
58
+ end
59
+
60
+ def markdown(name)
61
+ description = description(name)
62
+ source_type = source_type(name)
63
+ target_type = target_type(name)
64
+
65
+ full_description = []
66
+ empty_line = ''
67
+ full_description << ("# " << Misc.humanize(name))
68
+ full_description << empty_line
69
+
70
+ source_formats = begin
71
+ source_index(name).key_field.split(',')
72
+ rescue
73
+ []
74
+ end
75
+
76
+ target_formats = begin
77
+ target_index(name).key_field.split(',')
78
+ rescue
79
+ []
80
+ end
81
+
82
+ if source_type
83
+ full_description << "Source: #{source_type} - #{source(name)}"
84
+ else
85
+ full_description << "Source: #{source(name)}"
86
+ end
87
+ #full_description.last << ". Accepted formats: #{source_formats*", "}" if source_formats.any?
88
+
89
+ if target_type
90
+ full_description << "Target: #{target_type} - #{target(name)}"
91
+ else
92
+ full_description << "Target: #{target(name)}"
93
+ end
94
+ #full_description.last << ". Accepted formats: #{target_formats*", "}" if target_formats.any?
95
+
96
+ if undirected?(name)
97
+ full_description << "Undirected database, source and target can be reversed."
98
+ end
99
+
100
+ if description
101
+ full_description << empty_line
102
+ full_description << description
103
+ full_description << empty_line
104
+ end
105
+
106
+ full_description * "\n"
107
+ end
108
+ end
@@ -80,6 +80,7 @@ class KnowledgeBase
80
80
  end
81
81
  identifier_files.concat Entity.identifier_files(source(name)) if defined? Entity
82
82
  identifier_files.uniq!
83
+ identifier_files.collect!{|f| (Path === f) ? f : Path.setup(f.dup) }
83
84
  identifier_files.collect!{|f| f.annotate(f.gsub(/\bNAMESPACE\b/, namespace))} if namespace
84
85
  identifier_files.collect!{|f| f.annotate(f.gsub(/\bNAMESPACE\b/, db_namespace(name)))} if not namespace and db_namespace(name)
85
86
  identifier_files.reject!{|f| f.match(/\bNAMESPACE\b/)}
@@ -89,7 +90,11 @@ class KnowledgeBase
89
90
 
90
91
  def target_index(name)
91
92
  Persist.memory("Target index #{name}: KB directory #{dir}") do
92
- identifier_files = identifier_files(name)
93
+ if @identifier_files && @identifier_files.any?
94
+ identifier_files = @identifier_files
95
+ else
96
+ identifier_files = database_identifier_files(name)
97
+ end
93
98
  identifier_files.concat Entity.identifier_files(target(name)) if defined? Entity
94
99
  identifier_files.uniq!
95
100
  identifier_files.collect!{|f| f.annotate(f.gsub(/\bNAMESPACE\b/, namespace))} if self.namespace
@@ -15,9 +15,29 @@ class KnowledgeBase
15
15
  end
16
16
  end
17
17
 
18
+ def present_databases
19
+ dir.glob("*.database").collect{|file| File.basename(file, '.database')}
20
+ end
21
+
18
22
  def all_databases
19
23
  return [] unless @registry
20
- @registry.keys
24
+ (@registry.keys + present_databases).uniq
25
+ end
26
+
27
+ def database_file(name)
28
+ if @registry[name].nil?
29
+ nil
30
+ else
31
+ @registry[name].first
32
+ end
33
+ end
34
+
35
+ def registered_options(name)
36
+ if @registry[name].nil?
37
+ IndiferentHash.setup({})
38
+ else
39
+ IndiferentHash.setup(@registry[name].last)
40
+ end
21
41
  end
22
42
 
23
43
  def include?(name)
@@ -29,21 +49,21 @@ class KnowledgeBase
29
49
  @fields[name] ||= get_index(name).fields
30
50
  end
31
51
 
32
- def description(name)
33
- @descriptions ||= {}
34
- @descriptions[name] ||= get_index(name).key_field.split("~")
52
+ def pair(name)
53
+ @pairs ||= {}
54
+ @pairs[name] ||= get_index(name).key_field.split("~")
35
55
  end
36
56
 
37
57
  def source(name)
38
- description(name)[0]
58
+ pair(name)[0]
39
59
  end
40
60
 
41
61
  def target(name)
42
- description(name)[1]
62
+ pair(name)[1]
43
63
  end
44
64
 
45
65
  def undirected(name)
46
- description(name).length == 3
66
+ pair(name).length == 3
47
67
  end
48
68
 
49
69
  alias undirected? undirected
@@ -69,7 +89,12 @@ class KnowledgeBase
69
89
 
70
90
  persist_dir = dir
71
91
  persist_path = persist_dir[key].find
72
- file, registered_options = registry[name]
92
+
93
+ file = database_file(name)
94
+ registered_options = registered_options(name)
95
+ registered_options = IndiferentHash.setup(registered_options).except(:description)
96
+
97
+ registered_options = IndiferentHash.add_defaults registered_options, identifiers: self.identifier_files if registered_options
73
98
 
74
99
  options = IndiferentHash.add_defaults options, registered_options if registered_options and registered_options.any?
75
100
  options = IndiferentHash.add_defaults options, :persist_path => persist_path, :persist_dir => persist_dir, :persist => true
@@ -89,11 +114,11 @@ class KnowledgeBase
89
114
  Log.low "Re-opening index #{ name } from #{ Log.fingerprint persist_path }. #{options}"
90
115
  Association.index(file, **options, persist_options: persist_options.dup)
91
116
  else
92
- options = IndiferentHash.add_defaults options, registered_options if registered_options
93
- raise "Repo #{ name } not found and not registered" if file.nil?
94
- Log.medium "Opening index #{ name } from #{ Log.fingerprint file }. #{options}"
117
+ database = get_database name if file.nil?
95
118
  file = file.call if Proc === file
96
- Association.index(file, **options, persist_options: persist_options.dup)
119
+ Log.medium "Opening index #{ name } from #{ Log.fingerprint database }. #{options}"
120
+ options = IndiferentHash.add_defaults options, registered_options if registered_options
121
+ Association.index(file, **options, persist_options: persist_options.dup, database: database)
97
122
  end
98
123
 
99
124
  index.namespace = self.namespace unless self.namespace
@@ -128,7 +153,12 @@ class KnowledgeBase
128
153
 
129
154
  persist_dir = dir
130
155
  persist_path = persist_dir[key].find
131
- file, registered_options = registry[name]
156
+
157
+ file = database_file(name)
158
+ registered_options = registered_options(name)
159
+ registered_options = IndiferentHash.setup(registered_options).except(:description)
160
+
161
+ registered_options = IndiferentHash.add_defaults registered_options, identifiers: self.identifier_files if registered_options
132
162
 
133
163
  options = IndiferentHash.add_defaults options, registered_options if registered_options and registered_options.any?
134
164
  options = IndiferentHash.add_defaults options, :persist_path => persist_path, :persist => true
@@ -145,7 +175,6 @@ class KnowledgeBase
145
175
 
146
176
  database = if persist_path.exists? and persist_options[:persist] and not persist_options[:update]
147
177
  Log.low "Re-opening database #{ name } from #{ Log.fingerprint persist_path }. #{options}"
148
- #Association.database(file, **options, persist_options: persist_options)
149
178
  Association.database(file, **options.merge(persist_options: persist_options))
150
179
  else
151
180
  options = IndiferentHash.add_defaults options, registered_options if registered_options
@@ -153,7 +182,6 @@ class KnowledgeBase
153
182
  raise "Repo #{ name } not found and not registered" if file.nil?
154
183
  Log.medium "Opening database #{ name } from #{ Log.fingerprint file }. #{options}"
155
184
  file = file.call if Proc === file
156
- #Association.database(file, **options, persist_options: persist_options)
157
185
  Association.database(file, **options.merge(persist_options: persist_options))
158
186
  end
159
187
 
@@ -5,6 +5,7 @@ require_relative 'knowledge_base/entity'
5
5
  require_relative 'knowledge_base/query'
6
6
  require_relative 'knowledge_base/traverse'
7
7
  require_relative 'knowledge_base/list'
8
+ require_relative 'knowledge_base/description'
8
9
  #require 'scout/knowledge_base/query'
9
10
  #require 'scout/knowledge_base/syndicate'
10
11
 
@@ -23,12 +24,12 @@ class KnowledgeBase
23
24
  @entity_options ||= IndiferentHash.setup({})
24
25
 
25
26
  @format ||= IndiferentHash.setup({})
26
- @descriptions ||= IndiferentHash.setup({})
27
+ pairs ||= IndiferentHash.setup({})
27
28
  @indices ||= IndiferentHash.setup({})
28
29
  end
29
30
 
30
31
  def config_file(name)
31
- @dir.config[name.to_s]
32
+ @dir['config'][name.to_s]
32
33
  end
33
34
 
34
35
  def save_variable(name)
@@ -40,7 +40,7 @@ module TSV
40
40
  target_file = target_files.select{|file| fields = file_fields[file]; (fields & middle_fields).any? }.collect{|file,f| file }.first
41
41
  [source_file, middle_file, target_file]
42
42
  else
43
- raise "Could not traverse identifier path from #{Log.fingerprint source} to #{Log.fingerprint target} in #{Log.fingerprint file_fields}"
43
+ raise "Could not traverse identifier path from #{Log.fingerprint source} to #{Log.fingerprint target}. #{file_fields.empty? ? "No identifier files" : Log.fingerprint(file_fields)}"
44
44
  end
45
45
  end
46
46
  end
@@ -55,7 +55,14 @@ module TSV
55
55
  files = [files] unless Array === files
56
56
 
57
57
  files.each do |file|
58
- next if Path === file && ! Open.exist?(file)
58
+ #next if Path === file && ! Open.exist?(file)
59
+ begin
60
+ file = file.produce if Path === file
61
+ raise "Could no produce file" if FalseClass === file
62
+ rescue
63
+ Log.warn $!.message
64
+ next
65
+ end
59
66
  file = file.find if Path === file
60
67
  file_fields[file] = all_fields(file)
61
68
  end
@@ -151,6 +151,16 @@ module Open
151
151
  res = block.call(line)
152
152
  callback.call res if callback
153
153
  end
154
+ obj.close
155
+ obj.join if obj.respond_to? :join
156
+ elsif options[:type] == :matrix
157
+ Log.low "Traverse stream by lines #{Log.fingerprint obj}"
158
+ parser = options[:sep].nil? ? TSV::Parser.new(obj) : TSV::Parser.new(obj, sep: options[:sep])
159
+ parser.traverse **options do |parts|
160
+ res = block.call parts
161
+ callback.call res if callback
162
+ nil
163
+ end
154
164
  else
155
165
  Log.low "Traverse stream with parser #{Log.fingerprint obj}"
156
166
  parser = options[:sep].nil? ? TSV::Parser.new(obj) : TSV::Parser.new(obj, sep: options[:sep])
@@ -31,6 +31,12 @@ module TSV
31
31
 
32
32
  return nil if select && ! TSV.select(items[0], items[1..-1], select, fields: field_names, type: type, sep: sep2)
33
33
 
34
+ if String === key
35
+ raise "Key by name, but no field names" if field_names.nil?
36
+ key = field_names.index key
37
+ raise "Key #{key} not found in field names #{Log.fingerprint field_names}" if key.nil?
38
+ end
39
+
34
40
  if positions.nil? && key == 0
35
41
  key = items.shift
36
42
  elsif positions.nil?
@@ -65,7 +71,7 @@ module TSV
65
71
  [key, items]
66
72
  end
67
73
 
68
- def self.parse_stream(stream, data: nil, source_type: nil, type: :list, merge: true, one2one: false, fix: true, bar: false, first_line: nil, field_names: nil, head: nil, **kwargs, &block)
74
+ def self.parse_stream(stream, data: nil, source_type: nil, sep: "\t", type: :list, merge: true, one2one: false, fix: true, bar: false, first_line: nil, field_names: nil, head: nil, **kwargs, &block)
69
75
  begin
70
76
  bar = "Parsing #{Log.fingerprint stream}" if TrueClass === bar
71
77
  bar = Log::ProgressBar.get_obj_bar(stream, bar) if bar
@@ -81,7 +87,7 @@ module TSV
81
87
  data.serializer.to_s.include?("String") &&
82
88
  same_type &&
83
89
  ! (head || kwargs[:cast] || kwargs[:positions] || (kwargs[:key] && kwargs[:key] != 0) || Proc === fix ) &&
84
- (kwargs[:sep].nil? || kwargs[:sep] == "\t")
90
+ (sep.nil? || sep == "\t")
85
91
 
86
92
 
87
93
  Log.debug "Loading #{Log.fingerprint stream} directly into #{Log.fingerprint data}"
@@ -114,12 +120,17 @@ module TSV
114
120
  line = Misc.fixutf8(line)
115
121
  end
116
122
  bar.tick if bar
123
+
117
124
  if type == :array || type == :line
118
125
  block.call line
119
126
  next
127
+ elsif type == :matrix
128
+ parts = line.split(sep)
129
+ block.call parts
130
+ next
120
131
  end
121
132
 
122
- key, items = parse_line(line, type: source_type, field_names: field_names, **kwargs)
133
+ key, items = parse_line(line, type: source_type, field_names: field_names, sep: sep, **kwargs)
123
134
 
124
135
  next if key.nil?
125
136
 
@@ -1,6 +1,8 @@
1
1
  module Workflow
2
2
  class Orchestrator
3
3
 
4
+ class NoWork < Exception; end
5
+
4
6
  def self.job_workload(job)
5
7
  workload = {job => []}
6
8
  return workload if job.done? && job.updated?
@@ -202,7 +204,7 @@ module Workflow
202
204
 
203
205
  candidates = resources_used.keys + Orchestrator.candidates(workload, rules)
204
206
  candidates.uniq!
205
- raise "No candidates and no running jobs" if candidates.empty?
207
+ raise NoWork, "No candidates and no running jobs" if candidates.empty?
206
208
 
207
209
  candidates.each do |job|
208
210
  case
@@ -272,4 +274,10 @@ module Workflow
272
274
  orchestrator.process({}, produce_list)
273
275
  produce_list
274
276
  end
277
+
278
+ def self.produce(jobs, produce_cpus: Etc.nprocessors, produce_timer: 5)
279
+ jobs = [jobs] unless Array === jobs
280
+ orchestrator = Orchestrator.new produce_timer.to_i, cpus: produce_cpus.to_i
281
+ orchestrator.process({}, jobs)
282
+ end
275
283
  end
@@ -0,0 +1,26 @@
1
+ module Workflow
2
+ def self.name2clean_name(name)
3
+ name.reverse.partition("_").last.reverse
4
+ end
5
+
6
+ def self.queue_job(file)
7
+ workflow, task, name = file.split("/").values_at(-3, -2, -1) if file
8
+ workflow = Workflow.require_workflow workflow
9
+
10
+ if Open.directory?(file)
11
+ clean_name = name2clean_name name
12
+ inputs = workflow.tasks[task].load_inputs(file)
13
+ workflow.job(task, clean_name, inputs)
14
+ else
15
+ workflow.job(task, name)
16
+ end
17
+ end
18
+
19
+ def self.unqueue(file, &block)
20
+ Open.lock file do
21
+ job = queue_job(file)
22
+ puts job.run
23
+ Open.rm_rf file
24
+ end
25
+ end
26
+ end