ffi_dry 0.1.3

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,233 @@
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
+ (samples/ in the package for code)
14
+
15
+ A major feature is a DSL"-like" syntax for declaring structure members
16
+ in FFI::Struct or FFI::ManagedStruct definitions.
17
+
18
+ require 'rubygems'
19
+ require 'ffi'
20
+ require 'ffi/dry'
21
+
22
+ class SomeStruct < FFI::Struct
23
+ include FFI::DRY::StructHelper
24
+
25
+ # we get a new way of specifying layouts with a 'dsl'-like syntax
26
+ # The hash containing {:desc => ... } can contain arbitrary keys which
27
+ # can be used however we like. dsl_metadata will contain all these
28
+ # in the class and instance.
29
+ dsl_layout do
30
+ field :field1, :uint16, :desc => 'this is field 1'
31
+ field :field2, :uint16, :desc => 'this is field 2'
32
+ end
33
+ end
34
+
35
+ ss0=SomeStruct.new
36
+
37
+ With the declarations above, we specified :desc hash value in metadata
38
+ Let's check out in our instance.
39
+
40
+ pp ss0.dsl_metadata
41
+ [{:type=>:uint16, :name=>:field1, :desc=>"this is field 1"},
42
+ {:type=>:uint16, :name=>:field2, :desc=>"this is field 2"}]
43
+ # => nil
44
+
45
+ Or class.
46
+
47
+ pp SomeStruct.dsl_metadata
48
+ #...
49
+
50
+ We get some additional ways of instantiating and declaring values for free
51
+ during initialization. (The FFI standard ways still work too)
52
+
53
+ raw_data = "\x00\x00\xff\xff"
54
+
55
+ ss1=SomeStruct.new :raw => raw_data
56
+ ss2=SomeStruct.new :raw => raw_data, :field1 => 1, :field2 => 2
57
+ ss3=SomeStruct.new {|x| x.field1=1 }
58
+ ss4=SomeStruct.new(:raw => raw_data) {|x| x.field1=1 }
59
+
60
+ [ ss0,
61
+ ss1,
62
+ ss2,
63
+ ss3,
64
+ ss4 ].each_with_index {|x,i| p ["ss#{i}",[x.field1, x.field2]]}
65
+
66
+ # which will produce...
67
+ # ["ss0", [0, 0]]
68
+ # ["ss1", [0, 65535]]
69
+ # ["ss2", [1, 2]]
70
+ # ["ss3", [1, 0]]
71
+ # ["ss4", [1, 65535]]
72
+
73
+
74
+ Here's a broader example which utilizes that arbitrary ':desc' parameter in a
75
+ "neighborly" way. This also demonstrates superclasses to add common struct
76
+ features, declaring array fields, as well as nesting other structs.
77
+
78
+ require 'rubygems'
79
+ require 'ffi'
80
+ require 'ffi/dry'
81
+
82
+ class NeighborlyStruct < ::FFI::Struct
83
+ include ::FFI::DRY::StructHelper
84
+
85
+ def self.describe
86
+ print "Struct: #{self.name}"
87
+ dsl_metadata().each_with_index do |spec, i|
88
+ print " Field #{i}\n"
89
+ print " name: #{spec[:name].inspect}\n"
90
+ print " type: #{spec[:type].inspect}\n"
91
+ print " desc: #{spec[:desc]}\n\n"
92
+ end
93
+ print "\n"
94
+ end
95
+ def describe; self.class.describe; end
96
+ end
97
+
98
+ class TestStruct < NeighborlyStruct
99
+ dsl_layout do
100
+ field :field1, :uint8, :desc => "test field 1"
101
+ field :field2, :uint8, :desc => "test field 2"
102
+ end
103
+ end
104
+
105
+ class SomeStruct < NeighborlyStruct
106
+ dsl_layout do
107
+ field :kind, :uint8, :desc => "a type identifier"
108
+ struct :tst, TestStruct, :desc => "a nested TestStruct"
109
+ field :len, :uint8, :desc => "8-bit size value (>= self.size+2)"
110
+ array :str, [:char,255],
111
+ :desc => "a string up to 255 bytes bound by :len"
112
+ end
113
+
114
+ # override kind getter method with our own
115
+ # resolves kind to some kind of type array for example...
116
+ def kind
117
+ [:default, :bar, :baz][ self[:kind] ]
118
+ end
119
+ end
120
+
121
+ s1=TestStruct.new
122
+ s2=SomeStruct.new
123
+
124
+ # check out that 'kind' override:
125
+ s2.kind
126
+ # => :default
127
+
128
+ # oh and the regular FFI way is always intact
129
+ s2[:kind]
130
+ # => 0
131
+
132
+ s2[:kind]=1
133
+ s2.kind
134
+ # => :bar
135
+
136
+ s2.kind=3
137
+ s2.kind
138
+ # => :baz
139
+
140
+ puts "*"*70
141
+ s1.describe
142
+ ## we get a dump of metadata
143
+ # **********************************************************************
144
+ # Struct: TestStruct
145
+ # Field 0
146
+ # name: :field1
147
+ # type: :uint8
148
+ # desc: test field 1
149
+ #
150
+ # Field 1
151
+ # name: :field2
152
+ # type: :uint8
153
+ # desc: test field 2
154
+
155
+ puts "*"*70
156
+ s2.describe
157
+ ## we get a dump of metadata
158
+ # Struct: SomeStruct Field 0
159
+ # name: :kind
160
+ # type: :uint8
161
+ # desc: a type identifier
162
+ #
163
+ # Field 1
164
+ # name: :tst
165
+ # type: TestStruct
166
+ # desc: a nested TestStruct
167
+ #
168
+ # Field 2
169
+ # name: :len
170
+ # type: :uint8
171
+ # desc: 8-bit size value (>= self.size+2)
172
+ #
173
+ # Field 3
174
+ # name: :str
175
+ # type: [:char, 255]
176
+ # desc: a string up to 255 bytes bound by :len
177
+
178
+ puts "*"*70
179
+ s2.tst.describe
180
+ ## same as s1.describe
181
+ # **********************************************************************
182
+ # Struct: TestStruct
183
+ # Field 0
184
+ # name: :field1
185
+ # type: :uint8
186
+ # desc: test field 1
187
+ #
188
+ # Field 1
189
+ # name: :field2
190
+ # type: :uint8
191
+ # desc: test field 2
192
+
193
+ There's also some helpers for collecting lookup maps for constants, a common
194
+ and handy thing when porting various libraries. We use Socket here just for
195
+ example purposes, you can 'slurp' constants form any namespace this way.
196
+
197
+ require 'ffi/dry'
198
+ require 'socket'
199
+
200
+ module AddressFamily
201
+ include FFI::DRY::ConstMap
202
+ slurp_constants ::Socket, "AF_"
203
+ def list ; @@list ||= super() ; end # only generate the hash once
204
+ end
205
+
206
+ AddressFamily now has all the constants it found for Socket::AF_* minus the
207
+ prefix.
208
+
209
+ AddressFamily::INET
210
+ AddressFamily::LINK
211
+ AddressFamily::INET6
212
+
213
+ etc...
214
+
215
+ We can do type or value lookups using []
216
+
217
+ AddressFamily[2] # => "INET"
218
+ AddressFamily["INET"] # => 2
219
+
220
+ We can get a hash of all constant->value pairs with .list
221
+
222
+ AddressFamily.list
223
+ # => {"NATM"=>31, "DLI"=>13, "UNIX"=>1, "NETBIOS"=>33, ...}
224
+
225
+ ... and invert for a reverse mapping
226
+
227
+ AddressFamily.list.invert
228
+ # => {16=>"APPLETALK", 5=>"CHAOS", 27=>"NDRV", 0=>"UNSPEC", ...}
229
+
230
+
231
+ == License
232
+
233
+ Copyright (c) 2009 Eric Monti. See LICENSE for details.
data/Rakefile ADDED
@@ -0,0 +1,60 @@
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", ">= 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
46
+
47
+ require 'rake/rdoctask'
48
+ Rake::RDocTask.new do |rdoc|
49
+ if File.exist?('VERSION')
50
+ version = File.read('VERSION')
51
+ else
52
+ version = ""
53
+ end
54
+
55
+ rdoc.rdoc_dir = 'rdoc'
56
+ rdoc.title = "ffi_dry #{version}"
57
+ rdoc.rdoc_files.include('README*')
58
+ rdoc.rdoc_files.include('lib/**/*.rb')
59
+ end
60
+
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.3
data/ffi_dry.gemspec ADDED
@@ -0,0 +1,60 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run `rake gemspec`
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{ffi_dry}
8
+ s.version = "0.1.3"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Eric Monti"]
12
+ s.date = %q{2009-09-16}
13
+ s.description = %q{Provides a some useful modules, classes, and methods as well as a DSL-like syntax for FFI::Struct layouts}
14
+ s.email = %q{emonti@matasano.com}
15
+ s.extra_rdoc_files = [
16
+ "LICENSE",
17
+ "README.rdoc"
18
+ ]
19
+ s.files = [
20
+ ".document",
21
+ ".gitignore",
22
+ "LICENSE",
23
+ "README.rdoc",
24
+ "Rakefile",
25
+ "VERSION",
26
+ "ffi_dry.gemspec",
27
+ "lib/ffi/dry.rb",
28
+ "lib/ffi_dry.rb",
29
+ "samples/afmap.rb",
30
+ "samples/basic.rb",
31
+ "samples/describer.rb",
32
+ "spec/ffi_dry_spec.rb",
33
+ "spec/spec_helper.rb"
34
+ ]
35
+ s.homepage = %q{http://github.com/emonti/ffi_dry}
36
+ s.rdoc_options = ["--charset=UTF-8"]
37
+ s.require_paths = ["lib"]
38
+ s.rubygems_version = %q{1.3.4}
39
+ s.summary = %q{Syntactic sugar and helper utilities for FFI}
40
+ s.test_files = [
41
+ "spec/ffi_dry_spec.rb",
42
+ "spec/spec_helper.rb"
43
+ ]
44
+
45
+ if s.respond_to? :specification_version then
46
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
47
+ s.specification_version = 3
48
+
49
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
50
+ s.add_development_dependency(%q<rspec>, [">= 0"])
51
+ s.add_development_dependency(%q<yard>, [">= 0"])
52
+ else
53
+ s.add_dependency(%q<rspec>, [">= 0"])
54
+ s.add_dependency(%q<yard>, [">= 0"])
55
+ end
56
+ else
57
+ s.add_dependency(%q<rspec>, [">= 0"])
58
+ s.add_dependency(%q<yard>, [">= 0"])
59
+ end
60
+ end
data/lib/ffi/dry.rb ADDED
@@ -0,0 +1,366 @@
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
+ @dsl_metadata = self.class.dsl_metadata
80
+ params=nil
81
+
82
+ if args.size == 1 and (oparams=args[0]).is_a? Hash
83
+ params = oparams.dup
84
+ if raw=params.delete(:raw)
85
+ super( ::FFI::MemoryPointer.from_string(raw) )
86
+ else
87
+ super()
88
+ end
89
+ else
90
+ super(*args)
91
+ end
92
+
93
+ set_fields(params)
94
+ yield self if block_given?
95
+ end
96
+
97
+ # Sets field values in the struct specified by their symbolic name from a
98
+ # hash of ':field => value' pairs. Uses accessor field wrapper methods
99
+ # instead of a direct reference to the field (as in "obj.field1 = x",
100
+ # not "obj[:field] = x"). The difference is subtle, but this allows you
101
+ # to take advantage of any wrapper methods you override when initializing
102
+ # a new object. The only caveat is that the wrapper method must be named
103
+ # the same as the field, and the field must be included in members() from
104
+ # the layout.
105
+ #
106
+ # This method is called automatically if you are using the initialize()
107
+ # method provided in the DryStruct class and passing it a Hash as its only
108
+ # argument.
109
+ def set_fields(params=nil)
110
+ (params || {}).keys.each do |p|
111
+ if members().include?(p)
112
+ self.__send__(:"#{p}=", params[p])
113
+ else
114
+ raise(::ArgumentError, "#{self.class} does not have a '#{p}' field")
115
+ end
116
+ end
117
+ end
118
+
119
+ # Returns a new instance of self.class containing a seperately allocated
120
+ # copy of all our data. This abstract method should usually be called
121
+ # with super() from overridden 'copy' implementations for structures
122
+ # containing pointers to other memory or variable length data at the end.
123
+ #
124
+ # Note also that, by default, this implementation determine's size
125
+ # automatically based on the structure size. This is comparable to
126
+ # sizeof(some_struct) in C. However, you can supply a 'grown' parameter
127
+ # which can be used to add to the size of the copied instance as it is
128
+ # allocated and copied.
129
+ def copy(grown=0)
130
+ self.class.new( :raw => self.to_ptr.read_string(self.size+grown) )
131
+ end
132
+
133
+ # Returns a pointer to the specified field, which is the name assigned
134
+ # to a member in the layout.
135
+ def ptr_to(field)
136
+ x = self[field] # this is actually a test, to raise if missing
137
+ return (self.to_ptr + self.offset_of(field))
138
+ end
139
+
140
+ # Contains dsl_layout and some support methods that an 'includee' of
141
+ # DryStructHelper will have available as class methods.
142
+ module ClassMethods
143
+ # returns the structure metadata for this class based on
144
+ # the dsl_layout definitions
145
+ def dsl_metadata
146
+ @dsl_metadata
147
+ end
148
+
149
+ private
150
+
151
+ # This passes a block to an instance of DSL_StructLayoutBuilder, allowing
152
+ # for a more declarative syntax.
153
+ #
154
+ # It is a replacement to layout() and stores the dsl_metadata gathered
155
+ # about structure members locally.
156
+ #
157
+ #
158
+ def dsl_layout &block
159
+ builder = DSL_StructLayoutBuilder.new(self)
160
+ builder.instance_eval(&block)
161
+ @layout = builder.build
162
+ @size = @layout.size
163
+ _class_meths_from_dsl_metadata( builder.metadata )
164
+ return @layout
165
+ end
166
+
167
+ def _class_meths_from_dsl_metadata(meta)
168
+ (@dsl_metadata = meta).each do |spec|
169
+ name = spec[:name]
170
+ type = spec[:type]
171
+ define_method(:"#{name}") do
172
+ self[name]
173
+ end unless instance_methods.include?(:"#{name}")
174
+ define_method(:"#{name}=") do |val|
175
+ self[name]=val
176
+ end unless instance_methods.include?(:"#{name}=")
177
+ end
178
+ end
179
+ end
180
+
181
+ def self.included(base)
182
+ base.extend(ClassMethods)
183
+ end
184
+ end # class StructHelper
185
+
186
+ # This is a wrapper around the FFI::StructLayoutBuilder. Its goal is to
187
+ # provides a more declarative syntax for defining structures and include
188
+ # the ability to attach arbitrary dsl_metadata information to structure
189
+ # fields during definition.
190
+ #
191
+ # The "DSL" (and that's really very in-quotes) supplies 3 ways to
192
+ # define a field (for now):
193
+ #
194
+ # field()
195
+ # array()
196
+ # struct()
197
+ #
198
+ # See the individual method descriptions for more info.
199
+ #
200
+ class DSL_StructLayoutBuilder
201
+ attr_reader :builder, :metadata
202
+
203
+ # Initializes the builder with a reference to the structure using it
204
+ # Instead of duplicating Struct features, we just call back to them.
205
+ def initialize(pbind)
206
+ @pbind = pbind
207
+ @builder = ::FFI::StructLayoutBuilder.new
208
+ @metadata = []
209
+ super()
210
+ end
211
+
212
+ # calls StructLayoutBuider.build() on the bulder and returns its
213
+ # result.
214
+ def build
215
+ @builder.build
216
+ end
217
+
218
+ # Calls StructLayoutBuilder.add_struct() on the builder and stores
219
+ # a metadata hash entry (the opts hash with name and type overridden)
220
+ #
221
+ # struct field_name, RubyClass, { ... metadata ... }
222
+ #
223
+ # :offset is a special key in metadata, specifies the offset of the field.
224
+ def struct(name, klass, o={})
225
+ unless klass.kind_of?(Class) and klass < ::FFI::Struct
226
+ raise(::ArgumentError, "klass must be a struct")
227
+ end
228
+
229
+ opts = o.merge(:name => name, :type => klass)
230
+ offset = opts[:offset]
231
+ ret=@builder.add_struct(name, klass, offset)
232
+ @metadata << opts
233
+ return ret
234
+ end
235
+
236
+ # Calls StructLayoutBuider.add_array() on the builder and stores
237
+ # a metadata hash entry (the opts hash with name and type overridden)
238
+ #
239
+ # Syntax:
240
+ #
241
+ # array field_name, [ctype, N], { ... metadata ... }
242
+ #
243
+ # :offset is a special key in metadata, specifies the offset of the field.
244
+ def array(name, type, o={})
245
+ unless type.kind_of?(::Array)
246
+ raise(::ArgumentError, "type must be an array")
247
+ end
248
+
249
+ opts = o.merge(:name => name, :type => type)
250
+ offset = opts[:offset]
251
+ mod = enclosing_module
252
+ ret=
253
+ if @builder.respond_to?(:add_array)
254
+ @builder.add_array(name, find_type(type[0], mod), type[1], offset)
255
+ else
256
+ @builder.add_field(name, type, offset)
257
+ end
258
+
259
+ @metadata << opts
260
+ return ret
261
+ end
262
+
263
+ # Calls StructLayoutBuider.add_field() on the builder and stores
264
+ # a metadata hash entry (the opts hash with name and type overridden)
265
+ #
266
+ # Syntax:
267
+ #
268
+ # field field_name, ctype, { ... metadata ... }
269
+ #
270
+ # :offset is a special key in metadata, specifies the offset of the field.
271
+ def field(name, type, o={})
272
+ opts = o.merge(:name => name, :type => type)
273
+ offset = opts[:offset]
274
+ mod = enclosing_module
275
+ ret= @builder.add_field(name, find_type(type, mod), offset)
276
+ @metadata << opts
277
+ return ret
278
+ end
279
+
280
+ def find_type(*args)
281
+ @pbind.find_type(*args)
282
+ end
283
+
284
+ def enclosing_module(*args)
285
+ @pbind.enclosing_module(*args)
286
+ end
287
+
288
+ end
289
+
290
+ # Used for creating various value <=> constant mapping namespace modules.
291
+ module ConstMap
292
+
293
+ def self.included(klass)
294
+ klass.extend(ConstMap)
295
+ end
296
+
297
+ # A flexible lookup. Takes 'arg' as a Symbol or String as a name to lookup
298
+ # a value, or an Integer to lookup a corresponding name.
299
+ def [](arg)
300
+ if arg.is_a? Integer
301
+ list.invert[arg]
302
+ elsif arg.is_a? String or arg.is_a? Symbol
303
+ list[arg.to_s.upcase]
304
+ end
305
+ end
306
+
307
+ # Generates a hash of all the constant names mapped to value. Usually,
308
+ # it's a good idea to override this like so in derived modules:
309
+ #
310
+ # def list; @@list = super() ; end
311
+ #
312
+ def list
313
+ constants.inject({}){|h,c| h.merge! c => const_get(c) }
314
+ end
315
+
316
+ private
317
+ # When called from a module definition or class method, this method
318
+ # imports all the constants from # namespace 'nspace' that start with
319
+ # into the local namespace as constants named with whatever follows the
320
+ # prefix. Only constant names that match [A-Z][A-Z0-9_]+ are imported,
321
+ # the rest are ignored.
322
+ #
323
+ # This method also yields the (short) constant name and value to a block
324
+ # if one is provided. The block works like [...].select {|c,v| ... } in
325
+ # that the value is not mapped if the block returns nil or false.
326
+ def slurp_constants(nspace, prefix)
327
+ nspace.constants.grep(/^(#{prefix}([A-Z][A-Z0-9_]+))$/) do
328
+ c = $2
329
+ v = nspace.const_get($1)
330
+ next if block_given? and not yield(c,v)
331
+ const_set c, v
332
+ end
333
+ end
334
+ end
335
+
336
+ # Behaves just like ConstFlags, except that the [nnn] returns a list
337
+ # of names for the flags set on nnn. Name string lookups work same way as
338
+ # ConstFlags.
339
+ module ConstFlagsMap
340
+ include ConstMap
341
+
342
+ def self.included(klass)
343
+ klass.extend(ConstFlagsMap)
344
+ end
345
+
346
+ # A flexible lookup. Takes 'arg' as a Symbol or String as a name to lookup
347
+ # a bit-flag value, or an Integer to lookup a corresponding names for the
348
+ # flags present in it.
349
+ def [](arg)
350
+ if arg.is_a? Integer
351
+ ret = []
352
+ if arg == 0
353
+ n = list.invert[0]
354
+ ret << n if n
355
+ else
356
+ list.invert.sort.each {|v,n| ret << n if v !=0 and (v & arg) == v }
357
+ end
358
+ return ret
359
+ elsif arg.is_a? String or arg.is_a? Symbol
360
+ list[arg.to_s.upcase]
361
+ end
362
+ end
363
+ end
364
+ end
365
+
366
+
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,55 @@
1
+ #!/usr/bin/env ruby
2
+ # One major feature is a dsl"-like" syntax for declaring structure members
3
+ # in FFI::Struct or FFI::ManagedStruct definitions.
4
+
5
+ require 'rubygems'
6
+ require 'ffi'
7
+ require 'ffi/dry'
8
+
9
+ class SomeStruct < FFI::Struct
10
+ include FFI::DRY::StructHelper
11
+
12
+ # we get a new way of specifying layouts with a 'dsl'-like syntax
13
+ # The hash containing {:desc => ... } can contain arbitrary keys which
14
+ # can be used however we like. dsl_metadata will contain all these
15
+ # in the class and instance.
16
+ dsl_layout do
17
+ field :field1, :uint16, :desc => 'this is field 1'
18
+ field :field2, :uint16, :desc => 'this is field 2'
19
+ end
20
+ end
21
+
22
+ ss0=SomeStruct.new
23
+
24
+ # With the declaration above, we specified :desc hash value, which was stored
25
+ # in metadata along with the field name and type.
26
+
27
+ pp ss0.dsl_metadata
28
+ [{:type=>:uint16, :name=>:field1, :desc=>"this is field 1"},
29
+ {:type=>:uint16, :name=>:field2, :desc=>"this is field 2"}]
30
+ # => nil
31
+
32
+
33
+ # We get some free additional ways of instantiating and declaring values during
34
+ # initialization. (The FFI standard ways still work too)
35
+
36
+ raw_data = "\x00\x00\xff\xff"
37
+
38
+ ss1=SomeStruct.new :raw => raw_data
39
+ ss2=SomeStruct.new :raw => raw_data, :field1 => 1, :field2 => 2
40
+ ss3=SomeStruct.new {|x| x.field1=1 }
41
+ ss4=SomeStruct.new(:raw => raw_data) {|x| x.field1=1 }
42
+
43
+ [ ss0,
44
+ ss1,
45
+ ss2,
46
+ ss3,
47
+ ss4 ].each_with_index {|x,i| p ["ss#{i}",[x.field1, x.field2]]}
48
+
49
+ # which should produce...
50
+ # ["ss0", [0, 0]]
51
+ # ["ss1", [0, 65535]]
52
+ # ["ss2", [1, 2]]
53
+ # ["ss3", [1, 0]]
54
+ # ["ss4", [1, 65535]]
55
+
@@ -0,0 +1,119 @@
1
+ # Here's a broader example which utilizes that arbitrary ':desc' parameter in a
2
+ # "neighborly" way. This also demonstrates superclasses to add common struct
3
+ # features, declaring array fields, as well as nesting other structs.
4
+
5
+ require 'rubygems'
6
+ require 'ffi'
7
+ require 'ffi/dry'
8
+
9
+ class NeighborlyStruct < ::FFI::Struct
10
+ include ::FFI::DRY::StructHelper
11
+
12
+ def self.describe
13
+ print "Struct: #{self.name}"
14
+ dsl_metadata().each_with_index do |spec, i|
15
+ print " Field #{i}\n"
16
+ print " name: #{spec[:name].inspect}\n"
17
+ print " type: #{spec[:type].inspect}\n"
18
+ print " desc: #{spec[:desc]}\n\n"
19
+ end
20
+ print "\n"
21
+ end
22
+ def describe; self.class.describe; end
23
+ end
24
+
25
+ class TestStruct < NeighborlyStruct
26
+ dsl_layout do
27
+ field :field1, :uint8, :desc => "test field 1"
28
+ field :field2, :uint8, :desc => "test field 2"
29
+ end
30
+ end
31
+
32
+ class SomeStruct < NeighborlyStruct
33
+ dsl_layout do
34
+ field :kind, :uint8, :desc => "a type identifier"
35
+ struct :tst, TestStruct, :desc => "a nested TestStruct"
36
+ field :len, :uint8, :desc => "8-bit size value (>= self.size+2)"
37
+ array :str, [:char,255],
38
+ :desc => "a string up to 255 bytes bound by :len"
39
+ end
40
+
41
+ # override kind getter method with our own
42
+ # resolves kind to some kind of type array for example...
43
+ def kind
44
+ [:default, :bar, :baz][ self[:kind] ]
45
+ end
46
+ end
47
+
48
+ s1=TestStruct.new
49
+ s2=SomeStruct.new
50
+
51
+ # check out that 'kind' override:
52
+ s2.kind
53
+ # => :default
54
+
55
+ # oh and the regular FFI way is always intact
56
+ s2[:kind]
57
+ # => 0
58
+
59
+ s2[:kind]=1
60
+ s2.kind
61
+ # => :bar
62
+
63
+ s2.kind=3
64
+ s2.kind
65
+ # => :baz
66
+
67
+ puts "*"*70
68
+ s1.describe
69
+ ## we get a dump of metadata
70
+ # **********************************************************************
71
+ # Struct: TestStruct
72
+ # Field 0
73
+ # name: :field1
74
+ # type: :uint8
75
+ # desc: test field 1
76
+ #
77
+ # Field 1
78
+ # name: :field2
79
+ # type: :uint8
80
+ # desc: test field 2
81
+
82
+ puts "*"*70
83
+ s2.describe
84
+ ## we get a dump of metadata
85
+ # Struct: SomeStruct Field 0
86
+ # name: :kind
87
+ # type: :uint8
88
+ # desc: a type identifier
89
+ #
90
+ # Field 1
91
+ # name: :tst
92
+ # type: TestStruct
93
+ # desc: a nested TestStruct
94
+ #
95
+ # Field 2
96
+ # name: :len
97
+ # type: :uint8
98
+ # desc: 8-bit size value (>= self.size+2)
99
+ #
100
+ # Field 3
101
+ # name: :str
102
+ # type: [:char, 255]
103
+ # desc: a string up to 255 bytes bound by :len
104
+
105
+ puts "*"*70
106
+ s2.tst.describe
107
+ ## same as s1.describe
108
+ # **********************************************************************
109
+ # Struct: TestStruct
110
+ # Field 0
111
+ # name: :field1
112
+ # type: :uint8
113
+ # desc: test field 1
114
+ #
115
+ # Field 1
116
+ # name: :field2
117
+ # type: :uint8
118
+ # desc: test field 2
119
+
@@ -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,89 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ffi_dry
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.3
5
+ platform: ruby
6
+ authors:
7
+ - Eric Monti
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-09-16 00:00:00 -04:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: rspec
17
+ type: :development
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: "0"
24
+ version:
25
+ - !ruby/object:Gem::Dependency
26
+ name: yard
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
+ description: Provides a some useful modules, classes, and methods as well as a DSL-like syntax for FFI::Struct layouts
36
+ email: emonti@matasano.com
37
+ executables: []
38
+
39
+ extensions: []
40
+
41
+ extra_rdoc_files:
42
+ - LICENSE
43
+ - README.rdoc
44
+ files:
45
+ - .document
46
+ - .gitignore
47
+ - LICENSE
48
+ - README.rdoc
49
+ - Rakefile
50
+ - VERSION
51
+ - ffi_dry.gemspec
52
+ - lib/ffi/dry.rb
53
+ - lib/ffi_dry.rb
54
+ - samples/afmap.rb
55
+ - samples/basic.rb
56
+ - samples/describer.rb
57
+ - spec/ffi_dry_spec.rb
58
+ - spec/spec_helper.rb
59
+ has_rdoc: true
60
+ homepage: http://github.com/emonti/ffi_dry
61
+ licenses: []
62
+
63
+ post_install_message:
64
+ rdoc_options:
65
+ - --charset=UTF-8
66
+ require_paths:
67
+ - lib
68
+ required_ruby_version: !ruby/object:Gem::Requirement
69
+ requirements:
70
+ - - ">="
71
+ - !ruby/object:Gem::Version
72
+ version: "0"
73
+ version:
74
+ required_rubygems_version: !ruby/object:Gem::Requirement
75
+ requirements:
76
+ - - ">="
77
+ - !ruby/object:Gem::Version
78
+ version: "0"
79
+ version:
80
+ requirements: []
81
+
82
+ rubyforge_project:
83
+ rubygems_version: 1.3.4
84
+ signing_key:
85
+ specification_version: 3
86
+ summary: Syntactic sugar and helper utilities for FFI
87
+ test_files:
88
+ - spec/ffi_dry_spec.rb
89
+ - spec/spec_helper.rb