amalgalite 0.10.1-x86-mingw32
Sign up to get free protection for your applications and to get access to all the features.
- data/HISTORY +201 -0
- data/LICENSE +29 -0
- data/README +51 -0
- data/bin/amalgalite-pack +126 -0
- data/examples/a.rb +9 -0
- data/examples/blob.rb +88 -0
- data/examples/bootstrap.rb +36 -0
- data/examples/define_aggregate.rb +75 -0
- data/examples/define_function.rb +104 -0
- data/examples/gem-db.rb +94 -0
- data/examples/gems.db +0 -0
- data/examples/require_me.rb +11 -0
- data/examples/requires.rb +42 -0
- data/examples/schema-info.rb +34 -0
- data/ext/amalgalite/amalgalite3.c +290 -0
- data/ext/amalgalite/amalgalite3.h +151 -0
- data/ext/amalgalite/amalgalite3_blob.c +240 -0
- data/ext/amalgalite/amalgalite3_constants.c +221 -0
- data/ext/amalgalite/amalgalite3_database.c +1148 -0
- data/ext/amalgalite/amalgalite3_requires_bootstrap.c +210 -0
- data/ext/amalgalite/amalgalite3_statement.c +639 -0
- data/ext/amalgalite/extconf.rb +36 -0
- data/ext/amalgalite/gen_constants.rb +130 -0
- data/ext/amalgalite/sqlite3.c +106729 -0
- data/ext/amalgalite/sqlite3.h +5626 -0
- data/ext/amalgalite/sqlite3_options.h +4 -0
- data/ext/amalgalite/sqlite3ext.h +380 -0
- data/gemspec.rb +60 -0
- data/lib/amalgalite.rb +43 -0
- data/lib/amalgalite/1.8/amalgalite3.so +0 -0
- data/lib/amalgalite/1.9/amalgalite3.so +0 -0
- data/lib/amalgalite/aggregate.rb +67 -0
- data/lib/amalgalite/blob.rb +186 -0
- data/lib/amalgalite/boolean.rb +42 -0
- data/lib/amalgalite/busy_timeout.rb +47 -0
- data/lib/amalgalite/column.rb +97 -0
- data/lib/amalgalite/core_ext/kernel/require.rb +21 -0
- data/lib/amalgalite/database.rb +947 -0
- data/lib/amalgalite/function.rb +61 -0
- data/lib/amalgalite/index.rb +43 -0
- data/lib/amalgalite/packer.rb +226 -0
- data/lib/amalgalite/paths.rb +70 -0
- data/lib/amalgalite/profile_tap.rb +131 -0
- data/lib/amalgalite/progress_handler.rb +21 -0
- data/lib/amalgalite/requires.rb +120 -0
- data/lib/amalgalite/schema.rb +191 -0
- data/lib/amalgalite/sqlite3.rb +6 -0
- data/lib/amalgalite/sqlite3/constants.rb +80 -0
- data/lib/amalgalite/sqlite3/database/function.rb +48 -0
- data/lib/amalgalite/sqlite3/database/status.rb +68 -0
- data/lib/amalgalite/sqlite3/status.rb +60 -0
- data/lib/amalgalite/sqlite3/version.rb +37 -0
- data/lib/amalgalite/statement.rb +414 -0
- data/lib/amalgalite/table.rb +90 -0
- data/lib/amalgalite/taps.rb +2 -0
- data/lib/amalgalite/taps/console.rb +27 -0
- data/lib/amalgalite/taps/io.rb +71 -0
- data/lib/amalgalite/trace_tap.rb +35 -0
- data/lib/amalgalite/type_map.rb +63 -0
- data/lib/amalgalite/type_maps/default_map.rb +167 -0
- data/lib/amalgalite/type_maps/storage_map.rb +40 -0
- data/lib/amalgalite/type_maps/text_map.rb +22 -0
- data/lib/amalgalite/version.rb +37 -0
- data/lib/amalgalite/view.rb +26 -0
- data/spec/aggregate_spec.rb +169 -0
- data/spec/amalgalite_spec.rb +4 -0
- data/spec/blob_spec.rb +81 -0
- data/spec/boolean_spec.rb +23 -0
- data/spec/busy_handler.rb +165 -0
- data/spec/database_spec.rb +494 -0
- data/spec/default_map_spec.rb +87 -0
- data/spec/function_spec.rb +94 -0
- data/spec/integeration_spec.rb +111 -0
- data/spec/packer_spec.rb +60 -0
- data/spec/paths_spec.rb +28 -0
- data/spec/progress_handler_spec.rb +105 -0
- data/spec/requires_spec.rb +23 -0
- data/spec/rtree_spec.rb +71 -0
- data/spec/schema_spec.rb +120 -0
- data/spec/spec_helper.rb +27 -0
- data/spec/sqlite3/constants_spec.rb +65 -0
- data/spec/sqlite3/database_status_spec.rb +36 -0
- data/spec/sqlite3/status_spec.rb +18 -0
- data/spec/sqlite3/version_spec.rb +14 -0
- data/spec/sqlite3_spec.rb +53 -0
- data/spec/statement_spec.rb +161 -0
- data/spec/storage_map_spec.rb +41 -0
- data/spec/tap_spec.rb +59 -0
- data/spec/text_map_spec.rb +23 -0
- data/spec/type_map_spec.rb +17 -0
- data/spec/version_spec.rb +15 -0
- data/tasks/announce.rake +43 -0
- data/tasks/config.rb +107 -0
- data/tasks/distribution.rake +77 -0
- data/tasks/documentation.rake +32 -0
- data/tasks/extension.rake +141 -0
- data/tasks/rspec.rake +33 -0
- data/tasks/rubyforge.rake +59 -0
- data/tasks/utils.rb +80 -0
- metadata +237 -0
@@ -0,0 +1,21 @@
|
|
1
|
+
module Amalgalite
|
2
|
+
##
|
3
|
+
# A base class for use in creating your own progress handler classes
|
4
|
+
#
|
5
|
+
class ProgressHandler
|
6
|
+
def to_proc
|
7
|
+
self
|
8
|
+
end
|
9
|
+
|
10
|
+
# the arity of the call method
|
11
|
+
def arity() 0 ; end
|
12
|
+
|
13
|
+
##
|
14
|
+
# Override this method, returning +false+ if the SQLite should act as if
|
15
|
+
# +interrupt!+ had been invoked.
|
16
|
+
#
|
17
|
+
def call
|
18
|
+
raise NotImplementedError, "The progress handler call() method must be implemented"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,120 @@
|
|
1
|
+
require 'amalgalite'
|
2
|
+
require 'pathname'
|
3
|
+
require 'zlib'
|
4
|
+
require 'amalgalite/packer'
|
5
|
+
|
6
|
+
module Amalgalite
|
7
|
+
#
|
8
|
+
# Requires encapsulates requiring items from the database
|
9
|
+
#
|
10
|
+
class Requires
|
11
|
+
class << self
|
12
|
+
def load_path_db_connections
|
13
|
+
@load_path_db_connections ||= {}
|
14
|
+
end
|
15
|
+
|
16
|
+
def load_path
|
17
|
+
@load_path ||= []
|
18
|
+
end
|
19
|
+
|
20
|
+
#
|
21
|
+
# Allocate a database connection to the given filename
|
22
|
+
#
|
23
|
+
def db_connection_to( dbfile_name )
|
24
|
+
unless connection = load_path_db_connections[ dbfile_name ]
|
25
|
+
connection = ::Amalgalite::Database.new( dbfile_name )
|
26
|
+
load_path_db_connections[dbfile_name] = connection
|
27
|
+
end
|
28
|
+
return connection
|
29
|
+
end
|
30
|
+
|
31
|
+
#
|
32
|
+
# Setting a class level variable as a flag to know what we are currently
|
33
|
+
# in the middle of requiring
|
34
|
+
#
|
35
|
+
def requiring
|
36
|
+
@requiring ||= []
|
37
|
+
end
|
38
|
+
|
39
|
+
def require( filename )
|
40
|
+
if load_path.empty? then
|
41
|
+
raise ::LoadError, "Amalgalite load path is empty -- #{filename}"
|
42
|
+
elsif $LOADED_FEATURES.include?( filename ) then
|
43
|
+
return false
|
44
|
+
elsif Requires.requiring.include?( filename ) then
|
45
|
+
return false
|
46
|
+
else
|
47
|
+
Requires.requiring << filename
|
48
|
+
load_path.each do |lp|
|
49
|
+
if lp.require( filename ) then
|
50
|
+
Requires.requiring.delete( filename )
|
51
|
+
return true
|
52
|
+
end
|
53
|
+
end
|
54
|
+
Requires.requiring.delete( filename )
|
55
|
+
raise ::LoadError, "amalgalite has no such file to load -- #{filename}"
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
attr_reader :dbfile_name
|
61
|
+
attr_reader :table_name
|
62
|
+
attr_reader :filename_column
|
63
|
+
attr_reader :contents_column
|
64
|
+
attr_reader :compressed_column
|
65
|
+
attr_reader :db_connection
|
66
|
+
|
67
|
+
def initialize( opts = {} )
|
68
|
+
@dbfile_name = opts[:dbfile_name] || Bootstrap::DEFAULT_DB
|
69
|
+
@table_name = opts[:table_name] || Bootstrap::DEFAULT_TABLE
|
70
|
+
@filename_column = opts[:filename_column] || Bootstrap::DEFAULT_FILENAME_COLUMN
|
71
|
+
@contents_column = opts[:contents_column] || Bootstrap::DEFAULT_CONTENTS_COLUMN
|
72
|
+
@compressed_column = opts[:compressed_column] || Bootstrap::DEFAULT_COMPRESSED_COLUMN
|
73
|
+
@db_connection = Requires.db_connection_to( dbfile_name )
|
74
|
+
Requires.load_path << self
|
75
|
+
end
|
76
|
+
|
77
|
+
#
|
78
|
+
# return the sql to find the file contents for a file in this requires
|
79
|
+
#
|
80
|
+
def sql
|
81
|
+
@sql ||= "SELECT #{filename_column}, #{compressed_column}, #{contents_column} FROM #{table_name} WHERE #{filename_column} = ?"
|
82
|
+
end
|
83
|
+
|
84
|
+
#
|
85
|
+
# load a file in this database table. This will check and see if the
|
86
|
+
# file is already required. If it isn't it will select the contents
|
87
|
+
# associated with the row identified by the filename and eval those contents
|
88
|
+
# within the context of TOPLEVEL_BINDING. The filename is then appended to
|
89
|
+
# $LOADED_FEATURES.
|
90
|
+
#
|
91
|
+
# if the file was required then true is returned, otherwise false
|
92
|
+
#
|
93
|
+
def require( filename )
|
94
|
+
if $LOADED_FEATURES.include?( filename ) then
|
95
|
+
return false
|
96
|
+
else
|
97
|
+
begin
|
98
|
+
filename = filename.gsub(/\.rb\Z/,'')
|
99
|
+
rows = db_connection.execute(sql, filename)
|
100
|
+
if rows.size > 0 then
|
101
|
+
row = rows.first
|
102
|
+
contents = row[contents_column].to_s
|
103
|
+
if row[compressed_column] then
|
104
|
+
contents = ::Amalgalite::Packer.gunzip( contents )
|
105
|
+
end
|
106
|
+
|
107
|
+
eval( contents, TOPLEVEL_BINDING, row[filename_column] )
|
108
|
+
$LOADED_FEATURES << row[filename_column]
|
109
|
+
return true
|
110
|
+
else
|
111
|
+
return false
|
112
|
+
end
|
113
|
+
rescue => e
|
114
|
+
raise ::LoadError, "Failure loading #{filename} from #{dbfile_name} : #{e}"
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
require 'amalgalite/core_ext/kernel/require'
|
@@ -0,0 +1,191 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (c) 2008 Jeremy Hinegardner
|
3
|
+
# All rights reserved. See LICENSE and/or COPYING for details.
|
4
|
+
#++
|
5
|
+
|
6
|
+
require 'amalgalite/table'
|
7
|
+
require 'amalgalite/index'
|
8
|
+
require 'amalgalite/column'
|
9
|
+
require 'amalgalite/view'
|
10
|
+
|
11
|
+
module Amalgalite
|
12
|
+
#
|
13
|
+
# An object view of the schema in the SQLite database. If the schema changes
|
14
|
+
# after this class is created, it has no knowledge of that.
|
15
|
+
#
|
16
|
+
class Schema
|
17
|
+
|
18
|
+
attr_reader :catalog
|
19
|
+
attr_reader :schema
|
20
|
+
attr_writer :dirty
|
21
|
+
attr_reader :db
|
22
|
+
|
23
|
+
#
|
24
|
+
# Create a new instance of Schema
|
25
|
+
#
|
26
|
+
def initialize( db, catalog = 'main', schema = 'sqlite')
|
27
|
+
@db = db
|
28
|
+
@catalog = catalog
|
29
|
+
@schema = schema
|
30
|
+
@tables = {}
|
31
|
+
@views = {}
|
32
|
+
@dirty = true
|
33
|
+
load_schema!
|
34
|
+
end
|
35
|
+
|
36
|
+
def dirty?() @dirty; end
|
37
|
+
def dirty!() @dirty = true; end
|
38
|
+
|
39
|
+
#
|
40
|
+
# load the schema from the database
|
41
|
+
def load_schema!
|
42
|
+
load_tables
|
43
|
+
load_views
|
44
|
+
@dirty = false
|
45
|
+
end
|
46
|
+
|
47
|
+
##
|
48
|
+
# return the tables, reloading if dirty
|
49
|
+
def tables
|
50
|
+
load_schema! if dirty?
|
51
|
+
return @tables
|
52
|
+
end
|
53
|
+
|
54
|
+
##
|
55
|
+
# load all the tables
|
56
|
+
#
|
57
|
+
def load_tables
|
58
|
+
@tables = {}
|
59
|
+
@db.execute("SELECT tbl_name FROM sqlite_master WHERE type = 'table' AND name != 'sqlite_sequence'") do |table_info|
|
60
|
+
table = load_table( table_info['tbl_name'] )
|
61
|
+
table.indexes = load_indexes( table )
|
62
|
+
@tables[table.name] = table
|
63
|
+
end
|
64
|
+
return @tables
|
65
|
+
end
|
66
|
+
|
67
|
+
##
|
68
|
+
# Load a single table
|
69
|
+
def load_table( table_name )
|
70
|
+
rows = @db.execute("SELECT tbl_name, sql FROM sqlite_master WHERE type = 'table' AND tbl_name = ?", table_name)
|
71
|
+
table_info = rows.first
|
72
|
+
table = nil
|
73
|
+
if table_info then
|
74
|
+
table = Amalgalite::Table.new( table_info['tbl_name'], table_info['sql'] )
|
75
|
+
table.columns = load_columns( table )
|
76
|
+
table.schema = self
|
77
|
+
table.indexes = load_indexes( table )
|
78
|
+
@tables[table.name] = table
|
79
|
+
else
|
80
|
+
# might be a temporary table
|
81
|
+
table = Amalgalite::Table.new( table_name, nil )
|
82
|
+
cols = load_columns( table )
|
83
|
+
if cols.size > 0 then
|
84
|
+
table.columns = cols
|
85
|
+
table.schema = self
|
86
|
+
table.indexes = load_indexes( table )
|
87
|
+
@tables[table.name] = table
|
88
|
+
end
|
89
|
+
end
|
90
|
+
return table
|
91
|
+
end
|
92
|
+
|
93
|
+
##
|
94
|
+
# load all the indexes for a particular table
|
95
|
+
#
|
96
|
+
def load_indexes( table )
|
97
|
+
indexes = {}
|
98
|
+
|
99
|
+
@db.prepare("SELECT name, sql FROM sqlite_master WHERE type ='index' and tbl_name = $name") do |idx_stmt|
|
100
|
+
idx_stmt.execute( "$name" => table.name) do |idx_info|
|
101
|
+
indexes[idx_info['name']] = Amalgalite::Index.new( idx_info['name'], idx_info['sql'], table )
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
@db.execute("PRAGMA index_list( #{@db.quote(table.name)} );") do |idx_list|
|
106
|
+
idx = indexes[idx_list['name']]
|
107
|
+
|
108
|
+
# temporary indexes do not show up in the previous list
|
109
|
+
if idx.nil? then
|
110
|
+
idx = Amalgalite::Index.new( idx_list['name'], nil, table )
|
111
|
+
indexes[idx_list['name']] = idx
|
112
|
+
end
|
113
|
+
|
114
|
+
idx.sequence_number = idx_list['seq']
|
115
|
+
idx.unique = Boolean.to_bool( idx_list['unique'] )
|
116
|
+
|
117
|
+
@db.execute("PRAGMA index_info( #{@db.quote(idx.name)} );") do |col_info|
|
118
|
+
idx.columns << table.columns[col_info['name']]
|
119
|
+
end
|
120
|
+
end
|
121
|
+
return indexes
|
122
|
+
end
|
123
|
+
|
124
|
+
##
|
125
|
+
# load all the columns for a particular table
|
126
|
+
#
|
127
|
+
def load_columns( table )
|
128
|
+
cols = {}
|
129
|
+
idx = 0
|
130
|
+
@db.execute("PRAGMA table_info(#{@db.quote(table.name)})") do |row|
|
131
|
+
col = Amalgalite::Column.new( "main", table.name, row['name'], row['cid'])
|
132
|
+
|
133
|
+
col.default_value = row['dflt_value']
|
134
|
+
|
135
|
+
col.declared_data_type = row['type']
|
136
|
+
col.not_null_constraint = row['notnull']
|
137
|
+
col.primary_key = row['pk']
|
138
|
+
|
139
|
+
# need to remove leading and trailing ' or " from the default value
|
140
|
+
if col.default_value and col.default_value.kind_of?( String ) and ( col.default_value.length >= 2 ) then
|
141
|
+
fc = col.default_value[0].chr
|
142
|
+
lc = col.default_value[-1].chr
|
143
|
+
if fc == lc and ( fc == "'" || fc == '"' ) then
|
144
|
+
col.default_value = col.default_value[1..-2]
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
unless table.temporary? then
|
149
|
+
# get more exact information
|
150
|
+
@db.api.table_column_metadata( "main", table.name, col.name ).each_pair do |key, value|
|
151
|
+
col.send("#{key}=", value)
|
152
|
+
end
|
153
|
+
end
|
154
|
+
col.schema = self
|
155
|
+
cols[col.name] = col
|
156
|
+
idx += 1
|
157
|
+
end
|
158
|
+
return cols
|
159
|
+
end
|
160
|
+
|
161
|
+
##
|
162
|
+
# return the views, reloading if dirty
|
163
|
+
#
|
164
|
+
def views
|
165
|
+
reload_schema! if dirty?
|
166
|
+
return @views
|
167
|
+
end
|
168
|
+
|
169
|
+
##
|
170
|
+
# load a single view
|
171
|
+
#
|
172
|
+
def load_view( name )
|
173
|
+
rows = @db.execute("SELECT name, sql FROM sqlite_master WHERE type = 'view' AND name = ?", name )
|
174
|
+
view_info = rows.first
|
175
|
+
view = Amalgalite::View.new( view_info['name'], view_info['sql'] )
|
176
|
+
view.schema = self
|
177
|
+
return view
|
178
|
+
end
|
179
|
+
|
180
|
+
##
|
181
|
+
# load all the views for the database
|
182
|
+
#
|
183
|
+
def load_views
|
184
|
+
@db.execute("SELECT name, sql FROM sqlite_master WHERE type = 'view'") do |view_info|
|
185
|
+
view = load_view( view_info['name'] )
|
186
|
+
@views[view.name] = view
|
187
|
+
end
|
188
|
+
return @views
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (c) 2008 Jeremy Hinegardner
|
3
|
+
# All rights reserved. See LICENSE and/or COPYING for details.
|
4
|
+
#++
|
5
|
+
module Amalgalite::SQLite3
|
6
|
+
module Constants
|
7
|
+
module Helpers
|
8
|
+
#
|
9
|
+
# convert an integer value into the string representation of the associated
|
10
|
+
# constant. this is a helper method used by some of the other modules
|
11
|
+
#
|
12
|
+
def name_from_value( value )
|
13
|
+
unless defined? @const_map_from_value
|
14
|
+
@const_map_from_value = {}
|
15
|
+
constants.each do |const_name|
|
16
|
+
c_int = const_get( const_name )
|
17
|
+
@const_map_from_value[c_int] = const_name
|
18
|
+
end
|
19
|
+
end
|
20
|
+
return @const_map_from_value[ value ]
|
21
|
+
end
|
22
|
+
|
23
|
+
#
|
24
|
+
# convert a string into the constant value. This is helper method used by
|
25
|
+
# some of the other modules
|
26
|
+
#
|
27
|
+
def value_from_name( name )
|
28
|
+
unless defined? @const_map_from_name
|
29
|
+
@const_map_from_name = {}
|
30
|
+
constants.each do |const_name|
|
31
|
+
c_int = const_get( const_name )
|
32
|
+
@const_map_from_name[ const_name ] = c_int
|
33
|
+
end
|
34
|
+
end
|
35
|
+
return @const_map_from_name[ name.upcase ]
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
|
40
|
+
##
|
41
|
+
# DataType defines the namespace for all possible SQLite data types.
|
42
|
+
#
|
43
|
+
module DataType
|
44
|
+
end
|
45
|
+
DataType.freeze
|
46
|
+
|
47
|
+
##
|
48
|
+
# Open defines the namespace for all possible flags to the Database.open
|
49
|
+
# method
|
50
|
+
#
|
51
|
+
module Open
|
52
|
+
end
|
53
|
+
Open.freeze
|
54
|
+
|
55
|
+
##
|
56
|
+
# Status defines the namespace for all the possible status flags for
|
57
|
+
# Amalgalite::SQLite3::Status objects
|
58
|
+
#
|
59
|
+
module Status
|
60
|
+
extend Helpers
|
61
|
+
end
|
62
|
+
|
63
|
+
|
64
|
+
##
|
65
|
+
# DBStatus defines the namespace for all the possible status codes for the
|
66
|
+
# Amalgalite::SQlite3::Database::Status objects.
|
67
|
+
#
|
68
|
+
module DBStatus
|
69
|
+
extend Helpers
|
70
|
+
end
|
71
|
+
|
72
|
+
##
|
73
|
+
# ResultCode defines the namespace for all possible result codes from an
|
74
|
+
# SQLite API call.
|
75
|
+
#
|
76
|
+
module ResultCode
|
77
|
+
extend Helpers
|
78
|
+
end # end ResultCode
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module Amalgalite::SQLite3
|
2
|
+
class Database
|
3
|
+
##
|
4
|
+
# A wrapper around a proc for use as an SQLite Ddatabase fuction
|
5
|
+
#
|
6
|
+
# f = Function.new( 'md5', lambda { |x| Digest::MD5.hexdigest( x.to_s ) } )
|
7
|
+
#
|
8
|
+
class Function
|
9
|
+
|
10
|
+
# the name of the function, and how it will be called in SQL
|
11
|
+
attr_reader :name
|
12
|
+
|
13
|
+
# The unique signature of this function. This is used to determin if the
|
14
|
+
# function is already registered or not
|
15
|
+
#
|
16
|
+
def self.signature( name, arity )
|
17
|
+
"#{name}/#{arity}"
|
18
|
+
end
|
19
|
+
|
20
|
+
# Initialize with the name and the Proc
|
21
|
+
#
|
22
|
+
def initialize( name, _proc )
|
23
|
+
@name = name
|
24
|
+
@function = _proc
|
25
|
+
end
|
26
|
+
|
27
|
+
# The unique signature of this function
|
28
|
+
#
|
29
|
+
def signature
|
30
|
+
@signature ||= Function.signature( name, arity )
|
31
|
+
end
|
32
|
+
alias :to_s :signature
|
33
|
+
|
34
|
+
# The arity of SQL function, -1 means it is takes a variable number of
|
35
|
+
# arguments.
|
36
|
+
#
|
37
|
+
def arity
|
38
|
+
@function.arity
|
39
|
+
end
|
40
|
+
|
41
|
+
# Invoke the proc
|
42
|
+
#
|
43
|
+
def call( *args )
|
44
|
+
@function.call( *args )
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|