rbbt-util 5.32.27 → 5.32.28

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,203 +1,10 @@
1
- require 'rbbt/workflow/util/orchestrator'
1
+ #require 'rbbt/workflow/util/orchestrator'
2
+ require 'rbbt/hpc/orchestrate/rules'
3
+ require 'rbbt/hpc/orchestrate/chains'
4
+ require 'rbbt/hpc/orchestrate/batches'
2
5
  module HPC
3
6
  module Orchestration
4
7
 
5
- def job_rules(rules, job)
6
- workflow = job.workflow.to_s
7
- task_name = job.task_name.to_s
8
- task_name = job.overriden.to_s if Symbol === job.overriden
9
-
10
- defaults = rules["defaults"] || {}
11
- defaults = defaults.merge(rules[workflow]["defaults"] || {}) if rules[workflow]
12
-
13
- job_rules = IndiferentHash.setup(defaults.dup)
14
-
15
- rules["chains"].each do |name,info|
16
- IndiferentHash.setup(info)
17
- chain_tasks = info[:tasks].split(/,\s*/)
18
-
19
- chain_tasks.each do |task|
20
- task_workflow, chain_task = task.split("#")
21
- chain_task, task_workflow = task_workflow, info[:workflow] if chain_task.nil? or chain_tasks.empty?
22
- job_rules["chain_tasks"] ||= {}
23
- job_rules["chain_tasks"][task_workflow] ||= []
24
- job_rules["chain_tasks"][task_workflow] << chain_task
25
- next unless task_name == chain_task.to_s && workflow == task_workflow.to_s
26
- config_keys = job_rules.delete :config_keys
27
- job_rules = IndiferentHash.setup(job_rules.merge(info))
28
- if config_keys
29
- config_keys.gsub!(/,\s+/,',')
30
- job_rules[:config_keys] = job_rules[:config_keys] ? config_keys + "," + job_rules[:config_keys] : config_keys
31
- end
32
- end
33
-
34
- if job_rules["chain_tasks"][workflow] && job_rules["chain_tasks"][workflow].include?(task_name)
35
- break
36
- else
37
- job_rules.delete "chain_tasks"
38
- end
39
- end if rules["chains"]
40
-
41
- config_keys = job_rules.delete :config_keys
42
- job_rules = IndiferentHash.setup(job_rules.merge(rules[workflow][task_name])) if rules[workflow] && rules[workflow][task_name]
43
-
44
- if config_keys
45
- config_keys.gsub!(/,\s+/,',')
46
- job_rules[:config_keys] = job_rules[:config_keys] ? config_keys + "," + job_rules[:config_keys] : config_keys
47
- end
48
-
49
- if rules["skip"] && rules["skip"][workflow]
50
- job_rules["skip"] = true if rules["skip"][workflow].split(/,\s*/).include? task_name
51
- end
52
-
53
- job_rules
54
- end
55
-
56
- def get_job_dependencies(job, job_rules = nil)
57
- deps = job.dependencies || []
58
- deps += job.input_dependencies || []
59
- deps
60
- end
61
-
62
- def get_recursive_job_dependencies(job)
63
- deps = get_job_dependencies(job)
64
- (deps + deps.collect{|dep| get_recursive_job_dependencies(dep) }).flatten
65
- end
66
-
67
- def piggyback(job, job_rules, job_deps)
68
- return false unless job_rules["skip"]
69
- final_deps = job_deps - job_deps.collect{|dep| get_recursive_job_dependencies(dep)}.flatten.uniq
70
- final_deps = final_deps.reject{|dep| dep.done? }
71
- return final_deps.first if final_deps.length == 1
72
- return false
73
- end
74
-
75
- def get_chains(job, rules, chains = {})
76
- job_rules = self.job_rules(rules, job)
77
- job_deps = get_job_dependencies(job)
78
-
79
- input_deps = []
80
- job.rec_dependencies.each do |dep|
81
- input_deps.concat dep.input_dependencies
82
- end
83
-
84
- job_deps.each do |dep|
85
- input_deps.concat dep.input_dependencies
86
- get_chains(dep, rules, chains)
87
- end
88
-
89
- job_deps.select do |dep|
90
- chained = job_rules["chain_tasks"] &&
91
- job_rules["chain_tasks"][job.workflow.to_s] && job_rules["chain_tasks"][job.workflow.to_s].include?(job.task_name.to_s) &&
92
- job_rules["chain_tasks"][dep.workflow.to_s] && job_rules["chain_tasks"][dep.workflow.to_s].include?(dep.task_name.to_s)
93
-
94
- dep_skip = dep.done? && ! input_deps.include?(dep) && self.job_rules(rules, dep)["skip"]
95
- chained || dep_skip
96
- end.each do |dep|
97
- chains[job] ||= []
98
- chains[job] << dep
99
- chains[job].concat chains[dep] if chains[dep]
100
- chains[job].uniq!
101
- end
102
-
103
- chains
104
- end
105
-
106
- def workload(job, rules, chains, options, seen = nil)
107
- return [] if job.done?
108
- if seen.nil?
109
- seen = {}
110
- target_job = true
111
- end
112
-
113
- job_rules = self.job_rules(rules, job)
114
- job_deps = get_job_dependencies(job)
115
-
116
- chain = chains[job]
117
- chain = chain.reject{|j| seen.include? j.path} if chain
118
- chain = chain.reject{|dep| dep.done? } if chain
119
- piggyback = piggyback(job, job_rules, job_deps)
120
- dep_ids = job_deps.collect do |dep|
121
- seen[dep.path] ||= nil if chain && chain.include?(dep) #&& ! job.input_dependencies.include?(dep)
122
- next_options = IndiferentHash.setup(options.dup)
123
- if piggyback and piggyback == dep
124
- next_options[:piggyback] ||= []
125
- next_options[:piggyback].push job
126
- ids = workload(dep, rules, chains, next_options, seen)
127
- else
128
- next_options.delete :piggyback
129
- ids = workload(dep, rules, chains, next_options, seen)
130
- end
131
-
132
- ids = [ids].flatten.compact.collect{|id| ['canfail', id] * ":"} if job.canfail_paths.include? dep.path
133
-
134
- seen[dep.path] = ids
135
- ids
136
- end.compact.flatten.uniq
137
-
138
- return seen[job.path] || dep_ids if seen.include?(job.path)
139
-
140
- if piggyback and seen[piggyback.path]
141
- return seen[job.path] = seen[piggyback.path]
142
- end
143
-
144
- job_rules.delete :chain_tasks
145
- job_rules.delete :tasks
146
- job_rules.delete :workflow
147
-
148
-
149
- option_config_keys = options[:config_keys]
150
-
151
- job_options = IndiferentHash.setup(options.merge(job_rules).merge(:batch_dependencies => dep_ids))
152
- job_options.delete :orchestration_rules
153
-
154
- config_keys = job_rules.delete(:config_keys)
155
- if config_keys
156
- config_keys.gsub!(/,\s+/,',')
157
- job_options[:config_keys] = job_options[:config_keys] ? config_keys + "," + job_options[:config_keys] : config_keys
158
- end
159
-
160
- if option_config_keys
161
- option_config_keys = option_config_keys.gsub(/,\s+/,',')
162
- job_options[:config_keys] = job_options[:config_keys] ? job_options[:config_keys] + "," + option_config_keys : option_config_keys
163
- end
164
-
165
- if options[:piggyback]
166
- manifest = options[:piggyback].uniq
167
- manifest += [job]
168
- manifest.concat chain if chain
169
-
170
- job = options[:piggyback].first
171
-
172
- job_rules = self.job_rules(rules, job)
173
- new_config_keys = self.job_rules(rules, job)[:config_keys]
174
- if new_config_keys
175
- new_config_keys = new_config_keys.gsub(/,\s+/,',')
176
- job_options[:config_keys] = job_options[:config_keys] ? job_options[:config_keys] + "," + new_config_keys : new_config_keys
177
- end
178
-
179
- job_options.delete :piggyback
180
- else
181
- manifest = [job]
182
- manifest.concat chain if chain
183
- end
184
-
185
- manifest.uniq!
186
-
187
- job_options[:manifest] = manifest.collect{|j| j.task_signature }
188
-
189
- job_options[:config_keys] = job_options[:config_keys].split(",").uniq * "," if job_options[:config_keys]
190
-
191
- if options[:dry_run]
192
- puts Log.color(:magenta, "Manifest: ") + Log.color(:blue, job_options[:manifest] * ", ") + " - tasks: #{job_options[:task_cpus] || 1} - time: #{job_options[:time]} - config: #{job_options[:config_keys]}"
193
- puts Log.color(:yellow, "Deps: ") + Log.color(:blue, job_options[:batch_dependencies]*", ")
194
- job_options[:manifest].first
195
- else
196
- run_job(job, job_options)
197
- end
198
- end
199
-
200
-
201
8
  def orchestrate_job(job, options)
