ruddy 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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: a5472f2049ff6bfd144aeea055ab4e7bf2f28c2a
4
+ data.tar.gz: b631945110c72dbddb97b16f53a96b4aace394a9
5
+ SHA512:
6
+ metadata.gz: a48d3d9eb7bb0a5baa0c263675f629af983d940ad2322e6bf34035ad112ebecf1bf96d3901c73358d45494aa6f0eb359fb5a3656b54968b648282972b4a6cc86
7
+ data.tar.gz: 2cdb71ff621c0cdb7849428ce1d786569764d6542a62e42957df51e881f1f65b977f1a2cb4ea9f9a566654c1133654d03bc1ee7a4b5ed9179704f3631527d271
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ source "https://rubygems.org"
2
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Andrew Haines
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,19 @@
1
+ # Ruddy
2
+
3
+ Basic Win32 DDE client in Ruby.
4
+
5
+ ## Installation
6
+
7
+ $ gem install ruddy
8
+
9
+ ## Usage
10
+
11
+ Connect to a DDE server using the `Ruddy::Connection.open` method, and call `execute` on the yielded connection:
12
+
13
+ ```ruby
14
+ require "ruddy"
15
+
16
+ Ruddy::Connection.open("SERVICE", "topic") do |connection|
17
+ connection.execute("command")
18
+ end
19
+ ```
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
@@ -0,0 +1,76 @@
1
+ require "ffi"
2
+ require "dde/constants"
3
+
4
+ module DDE
5
+ extend FFI::Library
6
+ ffi_lib "user32"
7
+ ffi_convention :stdcall
8
+
9
+ # HDDEDATA CALLBACK DdeCallback(
10
+ # _In_ UINT uType,
11
+ # _In_ UINT uFmt,
12
+ # _In_ HCONV hconv,
13
+ # _In_ HSZ hsz1,
14
+ # _In_ HSZ hsz2,
15
+ # _In_ HDDEDATA hdata,
16
+ # _In_ ULONG_PTR dwData1,
17
+ # _In_ ULONG_PTR dwData2
18
+ # );
19
+ callback :dde_callback, [:uint, :uint, :pointer, :pointer, :pointer, :pointer, :pointer, :pointer], :pointer
20
+
21
+ # UINT WINAPI DdeInitialize(
22
+ # _Inout_ LPDWORD pidInst,
23
+ # _In_ PFNCALLBACK pfnCallback,
24
+ # _In_ DWORD afCmd,
25
+ # _Reserved_ DWORD ulRes
26
+ # );
27
+ attach_function :start, :DdeInitializeA, [:pointer, :dde_callback, :uint, :uint], :uint
28
+
29
+ # BOOL WINAPI DdeUninitialize(
30
+ # _In_ DWORD idInst
31
+ # );
32
+ attach_function :stop, :DdeUninitialize, [:uint], :bool
33
+
34
+ # HSZ WINAPI DdeCreateStringHandle(
35
+ # _In_ DWORD idInst,
36
+ # _In_ LPTSTR psz,
37
+ # _In_ int iCodePage
38
+ # );
39
+ attach_function :create_string_handle, :DdeCreateStringHandleA, [:uint, :pointer, :int], :pointer
40
+
41
+ # HCONV WINAPI DdeConnect(
42
+ # _In_ DWORD idInst,
43
+ # _In_ HSZ hszService,
44
+ # _In_ HSZ hszTopic,
45
+ # _In_opt_ PCONVCONTEXT pCC
46
+ # );
47
+ attach_function :connect, :DdeConnect, [:uint, :pointer, :pointer, :pointer], :pointer
48
+
49
+ # HDDEDATA WINAPI DdeClientTransaction(
50
+ # _In_opt_ LPBYTE pData,
51
+ # _In_ DWORD cbData,
52
+ # _In_ HCONV hConv,
53
+ # _In_opt_ HSZ hszItem,
54
+ # _In_ UINT wFmt,
55
+ # _In_ UINT wType,
56
+ # _In_ DWORD dwTimeout,
57
+ # _Out_opt_ LPDWORD pdwResult
58
+ # );
59
+ attach_function :client_transaction, :DdeClientTransaction, [:pointer, :uint, :pointer, :pointer, :uint, :uint, :uint, :pointer], :pointer
60
+
61
+ # UINT WINAPI DdeGetLastError(
62
+ # _In_ DWORD idInst
63
+ # );
64
+ attach_function :last_error, :DdeGetLastError, [:uint], :uint
65
+
66
+ # BOOL WINAPI DdeFreeDataHandle(
67
+ # _In_ HDDEDATA hData
68
+ # );
69
+ attach_function :free_data_handle, :DdeFreeDataHandle, [:pointer], :bool
70
+
71
+ # BOOL WINAPI DdeFreeStringHandle(
72
+ # _In_ DWORD idInst,
73
+ # _In_ HSZ hsz
74
+ # );
75
+ attach_function :free_string_handle, :DdeFreeStringHandle, [:uint, :pointer], :bool
76
+ end
@@ -0,0 +1,7 @@
1
+ module DDE
2
+ DMLERR_NO_ERROR = 0
3
+ CP_WINANSI = 1004
4
+ XTYP_EXECUTE = 0x4050
5
+ APPCLASS_STANDARD = 0x00000000
6
+ APPCMD_CLIENTONLY = 0x00000010
7
+ end
@@ -0,0 +1,3 @@
1
+ require "ruddy/version"
2
+ require "dde"
3
+ require "ruddy/connection"
@@ -0,0 +1,83 @@
1
+ module Ruddy
2
+ class Connection
3
+
4
+ attr_reader :service, :topic
5
+ attr_accessor :timeout
6
+
7
+ def initialize(service, topic, options = {})
8
+ @service = service
9
+ @topic = topic
10
+ @timeout = options.fetch(:timeout, 3000)
11
+
12
+ start
13
+ connect
14
+ end
15
+
16
+ def close
17
+ @closed = true
18
+ DDE.stop(instance)
19
+ end
20
+
21
+ def closed?
22
+ @closed
23
+ end
24
+
25
+ def execute(command)
26
+ raise IOError.new("closed connection") if closed?
27
+
28
+ buffer = FFI::MemoryPointer.from_string(command)
29
+ result = FFI::MemoryPointer.new(:uint)
30
+
31
+ data = DDE.client_transaction(buffer, buffer.size, conversation, nil, 0, DDE::XTYP_EXECUTE, timeout, result)
32
+ raise SystemCallError.new("command execution failed", DDE.last_error(instance)) if data.null?
33
+ DDE.free_data_handle(data)
34
+
35
+ return result.read_uint
36
+ end
37
+
38
+ def self.open(*args)
39
+ connection = new(*args)
40
+ return connection unless block_given?
41
+
42
+ begin
43
+ yield connection
44
+ ensure
45
+ connection.close
46
+ end
47
+ end
48
+
49
+ private
50
+
51
+ attr_reader :instance, :conversation
52
+
53
+ CALLBACK = ->(*){}
54
+
55
+ def start
56
+ instance = FFI::MemoryPointer.new(:uint)
57
+ error = DDE.start(instance, CALLBACK, DDE::APPCLASS_STANDARD | DDE::APPCMD_CLIENTONLY, 0)
58
+ raise SystemCallError.new("DDE management library initialization failed", error) if error != DDE::DMLERR_NO_ERROR
59
+ @instance = instance.read_uint
60
+ end
61
+
62
+ def connect
63
+ service_handle = create_string_handle(service)
64
+ topic_handle = create_string_handle(topic)
65
+
66
+ @conversation = DDE.connect(instance, service_handle, topic_handle, nil)
67
+
68
+ free_string_handle service_handle
69
+ free_string_handle topic_handle
70
+
71
+ raise SystemCallError.new(%{could not connect to DDE service "#{service}"}) if conversation.null?
72
+ end
73
+
74
+ def create_string_handle(string)
75
+ DDE.create_string_handle(instance, string, DDE::CP_WINANSI)
76
+ end
77
+
78
+ def free_string_handle(handle)
79
+ DDE.free_string_handle(instance, handle)
80
+ end
81
+
82
+ end
83
+ end
@@ -0,0 +1,3 @@
1
+ module Ruddy
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,24 @@
1
+ lib = File.expand_path("../lib", __FILE__)
2
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
+ require "ruddy/version"
4
+
5
+ Gem::Specification.new do |gem|
6
+ gem.name = "ruddy"
7
+ gem.version = Ruddy::VERSION
8
+ gem.authors = ["Andrew Haines"]
9
+ gem.email = ["andrew@haines.org.nz"]
10
+ gem.summary = %q{Basic Win32 DDE client in Ruby}
11
+ gem.homepage = "https://github.com/haines/ruddy"
12
+ gem.license = "MIT"
13
+
14
+ gem.files = `git ls-files`.split($/)
15
+ gem.executables = gem.files.grep(%r{^bin/}) { |f| File.basename(f) }
16
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
17
+ gem.require_paths = ["lib"]
18
+
19
+ gem.add_dependency "ffi", "~> 1.7.0"
20
+
21
+ gem.add_development_dependency "bundler", "~> 1.3"
22
+ gem.add_development_dependency "rake"
23
+ gem.add_development_dependency "rspec", "~> 2.13"
24
+ end
@@ -0,0 +1,206 @@
1
+ require "ruddy/connection"
2
+ require "ffi"
3
+
4
+ require "support/fake_dde"
5
+ require "support/string_pointer_matcher"
6
+
7
+ module Ruddy
8
+ describe Connection do
9
+ describe ".new" do
10
+ it "sets the service" do
11
+ connection = Connection.new("MY_SERVICE", "my_topic")
12
+
13
+ expect(connection.service).to eq "MY_SERVICE"
14
+ end
15
+
16
+ it "sets the topic" do
17
+ connection = Connection.new("MY_SERVICE", "my_topic")
18
+
19
+ expect(connection.topic).to eq "my_topic"
20
+ end
21
+
22
+ describe "timeout option" do
23
+ it "sets the timeout" do
24
+ connection = Connection.new("MY_SERVICE", "my_topic", timeout: 1000)
25
+
26
+ expect(connection.timeout).to eq 1000
27
+ end
28
+
29
+ it "defaults to 3 sec" do
30
+ connection = Connection.new("MY_SERVICE", "my_topic")
31
+
32
+ expect(connection.timeout).to eq 3000
33
+ end
34
+ end
35
+
36
+ it "connects to the service" do
37
+ stub_instance 42
38
+
39
+ service_handle = stub("service_handle")
40
+ DDE.stub(:create_string_handle).with(42, "MY_SERVICE", DDE::CP_WINANSI).and_return(service_handle)
41
+
42
+ topic_handle = stub("topic_handle")
43
+ DDE.stub(:create_string_handle).with(42, "my_topic", DDE::CP_WINANSI).and_return(topic_handle)
44
+
45
+ DDE.should_receive(:connect).with(42, service_handle, topic_handle, nil).and_return(valid_pointer)
46
+ Connection.new("MY_SERVICE", "my_topic")
47
+ end
48
+
49
+ context "when initializing the DDE management library fails" do
50
+ it "raises an error" do
51
+ DDE.stub start: 0x4006
52
+ expect{Connection.new("MY_SERVICE", "my_topic")}.to raise_error SystemCallError, /DDE management library initialization failed/
53
+ end
54
+
55
+ it "sets the error code" do
56
+ DDE.stub start: 0x4006
57
+ begin
58
+ Connection.new("MY_SERVICE", "my_topic")
59
+ rescue SystemCallError => error
60
+ expect(error.errno).to eq 0x4006
61
+ end
62
+ end
63
+ end
64
+
65
+ context "when the connection fails" do
66
+ it "raises an error" do
67
+ DDE.stub connect: null_pointer
68
+ expect{Connection.new("MY_SERVICE", "my_topic")}.to raise_error SystemCallError, /could not connect to DDE service "MY_SERVICE"/
69
+ end
70
+ end
71
+ end
72
+
73
+ describe "#close" do
74
+ it "uninitializes the DDE management library" do
75
+ stub_instance 42
76
+ connection = Connection.new("MY_SERVICE", "my_topic")
77
+
78
+ DDE.should_receive(:stop).with(42)
79
+ connection.close
80
+ end
81
+ end
82
+
83
+ describe "#closed?" do
84
+ it "is false initially" do
85
+ connection = Connection.new("MY_SERVICE", "my_topic")
86
+
87
+ expect(connection).not_to be_closed
88
+ end
89
+
90
+ it "is true after closing" do
91
+ connection = Connection.new("MY_SERVICE", "my_topic")
92
+ connection.close
93
+
94
+ expect(connection).to be_closed
95
+ end
96
+ end
97
+
98
+ describe "#execute" do
99
+ it "executes the given command" do
100
+ conversation = valid_pointer
101
+ DDE.stub connect: conversation
102
+ connection = Connection.new("MY_SERVICE", "my_topic", timeout: 12345)
103
+ command = "[Foo(bar)]"
104
+
105
+ DDE.should_receive(:client_transaction).with(string_pointer(command), command.length + 1, conversation, nil, 0, DDE::XTYP_EXECUTE, 12345, kind_of(FFI::MemoryPointer)).and_return(valid_pointer)
106
+ connection.execute(command)
107
+ end
108
+
109
+ it "returns the result" do
110
+ connection = Connection.new("MY_SERVICE", "my_topic")
111
+ DDE.stub :client_transaction do |*, result|
112
+ result.write_uint 43
113
+ end
114
+
115
+ expect(connection.execute("[Foo(bar)]")).to eq 43
116
+ end
117
+
118
+ it "frees the returned data structure" do
119
+ connection = Connection.new("MY_SERVICE", "my_topic")
120
+ data = valid_pointer
121
+ DDE.stub client_transaction: data
122
+
123
+ DDE.should_receive(:free_data_handle).with(data)
124
+ connection.execute("[Foo(bar)]")
125
+ end
126
+
127
+ context "when the execution fails" do
128
+ it "raises an error" do
129
+ connection = Connection.new("MY_SERVICE", "my_topic")
130
+ DDE.stub client_transaction: null_pointer
131
+
132
+ expect{connection.execute("[Foo(bar)]")}.to raise_error SystemCallError, /command execution failed/
133
+ end
134
+
135
+ it "sets the error code" do
136
+ stub_instance 42
137
+ connection = Connection.new("MY_SERVICE", "my_topic")
138
+ DDE.stub(:last_error).with(42).and_return(0x4000)
139
+ DDE.stub client_transaction: null_pointer
140
+
141
+ begin
142
+ connection.execute("[Foo(bar)]")
143
+ rescue SystemCallError => error
144
+ expect(error.errno).to eq 0x4000
145
+ end
146
+ end
147
+ end
148
+
149
+ context "when the connection is closed" do
150
+ it "raises an error" do
151
+ connection = Connection.new("MY_SERVICE", "my_topic")
152
+ connection.close
153
+
154
+ expect{connection.execute("[Foo(bar)]")}.to raise_error IOError, /closed connection/
155
+ end
156
+ end
157
+ end
158
+
159
+ describe ".open" do
160
+ context "with a block" do
161
+ it "yields a connection" do
162
+ yielded = false
163
+ Connection.open("MY_SERVICE", "my_topic") do |connection|
164
+ yielded = connection
165
+ end
166
+
167
+ expect(yielded).to be_a Connection
168
+ end
169
+
170
+ it "closes the connection afterwards" do
171
+ yielded = false
172
+ Connection.open("MY_SERVICE", "my_topic") do |connection|
173
+ expect(connection).not_to be_closed
174
+ yielded = connection
175
+ end
176
+
177
+ expect(yielded).to be_closed
178
+ end
179
+ end
180
+
181
+ context "without a block" do
182
+ it "returns an open connection" do
183
+ connection = Connection.open("MY_SERVICE", "my_topic")
184
+
185
+ expect(connection).to be_a Connection
186
+ expect(connection).not_to be_closed
187
+ end
188
+ end
189
+ end
190
+
191
+ def stub_instance(instance)
192
+ DDE.stub :start do |pointer, *|
193
+ pointer.write_uint instance
194
+ DDE::DMLERR_NO_ERROR
195
+ end
196
+ end
197
+
198
+ def valid_pointer
199
+ FFI::MemoryPointer.new(:uint)
200
+ end
201
+
202
+ def null_pointer
203
+ stub(null?: true)
204
+ end
205
+ end
206
+ end
@@ -0,0 +1,22 @@
1
+ require "dde/constants"
2
+
3
+ module DDE
4
+ module_function
5
+
6
+ def start(*)
7
+ DDE::DMLERR_NO_ERROR
8
+ end
9
+
10
+ def connect(*)
11
+ FFI::MemoryPointer.new(:uint)
12
+ end
13
+
14
+ def stop(*) end
15
+
16
+ def create_string_handle(*) end
17
+ def free_string_handle(*) end
18
+
19
+ def free_data_handle(*) end
20
+
21
+ def last_error(*) end
22
+ end
@@ -0,0 +1,17 @@
1
+ class StringPointerMatcher
2
+ def initialize(string)
3
+ @string = string
4
+ end
5
+
6
+ def ==(actual)
7
+ @string == actual.read_string
8
+ end
9
+
10
+ def description
11
+ "pointer to #{@string.inspect}"
12
+ end
13
+ end
14
+
15
+ def string_pointer(string)
16
+ StringPointerMatcher.new(string)
17
+ end
metadata ADDED
@@ -0,0 +1,117 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ruddy
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Andrew Haines
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2013-04-21 00:00:00.000000000 Z
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: 1.7.0
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ~>
25
+ - !ruby/object:Gem::Version
26
+ version: 1.7.0
27
+ - !ruby/object:Gem::Dependency
28
+ name: bundler
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ~>
32
+ - !ruby/object:Gem::Version
33
+ version: '1.3'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ~>
39
+ - !ruby/object:Gem::Version
40
+ version: '1.3'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
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: rspec
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ~>
60
+ - !ruby/object:Gem::Version
61
+ version: '2.13'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ~>
67
+ - !ruby/object:Gem::Version
68
+ version: '2.13'
69
+ description:
70
+ email:
71
+ - andrew@haines.org.nz
72
+ executables: []
73
+ extensions: []
74
+ extra_rdoc_files: []
75
+ files:
76
+ - .gitignore
77
+ - Gemfile
78
+ - LICENSE
79
+ - README.md
80
+ - Rakefile
81
+ - lib/dde.rb
82
+ - lib/dde/constants.rb
83
+ - lib/ruddy.rb
84
+ - lib/ruddy/connection.rb
85
+ - lib/ruddy/version.rb
86
+ - ruddy.gemspec
87
+ - spec/ruddy/connection_spec.rb
88
+ - spec/support/fake_dde.rb
89
+ - spec/support/string_pointer_matcher.rb
90
+ homepage: https://github.com/haines/ruddy
91
+ licenses:
92
+ - MIT
93
+ metadata: {}
94
+ post_install_message:
95
+ rdoc_options: []
96
+ require_paths:
97
+ - lib
98
+ required_ruby_version: !ruby/object:Gem::Requirement
99
+ requirements:
100
+ - - '>='
101
+ - !ruby/object:Gem::Version
102
+ version: '0'
103
+ required_rubygems_version: !ruby/object:Gem::Requirement
104
+ requirements:
105
+ - - '>='
106
+ - !ruby/object:Gem::Version
107
+ version: '0'
108
+ requirements: []
109
+ rubyforge_project:
110
+ rubygems_version: 2.0.0
111
+ signing_key:
112
+ specification_version: 4
113
+ summary: Basic Win32 DDE client in Ruby
114
+ test_files:
115
+ - spec/ruddy/connection_spec.rb
116
+ - spec/support/fake_dde.rb
117
+ - spec/support/string_pointer_matcher.rb