scout-gear 5.2.0 → 7.1.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.
@@ -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",