202
9
  options.delete "recursive_clean"
203
10
  options.delete "clean_task"
@@ -211,9 +18,26 @@ module HPC
211
18
  rules ||= {}
212
19
  IndiferentHash.setup(rules)
213
20
 
214
- chains = get_chains(job, rules)
215
-
216
- workload(job, rules, chains, options)
21
+ batches = HPC::Orchestration.job_batches(rules, job)
22
+
23
+ batch_ids = {}
24
+ while batches.any?
25
+ top = batches.select{|b| b[:deps].nil? || (b[:deps] - batch_ids.keys).empty? }.first
26
+ raise "No batch without unmet dependencies" if top.nil?
27
+ batches.delete top
28
+ job_options = options.merge(top[:rules])
29
+ job_options.merge!(:batch_dependencies => top[:deps].nil? ? [] : top[:deps].collect{|d| batch_ids[d] })
30
+ job_options.merge!(:manifest => top[:jobs].collect{|d| d.task_signature })
31
+
32
+ if options[:dry_run]
33
+ puts Log.color(:magenta, "Manifest: ") + Log.color(:blue, job_options[:manifest] * ", ") + " - tasks: #{job_options[:task_cpus] || 1} - time: #{job_options[:time]} - config: #{job_options[:config_keys]}"
34
+ puts Log.color(:yellow, "Deps: ") + Log.color(:blue, job_options[:batch_dependencies]*", ")
35
+ batch_ids[top] = top[:top_level].task_signature
36
+ else
37
+ id = run_job(top[:top_level], job_options)
38
+ batch_ids[top] = id
39
+ end
40
+ end
217
41
  end
