svn 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,249 @@
1
+ require 'rubygems'
2
+ require 'ffi'
3
+
4
+ module Svn #:nodoc:
5
+
6
+ # Utility functions for working with FFI
7
+ module Utils
8
+
9
+ module_function
10
+
11
+ # Returns a pointer to the object_id of +obj+
12
+ def wrap( obj )
13
+ ptr = FFI::MemoryPointer.new( :uint64 )
14
+ ptr.write_uint64( obj.object_id )
15
+ ptr
16
+ end
17
+
18
+ # Returns the object for the object_id stored in +ptr+
19
+ def unwrap( ptr )
20
+ ObjectSpace._id2ref( ptr.read_uint64 )
21
+ end
22
+
23
+ # a generic factory class for use with FFI data types that adds default
24
+ # arguments to constructor calls
25
+ #
26
+ # # when NativeHash is created, the args are [ptr, :string, :string]
27
+ # bind :get_hash, :returning => NativeHash.factory( :string, :string )
28
+ class Factory
29
+
30
+ # factories also work as DataConverters so they can be used with FFI
31
+ include FFI::DataConverter
32
+
33
+ def initialize( klass, *args )
34
+ @klass = klass
35
+ @added_args = args
36
+ end
37
+
38
+ def new( *args )
39
+ @klass.new( *args, *@added_args )
40
+ end
41
+
42
+ def from_native( ptr, ctx )
43
+ @klass.new( ptr, *@added_args )
44
+ end
45
+
46
+ def native_type
47
+ @klass.native_type
48
+ end
49
+
50
+ def real_class
51
+ @klass
52
+ end
53
+
54
+ def size
55
+ @klass.size
56
+ end
57
+ end
58
+
59
+ # returns the *contents* of the pointer as type
60
+ #
61
+ # for example:
62
+ # # void get_string( char **p ):
63
+ # get_string( out_ptr ); content_for( out_ptr, :string )
64
+ #
65
+ # # void get_hash( hash_t **p ):
66
+ # class Hash < FFI::AutoPointer; end
67
+ # get_hash( out_ptr ); content_for( out_ptr, Hash )
68
+ #
69
+ # if the type is a FFI::Pointer, this will try to instantiate it; to avoid
70
+ # instantiation, pass :pointer as the type
71
+ def content_from( pointer, type, len=nil )
72
+ return if pointer.nil?
73
+ if type.is_a? Array
74
+ type.inject( pointer ) do |ptr, subtype|
75
+ content_for( ptr, subtype, len )
76
+ end
77
+ elsif type.is_a? Factory
78
+ type.new( pointer.read_pointer ) unless pointer.null?
79
+ elsif type.is_a?( Class ) && type.ancestors.include?( FFI::Pointer )
80
+ type.new( pointer.read_pointer ) unless pointer.null?
81
+ elsif type.is_a?( FFI::Type::Mapped )
82
+ type.from_native( pointer.read_pointer, nil ) unless pointer.null?
83
+ elsif type == :string
84
+ pointer.read_pointer.read_string(
85
+ ( len == -1 ) ? nil : len
86
+ ) unless pointer.null?
87
+ else
88
+ pointer.send( :"read_#{type}" )
89
+ end
90
+ end
91
+
92
+ # returns the the pointer's value as type
93
+ #
94
+ # for example:
95
+ # # char *get_string( void ):
96
+ # ptr = get_string()
97
+ # str = content_for( ptr, :string )
98
+ #
99
+ # # hash *get_hash( void ):
100
+ # class Hash < FFI::AutoPointer; end
101
+ # ptr = get_hash( out_ptr )
102
+ # hash = content_for( out_ptr, Hash )
103
+ def content_for( pointer, type, len=nil )
104
+ return if pointer.nil?
105
+ if type.is_a? Array
106
+ type.inject( pointer ) do |ptr, subtype|
107
+ content_for( ptr, subtype, len )
108
+ end
109
+ elsif type.is_a? Factory
110
+ type.new( pointer ) unless pointer.null?
111
+ elsif type.is_a?( Class ) && type.ancestors.include?( FFI::Pointer )
112
+ type.new( pointer ) unless pointer.null?
113
+ elsif type.is_a?( FFI::Type::Mapped )
114
+ type.from_native( pointer, nil ) unless pointer.null?
115
+ elsif type == :string
116
+ # if len is nil or -1, use it for reading instead of counting on it to
117
+ # be null-terminated
118
+ pointer.read_string( ( len == -1 ) ? nil : len ) unless pointer.null?
119
+ else
120
+ pointer.send( :"read_#{type}" ) unless pointer.null?
121
+ end
122
+ end
123
+
124
+ def pointer_for( value, type )
125
+ if type.is_a? Array
126
+ type.reverse.inject( value ) do |val, subtype|
127
+ pointer_for( val, subtype )
128
+ end
129
+ elsif type.is_a? Factory
130
+ pointer_for( value, type.real_class )
131
+ elsif type.is_a?( Class ) && type.ancestors.include?( FFI::Pointer )
132
+ # use val directly
133
+ value
134
+ elsif type.is_a?( FFI::Type::Mapped )
135
+ # mapped types are really pointers to structs; they are read
136
+ # differently, but they should still be pointers we can use directly
137
+ value
138
+ elsif type == :string
139
+ # use from_string even if it isn't necessary to null-terminate
140
+ FFI::MemoryPointer.from_string( value )
141
+ else
142
+ # it must be a FFI type, use a new MemoryPointer
143
+ ptr = FFI::MemoryPointer.new( type )
144
+ ptr.send( :"write_#{type}", value )
145
+ ptr
146
+ end
147
+ end
148
+
149
+ # this module contains extensions for classes that inherit from FFI::Struct
150
+ # and FFI::AutoPointer to make binding instance methods to C methods more
151
+ # concise
152
+ module Extensions
153
+
154
+ # convenience method that returns a Factory instance for self with the
155
+ # given args
156
+ #
157
+ # the following are equivalent:
158
+ # NativeHash.factory( :string, :string )
159
+ #
160
+ # Factory( NativeHash, :string, :string )
161
+ def factory( *args )
162
+ Factory.new( self, *args )
163
+ end
164
+
165
+ module_function
166
+
167
+ # sets the module that will be used for all bound methods, until bind_to
168
+ # is called again
169
+ def bind_to( target )
170
+ @@target = target
171
+ end
172
+
173
+ # binds a method on the current target to self
174
+ #
175
+ # == arguments
176
+ # +sym+ :: the method on the target (set by +bind_to+) to use
177
+ # +options+ :: a Hash of optional method aliases or returned arguments
178
+ #
179
+ # == options
180
+ # +:as+ :: method name to use for the bound method
181
+ # +:to+ :: function name to use from the target object
182
+ # +:returning+ :: an array of types for out parameters
183
+ # +:validate+ :: a validation to call on the bound method's return value
184
+ # +:before_return+ :: a function to call on the return value
185
+ #
186
+ # TODO: call to_proc on symbols that should be procs
187
+ def bind( sym, options={}, &block )
188
+ # look up the method in the target module
189
+ meth_name = ( options[:to] || sym ).to_sym
190
+ # get a method obj from the target receiver so that if @@target is
191
+ # changed by another call to :bind_to, the method/target will not be
192
+ # changed (and broken)
193
+ meth = @@target.method( meth_name )
194
+ name = ( options[:as] || sym ).to_sym
195
+
196
+ # get the return types as an Array and save a copy
197
+ single_return = !options[:returning].is_a?( Array )
198
+ return_types = Array( options[:returning] ).dup
199
+
200
+ # get the C function validation
201
+ validation = options[:validate]
202
+
203
+ # get the before_return filter
204
+ before_return = options[:before_return] || lambda { |x| x }
205
+
206
+ # create a new method; blocks are used to re-arrange arguments
207
+ define_method( name ) do |*args|
208
+ # create new pointers for the specified out arguments
209
+ return_ptrs = return_types.map { |type| FFI::MemoryPointer.new( type ) }
210
+
211
+ return_val = nil # keep it in scope
212
+ if block
213
+ # call the method with the arguments after re-arranging via block
214
+ return_val = meth.call( *instance_exec(
215
+ *return_ptrs, self, *args, &block
216
+ ) )
217
+ else
218
+ # call the method with the standard argument order
219
+ return_val = meth.call( *return_ptrs, self, *args )
220
+ end
221
+
222
+ # call the return check, if present
223
+ instance_exec( return_val, &validation ) if validation
224
+
225
+ # if there are return types (out pointers), read the values out of
226
+ # the pointers and replace the return_val
227
+ unless return_types.empty?
228
+ return_val = return_ptrs.zip( return_types ).map do |ptr, type|
229
+ Utils.content_from( ptr, type )
230
+ end
231
+ return_val = return_val.first if single_return
232
+ end
233
+
234
+ # run the before_return filter and return the value from it; if no
235
+ # :before_return was in options, this will be the id function
236
+ instance_exec( return_val, &before_return )
237
+ end
238
+
239
+ end
240
+
241
+ end
242
+
243
+ # extend FFI objects with the new helper methods
244
+ #FFI::Struct.extend Extensions
245
+ FFI::AutoPointer.extend Extensions
246
+
247
+ end
248
+
249
+ end
metadata ADDED
@@ -0,0 +1,71 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: svn
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Ryan Blue
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2011-12-11 00:00:00.000000000Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: ffi
16
+ requirement: &15276700 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: '1.0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: *15276700
25
+ description:
26
+ email: rdblue@gmail.com
27
+ executables: []
28
+ extensions: []
29
+ extra_rdoc_files:
30
+ - README
31
+ files:
32
+ - lib/svn/misc.rb
33
+ - lib/svn/pools.rb
34
+ - lib/svn/apr_utils.rb
35
+ - lib/svn/diffs.rb
36
+ - lib/svn/utils.rb
37
+ - lib/svn/transactions.rb
38
+ - lib/svn/logs.rb
39
+ - lib/svn/roots.rb
40
+ - lib/svn/repos.rb
41
+ - lib/svn/streams.rb
42
+ - lib/svn/errors.rb
43
+ - lib/svn/revisions.rb
44
+ - lib/svn/counted_strings.rb
45
+ - lib/svn.rb
46
+ - README
47
+ homepage: http://github.com/codefoundry/svn
48
+ licenses: []
49
+ post_install_message:
50
+ rdoc_options: []
51
+ require_paths:
52
+ - lib
53
+ required_ruby_version: !ruby/object:Gem::Requirement
54
+ none: false
55
+ requirements:
56
+ - - ! '>='
57
+ - !ruby/object:Gem::Version
58
+ version: '0'
59
+ required_rubygems_version: !ruby/object:Gem::Requirement
60
+ none: false
61
+ requirements:
62
+ - - ! '>='
63
+ - !ruby/object:Gem::Version
64
+ version: '0'
65
+ requirements: []
66
+ rubyforge_project:
67
+ rubygems_version: 1.8.10
68
+ signing_key:
69
+ specification_version: 3
70
+ summary: Ruby bindings for SVN based on FFI
71
+ test_files: []