pycall 0.1.0.alpha.20170711 → 1.0.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 +4 -4
- data/.gitignore +3 -0
- data/.travis.yml +13 -1
- data/CHANGES.md +35 -0
- data/Gemfile +0 -5
- data/README.md +41 -49
- data/Rakefile +22 -1
- data/appveyor.yml +9 -26
- data/examples/classifier_comparison.rb +52 -52
- data/examples/hist.rb +11 -11
- data/examples/notebooks/classifier_comparison.ipynb +51 -66
- data/examples/notebooks/forest_importances.ipynb +26 -49
- data/examples/notebooks/iruby_integration.ipynb +15 -36
- data/examples/notebooks/lorenz_attractor.ipynb +16 -47
- data/examples/notebooks/polar_axes.ipynb +29 -64
- data/examples/notebooks/sum_benchmarking.ipynb +109 -103
- data/examples/notebooks/xkcd_style.ipynb +12 -12
- data/examples/plot_forest_importances_faces.rb +8 -8
- data/examples/sum_benchmarking.rb +15 -19
- data/ext/pycall/extconf.rb +3 -0
- data/ext/pycall/gc.c +74 -0
- data/ext/pycall/libpython.c +217 -0
- data/ext/pycall/pycall.c +2184 -0
- data/ext/pycall/pycall_internal.h +700 -0
- data/ext/pycall/range.c +69 -0
- data/ext/pycall/ruby_wrapper.c +432 -0
- data/lib/pycall.rb +91 -19
- data/lib/pycall/dict.rb +28 -82
- data/lib/pycall/error.rb +10 -0
- data/lib/pycall/import.rb +45 -40
- data/lib/pycall/init.rb +44 -20
- data/lib/pycall/libpython.rb +6 -380
- data/lib/pycall/libpython/finder.rb +170 -0
- data/lib/pycall/list.rb +21 -51
- data/lib/pycall/pretty_print.rb +9 -0
- data/lib/pycall/pyerror.rb +14 -20
- data/lib/pycall/pyobject_wrapper.rb +157 -158
- data/lib/pycall/python/PyCall/__init__.py +1 -0
- data/lib/pycall/python/PyCall/six.py +23 -0
- data/lib/pycall/pytypeobject_wrapper.rb +79 -0
- data/lib/pycall/slice.rb +3 -22
- data/lib/pycall/tuple.rb +1 -7
- data/lib/pycall/version.rb +1 -1
- data/lib/pycall/wrapper_object_cache.rb +61 -0
- data/pycall.gemspec +4 -2
- data/tasks/pycall.rake +7 -0
- metadata +65 -27
- data/lib/pycall/eval.rb +0 -57
- data/lib/pycall/exception.rb +0 -13
- data/lib/pycall/pyobject.rb +0 -58
- data/lib/pycall/ruby_wrapper.rb +0 -137
- data/lib/pycall/type_object.rb +0 -11
- data/lib/pycall/types.rb +0 -19
- data/lib/pycall/utils.rb +0 -106
@@ -0,0 +1 @@
|
|
1
|
+
|
@@ -0,0 +1,23 @@
|
|
1
|
+
import sys
|
2
|
+
|
3
|
+
PY3 = sys.version_info[0] == 3
|
4
|
+
|
5
|
+
if PY3:
|
6
|
+
import builtins
|
7
|
+
else:
|
8
|
+
import __builtin__ as builtins
|
9
|
+
|
10
|
+
if PY3:
|
11
|
+
exec_ = getattr(builtins, 'exec')
|
12
|
+
else:
|
13
|
+
def exec_(_code_, _globals_=None, _locals_=None):
|
14
|
+
"""Execute code in a namespace."""
|
15
|
+
if _globals_ is None:
|
16
|
+
frame = sys._getframe(1)
|
17
|
+
_globals_ = frame.f_globals
|
18
|
+
if _locals_ is None:
|
19
|
+
_locals_ = frame.f_locals
|
20
|
+
del frame
|
21
|
+
elif _locals_ is None:
|
22
|
+
_locals_ = _globals_
|
23
|
+
exec("""exec _code_ in _globals_, _locals_""")
|
@@ -0,0 +1,79 @@
|
|
1
|
+
require 'pycall/pyobject_wrapper'
|
2
|
+
|
3
|
+
module PyCall
|
4
|
+
module PyTypeObjectWrapper
|
5
|
+
include PyObjectWrapper
|
6
|
+
|
7
|
+
def self.extend_object(cls)
|
8
|
+
unless cls.kind_of? Class
|
9
|
+
raise TypeError, "PyTypeObjectWrapper cannot extend non-class objects"
|
10
|
+
end
|
11
|
+
pyptr = cls.instance_variable_get(:@__pyptr__)
|
12
|
+
unless pyptr.kind_of? PyTypePtr
|
13
|
+
raise TypeError, "@__pyptr__ should have PyCall::PyTypePtr object"
|
14
|
+
end
|
15
|
+
super
|
16
|
+
cls.include PyObjectWrapper
|
17
|
+
end
|
18
|
+
|
19
|
+
def inherited(subclass)
|
20
|
+
subclass.instance_variable_set(:@__pyptr__, __pyptr__)
|
21
|
+
end
|
22
|
+
|
23
|
+
def new(*args)
|
24
|
+
wrap_pyptr(LibPython::Helpers.call_object(__pyptr__, *args))
|
25
|
+
end
|
26
|
+
|
27
|
+
def wrap_pyptr(pyptr)
|
28
|
+
return pyptr if pyptr.kind_of? self
|
29
|
+
pyptr = pyptr.__pyptr__ if pyptr.kind_of? PyObjectWrapper
|
30
|
+
unless pyptr.kind_of? PyPtr
|
31
|
+
raise TypeError, "unexpected argument type #{pyptr.class} (expected PyCall::PyPtr)"
|
32
|
+
end
|
33
|
+
unless pyptr.kind_of? __pyptr__
|
34
|
+
raise TypeError, "unexpected argument Python type #{pyptr.__ob_type__.__tp_name__} (expected #{__pyptr__.__tp_name__})"
|
35
|
+
end
|
36
|
+
allocate.tap do |obj|
|
37
|
+
obj.instance_variable_set(:@__pyptr__, pyptr)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
def register_python_type_mapping
|
44
|
+
PyCall::Conversion.register_python_type_mapping(__pyptr__, self)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
module_function
|
49
|
+
|
50
|
+
class WrapperClassCache < WrapperObjectCache
|
51
|
+
def initialize
|
52
|
+
types = [LibPython::API::PyType_Type]
|
53
|
+
types << LibPython::API::PyClass_Type if defined? LibPython::API::PyClass_Type
|
54
|
+
super(*types)
|
55
|
+
end
|
56
|
+
|
57
|
+
def check_wrapper_object(wrapper_object)
|
58
|
+
unless wrapper_object.kind_of?(Class) && wrapper_object.kind_of?(PyTypeObjectWrapper)
|
59
|
+
raise TypeError, "unexpected type #{wrapper_object.class} (expected Class extended by PyTypeObjectWrapper)"
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def self.instance
|
64
|
+
@instance ||= self.new
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
private_constant :WrapperClassCache
|
69
|
+
|
70
|
+
def wrap_class(pytypeptr)
|
71
|
+
check_isclass(pytypeptr)
|
72
|
+
WrapperClassCache.instance.lookup(pytypeptr) do
|
73
|
+
Class.new do |cls|
|
74
|
+
cls.instance_variable_set(:@__pyptr__, pytypeptr)
|
75
|
+
cls.extend PyTypeObjectWrapper
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
data/lib/pycall/slice.rb
CHANGED
@@ -1,27 +1,8 @@
|
|
1
1
|
module PyCall
|
2
|
+
Slice = builtins.slice
|
2
3
|
class Slice
|
3
|
-
|
4
|
-
|
5
|
-
def self.new(*args)
|
6
|
-
start, stop, step = nil
|
7
|
-
case args.length
|
8
|
-
when 1
|
9
|
-
stop = args[0]
|
10
|
-
return super(stop) if stop.kind_of?(LibPython::PyObjectStruct)
|
11
|
-
when 2
|
12
|
-
start, stop = args
|
13
|
-
when 3
|
14
|
-
start, stop, step = args
|
15
|
-
else
|
16
|
-
much_or_few = args.length > 3 ? 'much' : 'few'
|
17
|
-
raise ArgumentError, "too #{much_or_few} arguments (#{args.length} for 1..3)"
|
18
|
-
end
|
19
|
-
start = start ? Conversions.from_ruby(start) : LibPython::PyObjectStruct.null
|
20
|
-
stop = stop ? Conversions.from_ruby(stop) : LibPython::PyObjectStruct.null
|
21
|
-
step = step ? Conversions.from_ruby(step) : LibPython::PyObjectStruct.null
|
22
|
-
pyobj = LibPython.PySlice_New(start, stop, step)
|
23
|
-
return pyobj.to_ruby unless pyobj.null?
|
24
|
-
raise PyError.fetch
|
4
|
+
def self.all
|
5
|
+
new(nil)
|
25
6
|
end
|
26
7
|
end
|
27
8
|
end
|
data/lib/pycall/tuple.rb
CHANGED
data/lib/pycall/version.rb
CHANGED
@@ -0,0 +1,61 @@
|
|
1
|
+
module PyCall
|
2
|
+
class WrapperObjectCache
|
3
|
+
def initialize(*restricted_pytypes)
|
4
|
+
unless restricted_pytypes.empty?
|
5
|
+
restricted_pytypes.each do |pytype|
|
6
|
+
next if pytype.kind_of? PyTypePtr
|
7
|
+
raise TypeError, "unexpected type of object in the arguments (#{pytype.class} for PyCall::PyTypePtr)"
|
8
|
+
end
|
9
|
+
end
|
10
|
+
@restricted_pytypes = restricted_pytypes
|
11
|
+
@wrapper_object_table = {}
|
12
|
+
@wrapped_pyptr_table = {}
|
13
|
+
@weakref_table = {}
|
14
|
+
end
|
15
|
+
|
16
|
+
def lookup(pyptr)
|
17
|
+
# TODO: check pytypeptr type
|
18
|
+
unless pyptr.kind_of? PyPtr
|
19
|
+
raise TypeError, "unexpected argument type #{pyptr.class} (expected PyCall::PyPtr)"
|
20
|
+
end
|
21
|
+
|
22
|
+
unless @restricted_pytypes.empty?
|
23
|
+
unless @restricted_pytypes.any? {|pytype| pyptr.kind_of? pytype }
|
24
|
+
raise TypeError, "unexpected argument Python type #{pyptr.__ob_type__.__name__} (expected either of them in [#{@restricted_pytypes.map(&:__tp_name__).join(', ')}])"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
wrapper_object_id = @wrapper_object_table[pyptr.__address__]
|
29
|
+
if wrapper_object_id
|
30
|
+
wrapper_object = ObjectSpace._id2ref(wrapper_object_id) rescue nil
|
31
|
+
return wrapper_object if wrapper_object
|
32
|
+
end
|
33
|
+
|
34
|
+
wrapper_object = yield(pyptr)
|
35
|
+
check_wrapper_object(wrapper_object)
|
36
|
+
register_wrapper_object(pyptr, wrapper_object)
|
37
|
+
|
38
|
+
wrapper_object
|
39
|
+
end
|
40
|
+
|
41
|
+
def check_wrapper_object(wrapper_object)
|
42
|
+
unless wrapper_object.kind_of?(PyObjectWrapper)
|
43
|
+
raise TypeError, "unexpected wrapper object (expected an object extended by PyObjectWrapper)"
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def register_wrapper_object(pyptr, wrapper_object)
|
48
|
+
@wrapper_object_table[pyptr.__address__] = wrapper_object.__id__
|
49
|
+
@wrapped_pyptr_table[wrapper_object.__id__] = pyptr.__address__
|
50
|
+
ObjectSpace.define_finalizer(wrapper_object, &method(:unregister_wrapper_object))
|
51
|
+
# TODO: weakref
|
52
|
+
self
|
53
|
+
end
|
54
|
+
|
55
|
+
def unregister_wrapper_object(wrapper_object_id)
|
56
|
+
pyptr_addr = @wrapped_pyptr_table.delete(wrapper_object_id)
|
57
|
+
@wrapper_object_table.delete(pyptr_addr) if pyptr_addr
|
58
|
+
self
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
data/pycall.gemspec
CHANGED
@@ -27,12 +27,14 @@ Gem::Specification.new do |spec|
|
|
27
27
|
spec.bindir = "exe"
|
28
28
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
29
29
|
spec.require_paths = ["lib"]
|
30
|
-
|
31
|
-
spec.add_dependency "ffi"
|
30
|
+
spec.extensions = ["ext/pycall/extconf.rb"]
|
32
31
|
|
33
32
|
spec.add_development_dependency "bundler", "~> 1.13"
|
34
33
|
spec.add_development_dependency "rake", "~> 10.0"
|
34
|
+
spec.add_development_dependency "rake-compiler"
|
35
|
+
spec.add_development_dependency "rake-compiler-dock"
|
35
36
|
spec.add_development_dependency "rspec", "~> 3.0"
|
36
37
|
spec.add_development_dependency "launchy"
|
37
38
|
spec.add_development_dependency "pry"
|
39
|
+
spec.add_development_dependency "pry-byebug"
|
38
40
|
end
|
data/tasks/pycall.rake
ADDED
metadata
CHANGED
@@ -1,29 +1,15 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: pycall
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 1.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Kenta Murata
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-07
|
11
|
+
date: 2017-09-07 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
|
-
- !ruby/object:Gem::Dependency
|
14
|
-
name: ffi
|
15
|
-
requirement: !ruby/object:Gem::Requirement
|
16
|
-
requirements:
|
17
|
-
- - ">="
|
18
|
-
- !ruby/object:Gem::Version
|
19
|
-
version: '0'
|
20
|
-
type: :runtime
|
21
|
-
prerelease: false
|
22
|
-
version_requirements: !ruby/object:Gem::Requirement
|
23
|
-
requirements:
|
24
|
-
- - ">="
|
25
|
-
- !ruby/object:Gem::Version
|
26
|
-
version: '0'
|
27
13
|
- !ruby/object:Gem::Dependency
|
28
14
|
name: bundler
|
29
15
|
requirement: !ruby/object:Gem::Requirement
|
@@ -52,6 +38,34 @@ dependencies:
|
|
52
38
|
- - "~>"
|
53
39
|
- !ruby/object:Gem::Version
|
54
40
|
version: '10.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rake-compiler
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rake-compiler-dock
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
55
69
|
- !ruby/object:Gem::Dependency
|
56
70
|
name: rspec
|
57
71
|
requirement: !ruby/object:Gem::Requirement
|
@@ -94,16 +108,32 @@ dependencies:
|
|
94
108
|
- - ">="
|
95
109
|
- !ruby/object:Gem::Version
|
96
110
|
version: '0'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: pry-byebug
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - ">="
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - ">="
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '0'
|
97
125
|
description: pycall
|
98
126
|
email:
|
99
127
|
- mrkn@mrkn.jp
|
100
128
|
executables: []
|
101
|
-
extensions:
|
129
|
+
extensions:
|
130
|
+
- ext/pycall/extconf.rb
|
102
131
|
extra_rdoc_files: []
|
103
132
|
files:
|
104
133
|
- ".gitignore"
|
105
134
|
- ".rspec"
|
106
135
|
- ".travis.yml"
|
136
|
+
- CHANGES.md
|
107
137
|
- Gemfile
|
108
138
|
- LICENSE.txt
|
109
139
|
- README.md
|
@@ -130,33 +160,41 @@ files:
|
|
130
160
|
- examples/notebooks/xkcd_style.ipynb
|
131
161
|
- examples/plot_forest_importances_faces.rb
|
132
162
|
- examples/sum_benchmarking.rb
|
163
|
+
- ext/pycall/extconf.rb
|
164
|
+
- ext/pycall/gc.c
|
165
|
+
- ext/pycall/libpython.c
|
166
|
+
- ext/pycall/pycall.c
|
167
|
+
- ext/pycall/pycall_internal.h
|
168
|
+
- ext/pycall/range.c
|
169
|
+
- ext/pycall/ruby_wrapper.c
|
133
170
|
- lib/pycall.rb
|
134
171
|
- lib/pycall/conversion.rb
|
135
172
|
- lib/pycall/dict.rb
|
136
|
-
- lib/pycall/
|
137
|
-
- lib/pycall/exception.rb
|
173
|
+
- lib/pycall/error.rb
|
138
174
|
- lib/pycall/gc_guard.rb
|
139
175
|
- lib/pycall/import.rb
|
140
176
|
- lib/pycall/init.rb
|
141
177
|
- lib/pycall/iruby_helper.rb
|
142
178
|
- lib/pycall/libpython.rb
|
179
|
+
- lib/pycall/libpython/finder.rb
|
143
180
|
- lib/pycall/libpython/pyobject_struct.rb
|
144
181
|
- lib/pycall/libpython/pytypeobject_struct.rb
|
145
182
|
- lib/pycall/list.rb
|
183
|
+
- lib/pycall/pretty_print.rb
|
146
184
|
- lib/pycall/pyerror.rb
|
147
|
-
- lib/pycall/pyobject.rb
|
148
185
|
- lib/pycall/pyobject_wrapper.rb
|
186
|
+
- lib/pycall/python/PyCall/__init__.py
|
187
|
+
- lib/pycall/python/PyCall/six.py
|
149
188
|
- lib/pycall/python/investigator.py
|
150
|
-
- lib/pycall/
|
189
|
+
- lib/pycall/pytypeobject_wrapper.rb
|
151
190
|
- lib/pycall/set.rb
|
152
191
|
- lib/pycall/slice.rb
|
153
192
|
- lib/pycall/tuple.rb
|
154
|
-
- lib/pycall/type_object.rb
|
155
|
-
- lib/pycall/types.rb
|
156
|
-
- lib/pycall/utils.rb
|
157
193
|
- lib/pycall/version.rb
|
194
|
+
- lib/pycall/wrapper_object_cache.rb
|
158
195
|
- pycall.gemspec
|
159
196
|
- tasks/docker.rake
|
197
|
+
- tasks/pycall.rake
|
160
198
|
homepage: https://github.com/mrkn/pycall
|
161
199
|
licenses:
|
162
200
|
- MIT
|
@@ -172,12 +210,12 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
172
210
|
version: '0'
|
173
211
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
174
212
|
requirements:
|
175
|
-
- - "
|
213
|
+
- - ">="
|
176
214
|
- !ruby/object:Gem::Version
|
177
|
-
version:
|
215
|
+
version: '0'
|
178
216
|
requirements: []
|
179
217
|
rubyforge_project:
|
180
|
-
rubygems_version: 2.6.
|
218
|
+
rubygems_version: 2.6.13
|
181
219
|
signing_key:
|
182
220
|
specification_version: 4
|
183
221
|
summary: pycall
|
data/lib/pycall/eval.rb
DELETED
@@ -1,57 +0,0 @@
|
|
1
|
-
module PyCall
|
2
|
-
module Eval
|
3
|
-
Py_file_input = 257
|
4
|
-
Py_eval_input = 258
|
5
|
-
|
6
|
-
def self.input_type(sym)
|
7
|
-
return Py_file_input if sym == :file
|
8
|
-
return Py_eval_input if sym == :eval
|
9
|
-
raise ArgumentError, "Unknown input_type for compile Python code"
|
10
|
-
end
|
11
|
-
|
12
|
-
def self.eval(str, filename: "pycall", input_type: :eval)
|
13
|
-
input_type = self.input_type(input_type)
|
14
|
-
globals_ptr = main_dict.__pyobj__
|
15
|
-
locals_ptr = main_dict.__pyobj__
|
16
|
-
defer_sigint do
|
17
|
-
py_code_ptr = LibPython.Py_CompileString(str, filename, input_type)
|
18
|
-
raise PyError.fetch if py_code_ptr.null?
|
19
|
-
LibPython.PyEval_EvalCode(py_code_ptr, globals_ptr, locals_ptr)
|
20
|
-
end
|
21
|
-
end
|
22
|
-
|
23
|
-
class << self
|
24
|
-
private
|
25
|
-
|
26
|
-
def main_dict
|
27
|
-
@main_dict ||= PyCall.import_module("__main__") do |main_module|
|
28
|
-
PyCall.incref(LibPython.PyModule_GetDict(main_module.__pyobj__)).to_ruby
|
29
|
-
end
|
30
|
-
end
|
31
|
-
|
32
|
-
def defer_sigint
|
33
|
-
# TODO: should be implemented
|
34
|
-
yield
|
35
|
-
end
|
36
|
-
end
|
37
|
-
end
|
38
|
-
|
39
|
-
def self.import_module(name)
|
40
|
-
name = name.to_s if name.kind_of? Symbol
|
41
|
-
raise TypeError, "name must be a String" unless name.kind_of? String
|
42
|
-
value = LibPython.PyImport_ImportModule(name)
|
43
|
-
raise PyError.fetch if value.null?
|
44
|
-
value = value.to_ruby
|
45
|
-
return value unless block_given?
|
46
|
-
begin
|
47
|
-
yield value
|
48
|
-
ensure
|
49
|
-
PyCall.decref(value.__pyobj__)
|
50
|
-
end
|
51
|
-
end
|
52
|
-
|
53
|
-
def self.eval(str, conversion: true, filename: "pycall", input_type: :eval)
|
54
|
-
result = Eval.eval(str, filename: filename, input_type: input_type)
|
55
|
-
conversion ? result.to_ruby : result
|
56
|
-
end
|
57
|
-
end
|