scout-gear 5.2.0 → 7.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,144 @@
1
+ module TSV
2
+ def self.cast_value(value, cast)
3
+ if Array === value
4
+ value.collect{|e| cast_value(e, cast) }
5
+ else
6
+ value.send(cast)
7
+ end
8
+ end
9
+
10
+ def self.parse_line(line, type: :list, key: 0, positions: nil, sep: "\t", sep2: "|", cast: nil)
11
+ items = line.split(sep, -1)
12
+
13
+ if positions.nil? && key == 0
14
+ key = items.shift
15
+ elsif positions.nil?
16
+ key = items.delete(key)
17
+ else
18
+ key, items = items[key], items.values_at(*positions)
19
+ end
20
+
21
+ items = case type
22
+ when :list
23
+ items
24
+ when :single
25
+ items.first
26
+ when :flat
27
+ [items]
28
+ when :double
29
+ items.collect{|i| i.split(sep2, -1) }
30
+ end
31
+
32
+ key = key.partition(sep2).first if type == :double
33
+
34
+ if cast
35
+ items = cast_value(items, cast)
36
+ end
37
+
38
+ [key, items]
39
+ end
40
+
41
+ def self.parse_stream(stream, data: nil, merge: true, type: :list, fix: true, bar: false, first_line: nil, **kargs, &block)
42
+ begin
43
+ bar = Log::ProgressBar.new_bar(bar) if bar
44
+
45
+ data = {} if data.nil?
46
+ merge = false if type != :double
47
+ line = first_line || stream.gets
48
+ while line
49
+ begin
50
+ line.strip!
51
+ line = Misc.fixutf8(line) if fix
52
+ bar.tick if bar
53
+ key, items = parse_line(line, type: type, **kargs)
54
+
55
+ if block_given?
56
+ res = block.call(key, items)
57
+ data[key] = res unless res.nil?
58
+ next
59
+ end
60
+
61
+ if ! merge || ! data.include?(key)
62
+ data[key] = items
63
+ else
64
+ current = data[key]
65
+ if merge == :concat
66
+ items.each_with_index do |new,i|
67
+ next if new.empty?
68
+ current[i].concat(new)
69
+ end
70
+ else
71
+ merged = []
72
+ items.each_with_index do |new,i|
73
+ next if new.empty?
74
+ merged[i] = current[i] + new
75
+ end
76
+ data[key] = merged
77
+ end
78
+ end
79
+ ensure
80
+ line = stream.gets
81
+ end
82
+ end
83
+ data
84
+ ensure
85
+ Log::ProgressBar.remove_bar(bar) if bar
86
+ end
87
+ end
88
+
89
+ def self.parse_header(stream, fix: true, header_hash: '#', sep: "\n")
90
+ raise "Closed stream" if IO === stream && stream.closed?
91
+
92
+ options = {}
93
+ preamble = []
94
+
95
+ # Get line
96
+
97
+ #Thread.pass while IO.select([stream], nil, nil, 1).nil? if IO === stream
98
+ line = stream.gets
99
+ return {} if line.nil?
100
+ line = Misc.fixutf8 line.chomp if fix
101
+
102
+ # Process options line
103
+ if line and (String === header_hash && m = line.match(/^#{header_hash}: (.*)/))
104
+ options = IndiferentHash.string2hash m.captures.first.chomp
105
+ line = stream.gets
106
+ line = Misc.fixutf8 line.chomp if line && fix
107
+ end
108
+
109
+ # Determine separator
110
+ sep = options[:sep] if options[:sep]
111
+
112
+ # Process fields line
113
+ preamble << line if line
114
+ while line && (TrueClass === header_hash || (String === header_hash && line.start_with?(header_hash)))
115
+ fields = line.split(sep, -1)
116
+ key_field = fields.shift
117
+ key_field = key_field.sub(header_hash, '') if String === header_hash && ! header_hash.empty?
118
+
119
+ line = (header_hash != "" ? stream.gets : nil)
120
+ line = Misc.fixutf8 line.chomp if line
121
+ preamble << line if line
122
+ break if TrueClass === header_hash || header_hash == ""
123
+ end
124
+
125
+ preamble = preamble[0..-3] * "\n"
126
+
127
+ line ||= stream.gets
128
+
129
+ first_line = line
130
+
131
+ [options, key_field, fields, first_line, preamble]
132
+ end
133
+
134
+ def self.parse(stream, **kwargs)
135
+ options, key_field, fields, first_line, preamble = parse_header(stream)
136
+
137
+ options.each do |option,value|
138
+ option = option.to_sym
139
+ kwargs[option] = value unless kwargs.include?(option)
140
+ end
141
+ data = parse_stream(stream, first_line: first_line, **kwargs)
142
+ TSV.setup data, :key_field => key_field, :fields => fields
143
+ end
144
+ end
data/lib/scout/tsv.rb ADDED
@@ -0,0 +1,14 @@
1
+ require_relative 'meta_extension'
2
+ require_relative 'tsv/parser'
3
+
4
+ module TSV
5
+ extend MetaExtension
6
+ extension_attr :key_field, :fields
7
+
8
+ def self.open(file, options = {})
9
+ Open.open(file) do |f|
10
+ TSV.parse(f,**options)
11
+ end
12
+ end
13
+ end
14
+
@@ -0,0 +1,119 @@
1
+ require 'scout/open'
2
+ require 'scout/semaphore'
3
+ require 'scout/exceptions'
4
+ class WorkQueue
5
+ class Socket
6
+ attr_accessor :sread, :swrite, :write_sem, :read_sem, :cleaned
7
+ def initialize(serializer = nil)
8
+ @sread, @swrite = Open.pipe
9
+
10
+ @serializer = serializer || Marshal
11
+
12
+ @key = "/" << rand(1000000000).to_s << '.' << Process.pid.to_s;
13
+ @write_sem = @key + '.in'
14
+ @read_sem = @key + '.out'
15
+ Log.debug "Creating socket semaphores: #{@key}"
16
+ ScoutSemaphore.create_semaphore(@write_sem,1)
17
+ ScoutSemaphore.create_semaphore(@read_sem,1)
18
+ end
19
+
20
+ def clean
21
+ @cleaned = true
22
+ @sread.close unless @sread.closed?
23
+ @swrite.close unless @swrite.closed?
24
+ Log.low "Destroying socket semaphores: #{[@key] * ", "}"
25
+ ScoutSemaphore.delete_semaphore(@write_sem)
26
+ ScoutSemaphore.delete_semaphore(@read_sem)
27
+ end
28
+
29
+
30
+ def dump(obj)
31
+ stream = @swrite
32
+ obj.concurrent_stream = nil if obj.respond_to?(:concurrent_stream)
33
+ case obj
34
+ when Integer
35
+ size_head = [obj,"I"].pack 'La'
36
+ str = size_head
37
+ when nil
38
+ size_head = [0,"N"].pack 'La'
39
+ str = size_head
40
+ when String
41
+ payload = obj
42
+ size_head = [payload.bytesize,"C"].pack 'La'
43
+ str = size_head << payload
44
+ else
45
+ payload = @serializer.dump(obj)
46
+ size_head = [payload.bytesize,"S"].pack 'La'
47
+ str = size_head << payload
48
+ end
49
+
50
+ write_length = str.length
51
+ wrote = stream.write(str)
52
+ while wrote < write_length
53
+ wrote += stream.write(str[wrote..-1])
54
+ end
55
+ end
56
+
57
+ def load
58
+ stream = @sread
59
+ size_head = Open.read_stream stream, 5
60
+
61
+ size, type = size_head.unpack('La')
62
+
63
+ return nil if type == "N"
64
+ return size.to_i if type == "I"
65
+ begin
66
+ payload = Open.read_stream stream, size
67
+ case type
68
+ when "S"
69
+ begin
70
+ @serializer.load(payload)
71
+ rescue Exception
72
+ Log.exception $!
73
+ raise $!
74
+ end
75
+ when "C"
76
+ payload
77
+ end
78
+ rescue TryAgain
79
+ retry
80
+ end
81
+ end
82
+
83
+ def closed_read?
84
+ @sread.closed?
85
+ end
86
+
87
+ def closed_write?
88
+ @swrite.closed?
89
+ end
90
+
91
+ def close_write
92
+ self.dump ClosedStream.new
93
+ @swrite.close unless closed_write?
94
+ end
95
+
96
+ def close_read
97
+ @sread.close unless closed_read?
98
+ end
99
+
100
+ #{{{ ACCESSOR
101
+ def push(obj)
102
+ ScoutSemaphore.synchronize(@write_sem) do
103
+ self.dump(obj)
104
+ end
105
+ end
106
+
107
+ def pop
108
+ ScoutSemaphore.synchronize(@read_sem) do
109
+ res = self.load
110
+ raise res if ClosedStream === res
111
+ res
112
+ end
113
+ end
114
+
115
+ alias write push
116
+
117
+ alias read pop
118
+ end
119
+ end
@@ -0,0 +1,59 @@
1
+ class WorkQueue
2
+ class Worker
3
+ attr_accessor :pid, :ignore_ouput
4
+ def initialize(ignore_ouput = false)
5
+ @ignore_output = ignore_ouput
6
+ end
7
+
8
+ def run
9
+ @pid = Process.fork do
10
+ Log.debug "Worker start with #{Process.pid}"
11
+ yield
12
+ end
13
+ end
14
+
15
+ def process(input, output = nil, &block)
16
+ run do
17
+ begin
18
+ while obj = input.read
19
+ if DoneProcessing === obj
20
+ output.write DoneProcessing.new
21
+ raise obj
22
+ end
23
+ res = block.call obj
24
+ output.write res unless output.nil? || ignore_ouput || res == :ignore
25
+ end
26
+ rescue DoneProcessing
27
+ rescue Interrupt
28
+ rescue Exception
29
+ output.write WorkerException.new($!, Process.pid)
30
+ exit -1
31
+ end
32
+ end
33
+ end
34
+
35
+ def abort
36
+ begin
37
+ Log.log "Aborting worker #{@pid}"
38
+ Process.kill "INT", @pid
39
+ rescue Errno::ECHILD
40
+ end
41
+ end
42
+
43
+ def join
44
+ Log.log "Joining worker #{@pid}"
45
+ Process.waitpid @pid
46
+ end
47
+
48
+ def self.join(workers)
49
+ workers = [workers] unless Array === workers
50
+ begin
51
+ while pid = Process.wait
52
+ status = $?
53
+ worker = workers.select{|w| w.pid == pid }.first
54
+ end
55
+ rescue Errno::ECHILD
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,113 @@
1
+ require_relative 'work_queue/socket'
2
+ require_relative 'work_queue/worker'
3
+
4
+ class WorkQueue
5
+ attr_accessor :workers, :worker_proc, :callback
6
+
7
+ def initialize(workers = 0, &block)
8
+ @input = WorkQueue::Socket.new
9
+ @output = WorkQueue::Socket.new
10
+ @workers = workers.times.collect{ Worker.new }
11
+ @worker_proc = block
12
+ @worker_mutex = Mutex.new
13
+ @removed_workers = []
14
+ end
15
+
16
+ def add_worker(&block)
17
+ worker = Worker.new
18
+ @worker_mutex.synchronize do
19
+ @workers.push(worker)
20
+ if block_given?
21
+ worker.process @input, @output, &block
22
+ else
23
+ worker.process @input, @output, &@worker_proc
24
+ end
25
+ end
26
+ worker
27
+ end
28
+
29
+ def ignore_ouput
30
+ @workers.each{|w| w.ignore_ouput = true }
31
+ end
32
+
33
+ def remove_one_worker
34
+ @input.write DoneProcessing.new
35
+ end
36
+
37
+ def remove_worker(pid)
38
+ @worker_mutex.synchronize do
39
+ @workers.delete_if{|w| w.pid == pid }
40
+ @removed_workers << pid
41
+ end
42
+ end
43
+
44
+ def process(&callback)
45
+ @reader = Thread.new do |parent|
46
+ begin
47
+ Thread.current.report_on_exception = false
48
+ Thread.current["name"] = "Output reader #{Process.pid}"
49
+ @done_workers ||= []
50
+ while true
51
+ obj = @output.read
52
+ if DoneProcessing === obj
53
+ done = @worker_mutex.synchronize do
54
+ Log.low "Worker #{obj.pid} done"
55
+ @done_workers << obj.pid
56
+ @done_workers.length == @removed_workers.length + @workers.length
57
+ end
58
+ break if done
59
+ elsif Exception === obj
60
+ raise obj
61
+ else
62
+ callback.call obj if callback
63
+ end
64
+ end
65
+ rescue DoneProcessing
66
+ rescue Aborted
67
+ rescue WorkerException
68
+ Log.error "Exception in worker #{obj.pid} #{Log.fingerprint obj.exception}"
69
+ self.abort
70
+ raise obj.exception
71
+ end
72
+ end
73
+
74
+ @workers.each do |w|
75
+ w.process @input, @output, &@worker_proc
76
+ end
77
+
78
+ Thread.pass until @reader["name"]
79
+
80
+ @waiter = Thread.new do
81
+ begin
82
+ Thread.current.report_on_exception = false
83
+ Thread.current["name"] = "Worker waiter #{Process.pid}"
84
+ while true
85
+ pid = Process.wait
86
+ remove_worker(pid)
87
+ break if workers.empty?
88
+ end
89
+ end
90
+ end
91
+
92
+ Thread.pass until @waiter["name"]
93
+ end
94
+
95
+ def write(obj)
96
+ @input.write obj
97
+ end
98
+
99
+ def abort
100
+ workers.each{|w| w.abort }
101
+ end
102
+
103
+ def close
104
+ @worker_mutex.synchronize{ @workers.length }.times do
105
+ @input.write DoneProcessing.new()
106
+ end
107
+ end
108
+
109
+ def join
110
+ @waiter.join if @waiter
111
+ @reader.join if @reader
112
+ end
113
+ end
@@ -55,9 +55,9 @@ class Step
55
55
 
56
56
  def report_status(status, message = nil)
57
57
  if message.nil?
58
- Log.info Log.color(:green, status.to_s) + " " + Log.color(:blue, path)
58
+ Log.info Log.color(:status, status, true) + " " + Log.color(:path, path)
59
59
  else
60
- Log.info Log.color(:green, status.to_s) + " " + Log.color(:blue, path) + " " + message
60
+ Log.info Log.color(:status, status, true) + " " + Log.color(:path, path) + " " + message
61
61
  end
62
62
  end
63
63
 
@@ -109,7 +109,8 @@ class Step
109
109
  end
110
110
 
111
111
  def clean
112
- FileUtils.rm path.find if path.exist?
112
+ Open.rm path if Open.exist?(path)
113
+ Open.rm info_file if Open.exist?(info_file)
113
114
  end
114
115
 
115
116
  def recursive_clean
@@ -143,8 +143,8 @@ module Task
143
143
  non_default_inputs.concat provided_inputs.keys.select{|k| String === k && k.include?("#") } if Hash === provided_inputs
144
144
 
145
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)}"
146
+ hash = Misc.digest(:inputs => input_hash, :dependencies => dependencies)
147
+ Log.debug "Hash #{name} - #{hash}: #{Misc.digest_str(:inputs => inputs, :non_default_inputs => non_default_inputs, :dependencies => dependencies)}"
148
148
  id = [id, hash] * "_"
