ruddy 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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