218
42
 
219
43
  end
@@ -78,7 +78,7 @@ module Persist
78
78
  end
79
79
  end
80
80
 
81
- def self.persist_tsv(source, filename, options = {}, persist_options = {}, &block)
81
+ def self.persist_tsv(source, filename = nil, options = {}, persist_options = {}, &block)
82
82
  persist_options[:prefix] ||= "TSV"
83
83
 
84
84
  if data = persist_options[:data]
data/lib/rbbt/util/log.rb CHANGED
@@ -108,8 +108,9 @@ module Log
108
108
 
109
109
 
110
110
  def self._ignore_stderr
111
- backup_stderr = STDERR.dup
112
- File.open('/dev/null', 'w') do |f|
111
+ begin
112
+ File.open('/dev/null', 'w') do |f|
113
+ backup_stderr = STDERR.dup
113
114
  STDERR.reopen(f)
114
115
  begin
115
116
  yield
@@ -117,6 +118,9 @@ module Log
117
118
  STDERR.reopen backup_stderr
118
119
  backup_stderr.close
119
120
  end
121
+ end
122
+ rescue Errno::ENOENT
123
+ yield
120
124
  end
121
125
  end
122
126
 
@@ -49,6 +49,7 @@ puts resource[path].find(search_path)
49
49
  test_str = options[:test] ? '-nv' : ''