149
149
  end
150
150
 
data/scout-gear.gemspec CHANGED
@@ -2,16 +2,16 @@
2
2
  # DO NOT EDIT THIS FILE DIRECTLY
3
3
  # Instead, edit Juwelier::Tasks in Rakefile, and run 'rake gemspec'
4
4
  # -*- encoding: utf-8 -*-
5
- # stub: scout-gear 5.2.0 ruby lib
5
+ # stub: scout-gear 7.1.0 ruby lib
6
6
 
7
7
  Gem::Specification.new do |s|
8
8
  s.name = "scout-gear".freeze
9
- s.version = "5.2.0"
9
+ s.version = "7.1.0"
10
10
 
11
11
  s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version=
12
12
  s.require_paths = ["lib".freeze]
13
13
  s.authors = ["Miguel Vazquez".freeze]
14
- s.date = "2023-04-28"
14
+ s.date = "2023-05-01"
15
15
  s.description = "Temporary files, logs, etc.".freeze
16
16
  s.email = "mikisvaz@gmail.com".freeze
17
17
  s.executables = ["scout".freeze]
@@ -69,6 +69,7 @@ Gem::Specification.new do |s|
69
69
  "lib/scout/resource/produce/rake.rb",
70
70
  "lib/scout/resource/scout.rb",
