dde 0.2.9 → 0.2.11

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.
@@ -1,190 +1,190 @@
1
- module DDE
2
- extend FFI::Library # todo: < Array ?
3
- # class Conv < FFI::Union
4
- # layout( :w, :ushort, # word should be 2 bytes, not 8
5
- # :d, :double, # it is 8 bytes
6
- # :b, [:char, 8]) # it is 8 bytes
7
- # end
8
- #
9
-
10
- # XLTable class represents a single chunk of DDE data formatted as an Excel table
11
- class XlTable
12
- include Win::DDE
13
-
14
- # Received data types
15
- TDT_FLOAT = 1
16
- TDT_STRING = 2
17
- TDT_BOOL = 3
18
- TDT_ERROR = 4
19
- TDT_BLANK = 5
20
- TDT_INT = 6
21
- TDT_SKIP = 7
22
- TDT_TABLE = 16
23
-
24
- TDT_TYPES = {
25
- TDT_FLOAT => 'TDT_FLOAT',
26
- TDT_STRING =>'TDT_STRING',
27
- TDT_BOOL => 'TDT_BOOL',
28
- TDT_ERROR => 'TDT_ERROR',
29
- TDT_BLANK => 'TDT_BLANK',
30
- TDT_INT => 'TDT_INT',
31
- TDT_SKIP => 'TDT_SKIP',
32
- TDT_TABLE => 'TDT_TABLE'
33
- }
34
-
35
- attr_accessor :topic, # topic prefix
36
- :time, # time spent parsing last transaction data
37
- :total_time, # total time spent parsing data
38
- :num_trans # number of data transactions
39
-
40
- def initialize
41
- @table = [] # Array contains Arrays of Strings
42
- @col = 0
43
- @row = 0
44
- @total_time = 0
45
- @total_records = 0
46
- # omitting separators for now
47
- end
48
-
49
- # tests if table data is empty or contains data in inconsistent state
50
- def empty?
51
- @table.empty? ||
52
- @row == 0 || @col == 0 ||
53
- @row != @table.size ||
54
- @col != @table.first.size # assumes first element is also an Array
55
- end
56
-
57
- def data?;
58
- !empty?
59
- end
60
-
61
- def draw
62
- return false if empty?
63
- Encoding.default_external = 'cp866'
64
- # omitting separator gymnastics for now
65
- cout "-----\n"
66
- @table.each{|row| cout @topic; row.each {|col| cout " #{col}"}; cout "\n"}
67
- end
68
-
69
- def debug
70
- return false if empty?
71
- Encoding.default_external = 'cp866'
72
- # omitting separator gymnastics for now
73
- cout "-----\n"
74
- @table.each_with_index{|row, i| (cout @topic, i; p row) unless row == []}
75
- STDIN.gets
76
- end
77
-
78
- def receive(data_handle, mode = :collect)
79
- $mode = mode
80
- start = Time.now
81
-
82
- @offset = 0
83
- @pos = 0 #; @c=0; @r=0
84
-
85
- @data, total_size = dde_get_data(data_handle) #dde_access_data(dde_handle)
86
- p @data.get_bytes(0, total_size) if $mode == :debug
87
-
88
- return nil unless @data && # DDE data is present at given dde_handle
89
- read_int == TDT_TABLE && # and, first data block is tdtTable
90
- read_int == 4 # and, its length is 4 bytes
91
-
92
- @row = read_int
93
- @col = read_int
94
- return nil unless @row != 0 && @col != 0 # Make sure nonzero row and col
95
-
96
- p "data set size #{total_size}, row #{@row}, col #{@col}" if $mode == :debug
97
- @strings = @floats = @flints = @ints = @blanks = @skips = @bools = @errors = 0
98
-
99
- @table = Array.new(@row){||Array.new}
100
-
101
- while @offset <= total_size-4 # Need at least 4 bytes ahead to read data type and size
102
- type = read_int # Next data field(s) type
103
- size = read_int # Next data field(s) length in bytes
104
-
105
- p "type #{TDT_TYPES[type]}, cb #{size}, row #{@pos/@col}, col #{@pos%@col}" if $mode == :debug
106
- case type
107
- when TDT_STRING # Strings, length byte followed by chars, no zero termination
108
- field_end = @offset + size
109
- while @offset < field_end do
110
- length = read_char
111
- self.table = @data.get_bytes(@offset, length) #read_bytes(length)#.force_encoding('CP1251').encode('CP866')
112
- @offset += length
113
- @strings += 1
114
- end
115
- when TDT_FLOAT # Float, 8 bytes (used to represent Integers too in Quik!)
116
- (size/8).times do
117
- float_or_int = @data.get_float64(@offset) # self.table = read_double
118
- @offset += 8
119
- int = float_or_int.round
120
- self.table = float_or_int == int ? (@flints += 1; int) : (@floats +=1; float_or_int)
121
- end
122
- when TDT_BLANK # Number of blank cells, 2 bytes
123
- (size/2).times { read_int.times { self.table = ""; @blanks += 1 } }
124
- when TDT_SKIP # Number of cells to skip, 2 bytes - in Quik, it means that these cells contain 0
125
- (size/2).times { read_int.times { self.table = 0; @skips += 1 } }
126
- when TDT_INT # Int, 2 bytes
127
- (size/2).times { self.table = read_int; @ints += 1 }
128
- when TDT_BOOL # Bool, 2 bytes 0/1
129
- (size/2).times { self.table = read_int == 0; @bools += 1 }
130
- when TDT_ERROR # Error enum, 2 bytes
131
- (size/2).times { self.table = "Error:#{read_int}"; @errors += 1 }
132
- else
133
- cout "Type: #{type}, #{TDT_TYPES[type]}"
134
- return nil
135
- end
136
- end
137
- #TODO: free FFI::Pointer ? delete []data; // Free memory
138
- @time = Time.now - start
139
- @total_time += @time
140
- @total_records += @row
141
- #dde_unaccess_data(dde_handle)
142
- true # Data acquisition successful
143
- end
144
-
145
- def timer
146
- cout "Last: #{@row} in #{@time} s(#{@time/@row} s/rec), total: #{@total_records} in #{
147
- @total_time} s(#{@total_time/@total_records} s/rec)\n"
148
- end
149
-
150
- def formats
151
- cout "Strings #{@strings} Floats #{@floats} FlInts #{@flints} Ints #{@ints} Blanks #{
152
- @blanks} Skips #{@skips} Bools #{@bools} Errors #{@errors}\n"
153
- end
154
-
155
- def read_char
156
- @offset += 1
157
- @data.get_int8(@offset-1)
158
- end
159
-
160
- def read_int
161
- @offset += 2
162
- @data.get_int16(@offset-2)
163
- end
164
-
165
- def read_double
166
- @offset += 8
167
- @data.get_float64(@offset-8)
168
- end
169
-
170
- def read_bytes(length=1)
171
- @offset += length
172
- @data.get_bytes(@offset-length, length)
173
- end
174
-
175
- def table=(value)
176
- # @table[@r][@c] = value
177
- # todo: Add code for adding value to data row here (pack?)
178
- # @c+=1
179
- # if @c == @col
180
- # @c =0
181
- # @r+=1
182
- # end
183
- # todo: Add code for (sync!) publishing of assembled data row here (bunny? rosetta_queue?)
184
- p value if $mode == :debug
185
- @table[@pos/@col][@pos%@col] = value
186
- @pos += 1
187
- end
188
-
189
- end
190
- end
1
+ module Dde
2
+ extend FFI::Library # todo: < Array ?
3
+ # class Conv < FFI::Union
4
+ # layout( :w, :ushort, # word should be 2 bytes, not 8
5
+ # :d, :double, # it is 8 bytes
6
+ # :b, [:char, 8]) # it is 8 bytes
7
+ # end
8
+ #
9
+
10
+ # XLTable class represents a single chunk of DDE data formatted as an Excel table
11
+ class XlTable
12
+ include Win::Dde
13
+
14
+ # Received data types
15
+ TDT_FLOAT = 1
16
+ TDT_STRING = 2
17
+ TDT_BOOL = 3
18
+ TDT_ERROR = 4
19
+ TDT_BLANK = 5
20
+ TDT_INT = 6
21
+ TDT_SKIP = 7
22
+ TDT_TABLE = 16
23
+
24
+ TDT_TYPES = {
25
+ TDT_FLOAT => 'TDT_FLOAT',
26
+ TDT_STRING =>'TDT_STRING',
27
+ TDT_BOOL => 'TDT_BOOL',
28
+ TDT_ERROR => 'TDT_ERROR',
29
+ TDT_BLANK => 'TDT_BLANK',
30
+ TDT_INT => 'TDT_INT',
31
+ TDT_SKIP => 'TDT_SKIP',
32
+ TDT_TABLE => 'TDT_TABLE'
33
+ }
34
+
35
+ attr_accessor :topic, # topic prefix
36
+ :time, # time spent parsing last transaction data
37
+ :total_time, # total time spent parsing data
38
+ :num_trans # number of data transactions
39
+
40
+ def initialize
41
+ @table = [] # Array contains Arrays of Strings
42
+ @col = 0
43
+ @row = 0
44
+ @total_time = 0
45
+ @total_records = 0
46
+ # omitting separators for now
47
+ end
48
+
49
+ # tests if table data is empty or contains data in inconsistent state
50
+ def empty?
51
+ @table.empty? ||
52
+ @row == 0 || @col == 0 ||
53
+ @row != @table.size ||
54
+ @col != @table.first.size # assumes first element is also an Array
55
+ end
56
+
57
+ def data?;
58
+ !empty?
59
+ end
60
+
61
+ def draw
62
+ return false if empty?
63
+ Encoding.default_external = 'cp866'
64
+ # omitting separator gymnastics for now
65
+ cout "-----\n"
66
+ @table.each{|row| cout @topic; row.each {|col| cout " #{col}"}; cout "\n"}
67
+ end
68
+
69
+ def debug
70
+ return false if empty?
71
+ Encoding.default_external = 'cp866'
72
+ # omitting separator gymnastics for now
73
+ cout "-----\n"
74
+ @table.each_with_index{|row, i| (cout @topic, i; p row) unless row == []}
75
+ STDIN.gets
76
+ end
77
+
78
+ def receive(data_handle, mode = :collect)
79
+ $mode = mode
80
+ start = Time.now
81
+
82
+ @offset = 0
83
+ @pos = 0 #; @c=0; @r=0
84
+
85
+ @data, total_size = dde_get_data(data_handle) #dde_access_data(dde_handle)
86
+ p @data.get_bytes(0, total_size) if $mode == :debug
87
+
88
+ return nil unless @data && # DDE data is present at given dde_handle
89
+ read_int == TDT_TABLE && # and, first data block is tdtTable
90
+ read_int == 4 # and, its length is 4 bytes
91
+
92
+ @row = read_int
93
+ @col = read_int
94
+ return nil unless @row != 0 && @col != 0 # Make sure nonzero row and col
95
+
96
+ p "data set size #{total_size}, row #{@row}, col #{@col}" if $mode == :debug
97
+ @strings = @floats = @flints = @ints = @blanks = @skips = @bools = @errors = 0
98
+
99
+ @table = Array.new(@row){||Array.new}
100
+
101
+ while @offset <= total_size-4 # Need at least 4 bytes ahead to read data type and size
102
+ type = read_int # Next data field(s) type
103
+ size = read_int # Next data field(s) length in bytes
104
+
105
+ p "type #{TDT_TYPES[type]}, cb #{size}, row #{@pos/@col}, col #{@pos%@col}" if $mode == :debug
106
+ case type
107
+ when TDT_STRING # Strings, length byte followed by chars, no zero termination
108
+ field_end = @offset + size
109
+ while @offset < field_end do
110
+ length = read_char
111
+ self.table = @data.get_bytes(@offset, length) #read_bytes(length)#.force_encoding('CP1251').encode('CP866')
112
+ @offset += length
113
+ @strings += 1
114
+ end
115
+ when TDT_FLOAT # Float, 8 bytes (used to represent Integers too in Quik!)
116
+ (size/8).times do
117
+ float_or_int = @data.get_float64(@offset) # self.table = read_double
118
+ @offset += 8
119
+ int = float_or_int.round
120
+ self.table = float_or_int == int ? (@flints += 1; int) : (@floats +=1; float_or_int)
121
+ end
122
+ when TDT_BLANK # Number of blank cells, 2 bytes
123
+ (size/2).times { read_int.times { self.table = ""; @blanks += 1 } }
124
+ when TDT_SKIP # Number of cells to skip, 2 bytes - in Quik, it means that these cells contain 0
125
+ (size/2).times { read_int.times { self.table = 0; @skips += 1 } }
126
+ when TDT_INT # Int, 2 bytes
127
+ (size/2).times { self.table = read_int; @ints += 1 }
128
+ when TDT_BOOL # Bool, 2 bytes 0/1
129
+ (size/2).times { self.table = read_int == 0; @bools += 1 }
130
+ when TDT_ERROR # Error enum, 2 bytes
131
+ (size/2).times { self.table = "Error:#{read_int}"; @errors += 1 }
132
+ else
133
+ cout "Type: #{type}, #{TDT_TYPES[type]}"
134
+ return nil
135
+ end
136
+ end
137
+ #TODO: free FFI::Pointer ? delete []data; // Free memory
138
+ @time = Time.now - start
139
+ @total_time += @time
140
+ @total_records += @row
141
+ #dde_unaccess_data(dde_handle)
142
+ true # Data acquisition successful
143
+ end
144
+
145
+ def timer
146
+ cout "Last: #{@row} in #{@time} s(#{@time/@row} s/rec), total: #{@total_records} in #{
147
+ @total_time} s(#{@total_time/@total_records} s/rec)\n"
148
+ end
149
+
150
+ def formats
151
+ cout "Strings #{@strings} Floats #{@floats} FlInts #{@flints} Ints #{@ints} Blanks #{
152
+ @blanks} Skips #{@skips} Bools #{@bools} Errors #{@errors}\n"
153
+ end
154
+
155
+ def read_char
156
+ @offset += 1
157
+ @data.get_int8(@offset-1)
158
+ end
159
+
160
+ def read_int
161
+ @offset += 2
162
+ @data.get_int16(@offset-2)
163
+ end
164
+
165
+ def read_double
166
+ @offset += 8
167
+ @data.get_float64(@offset-8)
168
+ end
169
+
170
+ def read_bytes(length=1)
171
+ @offset += length
172
+ @data.get_bytes(@offset-length, length)
173
+ end
174
+
175
+ def table=(value)
176
+ # @table[@r][@c] = value
177
+ # todo: Add code for adding value to data row here (pack?)
178
+ # @c+=1
179
+ # if @c == @col
180
+ # @c =0
181
+ # @r+=1
182
+ # end
183
+ # todo: Add code for (sync!) publishing of assembled data row here (bunny? rosetta_queue?)
184
+ p value if $mode == :debug
185
+ @table[@pos/@col][@pos%@col] = value
186
+ @pos += 1
187
+ end
188
+
189
+ end
190
+ end
@@ -1,85 +1,85 @@
1
- module DDETest
2
- shared_examples_for "DDE App" do
3
- before(:each ){ @app = described_class.new }
4
- after(:each ){ @app.stop_dde if @app.dde_active?}
5
-
6
- it 'starts with nil id and flags if no arguments given' do
7
- @app.id.should == nil
8
- @app.init_flags.should == nil
9
- @app.dde_active?.should == false
10
- end unless described_class == DDE::Monitor
11
-
12
- it 'starts DDE (initializes as STANDARD DDE app) with given callback block' do
13
- @app = described_class.new {|*args|}
14
- @app.id.should be_an Integer
15
- @app.id.should_not == 0
16
- @app.init_flags.should == APPCLASS_STANDARD
17
- @app.dde_active?.should == true
18
- end unless described_class == DDE::Monitor
19
-
20
- describe '#start_dde' do
21
- it 'starts DDE with callback and default init_flags' do
22
- res = @app.start_dde {|*args|}
23
- res.should be_true
24
- @app.id.should be_an Integer
25
- @app.id.should_not == 0
26
- @app.init_flags.should == APPCLASS_STANDARD
27
- @app.dde_active?.should == true
28
- end unless described_class == DDE::Monitor
29
-
30
- it 'returns self if success (allows method chain)' do
31
- @app.start_dde {|*args|}.should == @app
32
- end
33
-
34
- it 'starts DDE with callback and given init_flags' do
35
- res = @app.start_dde( APPCLASS_STANDARD | CBF_FAIL_CONNECTIONS ){|*args|}
36
- res.should be_true
37
- @app.id.should be_an Integer
38
- @app.id.should_not == 0
39
- @app.init_flags.should == APPCLASS_STANDARD | CBF_FAIL_CONNECTIONS
40
- @app.dde_active?.should == true
41
- end
42
-
43
- it 'raises InitError if no callback was given' do
44
- lambda{ @app.start_dde}.should raise_error DDE::Errors::InitError
45
- end
46
-
47
- it 'reinitializes with new flags and callback if it was already initialized' do
48
- @app.start_dde {|*args| 1}
49
- old_id = @app.id
50
- res = @app.start_dde( APPCLASS_STANDARD | CBF_FAIL_CONNECTIONS ){|*args| 2}
51
- res.should be_true
52
- @app.id.should == old_id
53
- @app.init_flags.should == APPCLASS_STANDARD | CBF_FAIL_CONNECTIONS
54
- @app.dde_active?.should == true
55
- end
56
- end
57
-
58
- describe '#stop_dde' do
59
- it 'stops DDE that was active' do
60
- @app.start_dde {|*args| 1}
61
-
62
- @app.stop_dde
63
- @app.id.should == nil
64
- @app.dde_active?.should == false
65
- end
66
-
67
- it 'preserves init_flags after DDE is stopped (for reinitialization)' do
68
- @app.start_dde(APPCLASS_STANDARD | CBF_FAIL_CONNECTIONS) {|*args| 1}
69
-
70
- @app.stop_dde
71
- @app.init_flags.should == APPCLASS_STANDARD | CBF_FAIL_CONNECTIONS
72
- end
73
-
74
- it 'returns self if success (allows method chain)' do
75
- @app.start_dde{|*args|}.stop_dde.should == @app
76
- end
77
-
78
- it 'raises InitError if dde was not active first' do
79
- lambda{ @app.stop_dde}.should raise_error DDE::Errors::InitError
80
- end
81
- end unless described_class == DDE::Monitor
82
-
83
-
84
- end
1
+ module DdeTest
2
+ shared_examples_for "DDE App" do
3
+ before(:each ){ @app = described_class.new }
4
+ after(:each ){ @app.stop_dde if @app.dde_active?}
5
+
6
+ it 'starts with nil id and flags if no arguments given' do
7
+ @app.id.should == nil
8
+ @app.init_flags.should == nil
9
+ @app.dde_active?.should == false
10
+ end unless described_class == Dde::Monitor
11
+
12
+ it 'starts DDE (initializes as STANDARD DDE app) with given callback block' do
13
+ @app = described_class.new {|*args|}
14
+ @app.id.should be_an Integer
15
+ @app.id.should_not == 0
16
+ @app.init_flags.should == APPCLASS_STANDARD
17
+ @app.dde_active?.should == true
18
+ end unless described_class == Dde::Monitor
19
+
20
+ describe '#start_dde' do
21
+ it 'starts DDE with callback and default init_flags' do
22
+ res = @app.start_dde {|*args|}
23
+ res.should be_true
24
+ @app.id.should be_an Integer
25
+ @app.id.should_not == 0
26
+ @app.init_flags.should == APPCLASS_STANDARD
27
+ @app.dde_active?.should == true
28
+ end unless described_class == Dde::Monitor
29
+
30
+ it 'returns self if success (allows method chain)' do
31
+ @app.start_dde {|*args|}.should == @app
32
+ end
33
+
34
+ it 'starts DDE with callback and given init_flags' do
35
+ res = @app.start_dde( APPCLASS_STANDARD | CBF_FAIL_CONNECTIONS ){|*args|}
36
+ res.should be_true
37
+ @app.id.should be_an Integer
38
+ @app.id.should_not == 0
39
+ @app.init_flags.should == APPCLASS_STANDARD | CBF_FAIL_CONNECTIONS
40
+ @app.dde_active?.should == true
41
+ end
42
+
43
+ it 'raises InitError if no callback was given' do
44
+ lambda{ @app.start_dde}.should raise_error Dde::Errors::InitError
45
+ end
46
+
47
+ it 'reinitializes with new flags and callback if it was already initialized' do
48
+ @app.start_dde {|*args| 1}
49
+ old_id = @app.id
50
+ res = @app.start_dde( APPCLASS_STANDARD | CBF_FAIL_CONNECTIONS ){|*args| 2}
51
+ res.should be_true
52
+ @app.id.should == old_id
53
+ @app.init_flags.should == APPCLASS_STANDARD | CBF_FAIL_CONNECTIONS
54
+ @app.dde_active?.should == true
55
+ end
56
+ end
57
+
58
+ describe '#stop_dde' do
59
+ it 'stops DDE that was active' do
60
+ @app.start_dde {|*args| 1}
61
+
62
+ @app.stop_dde
63
+ @app.id.should == nil
64
+ @app.dde_active?.should == false
65
+ end
66
+
67
+ it 'preserves init_flags after DDE is stopped (for reinitialization)' do
68
+ @app.start_dde(APPCLASS_STANDARD | CBF_FAIL_CONNECTIONS) {|*args| 1}
69
+
70
+ @app.stop_dde
71
+ @app.init_flags.should == APPCLASS_STANDARD | CBF_FAIL_CONNECTIONS
72
+ end
73
+
74
+ it 'returns self if success (allows method chain)' do
75
+ @app.start_dde{|*args|}.stop_dde.should == @app
76
+ end
77
+
78
+ it 'raises InitError if dde was not active first' do
79
+ lambda{ @app.stop_dde}.should raise_error Dde::Errors::InitError
80
+ end
81
+ end unless described_class == Dde::Monitor
82
+
83
+
84
+ end
85
85
  end