ffi_dry 0.1.3

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,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