scout-gear 2.0.0 → 5.2.0

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 (104) hide show
  1. checksums.yaml +4 -4
  2. data/.vimproject +65 -2
  3. data/Rakefile +2 -0
  4. data/VERSION +1 -1
  5. data/bin/scout +233 -24
  6. data/lib/scout/cmd.rb +344 -0
  7. data/lib/scout/concurrent_stream.rb +259 -0
  8. data/lib/scout/exceptions.rb +15 -8
  9. data/lib/scout/indiferent_hash/options.rb +8 -26
  10. data/lib/scout/log/color.rb +2 -2
  11. data/lib/scout/log/fingerprint.rb +11 -1
  12. data/lib/scout/log/progress/report.rb +0 -1
  13. data/lib/scout/log/progress/util.rb +1 -1
  14. data/lib/scout/log/progress.rb +4 -4
  15. data/lib/scout/log.rb +10 -2
  16. data/lib/scout/meta_extension.rb +19 -3
  17. data/lib/scout/misc/digest.rb +56 -0
  18. data/lib/scout/misc/filesystem.rb +26 -0
  19. data/lib/scout/misc/format.rb +17 -6
  20. data/lib/scout/misc/insist.rb +56 -0
  21. data/lib/scout/misc/monitor.rb +23 -0
  22. data/lib/scout/misc.rb +5 -11
  23. data/lib/scout/open/lock.rb +61 -0
  24. data/lib/scout/open/remote.rb +120 -0
  25. data/lib/scout/open/stream.rb +373 -0
  26. data/lib/scout/open/util.rb +225 -0
  27. data/lib/scout/open.rb +169 -0
  28. data/lib/scout/path/find.rb +68 -21
  29. data/lib/scout/path/tmpfile.rb +8 -0
  30. data/lib/scout/path/util.rb +14 -1
  31. data/lib/scout/path.rb +6 -30
  32. data/lib/scout/persist/open.rb +17 -0
  33. data/lib/scout/persist/path.rb +15 -0
  34. data/lib/scout/persist/serialize.rb +151 -0
  35. data/lib/scout/persist.rb +54 -0
  36. data/lib/scout/resource/path.rb +20 -0
  37. data/lib/scout/resource/produce/rake.rb +69 -0
  38. data/lib/scout/resource/produce.rb +246 -0
  39. data/lib/scout/resource/scout.rb +3 -0
  40. data/lib/scout/resource/util.rb +48 -0
  41. data/lib/scout/resource.rb +39 -0
  42. data/lib/scout/simple_opt/accessor.rb +1 -1
  43. data/lib/scout/simple_opt/doc.rb +29 -23
  44. data/lib/scout/simple_opt/parse.rb +4 -3
  45. data/lib/scout/tmpfile.rb +39 -1
  46. data/lib/scout/workflow/definition.rb +78 -0
  47. data/lib/scout/workflow/documentation.rb +83 -0
  48. data/lib/scout/workflow/step/info.rb +77 -0
  49. data/lib/scout/workflow/step/load.rb +18 -0
  50. data/lib/scout/workflow/step.rb +132 -0
  51. data/lib/scout/workflow/task/inputs.rb +114 -0
  52. data/lib/scout/workflow/task.rb +155 -0
  53. data/lib/scout/workflow/usage.rb +314 -0
  54. data/lib/scout/workflow/util.rb +11 -0
  55. data/lib/scout/workflow.rb +40 -0
  56. data/lib/scout-gear.rb +4 -0
  57. data/lib/scout.rb +1 -0
  58. data/lib/workflow-scout.rb +2 -0
  59. data/scout-gear.gemspec +77 -5
  60. data/scout_commands/alias +48 -0
  61. data/scout_commands/find +83 -0
  62. data/scout_commands/glob +0 -0
  63. data/scout_commands/rbbt +23 -0
  64. data/scout_commands/workflow/info +29 -0
  65. data/scout_commands/workflow/list +27 -0
  66. data/scout_commands/workflow/task +58 -0
  67. data/scout_commands/workflow/task_old +706 -0
  68. data/test/scout/indiferent_hash/test_options.rb +11 -1
  69. data/test/scout/misc/test_digest.rb +30 -0
  70. data/test/scout/misc/test_filesystem.rb +30 -0
  71. data/test/scout/misc/test_insist.rb +13 -0
  72. data/test/scout/open/test_lock.rb +52 -0
  73. data/test/scout/open/test_remote.rb +25 -0
  74. data/test/scout/open/test_stream.rb +515 -0
  75. data/test/scout/open/test_util.rb +73 -0
  76. data/test/scout/path/test_find.rb +28 -0
  77. data/test/scout/persist/test_open.rb +37 -0
  78. data/test/scout/persist/test_path.rb +37 -0
  79. data/test/scout/persist/test_serialize.rb +114 -0
  80. data/test/scout/resource/test_path.rb +40 -0
  81. data/test/scout/resource/test_produce.rb +62 -0
  82. data/test/scout/resource/test_util.rb +27 -0
  83. data/test/scout/simple_opt/test_doc.rb +16 -0
  84. data/test/scout/test_cmd.rb +85 -0
  85. data/test/scout/test_concurrent_stream.rb +29 -0
  86. data/test/scout/test_meta_extension.rb +9 -0
  87. data/test/scout/test_misc.rb +0 -7
  88. data/test/scout/test_open.rb +146 -0
  89. data/test/scout/test_path.rb +3 -1
  90. data/test/scout/test_persist.rb +83 -0
  91. data/test/scout/test_resource.rb +26 -0
  92. data/test/scout/test_workflow.rb +87 -0
  93. data/test/scout/workflow/step/test_info.rb +30 -0
  94. data/test/scout/workflow/step/test_load.rb +65 -0
  95. data/test/scout/workflow/task/test_inputs.rb +182 -0
  96. data/test/scout/workflow/test_definition.rb +0 -0
  97. data/test/scout/workflow/test_documentation.rb +30 -0
  98. data/test/scout/workflow/test_step.rb +36 -0
  99. data/test/scout/workflow/test_task.rb +179 -0
  100. data/test/scout/workflow/test_usage.rb +35 -0
  101. data/test/scout/workflow/test_util.rb +17 -0
  102. data/test/test_helper.rb +17 -0
  103. data/test/test_scout-gear.rb +0 -0
  104. metadata +75 -3