71
71
  "lib/scout/resource/util.rb",
72
+ "lib/scout/semaphore.rb",
72
73
  "lib/scout/simple_opt.rb",
73
74
  "lib/scout/simple_opt/accessor.rb",
74
75
  "lib/scout/simple_opt/doc.rb",
@@ -76,6 +77,11 @@ Gem::Specification.new do |s|
76
77
  "lib/scout/simple_opt/parse.rb",
77
78
  "lib/scout/simple_opt/setup.rb",
78
79
  "lib/scout/tmpfile.rb",
80
+ "lib/scout/tsv.rb",
81
+ "lib/scout/tsv/parser.rb",
82
+ "lib/scout/work_queue.rb",
83
+ "lib/scout/work_queue/socket.rb",
84
+ "lib/scout/work_queue/worker.rb",
79
85
  "lib/scout/workflow.rb",
80
86
  "lib/scout/workflow/definition.rb",
81
87
  "lib/scout/workflow/documentation.rb",
@@ -96,8 +102,11 @@ Gem::Specification.new do |s|
96
102
  "scout_commands/workflow/list",
97
103
  "scout_commands/workflow/task",
98
104
  "scout_commands/workflow/task_old",
105
+ "share/color/color_names",
106
+ "share/color/diverging_colors.hex",
99
107
  "test/scout/indiferent_hash/test_case_insensitive.rb",
100
108
  "test/scout/indiferent_hash/test_options.rb",
109
+ "test/scout/log/test_color.rb",
101
110
  "test/scout/log/test_progress.rb",
102
111
  "test/scout/misc/test_digest.rb",
103
112
  "test/scout/misc/test_filesystem.rb",
@@ -128,8 +137,14 @@ Gem::Specification.new do |s|
128
137
  "test/scout/test_path.rb",
129
138
  "test/scout/test_persist.rb",
130
139
  "test/scout/test_resource.rb",
140
+ "test/scout/test_semaphore.rb",
131
141
  "test/scout/test_tmpfile.rb",
142
+ "test/scout/test_tsv.rb",
143
+ "test/scout/test_work_queue.rb",
132
144
  "test/scout/test_workflow.rb",
145
+ "test/scout/tsv/test_parser.rb",
146
+ "test/scout/work_queue/test_socket.rb",
147
+ "test/scout/work_queue/test_worker.rb",
133
148
  "test/scout/workflow/step/test_info.rb",
134
149
  "test/scout/workflow/step/test_load.rb",
135
150
  "test/scout/workflow/task/test_inputs.rb",