emonti-ffi_dry 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.
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