com 0.3.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.
- data/README +225 -0
- data/Rakefile +10 -0
- data/lib/com.rb +73 -0
- data/lib/com/error.rb +44 -0
- data/lib/com/events.rb +68 -0
- data/lib/com/instantiable.rb +135 -0
- data/lib/com/methodinvocationerror.rb +38 -0
- data/lib/com/object.rb +79 -0
- data/lib/com/pathname.rb +10 -0
- data/lib/com/patternerror.rb +20 -0
- data/lib/com/standarderror.rb +61 -0
- data/lib/com/version.rb +5 -0
- data/lib/com/win32ole.rb +19 -0
- data/lib/com/wrapper.rb +96 -0
- data/test/unit/com.rb +23 -0
- data/test/unit/com/error.rb +11 -0
- data/test/unit/com/events.rb +28 -0
- data/test/unit/com/instantiable.rb +94 -0
- data/test/unit/com/methodinvocationerror.rb +67 -0
- data/test/unit/com/object.rb +33 -0
- data/test/unit/com/pathname.rb +21 -0
- data/test/unit/com/standarderror.rb +27 -0
- metadata +178 -0
@@ -0,0 +1,38 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
|
3
|
+
# Represents an COM method invocation error.
|
4
|
+
class COM::MethodInvocationError < COM::Error
|
5
|
+
extend COM::PatternError
|
6
|
+
|
7
|
+
pattern %r{^\s*([^\n]*)\n
|
8
|
+
\s*OLE\serror\scode:([0-9a-fA-F]+)
|
9
|
+
\s+in\s+([^\n]+)\n
|
10
|
+
\s*([^\n]+)\n
|
11
|
+
\s*HRESULT\serror\scode:0x([0-9a-fA-F]+)[^\n]*\n
|
12
|
+
\s*(.+)$}xu
|
13
|
+
|
14
|
+
class << self
|
15
|
+
# This is an internal method used by COM::Error.
|
16
|
+
def replace(error)
|
17
|
+
m = pattern.match(error.message)
|
18
|
+
new(m[4], m[1], m[3], m[2].to_i(16), m[5].to_i(16), m[6])
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
# Creates a new COM::MethodInvocationError with _message_.
|
23
|
+
#
|
24
|
+
# @param [#to_str] message Error message
|
25
|
+
# @param [String] method Method error occurred in
|
26
|
+
# @param [String] server Server error occurred in
|
27
|
+
# @param [Integer] code COM error code
|
28
|
+
# @param [Integer] hresult_code HRESULT error code
|
29
|
+
# @param [String] hresult_message HRESULT error message
|
30
|
+
def initialize(message, method = '', server = '', code = -1,
|
31
|
+
hresult_code = -1, hresult_message = '')
|
32
|
+
super message
|
33
|
+
@method, @server, @code, @hresult_code, @hresult_message =
|
34
|
+
method, server, code, hresult_code, hresult_message
|
35
|
+
end
|
36
|
+
|
37
|
+
attr_reader :method, :server, :code, :hresult_code, :hresult_message
|
38
|
+
end
|
data/lib/com/object.rb
ADDED
@@ -0,0 +1,79 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
|
3
|
+
class COM::Object
|
4
|
+
# Creates a new instance based on _com_. It is important that subclasses
|
5
|
+
# call `super` if they override this method.
|
6
|
+
#
|
7
|
+
# @param [WIN32OLE] com A WIN32OLE Com object
|
8
|
+
def initialize(com)
|
9
|
+
self.com = com
|
10
|
+
end
|
11
|
+
|
12
|
+
# Queries whether this COM object responds to _method_.
|
13
|
+
#
|
14
|
+
# @param [Symbol] method Method name to query for response
|
15
|
+
# @return [Boolean] Whether or not this COM object responds to _method_
|
16
|
+
def respond_to?(method)
|
17
|
+
super or com.respond_to? method
|
18
|
+
end
|
19
|
+
|
20
|
+
# Sets a bunch of properties, yield, and then restore them. If an exception
|
21
|
+
# is raised, any set properties are restored.
|
22
|
+
#
|
23
|
+
# @param [#to_hash] properties properties with values to set
|
24
|
+
# @return [COM::Object] `self` */
|
25
|
+
def with_properties(properties)
|
26
|
+
saved_properties = []
|
27
|
+
begin
|
28
|
+
properties.to_hash.each do |property, value|
|
29
|
+
saved_properties << [property, com[property]]
|
30
|
+
com.set_property property, value
|
31
|
+
end
|
32
|
+
yield
|
33
|
+
ensure
|
34
|
+
previous_error = $!
|
35
|
+
begin
|
36
|
+
saved_properties.reverse.each do |property, value|
|
37
|
+
begin com.set_property property, value; rescue COM::Error; end
|
38
|
+
end
|
39
|
+
rescue
|
40
|
+
raise if not previous_error
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def observe(event, observer = COM::Events::ArgumentMissing, &block)
|
46
|
+
com.observe event, observer, &block
|
47
|
+
self
|
48
|
+
end
|
49
|
+
|
50
|
+
def unobserve(event, observer = nil)
|
51
|
+
com.unobserve event, observer
|
52
|
+
self
|
53
|
+
end
|
54
|
+
|
55
|
+
def to_com
|
56
|
+
com.to_com
|
57
|
+
end
|
58
|
+
|
59
|
+
protected
|
60
|
+
|
61
|
+
attr_reader :com
|
62
|
+
|
63
|
+
def com=(com)
|
64
|
+
@com = WIN32OLE === com ? COM::Wrapper.new(com) : com
|
65
|
+
end
|
66
|
+
|
67
|
+
private
|
68
|
+
|
69
|
+
def method_missing(method, *args)
|
70
|
+
case method.to_s
|
71
|
+
when /=\z/
|
72
|
+
com.set_property($`.encode(COM.charset), *args)
|
73
|
+
else
|
74
|
+
com.invoke(method.to_s.encode(COM.charset), *args)
|
75
|
+
end
|
76
|
+
rescue NoMethodError => e
|
77
|
+
raise e, "undefined method `%s' for %p" % [method, self], e.backtrace
|
78
|
+
end
|
79
|
+
end
|
data/lib/com/pathname.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
|
3
|
+
module COM::PatternError
|
4
|
+
# @private method used by {COM::Error.find}.
|
5
|
+
def replaces?(error)
|
6
|
+
pattern =~ error.message
|
7
|
+
end
|
8
|
+
|
9
|
+
protected
|
10
|
+
|
11
|
+
def replace(error)
|
12
|
+
new(*pattern.match(error.message).captures)
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def pattern(pattern = nil)
|
18
|
+
(pattern or not defined? @pattern) ? @pattern = pattern : @pattern
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
|
3
|
+
# Sets up mappings between COM errors and Ruby errors.
|
4
|
+
#
|
5
|
+
# @private
|
6
|
+
module COM::StandardError
|
7
|
+
class << self
|
8
|
+
private
|
9
|
+
def define(code, errorclass, message = nil, &block)
|
10
|
+
block = proc{ |m| m[1] } unless message or block
|
11
|
+
Class.new(COM::Error) do
|
12
|
+
extend COM::PatternError
|
13
|
+
|
14
|
+
pattern %r{^\s*(.*)\n\s*HRESULT\serror\scode:(0x(?i:#{code.to_s(16)}))}xu
|
15
|
+
|
16
|
+
(class << self; self; end).class_eval do
|
17
|
+
define_method :replace do |error|
|
18
|
+
m = pattern.match(error.message)
|
19
|
+
errorclass.new(*Array(message ? message : block.call(m)))
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
define(0x80004001, ::NotImplementedError){ |m| '%s: not implemented' % m[1] }
|
27
|
+
define 0x80020005, ::TypeError
|
28
|
+
define 0x80020006, ::NoMethodError
|
29
|
+
define 0x8002000e, ::ArgumentError, 'wrong number of arguments'
|
30
|
+
define 0x800401e4, ::ArgumentError
|
31
|
+
define 0x800401f3, ::NameError do |m|
|
32
|
+
name = m[1].sub(/.*`(.*)'\z/, '\\1')
|
33
|
+
['unknown COM server: %s' % name, name]
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
# Sets up mappings between HRESULT errors and COM errors.
|
38
|
+
#
|
39
|
+
# @private
|
40
|
+
module COM::HResultError
|
41
|
+
class << self
|
42
|
+
def define(code, error, message = nil, &block)
|
43
|
+
block = proc{ |m| m[1] } unless message or block
|
44
|
+
COM.const_set error, Class.new(COM::Error){
|
45
|
+
extend COM::PatternError
|
46
|
+
|
47
|
+
pattern %r{^\s*(.*)\n\s*HRESULT\serror\scode:(0x(?i:#{code.to_s(16)}))}xu
|
48
|
+
|
49
|
+
(class << self; self; end).class_eval do
|
50
|
+
define_method :replace do |e|
|
51
|
+
m = pattern.match(e.message)
|
52
|
+
new(message ? message : block.call(m))
|
53
|
+
end
|
54
|
+
end
|
55
|
+
}
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
define 0x80020003, :MemberNotFoundError
|
60
|
+
define 0x800401e3, :OperationUnavailableError
|
61
|
+
end
|
data/lib/com/version.rb
ADDED
data/lib/com/win32ole.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
|
3
|
+
class WIN32OLE_TYPE
|
4
|
+
class << self
|
5
|
+
def enums(id)
|
6
|
+
ole_classes(id).select{ |c| c.visible? and c.ole_type == 'Enum' }
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
def constants
|
11
|
+
variables.select{ |v| v.visible? and v.variable_kind == 'CONSTANT' }
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
class WIN32OLE_VARIABLE
|
16
|
+
def const_name
|
17
|
+
name.sub(/^./){ |l| l.upcase }
|
18
|
+
end
|
19
|
+
end
|
data/lib/com/wrapper.rb
ADDED
@@ -0,0 +1,96 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
|
3
|
+
# This module provides a wrapper around WIN32OLE’s instance methods. This
|
4
|
+
# wrapper deals with converting errors to the appropriate type.
|
5
|
+
#
|
6
|
+
# @private
|
7
|
+
class COM::Wrapper
|
8
|
+
BacktraceFilter = File.dirname(File.dirname(__FILE__)) + File::SEPARATOR
|
9
|
+
|
10
|
+
class << self
|
11
|
+
def raise_in(method, e)
|
12
|
+
clean = e.backtrace.reject{ |s| s.start_with? BacktraceFilter }
|
13
|
+
raise COM::Error.from(e, clean.unshift(clean.first.sub(/:in `.*'$/, ":in `#{method}'")))
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def initialize(ole)
|
18
|
+
@ole = ole
|
19
|
+
end
|
20
|
+
|
21
|
+
def invoke(method, *args)
|
22
|
+
@ole.invoke(method, *args.map{ |e| e.respond_to?(:to_com) ? e.to_com : e })
|
23
|
+
rescue NoMethodError => e
|
24
|
+
error = NoMethodError.new("undefined method `%s' for %p" % [method, self],
|
25
|
+
method,
|
26
|
+
args)
|
27
|
+
error.set_backtrace e.backtrace.reject{ |s| s.start_with? BacktraceFilter }
|
28
|
+
raise error
|
29
|
+
rescue WIN32OLERuntimeError => e
|
30
|
+
raise COM::Wrapper.raise_in(method, e)
|
31
|
+
end
|
32
|
+
|
33
|
+
def set_property(property, *args)
|
34
|
+
@ole.setproperty(property, *args.map{ |e| e.respond_to?(:to_com) ? e.to_com : e })
|
35
|
+
rescue NoMethodError => e
|
36
|
+
error = NoMethodError.new("undefined property `%s' for %p" % [property, self],
|
37
|
+
property,
|
38
|
+
args)
|
39
|
+
error.set_backtrace e.backtrace.reject{ |s| s.start_with? BacktraceFilter }
|
40
|
+
raise error
|
41
|
+
rescue WIN32OLERuntimeError => e
|
42
|
+
raise COM::Wrapper.raise_in('%s=' % property, e)
|
43
|
+
end
|
44
|
+
|
45
|
+
def load_constants(into)
|
46
|
+
saved_verbose, $VERBOSE = $VERBOSE, nil
|
47
|
+
begin
|
48
|
+
begin
|
49
|
+
WIN32OLE.const_load @ole, into
|
50
|
+
rescue RuntimeError
|
51
|
+
WIN32OLE_TYPE.enums(program_id).each do |enum|
|
52
|
+
enum.constants.each do |constant|
|
53
|
+
into.const_set constant.const_name, constant.value
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
ensure
|
58
|
+
$VERBOSE = saved_verbose
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def observe(event, observer = COM::Events::ArgumentMissing, &block)
|
63
|
+
events.observe event, observer, &block
|
64
|
+
self
|
65
|
+
end
|
66
|
+
|
67
|
+
def unobserve(event, observer = nil)
|
68
|
+
events.unobserve event, observer
|
69
|
+
self
|
70
|
+
end
|
71
|
+
|
72
|
+
def to_com
|
73
|
+
@ole
|
74
|
+
end
|
75
|
+
|
76
|
+
protected
|
77
|
+
|
78
|
+
def events
|
79
|
+
@events ||= COM::Events.new(@ole)
|
80
|
+
end
|
81
|
+
|
82
|
+
private
|
83
|
+
|
84
|
+
def method_missing(method, *args)
|
85
|
+
case method.to_s
|
86
|
+
when /=\z/
|
87
|
+
begin
|
88
|
+
set_property($`.encode(COM.charset), *args)
|
89
|
+
rescue NoMethodError => e
|
90
|
+
raise e, "undefined method `%s' for %p" % [method, self], e.backtrace
|
91
|
+
end
|
92
|
+
else
|
93
|
+
invoke(method.to_s.encode(COM.charset), *args)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
data/test/unit/com.rb
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
|
3
|
+
Expectations do
|
4
|
+
expect 'UTF-8' do
|
5
|
+
stub(WIN32OLE).codepage{ WIN32OLE::CP_UTF8 }
|
6
|
+
COM.charset
|
7
|
+
end
|
8
|
+
|
9
|
+
expect RuntimeError do
|
10
|
+
stub(WIN32OLE).codepage{ :unknown_encoding }
|
11
|
+
COM.charset
|
12
|
+
end
|
13
|
+
|
14
|
+
expect COM::Error do
|
15
|
+
stub(WIN32OLE).connect{ raise WIN32OLERuntimeError }
|
16
|
+
COM.connect(stub)
|
17
|
+
end
|
18
|
+
|
19
|
+
expect COM::Error do
|
20
|
+
stub(WIN32OLE).new{ raise WIN32OLERuntimeError }
|
21
|
+
COM.new(stub)
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
|
3
|
+
Expectations do
|
4
|
+
expect WIN32OLE_EVENT.to.receive.new(:com, :interface) do
|
5
|
+
COM::Events.new(:com, :interface)
|
6
|
+
end
|
7
|
+
|
8
|
+
expect mock.to.receive.on_event(:on_something) do |o|
|
9
|
+
stub(WIN32OLE_EVENT).new{ o }
|
10
|
+
COM::Events.new(:ole, :interface).observe :on_something do end
|
11
|
+
end
|
12
|
+
|
13
|
+
expect mock.to.receive.call do |o|
|
14
|
+
events = stub
|
15
|
+
class << events
|
16
|
+
def on_event(event, &block)
|
17
|
+
@block = block
|
18
|
+
end
|
19
|
+
|
20
|
+
def trigger
|
21
|
+
@block.call
|
22
|
+
end
|
23
|
+
end
|
24
|
+
stub(WIN32OLE_EVENT).new{ events }
|
25
|
+
e = COM::Events.new(:ole, :interface)
|
26
|
+
e.observe(:on_something, proc{ events.trigger }){ o.call }
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
|
3
|
+
Expectations do
|
4
|
+
expect 'Program.Class' do
|
5
|
+
Class.new(COM::Instantiable).program_id('Program.Class')
|
6
|
+
end
|
7
|
+
|
8
|
+
expect 'Program.Class' do
|
9
|
+
Class.new(COM::Instantiable){
|
10
|
+
program_id 'Program.Class'
|
11
|
+
}.program_id
|
12
|
+
end
|
13
|
+
|
14
|
+
expect ArgumentError do
|
15
|
+
Class.new(COM::Instantiable).tap{ |c| stub(c).name{ 'Class' } }.program_id
|
16
|
+
end
|
17
|
+
|
18
|
+
expect 'Program.Class' do
|
19
|
+
Class.new(COM::Instantiable).tap{ |c| stub(c).name{ 'Program::Class' } }.program_id
|
20
|
+
end
|
21
|
+
|
22
|
+
expect 'Program.Class' do
|
23
|
+
Class.new(COM::Instantiable).tap{ |c| stub(c).name{ 'Vendor::Program::Class' } }.program_id
|
24
|
+
end
|
25
|
+
|
26
|
+
expect false do
|
27
|
+
Class.new(COM::Instantiable).connect?
|
28
|
+
end
|
29
|
+
|
30
|
+
expect true do
|
31
|
+
Class.new(COM::Instantiable).connect
|
32
|
+
end
|
33
|
+
|
34
|
+
expect true do
|
35
|
+
Class.new(COM::Instantiable){ connect }.connect?
|
36
|
+
end
|
37
|
+
|
38
|
+
expect true do
|
39
|
+
Class.new(COM::Instantiable).constants?
|
40
|
+
end
|
41
|
+
|
42
|
+
expect true do
|
43
|
+
Class.new(COM::Instantiable).constants(true)
|
44
|
+
end
|
45
|
+
|
46
|
+
expect true do
|
47
|
+
Class.new(COM::Instantiable){ constants true }.constants?
|
48
|
+
end
|
49
|
+
|
50
|
+
expect COM.to.receive.connect('A.B'){ stub } do
|
51
|
+
Class.new(COM::Instantiable){ program_id 'A.B' }.new(:connect => true, :constants => false)
|
52
|
+
end
|
53
|
+
|
54
|
+
expect COM.not.to.receive.new do
|
55
|
+
stub(COM).connect{ stub }
|
56
|
+
Class.new(COM::Instantiable){ program_id 'A.B' }.new(:connect => true, :constants => false)
|
57
|
+
end
|
58
|
+
|
59
|
+
expect COM.to.receive.new('A.B'){ stub } do
|
60
|
+
stub(COM).connect{ raise COM::OperationUnavailableError }
|
61
|
+
Class.new(COM::Instantiable){ program_id 'A.B' }.new(:connect => true, :constants => false)
|
62
|
+
end
|
63
|
+
|
64
|
+
expect COM.to.receive.new('A.B'){ stub } do
|
65
|
+
stub(COM).connect{ raise COM::OperationUnavailableError }
|
66
|
+
Class.new(COM::Instantiable){ program_id 'A.B' }.new(:constants => false)
|
67
|
+
end
|
68
|
+
|
69
|
+
expect true do
|
70
|
+
stub(COM).connect{ stub }
|
71
|
+
Class.new(COM::Instantiable){ program_id 'A.B' }.new(:connect => true, :constants => false).connected?
|
72
|
+
end
|
73
|
+
|
74
|
+
expect false do
|
75
|
+
stub(COM).connect{ raise COM::OperationUnavailableError }
|
76
|
+
stub(COM).new{ stub }
|
77
|
+
Class.new(COM::Instantiable){ program_id 'A.B' }.new(:connect => true, :constants => false).connected?
|
78
|
+
end
|
79
|
+
|
80
|
+
expect false do
|
81
|
+
stub(COM).new{ stub }
|
82
|
+
Class.new(COM::Instantiable){ program_id 'A.B' }.new(:constants => false).connected?
|
83
|
+
end
|
84
|
+
|
85
|
+
expect(Class.new(COM::Instantiable){ program_id 'A.B' }.to.receive.load_constants) do |o|
|
86
|
+
stub(COM).new{ stub }
|
87
|
+
o.new
|
88
|
+
end
|
89
|
+
|
90
|
+
expect(Class.new(COM::Instantiable){ program_id 'A.B' }.not.to.receive.load_constants) do |o|
|
91
|
+
stub(COM).new{ stub }
|
92
|
+
o.new(:constants => false)
|
93
|
+
end
|
94
|
+
end
|