dde 0.2.9 → 0.2.11

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