wineole 0.1.0 → 0.2.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/bin/wineole-vba +45 -0
- data/lib/wineole/client.rb +454 -20
- data/lib/wineole/dispatcher.rb +534 -0
- data/lib/wineole/errors.rb +6 -0
- data/lib/wineole/events.rb +353 -0
- data/lib/wineole/msoffice/address.rb +67 -0
- data/lib/wineole/msoffice/book.rb +114 -0
- data/lib/wineole/msoffice/color.rb +87 -0
- data/lib/wineole/msoffice/controls.rb +450 -0
- data/lib/wineole/msoffice/excel.rb +311 -0
- data/lib/wineole/msoffice/format.rb +362 -0
- data/lib/wineole/msoffice/forms.rb +163 -0
- data/lib/wineole/msoffice/passthrough.rb +46 -0
- data/lib/wineole/msoffice/paths.rb +62 -0
- data/lib/wineole/msoffice/range.rb +149 -0
- data/lib/wineole/msoffice/sheet.rb +101 -0
- data/lib/wineole/msoffice/vba.rb +199 -0
- data/lib/wineole/msoffice/vba_api.rb +366 -0
- data/lib/wineole/msoffice/vba_block.rb +144 -0
- data/lib/wineole/msoffice.rb +28 -0
- data/lib/wineole/proxy.rb +126 -15
- data/lib/wineole.rb +10 -1
- data/wineole-bridge-dist/aarch64-pc-windows-gnullvm/wineole-bridge.exe +0 -0
- data/wineole-bridge-dist/i686-pc-windows-gnu/wineole-bridge.exe +0 -0
- data/wineole-bridge-dist/x86_64-pc-windows-gnu/wineole-bridge.exe +0 -0
- metadata +21 -2
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
require_relative 'vba'
|
|
2
|
+
|
|
3
|
+
module WineOLE
|
|
4
|
+
module MSOffice
|
|
5
|
+
# A named span of code inside a VBA module, delimited by comment markers.
|
|
6
|
+
#
|
|
7
|
+
# The wrapper owns the span, never the module. A module may be one the
|
|
8
|
+
# caller wrote in, or one that cannot be deleted at all (ThisWorkbook, a
|
|
9
|
+
# worksheet), so "replace the module" is not available and would be too
|
|
10
|
+
# blunt even where it is.
|
|
11
|
+
module VBABlock
|
|
12
|
+
NAME = /\A[A-Za-z0-9_-]+\z/.freeze
|
|
13
|
+
|
|
14
|
+
# Matches any wineole marker line, open or close, for any name -- not
|
|
15
|
+
# just the one being written. Used to refuse a payload that would be
|
|
16
|
+
# indistinguishable from the wrapper's own delimiters.
|
|
17
|
+
MARKER_LINE = /\A'<\/?wineole:[A-Za-z0-9_-]+>\z/.freeze
|
|
18
|
+
|
|
19
|
+
def self.open_marker(name) = "'<wineole:#{name}>"
|
|
20
|
+
def self.close_marker(name) = "'</wineole:#{name}>"
|
|
21
|
+
|
|
22
|
+
# Replaces the block of this name if it is there, adds it if not.
|
|
23
|
+
def self.write(code_module, name, code)
|
|
24
|
+
check_name(name)
|
|
25
|
+
check_representable(code)
|
|
26
|
+
check_payload(code)
|
|
27
|
+
remove(code_module, name)
|
|
28
|
+
code_module.AddFromString(
|
|
29
|
+
"#{open_marker(name)}\n#{code.chomp}\n#{close_marker(name)}\n"
|
|
30
|
+
)
|
|
31
|
+
nil
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# False when there was nothing of this name to remove. Otherwise the
|
|
35
|
+
# module's remaining lines, as an Array -- so a caller that needs to
|
|
36
|
+
# know whether the module is now blank does not have to fetch the
|
|
37
|
+
# whole body a second time to ask.
|
|
38
|
+
#
|
|
39
|
+
# Block names match case-insensitively, as VBA identifiers do: two
|
|
40
|
+
# blocks named `main` and `Main` would hold procedures that collide,
|
|
41
|
+
# and VBA answers "Ambiguous name detected" for the whole module from
|
|
42
|
+
# then on. So `Main` replaces (and removes) a block written as `main`.
|
|
43
|
+
def self.remove(code_module, name)
|
|
44
|
+
check_name(name)
|
|
45
|
+
lines = body(code_module)
|
|
46
|
+
return false if lines.empty?
|
|
47
|
+
|
|
48
|
+
first = lines.index { |l| l.strip.casecmp?(open_marker(name)) }
|
|
49
|
+
last = lines.index { |l| l.strip.casecmp?(close_marker(name)) }
|
|
50
|
+
|
|
51
|
+
if first.nil?
|
|
52
|
+
if last
|
|
53
|
+
raise ArgumentError,
|
|
54
|
+
"the #{name.inspect} block in this module has a closing marker " \
|
|
55
|
+
'with no matching opening one -- the module is already ' \
|
|
56
|
+
'corrupted; refusing to guess what to remove'
|
|
57
|
+
end
|
|
58
|
+
return false
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
if last.nil? || last < first
|
|
62
|
+
raise ArgumentError,
|
|
63
|
+
"the #{name.inspect} block in this module has no closing marker -- " \
|
|
64
|
+
'refusing to guess where it ends'
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
code_module.DeleteLines(first + 1, last - first + 1)
|
|
68
|
+
lines[0...first] + lines[(last + 1)..]
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Nothing but whitespace. Not CountOfLines == 0: a module emptied of
|
|
72
|
+
# its blocks still reports the newlines that held them.
|
|
73
|
+
def self.blank?(code_module)
|
|
74
|
+
blank_lines?(body(code_module))
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# The same emptiness rule #blank? uses, applied to lines the caller
|
|
78
|
+
# already has (typically the Array #remove just handed back) instead
|
|
79
|
+
# of fetching the body again.
|
|
80
|
+
def self.blank_lines?(lines)
|
|
81
|
+
lines.all? { |l| l.strip.empty? }
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# One round trip, whatever the module's length. Excel reports 0 lines
|
|
85
|
+
# for a module never written to, and Lines(1, 0) is not a legal call.
|
|
86
|
+
def self.body(code_module)
|
|
87
|
+
count = code_module.CountOfLines.to_i
|
|
88
|
+
return [] if count.zero?
|
|
89
|
+
|
|
90
|
+
code_module.Lines(1, count).split(/\r?\n/)
|
|
91
|
+
end
|
|
92
|
+
private_class_method :body
|
|
93
|
+
|
|
94
|
+
# A module's text is held in the system ANSI codepage, not Unicode --
|
|
95
|
+
# measured, not assumed: on a CP932 host `café` comes back `cafe`,
|
|
96
|
+
# `✓` comes back `?`, and simplified Chinese comes back part `?`.
|
|
97
|
+
# Japanese survives only because CP932 can represent it, which is why
|
|
98
|
+
# an earlier measurement using Japanese alone concluded, wrongly, that
|
|
99
|
+
# this path carried Unicode.
|
|
100
|
+
#
|
|
101
|
+
# So this path is bound by exactly the same codepage as import_vba,
|
|
102
|
+
# and gets the same rule: refuse rather than substitute. Silently
|
|
103
|
+
# dropping an accent is the failure this whole phase exists to remove.
|
|
104
|
+
#
|
|
105
|
+
# ASCII skips the check, which is almost every call -- resolving the
|
|
106
|
+
# codepage costs a `wine reg` invocation.
|
|
107
|
+
def self.check_representable(code)
|
|
108
|
+
return if code.ascii_only?
|
|
109
|
+
|
|
110
|
+
code.encode(VBA.codepage)
|
|
111
|
+
rescue ::Encoding::UndefinedConversionError => e
|
|
112
|
+
VBA.unrepresentable!(e.error_char, 'this code')
|
|
113
|
+
end
|
|
114
|
+
private_class_method :check_representable
|
|
115
|
+
|
|
116
|
+
def self.check_name(name)
|
|
117
|
+
return if name.is_a?(::String) && name.match?(NAME)
|
|
118
|
+
|
|
119
|
+
raise ArgumentError,
|
|
120
|
+
"a block name must match #{NAME.inspect} -- it goes inside a VBA " \
|
|
121
|
+
"comment marker, so it cannot contain spaces, '>' or newlines. " \
|
|
122
|
+
"Got #{name.inspect}"
|
|
123
|
+
end
|
|
124
|
+
private_class_method :check_name
|
|
125
|
+
|
|
126
|
+
# A code body containing a line that is itself a wineole marker would
|
|
127
|
+
# be indistinguishable from a real one once written: #remove would
|
|
128
|
+
# find the caller's accidental marker instead of its own, delete up to
|
|
129
|
+
# the wrong place, and leave the rest as permanent garbage. The
|
|
130
|
+
# wrapper controls what it writes, so refusing the payload up front is
|
|
131
|
+
# what keeps that state from ever existing.
|
|
132
|
+
def self.check_payload(code)
|
|
133
|
+
offending = code.split(/\r?\n/).find { |line| line.strip.match?(MARKER_LINE) }
|
|
134
|
+
return if offending.nil?
|
|
135
|
+
|
|
136
|
+
raise ArgumentError,
|
|
137
|
+
"the code being written contains a line that is itself a wineole " \
|
|
138
|
+
"marker (#{offending.strip.inspect}) -- this cannot be told apart " \
|
|
139
|
+
'from the wrapper\'s own markers, so it is refused'
|
|
140
|
+
end
|
|
141
|
+
private_class_method :check_payload
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# The bundled Microsoft Office wrapper: Address, Paths, Range, Sheet, Book,
|
|
2
|
+
# Excel, and the Controls and Forms wrappers, all under WineOLE::MSOffice.
|
|
3
|
+
#
|
|
4
|
+
# Deliberately NOT required from lib/wineole.rb. The core layer is a
|
|
5
|
+
# general-purpose COM bridge; this is Office-specific, and it is where a
|
|
6
|
+
# Word and a PowerPoint wrapper will land too, so it will only grow.
|
|
7
|
+
# Someone who wants the bridge should not have to carry an Excel wrapper to
|
|
8
|
+
# get it.
|
|
9
|
+
#
|
|
10
|
+
# Defines no top-level constant. An earlier draft aliased WineOLE::MSOffice
|
|
11
|
+
# to a root-level MSOffice for code written against the older msoffice.rb;
|
|
12
|
+
# reaching into the root namespace from a library is intrusive enough that
|
|
13
|
+
# the alias was not worth the two failure modes it brought with it (a
|
|
14
|
+
# silent no-op when something else already defined MSOffice, and a test
|
|
15
|
+
# whose result depended on process-wide load order).
|
|
16
|
+
require_relative 'msoffice/address'
|
|
17
|
+
require_relative 'msoffice/vba'
|
|
18
|
+
require_relative 'msoffice/vba_block'
|
|
19
|
+
require_relative 'msoffice/vba_api'
|
|
20
|
+
require_relative 'msoffice/color'
|
|
21
|
+
require_relative 'msoffice/format'
|
|
22
|
+
require_relative 'msoffice/paths'
|
|
23
|
+
require_relative 'msoffice/range'
|
|
24
|
+
require_relative 'msoffice/sheet'
|
|
25
|
+
require_relative 'msoffice/controls'
|
|
26
|
+
require_relative 'msoffice/forms'
|
|
27
|
+
require_relative 'msoffice/book'
|
|
28
|
+
require_relative 'msoffice/excel'
|
data/lib/wineole/proxy.rb
CHANGED
|
@@ -1,21 +1,93 @@
|
|
|
1
1
|
require 'time'
|
|
2
|
+
require 'date'
|
|
2
3
|
require_relative 'errors'
|
|
4
|
+
# Stated here rather than left to wineole.rb: `ole_events` below names the
|
|
5
|
+
# constant, so anything that loads this file alone -- proxy_test.rb does --
|
|
6
|
+
# must get a working method rather than a NameError the first time it is
|
|
7
|
+
# called. events.rb requires nothing from here (it reaches Proxy at call
|
|
8
|
+
# time, not load time), so there is no cycle.
|
|
9
|
+
require_relative 'events'
|
|
3
10
|
|
|
4
11
|
module WineOLE
|
|
5
12
|
class Proxy
|
|
6
|
-
def self.create(class_name, client)
|
|
7
|
-
|
|
13
|
+
def self.create(class_name, client, cleanup: nil)
|
|
14
|
+
params = {class_name: class_name}
|
|
15
|
+
params[:cleanup] = build_cleanup(cleanup) if cleanup
|
|
16
|
+
handle = client.call('create', params)['$ole_ref']
|
|
17
|
+
register_cleanup(client, handle, cleanup)
|
|
8
18
|
new(client, session_id: client.object_id, handle: handle, created: true)
|
|
9
19
|
end
|
|
10
20
|
|
|
11
|
-
def self.connect(class_name, client)
|
|
12
|
-
|
|
21
|
+
def self.connect(class_name, client, cleanup: nil)
|
|
22
|
+
params = {class_name: class_name}
|
|
23
|
+
params[:cleanup] = build_cleanup(cleanup) if cleanup
|
|
24
|
+
handle = client.call('connect', params)['$ole_ref']
|
|
25
|
+
register_cleanup(client, handle, cleanup)
|
|
13
26
|
new(client, session_id: client.object_id, handle: handle, created: false)
|
|
14
27
|
end
|
|
15
28
|
|
|
16
|
-
def self.connect_or_create(class_name, client)
|
|
17
|
-
|
|
18
|
-
|
|
29
|
+
def self.connect_or_create(class_name, client, cleanup: nil)
|
|
30
|
+
params = {class_name: class_name}
|
|
31
|
+
params[:cleanup] = build_cleanup(cleanup) if cleanup
|
|
32
|
+
result = client.call('connect_or_create', params)
|
|
33
|
+
handle = result['$ole_ref']
|
|
34
|
+
register_cleanup(client, handle, cleanup)
|
|
35
|
+
new(client, session_id: client.object_id, handle: handle, created: result['created'])
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# `cleanup` is `{ steps: [[name, *args], ...], on_cleanup: proc_or_nil }`
|
|
39
|
+
# in Ruby terms; the wire wants `{ steps: [{name:, args:}, ...], callback: bool }`.
|
|
40
|
+
# `callback` tells the bridge whether to hold the root open and emit a
|
|
41
|
+
# `$cleanup` event for a registered closure, or just run the steps and
|
|
42
|
+
# release outright -- so its value comes from whether `on_cleanup` is
|
|
43
|
+
# present, not from anything the caller states separately.
|
|
44
|
+
def self.build_cleanup(cleanup)
|
|
45
|
+
steps = (cleanup[:steps] || []).map do |name, *args|
|
|
46
|
+
{name: name, args: args}
|
|
47
|
+
end
|
|
48
|
+
{steps: steps, callback: !cleanup[:on_cleanup].nil?}
|
|
49
|
+
end
|
|
50
|
+
private_class_method :build_cleanup
|
|
51
|
+
|
|
52
|
+
# Register the client closure (if any) so the dispatcher can deliver
|
|
53
|
+
# $cleanup for this root handle. The real Dispatcher#register_cleanup
|
|
54
|
+
# lands in a later task; this only needs `client.dispatcher` to answer to
|
|
55
|
+
# it, which is exactly what the real Client already does.
|
|
56
|
+
def self.register_cleanup(client, handle, cleanup)
|
|
57
|
+
return unless cleanup && cleanup[:on_cleanup]
|
|
58
|
+
|
|
59
|
+
client.dispatcher.register_cleanup(handle, cleanup[:on_cleanup])
|
|
60
|
+
end
|
|
61
|
+
private_class_method :register_cleanup
|
|
62
|
+
|
|
63
|
+
# One wire value, in Ruby terms.
|
|
64
|
+
#
|
|
65
|
+
# On the class rather than private to an instance because two different
|
|
66
|
+
# holders of a client need it: an invoke's RESULT, and an event's
|
|
67
|
+
# ARGUMENTS (WineOLE::Events#build_args). A second copy of this walk over
|
|
68
|
+
# there is how the same tagged value ends up a Time when a call returns it
|
|
69
|
+
# and a raw {"$type" => "time"} Hash when an event carries it -- and how a
|
|
70
|
+
# nested $ole_ref reaches a callback unwrapped.
|
|
71
|
+
#
|
|
72
|
+
# Recursive, because a bulk range read comes back as an array of rows and
|
|
73
|
+
# the values needing conversion sit inside it, not at the top level. A
|
|
74
|
+
# non-recursive decode would hand back raw {"$type" => "time"} hashes for
|
|
75
|
+
# every date cell in the range.
|
|
76
|
+
def self.decode(client, value)
|
|
77
|
+
case value
|
|
78
|
+
when Array
|
|
79
|
+
value.map { |v| decode(client, v) }
|
|
80
|
+
when Hash
|
|
81
|
+
if value.key?('$ole_ref')
|
|
82
|
+
wrap(client, client.object_id, value['$ole_ref'])
|
|
83
|
+
elsif value['$type'] == 'time'
|
|
84
|
+
Time.iso8601(value['iso8601'])
|
|
85
|
+
else
|
|
86
|
+
value.transform_values { |v| decode(client, v) }
|
|
87
|
+
end
|
|
88
|
+
else
|
|
89
|
+
value
|
|
90
|
+
end
|
|
19
91
|
end
|
|
20
92
|
|
|
21
93
|
def self.wrap(client, session_id, ole_ref)
|
|
@@ -75,8 +147,38 @@ module WineOLE
|
|
|
75
147
|
@created
|
|
76
148
|
end
|
|
77
149
|
|
|
150
|
+
# Tells the bridge to leave this root instance running rather than
|
|
151
|
+
# closing it when the connection goes away -- the counterpart to a
|
|
152
|
+
# `cleanup:` closure a caller wants to run later, on its own terms,
|
|
153
|
+
# rather than as part of this process's teardown.
|
|
154
|
+
def ole_leave_open
|
|
155
|
+
@client.call('leave_open', {handle: @ole_handle})
|
|
156
|
+
nil
|
|
157
|
+
end
|
|
158
|
+
|
|
78
159
|
def ole_release
|
|
79
|
-
@client.call('release', {handle: @ole_handle})
|
|
160
|
+
result = @client.call('release', {handle: @ole_handle})
|
|
161
|
+
# A client closure must run before this handle is actually gone: the
|
|
162
|
+
# bridge answers with the $cleanup sequence number instead of
|
|
163
|
+
# releasing outright, and this blocks until the dispatcher has
|
|
164
|
+
# delivered it and the release_event that follows. See
|
|
165
|
+
# Client#await_cleanup.
|
|
166
|
+
if result.is_a?(Hash) && (seq = result['cleanup'])
|
|
167
|
+
@client.await_cleanup(seq)
|
|
168
|
+
end
|
|
169
|
+
nil
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
# COM events for this object. Named with the `ole_` prefix like every
|
|
173
|
+
# other bookkeeping method here: a Proxy forwards unknown names straight
|
|
174
|
+
# to COM, so a bare `events` would shadow a real `Events` member.
|
|
175
|
+
#
|
|
176
|
+
# Memoized, because the Events owns a dispatcher thread and a bridge-side
|
|
177
|
+
# subscription set: a fresh one per call would mean `on` and the `off`
|
|
178
|
+
# that is meant to undo it talked to different objects.
|
|
179
|
+
def ole_events
|
|
180
|
+
check_live!
|
|
181
|
+
@ole_events ||= Events.new(@client, @ole_handle)
|
|
80
182
|
end
|
|
81
183
|
|
|
82
184
|
def ole_const_load
|
|
@@ -126,6 +228,21 @@ module WineOLE
|
|
|
126
228
|
'passed as an argument here'
|
|
127
229
|
end
|
|
128
230
|
{'$ole_ref' => value.ole_handle}
|
|
231
|
+
when Time
|
|
232
|
+
# The same tag the receive side emits for VT_DATE. The wall clock is
|
|
233
|
+
# sent as-is: a VT_DATE carries no timezone, so converting here would
|
|
234
|
+
# silently move the value the caller wrote. Matching
|
|
235
|
+
# wineole/proxy.py's `_encode`.
|
|
236
|
+
{'$type' => 'time', 'iso8601' => value.strftime('%Y-%m-%dT%H:%M:%S')}
|
|
237
|
+
when Date
|
|
238
|
+
# `date` is already loaded transitively -- `time`'s own lib/time.rb
|
|
239
|
+
# requires it -- so this require costs nothing new; it just states
|
|
240
|
+
# the real dependency instead of relying on another library's
|
|
241
|
+
# internals. DateTime is a subclass of Date, and both answer
|
|
242
|
+
# strftime, so one branch covers them. (Date is not in Time's
|
|
243
|
+
# hierarchy, which is why the `when Time` branch above doesn't catch
|
|
244
|
+
# it.) Matching wineole/proxy.py's `_encode`.
|
|
245
|
+
{'$type' => 'time', 'iso8601' => value.strftime('%Y-%m-%dT%H:%M:%S')}
|
|
129
246
|
when Hash
|
|
130
247
|
value.transform_values { |v| encode(v) }
|
|
131
248
|
when Array
|
|
@@ -136,13 +253,7 @@ module WineOLE
|
|
|
136
253
|
end
|
|
137
254
|
|
|
138
255
|
def decode(value)
|
|
139
|
-
|
|
140
|
-
Proxy.wrap(@client, @client.object_id, value['$ole_ref'])
|
|
141
|
-
elsif value.is_a?(Hash) && value['$type'] == 'time'
|
|
142
|
-
Time.iso8601(value['iso8601'])
|
|
143
|
-
else
|
|
144
|
-
value
|
|
145
|
-
end
|
|
256
|
+
Proxy.decode(@client, value)
|
|
146
257
|
end
|
|
147
258
|
end
|
|
148
259
|
end
|
data/lib/wineole.rb
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
require_relative 'wineole/errors'
|
|
2
2
|
require_relative 'wineole/client'
|
|
3
3
|
require_relative 'wineole/proxy'
|
|
4
|
+
require_relative 'wineole/dispatcher'
|
|
5
|
+
require_relative 'wineole/events'
|
|
4
6
|
|
|
5
7
|
module WineOLE
|
|
6
8
|
@default_client = nil
|
|
@@ -25,12 +27,19 @@ module WineOLE
|
|
|
25
27
|
# racing on a nil default contend for the lock, and only the actual
|
|
26
28
|
# winner calls Client.open -- everyone else sees it already set once they
|
|
27
29
|
# acquire the lock, and the `||=` means they never call Client.open again.
|
|
30
|
+
#
|
|
31
|
+
# Public (a deliberate exception to "the core layer is not changed" --
|
|
32
|
+
# the same exception, and for the same reason, as Client#loopback?):
|
|
33
|
+
# bundled wrappers such as WineOLE::MSOffice::Excel need the Client their
|
|
34
|
+
# Proxy belongs to (Book needs it to answer #loopback?), and this module is
|
|
35
|
+
# already the layer holding that connection. Making the caller guess, or
|
|
36
|
+
# open a second connection just to answer that question, would be worse
|
|
37
|
+
# than letting the layer that already knows the answer say so.
|
|
28
38
|
def self.default_client
|
|
29
39
|
return @default_client if @default_client
|
|
30
40
|
|
|
31
41
|
@mutex.synchronize { @default_client ||= Client.open }
|
|
32
42
|
end
|
|
33
|
-
private_class_method :default_client
|
|
34
43
|
|
|
35
44
|
def self.create(class_name)
|
|
36
45
|
default_client.create(class_name)
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: wineole
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- firelzrd
|
|
@@ -14,14 +14,33 @@ description: |
|
|
|
14
14
|
that lets a Linux Ruby process drive Win32OLE/COM automation of
|
|
15
15
|
Windows applications running under Wine, over a JSON Lines TCP
|
|
16
16
|
protocol, without requiring a Windows build of Ruby.
|
|
17
|
-
executables:
|
|
17
|
+
executables:
|
|
18
|
+
- wineole-vba
|
|
18
19
|
extensions: []
|
|
19
20
|
extra_rdoc_files: []
|
|
20
21
|
files:
|
|
21
22
|
- LICENSE
|
|
23
|
+
- bin/wineole-vba
|
|
22
24
|
- lib/wineole.rb
|
|
23
25
|
- lib/wineole/client.rb
|
|
26
|
+
- lib/wineole/dispatcher.rb
|
|
24
27
|
- lib/wineole/errors.rb
|
|
28
|
+
- lib/wineole/events.rb
|
|
29
|
+
- lib/wineole/msoffice.rb
|
|
30
|
+
- lib/wineole/msoffice/address.rb
|
|
31
|
+
- lib/wineole/msoffice/book.rb
|
|
32
|
+
- lib/wineole/msoffice/color.rb
|
|
33
|
+
- lib/wineole/msoffice/controls.rb
|
|
34
|
+
- lib/wineole/msoffice/excel.rb
|
|
35
|
+
- lib/wineole/msoffice/format.rb
|
|
36
|
+
- lib/wineole/msoffice/forms.rb
|
|
37
|
+
- lib/wineole/msoffice/passthrough.rb
|
|
38
|
+
- lib/wineole/msoffice/paths.rb
|
|
39
|
+
- lib/wineole/msoffice/range.rb
|
|
40
|
+
- lib/wineole/msoffice/sheet.rb
|
|
41
|
+
- lib/wineole/msoffice/vba.rb
|
|
42
|
+
- lib/wineole/msoffice/vba_api.rb
|
|
43
|
+
- lib/wineole/msoffice/vba_block.rb
|
|
25
44
|
- lib/wineole/proxy.rb
|
|
26
45
|
- wineole-bridge-dist/aarch64-pc-windows-gnullvm/wineole-bridge.exe
|
|
27
46
|
- wineole-bridge-dist/i686-pc-windows-gnu/wineole-bridge.exe
|