emonti-ffi_dry 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/.document ADDED
@@ -0,0 +1,5 @@
1
+ README.rdoc
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
data/.gitignore ADDED
@@ -0,0 +1,5 @@
1
+ *.sw?
2
+ .DS_Store
3
+ coverage
4
+ rdoc
5
+ pkg
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Eric Monti
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,150 @@
1
+ = ffi_dry
2
+
3
+ Helpers, sugar methods, and new features over Ruby FFI to do some common
4
+ things and add support for some uncommon ones.
5
+
6
+ == Requirements
7
+
8
+ * ffi-ffi (>= 0.5.0) - github.com/ffi/ffi
9
+
10
+
11
+ == Synopsis
12
+
13
+
14
+ One major feature is a 'dsl'-like syntax for declaring structure members
15
+ in FFI::Struct or FFI::ManagedStruct definitions.
16
+
17
+ require 'rubygems'
18
+ require 'ffi'
19
+ require 'ffi/dry'
20
+
21
+ class SomeStruct < FFI::Struct
22
+ include FFI::DRY::StructHelper
23
+
24
+ # we get a new way of specifying layouts with a 'dsl'-like syntax
25
+ # The :desc => ... part is arbitrary and can be used however we like.
26
+ dsl_layout do
27
+ field :field1, :uint16, :desc => 'this is field 1'
28
+ field :field2, :uint16, :desc => 'this is field 2'
29
+ end
30
+ end
31
+
32
+
33
+ ss0=SomeStruct.new
34
+
35
+ With the declaration above, we specified :desc hash value, which was stored
36
+ in metadata along with the field name and type.
37
+
38
+ pp ss0.dsl_metadata
39
+ [{:type=>:uint16, :name=>:field1, :desc=>"this is field 1"},
40
+ {:type=>:uint16, :name=>:field2, :desc=>"this is field 2"}]
41
+ # => nil
42
+
43
+
44
+ And we get free additional ways of instantiating and declaring values during
45
+ initialization. (The FFI standard ways still work too)
46
+
47
+ raw_data = "\x00\x00\xff\xff"
48
+
49
+ ss1=SomeStruct.new :raw => raw_data
50
+ ss2=SomeStruct.new :raw => raw_data, :field1 => 1, :field2 => 2
51
+ ss3=SomeStruct.new {|x| x.field1=1 }
52
+ ss4=SomeStruct.new(:raw => raw_data) {|x| x.field1=1 }
53
+
54
+ [ ss0,
55
+ ss1,
56
+ ss2,
57
+ ss3,
58
+ ss4].each_with_index {|x,i| p ["ss#{i}",[x.field1, x.field2]]}
59
+
60
+ Here's an example which utilizes that arbitrary ':desc' parameter in a
61
+ "neighborly" way.
62
+
63
+ require 'rubygems'
64
+ require 'ffi'
65
+ require 'ffi/dry'
66
+
67
+ class NeighborlyStruct < ::FFI::Struct
68
+ include ::FFI::DRY::StructHelper
69
+
70
+ def self.describe
71
+ print "Struct: #{self.class}"
72
+ dsl_metadata().each_with_index do |spec, i|
73
+ print " Field #{i}\n"
74
+ print " name: #{spec[:name].inspect}\n"
75
+ print " type: #{spec[:type].inspect}\n"
76
+ print " desc: #{spec[:desc]}\n\n"
77
+ end
78
+ print "\n"
79
+ end
80
+ def describe; self.class.describe; end
81
+ end
82
+
83
+ class TestStruct < NeighborlyStruct
84
+ dsl_layout do
85
+ field :field1, :uint8, :desc => "test field 1"
86
+ field :field2, :uint8, :desc => "test field 2"
87
+ end
88
+ end
89
+
90
+ class SomeStruct < NeighborlyStruct
91
+ dsl_layout do
92
+ field :kind, :uint8, :desc => "a type identifier"
93
+ struct :tst, TestStruct, :desc => "a nested TestStruct"
94
+ field :len, :uint8, :desc => "8-bit size value (>= self.size+2)"
95
+ array :str, [:char,255],
96
+ :desc => "a string up to 255 bytes bound by :len"
97
+ end
98
+
99
+ # override kind getter method
100
+ def kind
101
+ [:default, :bar, :baz][ self[:kind] ]
102
+ end
103
+ end
104
+
105
+ s1=TestStruct.new
106
+ s2=SomeStruct.new
107
+
108
+ puts "*"*70
109
+ s1.describe
110
+ # we get a dump of metadata
111
+ puts "*"*70
112
+ s2.describe
113
+ # we get a dump of metadata
114
+
115
+
116
+ There's also some helpers for collecting lookup maps for constants, a common
117
+ and handy thing when porting various libraries.
118
+
119
+ require 'ffi/dry'
120
+ require 'socket'
121
+
122
+ module AddressFamily
123
+ include FFI::DRY::ConstMap
124
+ slurp_constants ::Socket, "AF_"
125
+ def list ; @@list ||= super() ; end
126
+ end
127
+
128
+ AddressFamily now has all the constants it found for Socket::AF_*
129
+
130
+ AddressFamily::INET
131
+ AddressFamily::LINK
132
+ AddressFamily::INET6
133
+ etc...
134
+
135
+ We can do type and value lookups using []
136
+ AddressFamily[2] # => "INET"
137
+ AddressFamily["INET"] # => 2
138
+
139
+ We can get a hash of all key-value pairs with .list
140
+ AddressFamily.list
141
+ # => {"NATM"=>31, "DLI"=>13, "UNIX"=>1, "NETBIOS"=>33, ...}
142
+
143
+ ... which can be inverted for a reverse mapping
144
+ AddressFamily.list.invert
145
+ # => {16=>"APPLETALK", 5=>"CHAOS", 27=>"NDRV", 0=>"UNSPEC", ...}
146
+
147
+
148
+ == Copyright
149
+
150
+ Copyright (c) 2009 Eric Monti. See LICENSE for details.
data/Rakefile ADDED
@@ -0,0 +1,45 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+ require 'rake/clean'
4
+
5
+ begin
6
+ require 'jeweler'
7
+ Jeweler::Tasks.new do |gem|
8
+ gem.name = "ffi_dry"
9
+ gem.summary = %Q{Syntactic sugar and helper utilities for FFI}
10
+ gem.description = %Q{Provides a some useful modules, classes, and methods as well as a DSL-like syntax for FFI::Struct layouts}
11
+ gem.email = "emonti@matasano.com"
12
+ gem.homepage = "http://github.com/emonti/ffi_dry"
13
+ gem.authors = ["Eric Monti"]
14
+ gem.add_dependency "ffi-ffi", ">= 0.5.0"
15
+ gem.add_development_dependency "rspec"
16
+ gem.add_development_dependency "yard"
17
+ end
18
+ rescue LoadError
19
+ puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
20
+ end
21
+
22
+ require 'spec/rake/spectask'
23
+ Spec::Rake::SpecTask.new(:spec) do |spec|
24
+ spec.libs << 'lib' << 'spec'
25
+ spec.spec_files = FileList['spec/**/*_spec.rb']
26
+ end
27
+
28
+ Spec::Rake::SpecTask.new(:rcov) do |spec|
29
+ spec.libs << 'lib' << 'spec'
30
+ spec.pattern = 'spec/**/*_spec.rb'
31
+ spec.rcov = true
32
+ end
33
+
34
+ task :spec => :check_dependencies
35
+
36
+ task :default => :spec
37
+
38
+ begin
39
+ require 'yard'
40
+ YARD::Rake::YardocTask.new
41
+ rescue LoadError
42
+ task :yardoc do
43
+ abort "YARD is not available. In order to run yardoc, you must: sudo gem install yard"
44
+ end
45
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.0
data/lib/ffi/dry.rb ADDED
@@ -0,0 +1,350 @@
1
+ begin; require 'rubygems'; rescue LoadError; end
2
+
3
+ require 'ffi'
4
+
5
+ module FFI::DRY
6
+
7
+ # A module to add syntactic sugar and some nice automatic getter/setter
8
+ # logic to FFI::Struct, FFI::ManagedStruct, etc.
9
+ #
10
+ # For example:
11
+ # require 'rubygems'
12
+ # require 'ffi'
13
+ # require 'ffi/dry'
14
+ # require 'pp'
15
+ #
16
+ # class SomeStruct < FFI::Struct
17
+ # include FFI::DRY::StructHelper
18
+ #
19
+ # # we get a new way of specifying layouts with a 'dsl'-like syntax
20
+ # dsl_layout do
21
+ # field :field1, :uint16, :desc => 'this is field 1'
22
+ # field :field2, :uint16, :desc => 'this is field 2'
23
+ # end
24
+ # end
25
+ #
26
+ # ss0=SomeStruct.new
27
+ #
28
+ # pp ss0.dsl_metadata # we can look at definition metadata
29
+ #
30
+ # # produces...
31
+ # # [{:type=>:uint16, :name=>:field1, :desc=>"this is field 1"},
32
+ # # {:type=>:uint16, :name=>:field2, :desc=>"this is field 2"}]
33
+ #
34
+ # # And we have additional ways of instantiating and declaring values
35
+ # # during initialization. (The FFI standard ways still work too)
36
+ #
37
+ # raw_data = "\x00\x00\xff\xff"
38
+ #
39
+ # ss1=SomeStruct.new :raw => raw_data
40
+ # ss2=SomeStruct.new :raw => raw_data, :field1 => 1, :field2 => 2
41
+ # ss3=SomeStruct.new {|x| x.field1=1 }
42
+ # ss4=SomeStruct.new(:raw => raw_data) {|x| x.field1=1 }
43
+ #
44
+ # [ ss0,
45
+ # ss1,
46
+ # ss2,
47
+ # ss3,
48
+ # ss4].each_with_index {|x,i| pp ["ss#{i}",[x.field1, x.field2]]}
49
+ #
50
+ # # produces...
51
+ # # ["ss0", [0, 0]]
52
+ # # ["ss1", [0, 65535]]
53
+ # # ["ss2", [1, 2]]
54
+ # # ["ss3", [1, 0]]
55
+ # # ["ss4", [1, 65535]]
56
+ #
57
+ module StructHelper #< ::FFI::Struct
58
+
59
+ attr_reader :dsl_metadata
60
+
61
+ # Adds field setting on initialization to ::FFI::Struct.new as well as
62
+ # a "yield(self) if block_given?" at the end.
63
+ #
64
+ # The field initialization kicks in if there is only one argument, and it
65
+ # is a Hash.
66
+ #
67
+ # Note:
68
+ # The :raw parameter is a special tag in the hash. The value is taken as a
69
+ # string and initialized into a new FFI::MemoryPointer which this Struct
70
+ # then overlays.
71
+ #
72
+ # If your struct layout has a field named :raw field, it won't be
73
+ # assignable through the hash argument.
74
+ #
75
+ # See also: set_fields() which is called automatically on the hash, minus
76
+ # the :raw tag.
77
+ #
78
+ def initialize(*args)
79
+ if args.size == 1 and (oparams=args[0]).is_a? Hash
80
+ params = oparams.dup
81
+ if raw=params.delete(:raw)
82
+ super( ::FFI::MemoryPointer.from_string(raw) )
83
+ else
84
+ super()
85
+ end
86
+ set_fields(params)
87
+ else
88
+ super(*args)
89
+ end
90
+ @dsl_metadata = self.class.dsl_metadata
91
+ yield self if block_given?
92
+ end
93
+
94
+ # Sets field values in the struct specified by their symbolic name from a
95
+ # hash of ':field => value' pairs. Uses accessor field wrapper methods
96
+ # instead of a direct reference to the field (as in "obj.field1 = x",
97
+ # not "obj[:field] = x"). The difference is subtle, but this allows you
98
+ # to take advantage of any wrapper methods you override when initializing
99
+ # a new object. The only caveat is that the wrapper method must be named
100
+ # the same as the field, and the field must be included in members() from
101
+ # the layout.
102
+ #
103
+ # This method is called automatically if you are using the initialize()
104
+ # method provided in the DryStruct class and passing it a Hash as its only
105
+ # argument.
106
+ def set_fields(params)
107
+ params.keys.each do |p|
108
+ if members().include?(p)
109
+ self.__send__(:"#{p}=", params[p])
110
+ else
111
+ raise(::ArgumentError, "#{self.class} does not have a '#{p}' field")
112
+ end
113
+ end
114
+ end
115
+
116
+ # Returns a new instance of self.class containing a seperately allocated
117
+ # copy of all our data. This abstract method should usually be called
118
+ # with super() from overridden 'copy' implementations for structures
119
+ # containing pointers to other memory or variable length data at the end.
120
+ #
121
+ # Note also that, by default, this implementation determine's size
122
+ # automatically based on the structure size. This is comparable to
123
+ # sizeof(some_struct) in C. However, you can supply a 'grown' parameter
124
+ # which can be used to add to the size of the copied instance as it is
125
+ # allocated and copied.
126
+ def copy(grown=0)
127
+ self.class.new( :raw => self.to_ptr.read_string(self.size+grown) )
128
+ end
129
+
130
+ # Returns a pointer to the specified field, which is the name assigned
131
+ # to a member in the layout.
132
+ def ptr_to(field)
133
+ x = self[field] # this is actually a test, to raise if missing
134
+ return (self.to_ptr + self.offset_of(field))
135
+ end
136
+
137
+ # Contains dsl_layout and some support methods that an 'includee' of
138
+ # DryStructHelper will have available as class methods.
139
+ module ClassMethods
140
+ # returns the structure metadata for this class based on
141
+ # the dsl_layout definitions
142
+ def dsl_metadata
143
+ @dsl_metadata
144
+ end
145
+
146
+ private
147
+
148
+ # This passes a block to an instance of DSL_StructLayoutBuilder, allowing
149
+ # for a more declarative syntax.
150
+ #
151
+ # It is a replacement to layout() and stores the dsl_metadata gathered
152
+ # about structure members locally.
153
+ #
154
+ #
155
+ def dsl_layout &block
156
+ builder = DSL_StructLayoutBuilder.new(self)
157
+ builder.instance_eval(&block)
158
+ @layout = builder.build
159
+ @size = @layout.size
160
+ _class_do_dsl_metadata( builder.metadata )
161
+ return @layout
162
+ end
163
+
164
+ def _class_do_dsl_metadata(meta)
165
+ (@dsl_metadata = meta).each do |spec|
166
+ name = spec[:name]
167
+ type = spec[:type]
168
+ define_method(:"#{name}") do
169
+ self[name]
170
+ end unless instance_methods.include?(:"#{name}")
171
+ define_method(:"#{name}=") do |val|
172
+ self[name]=val
173
+ end unless instance_methods.include?(:"#{name}=")
174
+ end
175
+ end
176
+ end
177
+
178
+ def self.included(base)
179
+ base.extend(ClassMethods)
180
+ end
181
+ end # class StructHelper
182
+
183
+ # This is a wrapper around the FFI::StructLayoutBuilder. Its goal is to
184
+ # provides a more declarative syntax for defining structures and include
185
+ # the ability to attach arbitrary dsl_metadata information to structure
186
+ # fields during definition.
187
+ #
188
+ # The "DSL" (and that's really very in-quotes) supplies 3 ways to
189
+ # define a field (for now):
190
+ #
191
+ # field()
192
+ # array()
193
+ # struct()
194
+ #
195
+ # See the individual method descriptions for more info.
196
+ #
197
+ class DSL_StructLayoutBuilder
198
+ attr_reader :builder, :metadata
199
+
200
+ # Initializes the builder with a reference to the structure using it
201
+ # Instead of duplicating Struct features, we just call back to them.
202
+ def initialize(pbind)
203
+ @pbind = pbind
204
+ @builder = ::FFI::StructLayoutBuilder.new
205
+ @metadata = []
206
+ super()
207
+ end
208
+
209
+ # calls StructLayoutBuider.build() on the bulder and returns its
210
+ # result.
211
+ def build
212
+ @builder.build
213
+ end
214
+
215
+ # Calls StructLayoutBuilder.add_struct() on the builder and stores
216
+ # a metadata hash entry (the opts hash with name and type overridden)
217
+ #
218
+ # struct field_name, RubyClass, { ... metadata ... }
219
+ #
220
+ def struct(name, klass, o={})
221
+ unless klass.kind_of?(Class) and klass < ::FFI::Struct
222
+ raise(::ArgumentError, "klass must be a struct")
223
+ end
224
+
225
+ opts = o.merge(:name => name, :type => klass)
226
+ offset = opts[:offset]
227
+ ret=@builder.add_struct(name, klass, offset)
228
+ @metadata << opts
229
+ return ret
230
+ end
231
+
232
+ # Calls StructLayoutBuider.add_array() on the builder and stores
233
+ # a metadata hash entry (the opts hash with name and type overridden)
234
+ #
235
+ # Syntax:
236
+ #
237
+ # array field_name, [ctype, N], { ... metadata ... }
238
+ #
239
+ def array(name, type, o={})
240
+ unless type.kind_of?(::Array)
241
+ raise(::ArgumentError, "type must be an array")
242
+ end
243
+
244
+ opts = o.merge(:name => name, :type => type)
245
+ offset = opts[:offset]
246
+ mod = enclosing_module
247
+ ret=@builder.add_array(name, find_type(type[0], mod), type[1], offset)
248
+ @metadata << opts
249
+ return ret
250
+ end
251
+
252
+ # Calls StructLayoutBuider.add_field() on the builder and stores
253
+ # a metadata hash entry (the opts hash with name and type overridden)
254
+ #
255
+ # Syntax:
256
+ #
257
+ # field field_name, ctype, { ... metadata ... }
258
+ #
259
+ def field(name, type, o={})
260
+ opts = o.merge(:name => name, :type => type)
261
+ offset = opts[:offset]
262
+ mod = enclosing_module
263
+ ret= @builder.add_field(name, find_type(type, mod), offset)
264
+ @metadata << opts
265
+ return ret
266
+ end
267
+
268
+ def find_type(*args)
269
+ @pbind.find_type(*args)
270
+ end
271
+
272
+ def enclosing_module(*args)
273
+ @pbind.enclosing_module(*args)
274
+ end
275
+
276
+ end
277
+
278
+ # Used for creating various value <=> constant mapping modules such as
279
+ # Ip::Hdr::Proto for IP protocols.
280
+ module ConstMap
281
+
282
+ def self.included(klass)
283
+ klass.extend(ConstMap)
284
+ end
285
+
286
+ # A flexible lookup. Takes 'arg' as a Symbol or String as a name to lookup
287
+ # a value, or an Integer to lookup a corresponding name.
288
+ def [](arg)
289
+ if arg.is_a? Integer
290
+ list.invert[arg]
291
+ elsif arg.is_a? String or arg.is_a? Symbol
292
+ list[arg.to_s.upcase]
293
+ end
294
+ end
295
+
296
+ # Generates a hash of all the constant names mapped to value. Usually,
297
+ # it's a good idea to override this like so in derived modules:
298
+ #
299
+ # def list; @@list = super() ; end
300
+ #
301
+ def list
302
+ constants.inject({}){|h,c| h.merge! c => const_get(c) }
303
+ end
304
+
305
+ private
306
+ # When called from a module definition or class method, this method
307
+ # imports all the constants from # namespace 'nspace' that start with
308
+ # into the local namespace as constants named with whatever follows the
309
+ # prefix. Only constant names that match [A-Z][A-Z0-9_]+ are imported,
310
+ # the rest are ignored.
311
+ #
312
+ # This method also yields the (short) constant name and value to a block
313
+ # if one is provided. The block works like [...].select {|c,v| ... } in
314
+ # that the value is not mapped if the block returns nil or false.
315
+ def slurp_constants(nspace, prefix)
316
+ nspace.constants.grep(/^(#{prefix}([A-Z][A-Z0-9_]+))$/) do
317
+ c = $2
318
+ v = nspace.const_get($1)
319
+ next if block_given? and not yield(c,v)
320
+ const_set c, v
321
+ end
322
+ end
323
+ end
324
+
325
+ # Behaves just like ConstFlags, except that the [nnn] returns a list
326
+ # of names for the flags set on nnn. Name string lookups work same way as
327
+ # ConstFlags.
328
+ module ConstFlagsMap
329
+ include ConstMap
330
+
331
+ def self.included(klass)
332
+ klass.extend(ConstFlagsMap)
333
+ end
334
+
335
+ # A flexible lookup. Takes 'arg' as a Symbol or String as a name to lookup
336
+ # a value, or an Integer to lookup a corresponding names for the flags
337
+ # present in it.
338
+ def [](arg)
339
+ if arg.is_a? Integer
340
+ ret = []
341
+ list.invert.sort.each {|v,n| ret << n if (v & arg) == v }
342
+ return ret
343
+ elsif arg.is_a? String or arg.is_a? Symbol
344
+ list[arg.to_s.upcase]
345
+ end
346
+ end
347
+ end
348
+ end
349
+
350
+
data/lib/ffi_dry.rb ADDED
@@ -0,0 +1 @@
1
+ require 'ffi/dry'
data/samples/afmap.rb ADDED
@@ -0,0 +1,28 @@
1
+ require 'ffi/dry'
2
+ require 'socket'
3
+
4
+ module AddressFamily
5
+ include FFI::DRY::ConstMap
6
+ slurp_constants ::Socket, "AF_"
7
+ def list ; @@list ||= super() ; end
8
+ end
9
+
10
+ # AddressFamily now has all the constants it found for Socket::AF_*
11
+ #
12
+ # i.e. AddressFamily::INET
13
+ # AddressFamily::LINK
14
+ # AddressFamily::INET6
15
+ # etc...
16
+ #
17
+ # We can do quick lookups
18
+ # AddressFamily[2] # => "INET"
19
+ # AddressFamily["INET"] # => 2
20
+ #
21
+ # We can get a hash of all key-value pairs with .list
22
+ # AddressFamily.list
23
+ # # => {"NATM"=>31, "DLI"=>13, "UNIX"=>1, "NETBIOS"=>33, ...}
24
+ #
25
+ # ... which can be inverted for a reverse mapping
26
+ # AddressFamily.list.invert
27
+ # # => {16=>"APPLETALK", 5=>"CHAOS", 27=>"NDRV", 0=>"UNSPEC", ...}
28
+ #
data/samples/basic.rb ADDED
@@ -0,0 +1,35 @@
1
+ require 'rubygems'
2
+ require 'ffi'
3
+ require 'ffi/dry'
4
+
5
+ class SomeStruct < FFI::Struct
6
+ include FFI::DRY::StructHelper
7
+
8
+ # we get a new way of specifying layouts with a 'dsl'-like syntax
9
+ dsl_layout do
10
+ field :field1, :uint16, :desc => 'this is field 1'
11
+ field :field2, :uint16, :desc => 'this is field 2'
12
+ end
13
+ end
14
+
15
+
16
+ ss0=SomeStruct.new
17
+
18
+ p ss0.dsl_metadata # we can look at definition metadata
19
+
20
+ # And we have additional ways of instantiating and declaring values
21
+ # during initialization. (The FFI standard ways still work too)
22
+
23
+ raw_data = "\x00\x00\xff\xff"
24
+
25
+ ss1=SomeStruct.new :raw => raw_data
26
+ ss2=SomeStruct.new :raw => raw_data, :field1 => 1, :field2 => 2
27
+ ss3=SomeStruct.new {|x| x.field1=1 }
28
+ ss4=SomeStruct.new(:raw => raw_data) {|x| x.field1=1 }
29
+
30
+ [ ss0,
31
+ ss1,
32
+ ss2,
33
+ ss3,
34
+ ss4].each_with_index {|x,i| p ["ss#{i}",[x.field1, x.field2]]}
35
+
@@ -0,0 +1,48 @@
1
+ require 'rubygems'
2
+ require 'ffi'
3
+ require 'ffi/dry'
4
+
5
+ class NeighborlyStruct < ::FFI::Struct
6
+ include ::FFI::DRY::StructHelper
7
+
8
+ def self.describe
9
+ print "Struct: #{self.class}"
10
+ dsl_metadata().each_with_index do |spec, i|
11
+ print " Field #{i}\n"+
12
+ " name: #{spec[:name].inspect}\n"+
13
+ " type: #{spec[:type].inspect}\n"+
14
+ " desc: #{spec[:desc]}\n\n"
15
+ end
16
+ print "\n"
17
+ end
18
+ def describe; self.class.describe; end
19
+ end
20
+
21
+ class TestStruct < NeighborlyStruct
22
+ dsl_layout do
23
+ field :field1, :uint8, :desc => "test field 1"
24
+ field :field2, :uint8, :desc => "test field 2"
25
+ end
26
+ end
27
+
28
+ class SomeStruct < NeighborlyStruct
29
+ dsl_layout do
30
+ field :kind, :uint8, :desc => "a type identifier"
31
+ struct :tst, TestStruct, :desc => "a nested TestStruct"
32
+ field :len, :uint8, :desc => "8-bit size value (>= self.size+2)"
33
+ array :str, [:char,255], :desc => "a string up to 255 bytes bound by :len"
34
+ end
35
+
36
+ # override kind getter method
37
+ def kind
38
+ [:default, :bar, :baz][ self[:kind] ]
39
+ end
40
+ end
41
+
42
+ s1=TestStruct.new
43
+ s2=SomeStruct.new
44
+
45
+ puts "*"*70
46
+ s1.describe
47
+ puts "*"*70
48
+ s2.describe
@@ -0,0 +1,7 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ describe "FfiDry" do
4
+ it "fails" do
5
+ fail "hey buddy, you should probably rename this file and start specing for real"
6
+ end
7
+ end
@@ -0,0 +1,9 @@
1
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
2
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
3
+ require 'ffi_dry'
4
+ require 'spec'
5
+ require 'spec/autorun'
6
+
7
+ Spec::Runner.configure do |config|
8
+
9
+ end
metadata ADDED
@@ -0,0 +1,96 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: emonti-ffi_dry
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Eric Monti
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-09-12 00:00:00 -07:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: ffi-ffi
17
+ type: :runtime
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: 0.5.0
24
+ version:
25
+ - !ruby/object:Gem::Dependency
26
+ name: rspec
27
+ type: :development
28
+ version_requirement:
29
+ version_requirements: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: "0"
34
+ version:
35
+ - !ruby/object:Gem::Dependency
36
+ name: yard
37
+ type: :development
38
+ version_requirement:
39
+ version_requirements: !ruby/object:Gem::Requirement
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ version: "0"
44
+ version:
45
+ description: Provides a some useful modules, classes, and methods as well as a DSL-like syntax for FFI::Struct layouts
46
+ email: emonti@matasano.com
47
+ executables: []
48
+
49
+ extensions: []
50
+
51
+ extra_rdoc_files:
52
+ - LICENSE
53
+ - README.rdoc
54
+ files:
55
+ - .document
56
+ - .gitignore
57
+ - LICENSE
58
+ - README.rdoc
59
+ - Rakefile
60
+ - VERSION
61
+ - lib/ffi/dry.rb
62
+ - lib/ffi_dry.rb
63
+ - samples/afmap.rb
64
+ - samples/basic.rb
65
+ - samples/describer.rb
66
+ - spec/ffi_dry_spec.rb
67
+ - spec/spec_helper.rb
68
+ has_rdoc: false
69
+ homepage: http://github.com/emonti/ffi_dry
70
+ post_install_message:
71
+ rdoc_options:
72
+ - --charset=UTF-8
73
+ require_paths:
74
+ - lib
75
+ required_ruby_version: !ruby/object:Gem::Requirement
76
+ requirements:
77
+ - - ">="
78
+ - !ruby/object:Gem::Version
79
+ version: "0"
80
+ version:
81
+ required_rubygems_version: !ruby/object:Gem::Requirement
82
+ requirements:
83
+ - - ">="
84
+ - !ruby/object:Gem::Version
85
+ version: "0"
86
+ version:
87
+ requirements: []
88
+
89
+ rubyforge_project:
90
+ rubygems_version: 1.2.0
91
+ signing_key:
92
+ specification_version: 3
93
+ summary: Syntactic sugar and helper utilities for FFI
94
+ test_files:
95
+ - spec/ffi_dry_spec.rb
96
+ - spec/spec_helper.rb