scout-rig 0.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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: bcb31a6421de24abf3a11f37bcf00a295ffcf9d5163c55f02ccdb806ff54c4fb
4
+ data.tar.gz: b45379e2fb720a9e397faf467f7d3fcd9b14f0212096d40fcb62d2208a4936d1
5
+ SHA512:
6
+ metadata.gz: cf8a3e4b67bdda6d867279d0b3e6166f5a942f0ee3a51d90105355121e8b2d8205b5f6468ec43c7cc487964d671bbeed73625815779fca633e64e4647e62a877
7
+ data.tar.gz: 8576a2ef1cd982db94fdab16ff207a1d9e7f189c125c5f92d4d0940091b452a64e69d3fa5af95f477131e8969686f47732ea79308c2f4f9734584e3ac3fbfffd
data/.document ADDED
@@ -0,0 +1,5 @@
1
+ lib/**/*.rb
2
+ bin/*
3
+ -
4
+ features/**/*.feature
5
+ LICENSE.txt
data/.vimproject ADDED
@@ -0,0 +1,30 @@
1
+ scout-rig=/$PWD filter="*" {
2
+ LICENSE.txt
3
+ README.rdoc
4
+ Rakefile
5
+ lib=lib {
6
+ scout-rig.rb
7
+ scout=scout{
8
+ python.rb
9
+ python=python{
10
+ paths.rb
11
+ run.rb
12
+ script.rb
13
+ util.rb
14
+ }
15
+ }
16
+ }
17
+ test=test{
18
+ test_helper.rb
19
+ }
20
+ python=python{
21
+ test.py
22
+ scout=scout{
23
+ __init__.py
24
+ workflow.py
25
+ workflow=workflow{
26
+ remote.py
27
+ }
28
+ }
29
+ }
30
+ }
data/LICENSE.txt ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2025 Miguel Vazquez
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,18 @@
1
+ = scout-rig
2
+
3
+ Description goes here.
4
+
5
+ == Contributing to scout-rig
6
+
7
+ * Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet.
8
+ * Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it.
9
+ * Fork the project.
10
+ * Start a feature/bugfix branch.
11
+ * Commit and push until you are happy with your contribution.
12
+ * Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
13
+ * Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
14
+
15
+ == Copyright
16
+
17
+ Copyright (c) 2025 Miguel Vazquez. See LICENSE.txt for
18
+ further details.
data/Rakefile ADDED
@@ -0,0 +1,50 @@
1
+ # encoding: utf-8
2
+
3
+ ENV["BRANCH"] = 'main'
4
+
5
+ require 'rubygems'
6
+ require 'rake'
7
+ require 'juwelier'
8
+ Juwelier::Tasks.new do |gem|
9
+ # gem is a Gem::Specification... see http://guides.rubygems.org/specification-reference/ for more options
10
+ gem.name = "scout-rig"
11
+ gem.homepage = "http://github.com/mikisvaz/scout-rig"
12
+ gem.license = "MIT"
13
+ gem.summary = %Q{Scouts rigging things together}
14
+ gem.description = %Q{Use other coding languages in your scout applications}
15
+ gem.email = "mikisvaz@gmail.com"
16
+ gem.authors = ["Miguel Vazquez"]
17
+
18
+ # Include your dependencies below. Runtime dependencies are required when using your gem,
19
+ # and development dependencies are only needed for development (ie running rake tasks, tests, etc)
20
+ # gem.add_runtime_dependency 'jabber4r', '> 0.1'
21
+ # gem.add_development_dependency 'rspec', '> 1.2.3'
22
+ gem.add_development_dependency "juwelier", "~> 2.1.0"
23
+
24
+ gem.add_runtime_dependency 'pycall', '> 0'
25
+ end
26
+ Juwelier::RubygemsDotOrgTasks.new
27
+ require 'rake/testtask'
28
+ Rake::TestTask.new(:test) do |test|
29
+ test.libs << 'lib' << 'test'
30
+ test.pattern = 'test/**/test_*.rb'
31
+ test.verbose = true
32
+ end
33
+
34
+ desc "Code coverage detail"
35
+ task :simplecov do
36
+ ENV['COVERAGE'] = "true"
37
+ Rake::Task['test'].execute
38
+ end
39
+
40
+ task :default => :test
41
+
42
+ require 'rdoc/task'
43
+ Rake::RDocTask.new do |rdoc|
44
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
45
+
46
+ rdoc.rdoc_dir = 'rdoc'
47
+ rdoc.title = "scout-rig #{version}"
48
+ rdoc.rdoc_files.include('README*')
49
+ rdoc.rdoc_files.include('lib/**/*.rb')
50
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.0
@@ -0,0 +1,27 @@
1
+ module ScoutPython
2
+ class << self
3
+ attr_accessor :paths
4
+ def paths
5
+ @paths ||= []
6
+ end
7
+ end
8
+
9
+ def self.add_path(path)
10
+ self.paths << path
11
+ end
12
+
13
+ def self.add_paths(paths)
14
+ self.paths.concat paths
15
+ end
16
+
17
+ def self.process_paths
18
+ ScoutPython.run_direct 'sys' do
19
+ ScoutPython.paths.each do |path|
20
+ sys.path.append path
21
+ end
22
+ nil
23
+ end
24
+ end
25
+
26
+ add_paths(Scout.python.find_all)
27
+ end
@@ -0,0 +1,122 @@
1
+ module ScoutPython
2
+ class << self
3
+ attr_accessor :thread
4
+ end
5
+
6
+ class Binding
7
+ include PyCall::Import
8
+
9
+ def run(*args, &block)
10
+ instance_exec(*args, &block)
11
+ end
12
+ end
13
+
14
+ def self.thread
15
+ @thread ||= defined?(@thread) ? @thread : nil
16
+ end
17
+
18
+ MUTEX= Mutex.new
19
+ QUEUE_IN ||= Queue.new
20
+ QUEUE_OUT ||= Queue.new
21
+ def self.synchronize(&block)
22
+ MUTEX.synchronize &block
23
+ end
24
+
25
+ def self.init_thread
26
+ if defined?(self.thread) && (self.thread && ! self.thread.alive?)
27
+ Log.warn "Reloading ScoutPython thread"
28
+ self.thread.join
29
+ self.thread = nil
30
+ end
31
+
32
+ self.thread ||= Thread.new do
33
+ require 'pycall'
34
+ ScoutPython.process_paths
35
+ begin
36
+ while block = QUEUE_IN.pop
37
+ break if block == :stop
38
+ res =
39
+ begin
40
+ module_eval(&block)
41
+ rescue Exception
42
+ Log.exception $!
43
+ raise $!
44
+ end
45
+
46
+ QUEUE_OUT.push res
47
+ end
48
+ rescue Exception
49
+ Log.exception $!
50
+ raise $!
51
+ ensure
52
+ PyCall.finalize if PyCall.respond_to?(:finalize)
53
+ end
54
+ end
55
+ end
56
+
57
+ def self.run_in_thread(&block)
58
+ self.synchronize do
59
+ init_thread
60
+ QUEUE_IN.push block
61
+ QUEUE_OUT.pop
62
+ end
63
+ end
64
+
65
+ def self.stop_thread
66
+ self.synchronize do
67
+ QUEUE_IN.push :stop
68
+ end if self.thread && self.thread.alive?
69
+ self.thread.join if self.thread
70
+ end
71
+
72
+ def self.run_direct(mod = nil, imports = nil, &block)
73
+ if mod
74
+ if Hash === imports
75
+ pyimport mod, **imports
76
+ elsif imports.nil?
77
+ pyimport mod
78
+ else
79
+ pyfrom mod, :import => imports
80
+ end
81
+ end
82
+
83
+ module_eval(&block)
84
+ end
85
+
86
+ def self.run_threaded(mod = nil, imports = nil, &block)
87
+ run_in_thread do
88
+ if Hash === imports
89
+ pyimport mod, **imports
90
+ elsif imports.nil?
91
+ pyimport mod
92
+ else
93
+ pyfrom mod, :import => imports
94
+ end
95
+ end if mod
96
+
97
+ run_in_thread(&block)
98
+ end
99
+
100
+ def self.run_simple(mod = nil, imports = nil, &block)
101
+ self.synchronize do
102
+ ScoutPython.process_paths
103
+ run_direct(mod, imports, &block)
104
+ end
105
+ end
106
+
107
+ class << self
108
+ alias run run_simple
109
+ end
110
+
111
+ def self.run_log(mod = nil, imports = nil, severity = 0, severity_err = nil, &block)
112
+ Log.trap_std("Python STDOUT", "Python STDERR", severity, severity_err) do
113
+ run(mod, imports, &block)
114
+ end
115
+ end
116
+
117
+ def self.run_log_stderr(mod = nil, imports = nil, severity = 0, &block)
118
+ Log.trap_stderr("Python STDERR", severity) do
119
+ run(mod, imports, &block)
120
+ end
121
+ end
122
+ end
@@ -0,0 +1,110 @@
1
+ module ScoutPython
2
+ def self.ruby2python(object)
3
+ case object
4
+ when Float::INFINITY
5
+ "inf"
6
+ when nil
7
+ "None"
8
+ when ":NA"
9
+ "None"
10
+ when Symbol
11
+ "#{ object }"
12
+ when String
13
+ object = object.dup if Path === object
14
+ object[0] == ":" ? object[1..-1] : "'#{ object }'"
15
+ when Numeric
16
+ object
17
+ when TrueClass
18
+ "True"
19
+ when FalseClass
20
+ "False"
21
+ when Array
22
+ "[#{object.collect{|e| ruby2python(e) } * ", "}]"
23
+ when Hash
24
+ "{" << object.collect{|k,v| [ruby2python(k.to_s), ruby2python(v)] * ":"} * ", " << "}"
25
+ else
26
+ raise "Type of object not known: #{ object.inspect }"
27
+ end
28
+ end
29
+
30
+ def self.load_script_variables(variables = {})
31
+ code = "# Variables\nimport scout\n"
32
+ tmp_files = []
33
+ variables.each do |name,value|
34
+ case value
35
+ when TSV
36
+ tmp_file = TmpFile.tmp_file
37
+ tmp_files << tmp_file
38
+ Open.write(tmp_file, value.to_s)
39
+ code << "#{name} = scout.tsv('#{tmp_file}')" << "\n"
40
+ else
41
+ code << "#{name} = #{ScoutPython.ruby2python(value)}" << "\n"
42
+ end
43
+ end
44
+
45
+ [code, tmp_files]
46
+ end
47
+
48
+ def self.save_script_result_pickle(file)
49
+ <<-EOF
50
+
51
+ # Save
52
+ try: result
53
+ except NameError: result = None
54
+ if result is not None:
55
+ import pickle
56
+ file = open('#{file}', 'wb')
57
+ # dump information to that file
58
+ pickle.dump(result, file)
59
+ EOF
60
+ end
61
+
62
+ def self.load_pickle(file)
63
+ require 'python/pickle'
64
+ Log.debug ("Loading pickle #{file}")
65
+ Python::Pickle.load_file(file)
66
+ end
67
+
68
+ def self.save_script_result_json(file)
69
+ <<-EOF
70
+
71
+ # Save
72
+ try: result
73
+ except NameError: result = None
74
+ if result is not None:
75
+ import json
76
+ file = open('#{file}', 'w', encoding='utf-8')
77
+ # dump information to that file
78
+ file.write(json.dumps(result))
79
+ file.flush
80
+ file.close
81
+ EOF
82
+ end
83
+
84
+ def self.load_json(file)
85
+ JSON.load_file(file)
86
+ end
87
+
88
+ class << self
89
+ alias save_script_result save_script_result_pickle
90
+ alias load_result load_pickle
91
+ end
92
+
93
+ def self.script(text, variables = {})
94
+ if variables.any?
95
+ variable_definitions, tmp_files = load_script_variables(variables)
96
+ text = variable_definitions + "\n# Script\n" + text
97
+ end
98
+
99
+ TmpFile.with_file do |tmp_file|
100
+ text += save_script_result(tmp_file)
101
+ Log.debug "Running python script:\n#{text.dup}"
102
+ path_env = ScoutPython.paths * ":"
103
+ CMD.cmd_log("env PYTHONPATH=#{path_env} python", {in: text})
104
+ tmp_files.each{|file| Open.rm_rf file } if tmp_files
105
+ if Open.exists?(tmp_file)
106
+ load_result(tmp_file)
107
+ end
108
+ end
109
+ end
110
+ end
@@ -0,0 +1,52 @@
1
+ module ScoutPython
2
+ def self.py2ruby_a(array)
3
+ PyCall::List.(array).to_a
4
+ end
5
+
6
+ class << self
7
+ alias to_a py2ruby_a
8
+ end
9
+
10
+ def self.tsv2df(tsv)
11
+ df = nil
12
+ ScoutPython.run_direct 'pandas' do
13
+ df = pandas.DataFrame.new(tsv.values, columns: tsv.fields, index: tsv.keys)
14
+ df.columns.name = tsv.key_field
15
+ end
16
+ df
17
+ end
18
+
19
+ def self.df2tsv(tuple, options = {})
20
+ options = IndiferentHash.add_defaults options, :type => :list
21
+ IndiferentHash.setup options
22
+ tsv = TSV.setup({}, options)
23
+ tsv.key_field = options[:key_field] || tuple.columns.name.to_s
24
+ tsv.fields = py2ruby_a(tuple.columns.values)
25
+ keys = py2ruby_a(tuple.index.values)
26
+ PyCall.len(tuple.index).times do |i|
27
+ k = keys[i]
28
+ tsv[k] = py2ruby_a(tuple.values[i])
29
+ end
30
+ tsv
31
+ end
32
+
33
+ def self.list2ruby(list)
34
+ return list unless PyCall::List === list
35
+ list.collect do |e|
36
+ list2ruby(e)
37
+ end
38
+ end
39
+
40
+ def self.numpy2ruby(numpy)
41
+ list2ruby(numpy.tolist)
42
+ end
43
+
44
+ def self.obj2hash(obj)
45
+ hash = {}
46
+ ScoutPython.iterate obj.keys do |k|
47
+ hash[k] = obj[k]
48
+ end
49
+ hash
50
+ end
51
+ end
52
+
@@ -0,0 +1,140 @@
1
+ require 'scout'
2
+ require 'pycall/import'
3
+ require_relative 'python/paths'
4
+ require_relative 'python/run'
5
+
6
+ module ScoutPython
7
+ extend PyCall::Import
8
+
9
+ class ScoutPythonException < StandardError; end
10
+
11
+ def self.init_scout
12
+ if ! defined?(@@__init_scout_python) || ! @@__init_scout_python
13
+ ScoutPython.process_paths
14
+ res = ScoutPython.run do
15
+ Log.debug "Loading python 'scout' module into pycall ScoutPython module"
16
+ pyimport("scout")
17
+ end
18
+ @@__init_scout_python = true
19
+ end
20
+ end
21
+
22
+ def self.import_method(module_name, method_name, as = nil)
23
+ init_scout
24
+ ScoutPython.pyfrom module_name, import: method_name
25
+ ScoutPython.method(method_name)
26
+ end
27
+
28
+ def self.call_method(module_name, method_name, *args)
29
+ ScoutPython.import_method(module_name, method_name).call(*args)
30
+ end
31
+
32
+ def self.get_module(module_name)
33
+ init_scout
34
+ save_module_name = module_name.to_s.gsub(".", "_")
35
+ ScoutPython.pyimport(module_name, as: save_module_name)
36
+ ScoutPython.send(save_module_name)
37
+ end
38
+
39
+ def self.get_class(module_name, class_name)
40
+ mod = get_module(module_name)
41
+ mod.send(class_name)
42
+ end
43
+
44
+ def self.class_new_obj(module_name, class_name, args={})
45
+ ScoutPython.get_class(module_name, class_name).new(**args)
46
+ end
47
+
48
+ def self.exec(script)
49
+ PyCall.exec(script)
50
+ end
51
+
52
+ def self.iterate_index(elem, options = {})
53
+ bar = options[:bar]
54
+
55
+ len = PyCall.len(elem)
56
+ case bar
57
+ when TrueClass
58
+ bar = Log::ProgressBar.new nil, :desc => "ScoutPython iterate"
59
+ when String
60
+ bar = Log::ProgressBar.new nil, :desc => bar
61
+ end
62
+
63
+ len.times do |i|
64
+ begin
65
+ yield elem[i]
66
+ bar.tick if bar
67
+ rescue PyCall::PyError
68
+ if $!.type.to_s == "<class 'StopIteration'>"
69
+ break
70
+ else
71
+ raise $!
72
+ end
73
+ rescue
74
+ bar.error if bar
75
+ raise $!
76
+ end
77
+ end
78
+
79
+ Log::ProgressBar.remove_bar bar if bar
80
+ nil
81
+ end
82
+
83
+ def self.iterate(iterator, options = {}, &block)
84
+ if ! iterator.respond_to?(:__next__)
85
+ if iterator.respond_to?(:__iter__)
86
+ iterator = iterator.__iter__
87
+ else
88
+ return iterate_index(iterator, options, &block)
89
+ end
90
+ end
91
+
92
+ bar = options[:bar]
93
+
94
+ case bar
95
+ when TrueClass
96
+ bar = Log::ProgressBar.new nil, :desc => "ScoutPython iterate"
97
+ when String
98
+ bar = Log::ProgressBar.new nil, :desc => bar
99
+ end
100
+
101
+ while true
102
+ begin
103
+ elem = iterator.__next__
104
+ yield elem
105
+ bar.tick if bar
106
+ rescue PyCall::PyError
107
+ if $!.type.to_s == "<class 'StopIteration'>"
108
+ break
109
+ else
110
+ raise $!
111
+ end
112
+ rescue
113
+ bar.error if bar
114
+ raise $!
115
+ end
116
+ end
117
+
118
+ Log::ProgressBar.remove_bar bar if bar
119
+ nil
120
+ end
121
+
122
+ def self.collect(iterator, options = {}, &block)
123
+ acc = []
124
+ self.iterate(iterator, options) do |elem|
125
+ res = block.call elem
126
+ acc << res
127
+ end
128
+ acc
129
+ end
130
+
131
+ def self.new_binding
132
+ Binding.new
133
+ end
134
+
135
+ def self.binding_run(binding = nil, *args, &block)
136
+ binding = new_binding
137
+ binding.instance_exec *args, &block
138
+ end
139
+ end
140
+
data/lib/scout-rig.rb ADDED
@@ -0,0 +1,5 @@
1
+ require 'scout'
2
+ require 'scout/path'
3
+ require 'scout/resource'
4
+ Path.add_path :scout_rig, File.join(Path.caller_lib_dir(__FILE__), "{TOPLEVEL}/{SUBPATH}")
5
+