50
50
 
51
51
  real_paths.each do |source_path|
52
+ Log.medium "Migrating #{source_path} #{options[:files].length} files to #{target} - #{Misc.fingerprint(options[:files])}}"
52
53
  if File.directory?(source_path) || source_path =~ /\/$/
53
54
  source_path += "/" unless source_path[-1] == "/"
54
55
  target += "/" unless target[-1] == "/"
@@ -76,8 +77,12 @@ puts resource[path].find(search_path)
76
77
  files_from_str = ""
77
78
  end
78
79
 
79
- cmd = "rsync -avztAXHP --copy-unsafe-links #{test_str} #{files_from_str} #{excludes_str} '#{source_path}' #{target_path} #{other * " "}"
80
+ #cmd = "rsync -avztAXHP --copy-unsafe-links #{test_str} #{files_from_str} #{excludes_str} '#{source_path}' #{target_path} #{other * " "}"
81
+
82
+ # rsync_args = "-avztAXHP --copy-unsafe-links"
83
+ rsync_args = "-avztAHP --copy-unsafe-links"
80
84
 
85
+ cmd = "rsync #{rsync_args} #{test_str} #{files_from_str} #{excludes_str} '#{source_path}' #{target_path} #{other * " "}"
81
86
 
82
87
  cmd << " && rm -Rf #{source_path}" if options[:delete] && ! options[:files]
83
88
 
@@ -280,8 +280,11 @@ module Misc
280
280
  i = parts.index job
281
281
  begin
282
282
  workflow, task = parts.values_at i - 2, i - 1
283
- return Kernel.const_get(workflow).tasks.include? task.to_sym
283
+ Workflow.require_workflow workflow
284
+ #return Kernel.const_get(workflow).tasks.include? task.to_sym
285
+ return true
284
286
  rescue
287
+ Log.exception $!
285
288
  end
286
289
  end
287
290
  false
@@ -70,6 +70,11 @@ module Misc
70
70
  end
71
71
 
72
72
  def self.timespan(str, default = "s")