@@ -0,0 +1,83 @@
1
+ module Workflow
2
+ attr_accessor :title, :description
3
+
4
+ def self.doc_parse_first_line(str)
5
+ if str.match(/^([^\n]*)\n\n(.*)/sm)
6
+ str.replace $2
7
+ $1
8
+ else
9
+ ""
10
+ end
11
+ end
12
+
13
+ def self.doc_parse_up_to(str, pattern, keep = false)
14
+ pre, _pat, _post = str.partition pattern
15
+ if _pat
16
+ [pre, (keep ? _pat << _post : _post)]
17
+ else
18
+ _post
19
+ end
20
+ end
21
+
22
+ def self.doc_parse_chunks(str, pattern)
23
+ parts = str.split(pattern)
24
+ return {} if parts.length < 2
25
+ tasks = Hash[*parts[1..-1].collect{|v| v.strip}]
26
+ tasks.delete_if{|t,d| d.empty?}
27
+ tasks
28
+ end
29
+
30
+ def self.parse_workflow_doc(doc)
31
+ title = doc_parse_first_line doc
32
+ description, task_info = doc_parse_up_to doc, /^# Tasks/i
33
+ task_description, tasks = doc_parse_up_to task_info, /^##/, true
34
+ tasks = doc_parse_chunks tasks, /## (.*)/
35
+ {:title => title.strip, :description => description.strip, :task_description => task_description.strip, :tasks => tasks}
36
+ end
37
+
38
+ def documentation_markdown
39
+ return "" if @libdir.nil?
40
+ file = @libdir['workflow.md'].find
41
+ file = @libdir['README.md'].find unless file.exists?
42
+ if file.exists?
43
+ file.read
44
+ else
45
+ ""
46
+ end
47
+ end
48
+
49
+ attr_accessor :documentation
50
+ def documentation
51
+ @documentation ||= begin
52
+ documentation = Workflow.parse_workflow_doc documentation_markdown
53
+
54
+ if @description && (documentation[:description].nil? || documentation[:description].empty?)
55
+ documentation[:description] = @description
56
+ end
57
+
58
+ if @title && (documentation[:title].nil? || documentation[:title].empty?)
59
+ documentation[:title] = @title
60
+ end
61
+ documentation[:tasks].each do |task, description|
62
+ if task.include? "#"
63
+ workflow, task = task.split("#")
64
+ workflow = begin
65
+ Kernel.const_get workflow
66
+ rescue
67
+ next
68
+ end
69
+ else
70
+ workflow = self
71
+ end
72
+
73
+ task = task.to_sym
74
+ if workflow.tasks.include? task
75
+ workflow.tasks[task].description = description
76
+ else
77
+ Log.low "Documentation for #{ task }, but not a #{ workflow.to_s } task"
78
+ end
79
+ end
80
+ documentation
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,77 @@
1
+ class Step
2
+ SERIALIZER = :json
3
+ def info_file
4
+ @info_file ||= begin
5
+ info_file = @path + ".info"
6
+ @path.annotate info_file if Path === @path
7
+ info_file
8
+ end
9
+ end
10
+
11
+ def load_info
12
+ @info = Persist.load(info_file, SERIALIZER) || {}
13
+ IndiferentHash.setup(@info)
14
+ @info_load_time = Time.now
15
+ end
16
+
17
+ def save_info(info = nil)
18
+ Persist.save(info, info_file, SERIALIZER)
19
+ @info_load_time = Time.now
20
+ end
21
+
22
+ def info
23
+ outdated = @info && Open.exists?(info_file) && @info_load_time && Open.mtime(info_file) > @info_load_time
24
+
25
+ if @info.nil? || outdated
26
+ load_info
27
+ end
28
+
29
+ @info
30
+ end
31
+
32
+ def merge_info(new_info)
33
+ info = self.info
34
+ new_info.each do |key,value|
35
+ report_status new_info[:status], new_info[:message] if key == :status
36
+ if info.include?(key)
37
+ case info[key]
38
+ when Array
39
+ info[key].concat Array === value ? value : [value]
40
+ when Hash
41
+ info[key].merge! value
42
+ else
43
+ info[key] = value
44
+ end
45
+ else
46
+ info[key] = value
47
+ end
48
+ end
49
+ save_info(info)
50
+ end
51
+
52
+ def set_info(key, value)
53
+ merge_info(key => value)
54
+ end
55
+
56
+ def report_status(status, message = nil)
57
+ if message.nil?
58
+ Log.info Log.color(:green, status.to_s) + " " + Log.color(:blue, path)
59
+ else
60
+ Log.info Log.color(:green, status.to_s) + " " + Log.color(:blue, path) + " " + message
61
+ end
62
+ end
63
+
64
+ def log(status, message = nil)
65
+ report_status status, message
66
+ if message
67
+ merge_info :status => status, :messages => [message]
68
+ else
69
+ merge_info :status => status
70
+ end
71
+ end
72
+
73
+ def status
74
+ info[:status]
75
+ end
76
+
77
+ end
@@ -0,0 +1,18 @@
1
+ class Step
2
+ def self.relocate(path)
3
+ return path if Open.exists?(path)
4
+ Path.setup(path) unless Path === path
5
+ relocated = path.relocate
6
+ return relocated if Open.exists?(relocated)
7
+ subpath = path.split("/")[-3..-1] * "/"
8
+ relocated = Path.setup("var/jobs")[subpath]
9
+ return relocated if Open.exists?(relocated)
10
+ path
11
+ end
12
+
13
+ def self.load(path)
14
+ path = relocate(path) unless Open.exists?(path)
15
+ raise "Could not load #{path}" unless Open.exists?(path)
16
+ s = Step.new path
17
+ end
18
+ end
@@ -0,0 +1,132 @@
1
+ require_relative '../path'
2
+ require_relative '../persist'
3
+ require_relative 'step/info'
4
+ require_relative 'step/load'
5
+
6
+ class Step
7
+
8
+ attr_accessor :path, :inputs, :dependencies, :task
9
+ def initialize(path, inputs = nil, dependencies = nil, &task)
10
+ @path = path
11
+ @inputs = inputs
12
+ @dependencies = dependencies
13
+ @task = task
14
+ end
15
+
16
+ def inputs
17
+ @inputs ||= begin
18
+ if Open.exists?(info_file)
19
+ info[:inputs]
20
+ else
21
+ []
22
+ end
23
+ end
24
+ end
25
+
26
+ def dependencies
27
+ @dependencies ||= begin
28
+ if Open.exists?(info_file)
29
+ info[:dependencies].collect do |path|
30
+ Step.load(path)
31
+ end
32
+ else
33
+ []
34
+ end
35
+ end
36
+ end
37
+
38
+ attr_accessor :type
39
+ def type
40
+ @type ||= @task.respond_to?(:type) ? @task.type : info[:type]
41
+ end
42
+
43
+ def name
44
+ @name ||= File.basename(@path)
45
+ end
46
+
47
+ def task_name
48
+ @task_name ||= @task.name if @task.respond_to?(:name)
49
+ end
50
+
51
+ def exec
52
+ self.instance_exec(*inputs, &task)
53
+ end
54
+
55
+ attr_reader :result
56
+ def run
57
+ return @result || self.load if done?
58
+ dependencies.each{|dep| dep.run }
59
+ @result = Persist.persist(name, type, :path => path) do
60
+ begin
61
+ merge_info :status => :start, :start => Time.now,
62
+ :pid => Process.pid, :pid_hostname => ENV["HOSTNAME"],
63
+ :inputs => inputs, :type => type,
64
+ :dependencies => dependencies.collect{|d| d.path }
65
+
66
+ @result = exec
67
+ ensure
68
+ if streaming?
69
+ ConcurrentStream.setup(@result) do
70
+ merge_info :status => :done, :end => Time.now
71
+ end
72
+ log :streaming
73
+ else
74
+ merge_info :status => :done, :end => Time.now
75
+ end
76
+ end
77
+ end
78
+ end
79
+
80
+ def done?
81
+ Open.exist?(path)
82
+ end
83
+
84
+ def streaming?
85
+ IO === @result || StringIO === @result
86
+ end
87
+
88
+ def stream
89
+ join
90
+ streaming? ? @result : Open.open(path)
91
+ end
92
+
93
+ def join
94
+ if streaming?
95
+ Open.consume_stream(@result, false)
96
+ @result = nil
97
+ end
98
+ end
99
+
100
+ def produce
101
+ run
102
+ join
103
+ end
104
+
105
+ def load
106
+ return @result unless @result.nil? || streaming?
107
+ join
108
+ done? ? Persist.load(path, type) : exec
109
+ end
110
+
111
+ def clean
112
+ FileUtils.rm path.find if path.exist?
113
+ end
114
+
115
+ def recursive_clean
116
+ dependencies.each do |dep|
117
+ dep.recursive_clean
118
+ end
119
+ clean
120
+ end
121
+
122
+ def step(task_name)
123
+ dependencies.each do |dep|
124
+ return dep if dep.task_name == task_name
125
+ end
126
+ nil
127
+ end
128
+
129
+ def digest_str
130
+ path
131
+ end
132
+ end
@@ -0,0 +1,114 @@
1
+ module Task
2
+
3
+ def format_input(value, type, options = {})
4
+ return value if IO === value || StringIO === value
5
+
6
+ value = value.load if Step === value
7
+ if String === value && ! [:path, :file].include?(type)
8
+ if Open.exists?(value)
9
+ Persist.load(value, type)
10
+ else
11
+ Persist.deserialize(value, type)
12
+ end
13
+ else
14
+ if m = type.to_s.match(/(.*)_array/)
15
+ if Array === value
16
+ value.collect{|v| format_input(v, m[1].to_sym, options) }
17
+ end
18
+ else
19
+ value
20
+ end
21
+ end
22
+ end
23
+
24
+ def assign_inputs(provided_inputs = {})
25
+ if self.inputs.nil? || (self.inputs.empty? && Array === provided_inputs)
26
+ case provided_inputs
27
+ when Array
28
+ return [provided_inputs, provided_inputs]
29
+ else
30
+ return [[], []]
31
+ end
32
+ end
33
+
34
+ IndiferentHash.setup(provided_inputs) if Hash === provided_inputs
35
+
36
+ input_array = []
37
+ non_default_inputs = []
38
+ self.inputs.each_with_index do |p,i|
39
+ name, type, desc, value, options = p
40
+ provided = Hash === provided_inputs ? provided_inputs[name] : provided_inputs[i]
41
+ provided = format_input(provided, type, options || {})
42
+ if ! provided.nil? && provided != value
43
+ non_default_inputs << name
44
+ input_array << provided
45
+ else
46
+ input_array << value
47
+ end
48
+ end
49
+
50
+ [input_array, non_default_inputs]
51
+ end
52
+
53
+ def digest_inputs(provided_inputs = {})
54
+ input_array, non_default_inputs = assign_inputs(provided_inputs)
55
+ if Array === provided_inputs
56
+ Misc.digest(input_array)
57
+ else
58
+ Misc.digest(input_array)
59
+ end
60
+ end
61
+
62
+ def process_inputs(provided_inputs = {})
63
+ input_array, non_default_inputs = assign_inputs provided_inputs
64
+ digest = Misc.digest(input_array)
65
+ [input_array, non_default_inputs, digest]
66
+ end
67
+
68
+ def save_file_input(orig_file, directory)
69
+ basename = File.basename(orig_file)
70
+ digest = Misc.digest(orig_file)
71
+ if basename.include? '.'
72
+ basename.sub!(/(.*)\.(.*)/, "\1-#{digest}.\2")
73
+ else
74
+ basename += "-#{digest}"
75
+ end
76
+ new_file = File.join(directory, 'saved_input_files', basename)
77
+ relative_file = File.join('.', 'saved_input_files', basename)
78
+ Open.link orig_file, new_file
79
+ relative_file
80
+ end
81
+
82
+ def save_inputs(directory, provided_inputs = {})
83
+ input_array, non_default_inputs = assign_inputs(provided_inputs)
84
+ self.inputs.each_with_index do |p,i|
85
+ name, type, desc, value, options = p
86
+ next unless non_default_inputs.include?(name)
87
+ input_file = File.join(directory, name.to_s)
88
+
89
+ if type == :file
90
+ relative_file = save_file_input(input_array[i], directory)
91
+ Persist.save(relative_file, input_file, type)
92
+ elsif type == :file_array
93
+ new_files = input_array[i].collect do |orig_file|
94
+ save_file_input(orig_file, directory)
95
+ end
96
+ Persist.save(new_files, input_file, type)
97
+ else
98
+ Persist.save(input_array[i], input_file, type)
99
+ end
100
+ end
101
+ end
102
+
103
+ def load_inputs(directory)
104
+ self.inputs.collect do |p|
105
+ name, type, desc, value, options = p
106
+ filename = File.join(directory, name.to_s)
107
+ if Open.exists?(filename) || filename = Dir.glob(File.join(filename + ".*")).first
108
+ Persist.load(filename, type)
109
+ else
110
+ value
111
+ end
112
+ end
113
+ end
114
+ end
@@ -0,0 +1,155 @@
1
+ require_relative '../meta_extension'
2
+ require_relative 'step'
3
+ require_relative 'task/inputs'
4
+
5
+ module Task
6
+ extend MetaExtension
7
+ extension_attr :name, :type, :inputs, :deps, :directory, :description
8
+
9
+ DEFAULT_NAME = "Default"
10
+
11
+ class << self
12
+ attr_accessor :default_directory
13
+
14
+ def default_directory
15
+ @default_directory ||= Path.setup('var/jobs/Task')
16
+ end
17
+ end
18
+
19
+ def inputs
20
+ @inputs ||= []
21
+ end
22
+
23
+ def recursive_inputs
24
+ return inputs if deps.nil?
25
+ deps.inject(inputs) do |acc,dep|
26
+ workflow, task = dep
27
+ next if workflow.nil?
28
+ acc += workflow.tasks[task].recursive_inputs
29
+ end
30
+ end
31
+
32
+ def directory
33
+ @directory ||= Task.default_directory
34
+ end
35
+
36
+ def exec_on(binding = self, *inputs)
37
+ binding.instance_exec(*inputs, &self)
38
+ end
39
+
40
+ def dependencies(id, provided_inputs, non_default_inputs = [])
41
+ return [] if deps.nil?
42
+ dependencies = []
43
+
44
+ provided_inputs ||= {}
45
+
46
+ load_dep = proc do |id, workflow, task, inputs, hash_options, dependencies|
47
+ task = hash_options[:task] if hash_options.include?(:task)
48
+ workflow = hash_options[:workflow] if hash_options.include?(:workflow)
49
+ id = hash_options[:id] if hash_options.include? :id
50
+
51
+ hash_inputs = hash_options.include?(:inputs)? hash_options[:inputs] : hash_options
52
+ inputs = IndiferentHash.add_defaults hash_inputs, inputs
53
+
54
+ resolved_inputs = {}
55
+ inputs.each do |k,v|
56
+ if Symbol === v
57
+ input_dep = dependencies.select{|d| d.task_name == v}.first
58
+ resolved_inputs[k] = input_dep || inputs[v] || k
59
+ else
60
+ resolved_inputs[k] = v
61
+ end
62
+ end
63
+ workflow.job(task, id, resolved_inputs)
64
+ end
65
+
66
+ deps.each do |workflow,task,options,block=nil|
67
+ if provided_inputs.include?(overriden = [workflow.name, task] * "#")
68
+ dep = provided_inputs[overriden]
69
+ dep = Step.new dep unless Step === dep
70
+ dep.type = workflow.tasks[task].type
71
+ dependencies << dep
72
+ non_default_inputs << overriden
73
+ next
74
+ end
75
+
76
+ options ||= {}
77
+ if block
78
+ inputs = IndiferentHash.add_defaults options.dup, provided_inputs
79
+
80
+ res = block.call id, inputs, dependencies
81
+
82
+ case res
83
+ when Step
84
+ dep = res
85
+ dependencies << dep
86
+ dep_non_default_inputs = dep.task.assign_inputs(dep.inputs).last
87
+ non_default_inputs.concat(dep_non_default_inputs - options.keys)
88
+ when Hash
89
+ new_options = res
90
+ dep = load_dep.call(id, workflow, task, inputs, new_options, dependencies)
91
+ dependencies << dep
92
+ dep_non_default_inputs = dep.task.assign_inputs(dep.inputs).last
93
+ dep_non_default_inputs -= options.keys
94
+ if new_options.include?(:inputs)
95
+ dep_non_default_inputs -= new_options[:inputs].keys
96
+ else
97
+ dep_non_default_inputs -= new_options.keys
98
+ end
99
+ non_default_inputs.concat(dep_non_default_inputs)
100
+ when Array
101
+ res.each do |_res|
102
+ if Hash === _res
103
+ new_options = _res
104
+ dep = load_dep.call(id, workflow, task, inputs, new_options, dependencies)
105
+ dependencies << dep
106
+ dep_non_default_inputs = dep.task.assign_inputs(dep.inputs).last
107
+ dep_non_default_inputs -= options.keys
108
+ if new_options.include?(:inputs)
109
+ dep_non_default_inputs -= new_options[:inputs].keys
110
+ else
111
+ dep_non_default_inputs -= new_options.keys
112
+ end
113
+ non_default_inputs.concat(dep_non_default_inputs)
114
+ else
115
+ dep = _res
116
+ dependencies << dep
117
+ dep_non_default_inputs = dep.task.assign_inputs(dep.inputs).last
118
+ non_default_inputs.concat(dep_non_default_inputs - options.keys)
119
+ end
120
+ end
121
+ end
122
+ else
123
+ inputs = IndiferentHash.add_defaults options.dup, provided_inputs
124
+ dep = load_dep.call(id, workflow, task, inputs, {}, dependencies)
125
+ dependencies << dep
126
+ dep_non_default_inputs = dep.task.assign_inputs(dep.inputs).last
127
+ non_default_inputs.concat(dep_non_default_inputs - options.keys)
128
+ end
129
+ end
130
+
131
+ dependencies
132
+ end
133
+
134
+ def job(id = DEFAULT_NAME, provided_inputs = nil )
135
+ provided_inputs, id = id, DEFAULT_NAME if (provided_inputs.nil? || provided_inputs.empty?) && (Hash === id || Array === id)
136
+ provided_inputs = {} if provided_inputs.nil?
137
+ id = DEFAULT_NAME if id.nil?
138
+
139
+ inputs, non_default_inputs, input_hash = process_inputs provided_inputs
140
+
141
+ dependencies = dependencies(id, provided_inputs, non_default_inputs)
142
+
143
+ non_default_inputs.concat provided_inputs.keys.select{|k| String === k && k.include?("#") } if Hash === provided_inputs
144
+
145
+ if non_default_inputs.any?
146
+ hash = Misc.digest(:inputs => input_hash, :non_default_inputs => non_default_inputs, :dependencies => dependencies)
147
+ Log.debug "Hash #{name} - #{hash}: #{Misc.digest_str(:inputs => inputs, :dependencies => dependencies)}"
148
+ id = [id, hash] * "_"
149
+ end
150
+
151
+ path = directory[id]
152
+
153
+ Step.new path.find, inputs, dependencies, &self
154
+ end
155
+ end