73
+ if str.include?(":")
74
+ seconds, minutes, hours = str.split(":").reverse
75
+ return seconds.to_i + minutes.to_i * 60 + hours.to_i * 60 * 60
76
+ end
77
+
73
78
  tokens = {
74
79
  "s" => (1),
75
80
  "sec" => (1),
@@ -52,7 +52,7 @@ module RbbtPython
52
52
  if Array === imports
53
53
  pyfrom mod, :import => imports
54
54
  elsif Hash === imports
55
- pyimport mod, imports
55
+ pyimport mod, **imports
56
56
  else
57
57
  pyimport mod
58
58
  end
@@ -1,37 +1,97 @@
1
1
  module Workflow
2
- def nextflow_file(file, name = nil)
2
+ def self.nextflow_file_params(file)
3
+ Open.read(file).scan(/params\.\w+/).collect{|p| p.split(".").last}.uniq
4
+ end
5
+
6
+ def self.nextflow_includes(file)
7
+ Open.read(file).scan(/^include\s*{\s*(.*?)(?:\s*as.*?)?}\s*from\s+"(.*?)"(?:\s*params.*)?/).collect{|p| p}.uniq
8
+ end
9
+
10
+ def self.nextflow_recursive_params(file)
11
+ params = nextflow_file_params(file)
12
+ dir = File.dirname(file)
13
+ nextflow_includes(file).inject(params) do |params,info|
14
+ name_str, included_file = info
15
+ included_file = File.join(dir, included_file)
16
+ included_file += '.nf' unless File.exists?(included_file) || ! File.exists?(included_file + '.nf')
17
+ name_str.split(";").each do |name|
18
+ name = name.strip
19
+ include_params = nextflow_recursive_params(included_file).collect{|p| [p,name] * "-"}
20
+ params += include_params
21
+ end
22
+ params
23
+ end
24
+ end
25
+
26
+ def nextflow_file(file, name = nil, output = nil)
27
+ name, output = nil, name if Hash === name
28
+
29
+ if Hash === output
30
+ result, output = output.collect.first
31
+ else
32
+ result = :text
33
+ end
34
+
3
35
  file = file + '.nf' unless File.exists?(file) || ! File.exists?(file + '.nf')
4
36
  file = File.expand_path(file)
5
- name ||= File.basename(file).sub(/\.nf$/,'')
6
- params = Open.read(file).scan(/params\.\w+/).collect{|p| p.split(".").last}.uniq
37
+ name ||= File.basename(file).sub(/\.nf$/,'').gsub(/\s/,'_')
38
+ params = Workflow.nextflow_recursive_params(file)
7
39
 
8
40
  params.each do |param|
9
- input param, :string
41
+ p,_sep, section = param.partition("-")
42
+ if section.nil? || section.empty?
43
+ input param, :string, "Nextflow param #{p}", nil, :nofile => true
44
+ else
45
+ input param, :string, "Nextflow param #{p} from import #{section}", nil, :nofile => true
46
+ end
10
47
  end
11
- task name => :text do
48
+ task name => result do
12
49
  work = file('work')
13
- output = file('output')
14
50
  profile = config :profile, :nextflow
15
- Misc.in_dir output do
51
+
52
+ new_inputs = inputs.zip(inputs.fields).collect do |v,f|
53
+ if String === v && m = v.match(/^JOB_FILE:(.*)/)
54
+ file(m[1])
55
+ elsif v.nil?
56
+ Rbbt::Config.get(['nextflow', f] * "_", 'default', f)
57
+ else
58
+ v
59
+ end
60
+ end
61
+
62
+ inputs.replace new_inputs
63
+
64
+ Misc.in_dir file('stage') do
16
65
  if profile
17
- cmd("nextflow run -work-dir #{work} -name #{clean_name} -ansi-log false -profile #{profile} #{file}", inputs.to_hash.merge('add_option_dashes' => true))
66
+ cmd("nextflow run -work-dir #{work} -ansi-log false -profile #{profile} #{file}", inputs.to_hash.merge('add_option_dashes' => true))
18
67
  else
19
- cmd("nextflow run -work-dir #{work} -name #{clean_name} -ansi-log false #{file}", inputs.to_hash.merge('add_option_dashes' => true))
68
+ cmd("nextflow run -work-dir #{work} -ansi-log false #{file}", inputs.to_hash.merge('add_option_dashes' => true))
20
69
  end
21
70
  end
71
+
72
+ output_file = file(output).glob.first if output
73
+ output_file = work[File.join('*', '*', output)].glob.first if output && output_file.nil?
74
+
75
+ if output_file.nil?
76
+ work[File.join("*", "*", "*")].glob * "\n"
77
+ else
78
+ Open.link output_file, self.tmp_path
79
+ #Open.rm_rf file('work')
80
+ nil
81
+ end
22
82
  end
23
83
  end
24
84
 
25
- def nextflow_dir(path)
85
+ def nextflow_dir(path, output = nil)
26
86
  main = File.join(path, 'main.nf')
27
- nextflow_file main, File.basename(path)
87
+ nextflow_file main, File.basename(path), output
28
88
  end
29
89
 
30
- def nextflow(path)
90
+ def nextflow(path, *args)
31
91
  if File.directory?(path)
32
- nextflow_dir path
92
+ nextflow_dir path, *args
33
93
  else
34
- nextflow_file path
94
+ nextflow_file path, *args
35
95
  end
36
96
  end
37
97
  end
@@ -419,7 +419,7 @@ class Step
419
419
  set_info :dependencies, dependencies.collect{|dep| [dep.task_name, dep.name, dep.path]}
420
420
 
421
421
  config_keys = Rbbt::Config::GOT_KEYS[config_keys_pre.length..-1]
422
- set_info :config_keys, config_keys
422
+ set_info :config_keys, config_keys.uniq
423
423
 
424
424
  if result.nil? && File.exists?(self.tmp_path) && ! File.exists?(self.path)
425
425
  Open.mv self.tmp_path, self.path
data/lib/rbbt/workflow.rb CHANGED
@@ -244,7 +244,7 @@ module Workflow
244
244
  when :hash
245
245
  clean_inputs = Annotated.purge(inputs)
246
246
  clean_inputs = clean_inputs.collect{|i| Symbol === i ? i.to_s : i }
247
- deps_str = dependencies.collect{|d| (Step === d || (defined?(RemoteStep) && RemoteStep === Step)) ? "Step: " << (d.overriden? ? d.path : d.short_path) : d }
247
+ deps_str = dependencies.collect{|d| (Step === d || (defined?(RemoteStep) && RemoteStep === Step)) ? "Step: " << (Symbol === d.overriden ? d.path : d.short_path) : d }
248
248
  key_obj = {:inputs => clean_inputs, :dependencies => deps_str }
249
249
  key_str = Misc.obj2str(key_obj)
250
250
  hash_str = Misc.digest(key_str)
@@ -465,7 +465,14 @@ module Workflow
465
465
  extension = nil
466
466
  if dependencies.any?
467
467
  dep_basename = File.basename(dependencies.last.path)
468
- extension = dep_basename.split(".").last if dep_basename.include?('.')
468
+ if dep_basename.include? "."
469
+ parts = dep_basename.split(".")
470
+ extension = [parts.pop]
471
+ while parts.last.length <= 4
472
+ extension << parts.pop
473
+ end
474
+ extension = extension.reverse * "."
475
+ end
469
476
  end
470
477
  end
471
478
 
@@ -13,19 +13,6 @@ Queue a job in Marenostrum
13
13
  $ rbbt slurm tail <directory> [options]
14
14
 
15
15
  -h--help Print this help
16
- -d--done Done jobs only
17
- -e--error Error jobs only
18
- -a--aborted SLURM aboted jobs
19
- -r--running Running jobs only
20
- -q--queued Queued jobs only
21
- -j--job* Job ids
22
- -s--search* Regular expression
23
- -t--tail* Show the last lines of the STDERR
24
- -p--progress Report progress of job and the dependencies
25
- -SBP--sbatch_parameters show sbatch parameters
26
- -PERF--procpath_performance show Procpath performance summary
27
- -sacct--sacct_peformance show sacct performance summary
28
- -bs--batch_system* Batch system to use: auto, lsf, slurm (default is auto-detect)
29
16
  EOF
30
17
 
31
18
  if options[:help]
@@ -13,19 +13,6 @@ Queue a job in Marenostrum
13
13
  $ rbbt slurm tail <directory> [options]
14
14
 
15
15
  -h--help Print this help
16
- -d--done Done jobs only
17
- -e--error Error jobs only
18
- -a--aborted SLURM aboted jobs
19
- -r--running Running jobs only
20
- -q--queued Queued jobs only
21
- -j--job* Job ids
22
- -s--search* Regular expression
23
- -t--tail* Show the last lines of the STDERR
24
- -p--progress Report progress of job and the dependencies
25
- -SBP--sbatch_parameters show sbatch parameters
26
- -PERF--procpath_performance show Procpath performance summary
27
- -sacct--sacct_peformance show sacct performance summary
28
- -bs--batch_system* Batch system to use: auto, lsf, slurm (default is auto-detect)
29
16
  EOF
30
17
 
31
18
  if options[:help]
@@ -13,19 +13,6 @@ Queue a job in Marenostrum
13
13
  $ rbbt slurm tail <directory> [options]
14
14
 
15
15
  -h--help Print this help
16
- -d--done Done jobs only
17
- -e--error Error jobs only
18
- -a--aborted SLURM aboted jobs
19
- -r--running Running jobs only
20
- -q--queued Queued jobs only
21
- -j--job* Job ids
22
- -s--search* Regular expression
23
- -t--tail* Show the last lines of the STDERR
24
- -p--progress Report progress of job and the dependencies
25
- -SBP--sbatch_parameters show sbatch parameters
26
- -PERF--procpath_performance show Procpath performance summary
27
- -sacct--sacct_peformance show sacct performance summary
28
- -bs--batch_system* Batch system to use: auto, lsf, slurm (default is auto-detect)
29
16
  EOF
30
17
 
31
18
  if options[:help]
@@ -41,19 +41,18 @@ parser = TSV::Parser.new TSV.get_stream(file), options.merge(:fields => [])
41
41
 
42
42
  options[:merge] = false if options[:merge] == "false"
43
43
 
44
- Thread.new do
45
- line = parser.first_line
46
- bar = Log::ProgressBar.new
47
- while line
48
- bar.tick
49
-
50
- line = Misc.fixutf8(line)
51
- line = parser.process line
52
- raise SKIP_LINE if line.empty?
53
- parts = parser.chop_line line
54
- key, values = parser.get_values parts
55
- values = parser.cast_values values if parser.cast?
56
-
57
- puts key
58
- end
44
+ line = parser.first_line
45
+ bar = Log::ProgressBar.new
46
+ while line
47
+ bar.tick
48
+
49
+ line = Misc.fixutf8(line)
50
+ line = parser.process line
51
+ raise SKIP_LINE if line.empty?
52
+ parts = parser.chop_line line
53
+ key, values = parser.get_values parts
54
+ values = parser.cast_values values if parser.cast?
55
+
56
+ puts key
57
+ line = parser.stream.gets
59
58
  end
@@ -573,10 +573,14 @@ when Step
573
573
  elsif detach
574
574
  exit! 0
575
575
  else
576
- res.join
577
- Open.open(res.path, :mode => 'rb') do |io|
578
- Misc.consume_stream(io, false, out)
579
- end if Open.exist?(res.path) || Open.remote?(res.path) || Open.ssh?(res.path)
576
+ if %w(float integer string boolean).include?(res.result_type.to_s)
577
+ out.puts res.load
578
+ else
579
+ res.join
580
+ Open.open(res.path, :mode => 'rb') do |io|
581
+ Misc.consume_stream(io, false, out)
582
+ end if Open.exist?(res.path) || Open.remote?(res.path) || Open.ssh?(res.path)
583
+ end
580
584
  end
581
585
  else
582
586
  if Array === res
@@ -1,6 +1,11 @@
1
1
  require File.join(File.expand_path(File.dirname(__FILE__)), '../..', 'test_helper.rb')
2
2
  require 'rbbt/annotations'
3
3
 
4
+ module TestEntityString
5
+ extend Entity
6
+ self.annotation :code
7
+ end
8
+
4
9
  class TestAnnotation < Test::Unit::TestCase
5
10
  def test_marshal
6
11
  a = "STRING"
@@ -9,5 +14,11 @@ class TestAnnotation < Test::Unit::TestCase
9
14
  assert !(Annotated === Marshal.load(Marshal.dump(a)))
10
15
  assert_equal a, Marshal.load(Marshal.dump(a))
11
16
  end
17
+
18
+ def test_hash
19
+ e = TestEntityString.setup("TEST", :code => 10)
20
+ assert_equal "TEST", Annotated.to_hash(e)[:literal]
21
+ assert_equal 10, Annotated.to_hash(e)[:info][:code]
22
+ end
12
23
  end
13
24