og 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,224 @@
1
+ # code:
2
+ # * George Moschovitis <gm@navel.gr>
3
+ # * Anastasios Koutoumanos <ak@navel.gr>
4
+ # * Elias Karakoulakis <ekarak@ktismata.com>
5
+ #
6
+ # (c) 2004 Navel, all rights reserved.
7
+ # $Id: string.rb 165 2004-11-18 12:04:04Z gmosx $
8
+
9
+ require "uri"
10
+
11
+ module G;
12
+
13
+ # = StringUtils
14
+ #
15
+ # General string utilities collection.
16
+ #
17
+ # === Design:
18
+ #
19
+ # Implement as a module to avoid class polution. You can
20
+ # still Ruby's advanced features to include the module in your
21
+ # class. Passing the object to act upon allows to check for nil,
22
+ # which isn't possible if you use self.
23
+ #
24
+ # === TODO:
25
+ #
26
+ # - implement a method that returns easy to remember
27
+ # pseudo-random strings
28
+ # - add aliases for those methods in Kernel.
29
+ #
30
+ module StringUtils
31
+
32
+ @@map_to_greeklish = {
33
+ "�" => "a", "�" => "A", "�" => "a", "�" => "A",
34
+ "�" => "b", "�" => "B",
35
+ "�" => "g", "�" => "G",
36
+ "�" => "d", "�" => "D",
37
+ "�" => "e", "�" => "E", "�" => "e", "�" => "E",
38
+ "�" => "z", "�" => "Z",
39
+ "�" => "h", "�" => "H", "�" => "h", "�" => "H",
40
+ "�" => "8", "�" => "8",
41
+ "�" => "i", "�" => "I", "�" => "i", "�" => "I",
42
+ "�" => "k", "�" => "K",
43
+ "�" => "l", "�" => "L",
44
+ "�" => "m", "�" => "M",
45
+ "�" => "n", "�" => "N",
46
+ "�" => "3", "�" => "3",
47
+ "�" => "o", "�" => "O", "�" => "o", "�" => "O",
48
+ "�" => "p", "�" => "P",
49
+ "�" => "r", "�" => "R",
50
+ "�" => "s", "�" => "s", "�" => "S",
51
+ "�" => "t", "�" => "T",
52
+ "�" => "y", "�" => "Y", "�" => "y", "�" => "Y",
53
+ "�" => "f", "�" => "F",
54
+ "�" => "x", "�" => "X",
55
+ "�" => "ps","�" => "PS",
56
+ "�" => "w", "�" => "W", "�" => "w", "�"=>"W"
57
+ }
58
+
59
+ # Convert the input string to greeklish.
60
+ #--
61
+ # gmosx, TODO: remove from public distribution
62
+ #++
63
+ #
64
+ def self.to_greeklish(input)
65
+ return nil unless input
66
+ output = ""
67
+ # gmosx: also parse new lines
68
+ input.scan(/./m) { |w|
69
+ c = @@map_to_greeklish[w]
70
+ output << (c.nil?? w: c)
71
+ }
72
+ return output
73
+ end
74
+
75
+ # Move this in String class?
76
+ #
77
+ # Tests a string for a valid value (non nil, not empty)
78
+ #
79
+ def self.valid?(string)
80
+ return (not ((nil == string) or (string.empty?)))
81
+ end
82
+
83
+ # returns short abstract of long strings (first 'count'
84
+ # characters, chopped at the nearest word, appended by '...')
85
+ # force_cutoff: break forcibly at 'count' chars. Does not accept
86
+ # count < 2.
87
+ #
88
+ def self.head(string, count = 128, force_cutoff = false, ellipsis="...")
89
+ return nil unless string
90
+ return nil if count < 2
91
+
92
+ if string.size > count
93
+ cut_at = force_cutoff ? count : (string.index(' ', count-1) || count)
94
+ xstring = string.slice(0, cut_at)
95
+ return xstring.chomp(" ") + ellipsis
96
+ else
97
+ return string
98
+ end
99
+ end
100
+
101
+ # Apply a set of rules (regular expression matches) to the
102
+ # string
103
+ #
104
+ # === Requirements:
105
+ # - the rules must be applied in order! So we cannot use a
106
+ # hash because the ordering is not guaranteed! we use an
107
+ # array instead.
108
+ #
109
+ # === Input:
110
+ # the string to rewrite
111
+ # the array containing rule-pairs (match, rewrite)
112
+ #
113
+ # === Output:
114
+ # the rewritten string
115
+
116
+ MATCH = 0
117
+ REWRITE = 1
118
+
119
+ def self.rewrite(string, rules)
120
+ return nil unless string
121
+
122
+ # gmosx: helps to find bugs
123
+ raise ArgumentError.new("the rules parameter is nil") unless rules
124
+
125
+ rewritten_string = string.dup
126
+
127
+ for rule in rules
128
+ rewritten_string.gsub!(rule[MATCH], rule[REWRITE])
129
+ end
130
+
131
+ return (rewritten_string or string)
132
+ end
133
+
134
+ # Enforces a maximum width of a string inside an
135
+ # html container. If the string exceeds this maximum width
136
+ # the string gets wraped.
137
+ #
138
+ # Not really useful, better use the CSS overflow: hidden
139
+ # functionality.
140
+ #
141
+ # === Input:
142
+ # the string to be wrapped
143
+ # the enforced width
144
+ # the separator used for wrapping
145
+ #
146
+ # === Output:
147
+ # the wrapped string
148
+ #
149
+ # === Example:
150
+ # text = "1111111111111111111111111111111111111111111"
151
+ # text = wrap(text, 10, " ")
152
+ # p text # => "1111111111 1111111111 1111111111"
153
+ #
154
+ # See the test cases to better understand the behaviour!
155
+ #
156
+ def self.wrap(string, width = 20, separator = " ")
157
+ return nil unless string
158
+
159
+ re = /([^#{separator}]{1,#{width}})/
160
+ wrapped_string = string.scan(re).join(separator)
161
+
162
+ return wrapped_string
163
+ end
164
+
165
+ # Replace dangerours chars in filenames
166
+ #
167
+ def self.rationalize_filename(filename)
168
+ return nil unless filename
169
+ # gmosx: rationalize a copy!!! (add unit test)
170
+ xfilename = filename.dup()
171
+ # gmosx: replace some dangerous chars!
172
+ xfilename.gsub!(/ /, "-")
173
+ xfilename.gsub!(/!/, "")
174
+ xfilename.gsub!(/'/, "")
175
+ xfilename.gsub!(/\(/, "")
176
+ xfilename.gsub!(/\)/, "")
177
+ xfilename = self.to_greeklish(xfilename)
178
+ return xfilename
179
+ end
180
+
181
+ # Returns a random string. one possible use is
182
+ # password initialization.
183
+ #
184
+ # === Input:
185
+ # the maximum length of the string
186
+ #
187
+ # === Output:
188
+ # the random string
189
+ #
190
+ def self.random(max_length = 8, char_re = /[\w\d]/)
191
+ # gmosx: this is a nice example of input parameter checking.
192
+ # this is NOT a real time called method so we can add this
193
+ # check. Congrats to the author.
194
+ raise ArgumentError.new("char_re must be a regular expression!") unless char_re.is_a?(Regexp)
195
+
196
+ string = ""
197
+
198
+ while string.length < max_length
199
+ ch = rand(255).chr
200
+ string << ch if ch =~ char_re
201
+ end
202
+
203
+ return string
204
+ end
205
+
206
+ # Screen an IP address
207
+ #--
208
+ # gmosx: copied this method from n1, check how it works!
209
+ # probably deprecate?
210
+ #++
211
+ def self.screen_ip_address(address)
212
+ if address
213
+ return address.split(',').collect { |hostip|
214
+ hostip.gsub(/\.[^\.]*$/, ".*")
215
+ }.join(', ')
216
+ else
217
+ return "*.*.*.*"
218
+ end
219
+ end
220
+
221
+ end
222
+
223
+ end # module
224
+
@@ -0,0 +1,93 @@
1
+ # code:
2
+ # * George Moschovitis <gm@navel.gr>
3
+ #
4
+ # (c) 2004 Navel, all rights reserved.
5
+ # $Id: time.rb 167 2004-11-23 14:03:10Z gmosx $
6
+
7
+ require "time.rb"
8
+
9
+ module G;
10
+
11
+ # = Time
12
+ #
13
+ # General time utilities collection
14
+ #
15
+ # === Design:
16
+ #
17
+ # Implement as a module to avoid class polution. You can
18
+ # still Ruby's advanced features to include the module in your
19
+ # class. Passing the object to act upon allows to check for nil,
20
+ # which isn't possible if you use self.
21
+ #
22
+ # === TODO:
23
+ #
24
+ # - SOS: add test units.
25
+ # - add aliases for those methods in Kernel ?
26
+ #
27
+ module TimeUtils
28
+
29
+ NOW = Time.now
30
+ NEVER = Time.mktime(2038)
31
+ ZERO = Time.mktime(1972)
32
+
33
+ # Convert the time to a nice String representation.
34
+ #
35
+ def self.date_time(time)
36
+ return nil unless time
37
+ return time.strftime("%d-%m-%Y %H:%M")
38
+ end
39
+
40
+ # this method calculates the days extrema given two time objects.
41
+ # start time is the given time1 at 00:00:00
42
+ # end time is the given time2 at 23:59:59:999
43
+ #
44
+ # Input:
45
+ # - the two times (if only time1 is provided then you get an extrema
46
+ # of exactly one day extrema.
47
+ #
48
+ # Output
49
+ # - the time range. you can get the start/end times using
50
+ # range methods.
51
+ #
52
+ def self.days_extrema(time1, time2=nil)
53
+ time2 = time1 if (not time2.valid? Time)
54
+ time2 = NEVER if (time2 <= time1)
55
+ start_time = Time.self.start_of_day(time1)
56
+ end_time = self.end_of_day(time2)
57
+ return (start_time..end_time)
58
+ end
59
+
60
+ #
61
+ # set time to start of day
62
+ #
63
+ def self.start_of_day(time)
64
+ return Time.mktime(time.year, time.month, time.day, 0, 0, 0, 0)
65
+ end
66
+
67
+ #
68
+ # set time to end of day
69
+ #
70
+ def self.end_of_day(time)
71
+ return Time.mktime(time.year, time.month, time.day, 23, 59, 59, 999)
72
+ end
73
+
74
+
75
+ # returns true only if day of time is included in the
76
+ # range (stime..etime). Only year days are checked.
77
+ #
78
+ def self.time_in_day_range(time, stime=ZERO, etime=NEVER)
79
+ if (etime <= stime)
80
+ $log.debug "Invalid end time (#{etime} < #{stime})" if $DBG
81
+ etime = NEVER
82
+ end
83
+
84
+ stime = start_of_day(stime)
85
+ etime = end_of_day(etime)
86
+
87
+ return (stime..etime).include?(time)
88
+ end
89
+
90
+ end
91
+
92
+ end # module
93
+
@@ -0,0 +1,411 @@
1
+ # code:
2
+ # * George Moschovitis <gm@navel.gr>
3
+ #
4
+ # (c) 2004 Navel, all rights reserved.
5
+ # $Id: og.rb 167 2004-11-23 14:03:10Z gmosx $
6
+
7
+ require "glue/property"
8
+ require "glue/array"
9
+ require "glue/hash"
10
+ require "glue/time"
11
+ require "glue/pool"
12
+
13
+ require "og/meta"
14
+
15
+ # = Og
16
+ #
17
+ # Og (ObjectGraph) is an efficient, yet simple Object-Relational
18
+ # mapping library.
19
+ #
20
+ # == Features
21
+ #
22
+ # The library provides the following features:
23
+ #
24
+ # + Object-Relational mapping.
25
+ # + Absolutely no configuration files.
26
+ # + Multiple backends (PostgreSQL, MySQL).
27
+ # + ActiveRecord-style meta language and db aware methods.
28
+ # + Deserialize to Ruby Objects or ResultSets.
29
+ # + Deserialize sql join queries to Ruby Objects.
30
+ # + Serialize arbitrary ruby object graphs through YAML.
31
+ # + Connection pooling.
32
+ # + Thread safety.
33
+ # + SQL transactions.
34
+ # + Lifecycle callbacks.
35
+ # + Transparent support for cascading deletes for all backends.
36
+ # + Hierarchical structures (preorder traversal, materialized paths)
37
+ # + Works safely as part of distributed application.
38
+ # + Simple implementation < 2k lines of code.
39
+ #
40
+ # === Meta language
41
+ #
42
+ # primary_key :pid
43
+ # prop_accessor Fixnum, :pid, :sql => "smallint DEFAULT 1"
44
+ # has_many Child, :children
45
+ # many_to_many Role, :roles
46
+ # sql_index :pid
47
+ #
48
+ # === Design
49
+ #
50
+ # Keep the main classes backend agnostic.
51
+ #
52
+ # Try to make the methods work with oids. Do NOT implement descendants
53
+ # use a root id (rid).
54
+ #
55
+ # For class ids we use the name instead of a hash. Class ids are
56
+ # typically not used in querys, they are stored for completeness.
57
+ # If we store a hash we cannot reclaim the class thus invalidating
58
+ # the point. Instead of .name(), to_s() is used so the methods
59
+ # are more flexible (they accept class names too!!)
60
+ #
61
+ # Og allows the serialization of arbitrary Ruby objects. Just
62
+ # mark them as Object (or Array or Hash) in the prop_accessor
63
+ # and the engine will serialize a YAML dump of the object.
64
+ # Arbitrary object graphs are supported too.
65
+ #
66
+ # This is NOT a singleton, an application may access multiple
67
+ # databases.
68
+ #
69
+ # The $og.xxx methods are more flexible and allow you to use
70
+ # multiple databases for example.
71
+ #
72
+ # === Managed Objects Lifecycle Callbacks
73
+ #
74
+ # * og_pre_insert
75
+ # * og_post_insert
76
+ # * og_pre_update
77
+ # * og_post_update
78
+ # * og_pre_insert_update
79
+ # * og_post_insert_update
80
+ # * self.og_pre_delete
81
+ # * validate
82
+ #
83
+ # A class level callback is used for delete because typically you call
84
+ # delete with an oid and not an object to avoid a deserialization.
85
+ #
86
+ # === Future
87
+ #
88
+ # * Support prepared statements (pgsql)
89
+ # * Support stored procedures (pgsql)
90
+ # * Support caching.
91
+ # * Deserialize to OpenStruct.
92
+ # * Better documentation.
93
+ #
94
+ module Og
95
+
96
+ # If true, only allow reading from the database. Usefull
97
+ # for maintainance.
98
+ #
99
+ $og_read_only_mode = false
100
+
101
+ # If true, the library automatically 'enchants' managed classes.
102
+ # In enchant mode, special db aware methods are added to
103
+ # managed classes and instances.
104
+ #
105
+ $og_enchant_managed_classes = true
106
+
107
+ # If true, use Ruby's advanced introspection capabilities to
108
+ # automatically manage classes tha define properties.
109
+ $og_auto_manage_classes = true
110
+
111
+ # = Unmanageable
112
+ #
113
+ # Marker module. If included this in a class, the Og automanager
114
+ # ignores this class.
115
+ #
116
+ module Unmanageable; end
117
+
118
+ # = Database
119
+ #
120
+ # Encapsulates an Og Database.
121
+ #
122
+ class Database
123
+
124
+ # Managed class metadata
125
+ #
126
+ class ManagedClassMeta
127
+ # The managed class.
128
+ attr_accessor :klass
129
+
130
+ # A mapping of the database fields to the object properties.
131
+ attr_accessor :field_index
132
+
133
+ def initialize(klass = nil)
134
+ @klass = klass
135
+ @field_index = {}
136
+ end
137
+ end
138
+
139
+ # hash of configuration options.
140
+ attr_accessor :config
141
+
142
+ # Pool of connections to the backend.
143
+ attr_accessor :connection_pool
144
+
145
+ # Managed classes.
146
+ attr_accessor :managed_classes
147
+
148
+ # Initialize the database interface.
149
+ #
150
+ def initialize(config)
151
+ @config = config
152
+
153
+ # populate with default options if needed.
154
+ @config[:connection_count] ||= 1
155
+
156
+ # require the backend.
157
+ backend = @config[:backend] || "psql"
158
+ require "og/backends/#{backend}"
159
+ eval %{ @config[:backend] = #{backend.capitalize}Backend }
160
+
161
+ @connection_pool = G::Pool.new
162
+ @managed_classes = G::SafeHash.new
163
+
164
+ $log.info "Connecting to database '#{@config[:database]}' using backend '#{backend}'."
165
+
166
+ @config[:connection_count].times do
167
+ @connection_pool << Og::Connection.new(self)
168
+ end
169
+
170
+ if $og_auto_manage_classes
171
+ # automatically manage classes with properties and metadata.
172
+ # gmosx: Any idea how to optimize this?
173
+ classes_to_manage = []
174
+ ObjectSpace.each_object(Class) do |c|
175
+ if c.respond_to?(:__props) and c.__props
176
+ classes_to_manage << c
177
+ end
178
+ end
179
+ $log.info "Og auto manages the following classes:"
180
+ $log.info "#{classes_to_manage.inspect}"
181
+ manage_classes(*classes_to_manage)
182
+ end
183
+ end
184
+
185
+ # Shutdown the database interface.
186
+ #
187
+ def shutdown
188
+ for con in @connection_pool
189
+ con.close()
190
+ end
191
+ end
192
+ alias_method :close, :shutdown
193
+
194
+ # Get a connection from the pool to access the database.
195
+ # Stores the connection in a thread-local variable.
196
+ #
197
+ def get_connection
198
+ $log.debug "Get Og connection" if $DBG
199
+
200
+ thread = Thread.current
201
+
202
+ unless conn = thread[:og_conn]
203
+ conn = @connection_pool.pop()
204
+ thread[:og_conn] = conn
205
+ end
206
+
207
+ return conn
208
+ end
209
+
210
+ # Restore an unused connection to the pool.
211
+ #
212
+ def put_connection
213
+ $log.debug "Put Og connection" if $DBG
214
+
215
+ thread = Thread.current
216
+
217
+ if conn = thread[:og_conn]
218
+ thread[:og_conn] = nil
219
+ return @connection_pool.push(conn)
220
+ end
221
+ end
222
+
223
+ # Utility method, automatically restores a connection to the pool.
224
+ #
225
+ def connect(deserialize = nil, &block)
226
+ result = nil
227
+
228
+ begin
229
+ conn = get_connection()
230
+ conn.deserialize = deserialize unless deserialize.nil?
231
+
232
+ result = yield(conn)
233
+
234
+ conn.deserialize = true
235
+ ensure
236
+ put_connection()
237
+ end
238
+
239
+ return result
240
+ end
241
+ alias_method :open, :connect
242
+
243
+
244
+ # Register a standard Ruby class as managed.
245
+ #
246
+ def manage(klass)
247
+ return if managed?(klass) or klass.ancestors.include?(Og::Unmanageable)
248
+
249
+ @managed_classes[klass] = ManagedClassMeta.new(klass)
250
+
251
+ # Add standard og methods to the class.
252
+ convert(klass)
253
+
254
+ # Add helper methods to the class.
255
+ enchant(klass) if $og_enchant_managed_classes
256
+ end
257
+
258
+ # Helper method to set multiple managed classes.
259
+ #
260
+ def manage_classes(*klasses)
261
+ for klass in klasses
262
+ manage(klass)
263
+ end
264
+ end
265
+
266
+ # Stop managing a Ruby class
267
+ #
268
+ def unmanage(klass)
269
+ @managed_classes.delete(klass)
270
+ end
271
+
272
+ # Is this class managed?
273
+ #
274
+ def managed?(klass)
275
+ return @managed_classes.include?(klass)
276
+ end
277
+
278
+ # Add standard og functionality to the class
279
+ #
280
+ def convert(klass)
281
+ # gmosx: this check is needed to allow the developer to customize
282
+ # the sql generated for oid
283
+ Og::Utils.eval_og_oid(klass) unless klass.instance_methods.include?(:oid)
284
+
285
+ klass.class_eval %{
286
+ inherit_meta(superclass) if superclass
287
+
288
+ DBTABLE = "#{Og::Utils.table(klass)}"
289
+ DBSEQ = "#{Og::Utils.table(klass)}_oids_seq"
290
+
291
+ def to_i()
292
+ @oid
293
+ end
294
+ }
295
+
296
+ # Create the schema for this class if not available.
297
+ create_table(klass)
298
+
299
+ # Precompile some code that gets executed all the time.
300
+ # Deletion code is not precompiled, because it is not used
301
+ # as frequently.
302
+ Og::Utils.eval_og_insert(klass)
303
+ Og::Utils.eval_og_update(klass)
304
+ Og::Utils.eval_og_deserialize(klass, self)
305
+ end
306
+
307
+ # Enchant a managed class. Add useful DB related methods to the
308
+ # class and its instances.
309
+ #
310
+ def enchant(klass)
311
+ klass.class_eval %{
312
+ def self.save(obj)
313
+ $og << obj
314
+ end
315
+
316
+ def self.load(oid_or_name)
317
+ $og.load(oid_or_name, #{klass})
318
+ end
319
+
320
+ def self.[](oid_or_name)
321
+ $og.load(oid_or_name, #{klass})
322
+ end
323
+
324
+ def self.load_all(extra_sql = nil)
325
+ $og.load_all(#{klass}, extra_sql)
326
+ end
327
+
328
+ def self.all(extra_sql = nil)
329
+ $og.load_all(#{klass}, extra_sql)
330
+ end
331
+
332
+ def self.select(sql)
333
+ $og.select(sql, #{klass})
334
+ end
335
+
336
+ def self.select_one(sql)
337
+ $og.select_one(sql, #{klass})
338
+ end
339
+
340
+ def self.delete(obj_or_oid)
341
+ $og.delete(obj_or_oid, #{klass})
342
+ end
343
+
344
+ def save
345
+ $og << self
346
+ end
347
+ alias_method :save!, :save
348
+
349
+ def update_properties(updatesql)
350
+ $og.pupdate(updatesql, self.oid, #{klass})
351
+ end
352
+ alias_method :pupdate!, :update_properties
353
+
354
+ def delete!
355
+ $og.delete(@oid, #{klass})
356
+ end
357
+ }
358
+ end
359
+
360
+ # Automatically wrap connection methods.
361
+ #
362
+ def self.wrap_method(method, args)
363
+ args = args.split(/,/)
364
+ class_eval %{
365
+ def #{method}(#{args.join(", ")})
366
+ thread = Thread.current
367
+
368
+ unless conn = thread[:og_conn]
369
+ conn = @connection_pool.pop()
370
+ thread[:og_conn] = conn
371
+ end
372
+
373
+ return conn.#{method}(#{args.collect {|a| a.split(/=/)[0]}.join(", ")})
374
+ end
375
+ }
376
+ end
377
+
378
+ wrap_method :create_table, "klass"
379
+ wrap_method :drop_table, "klass"
380
+ wrap_method :save, "obj"; alias_method :<<, :save; alias_method :put, :save
381
+ wrap_method :insert, "obj"
382
+ wrap_method :update, "obj"
383
+ wrap_method :update_properties, "update_sql, obj_or_oid, klass = nil"
384
+ wrap_method :pupdate, "update_sql, obj_or_oid, klass = nil"
385
+ wrap_method :load, "oid, klass"; alias_method :get, :load
386
+ wrap_method :load_by_oid, "oid, klass"
387
+ wrap_method :load_by_name, "name, klass"
388
+ wrap_method :load_all, "klass, extrasql = nil"
389
+ wrap_method :select, "sql, klass"
390
+ wrap_method :select_one, "sql, klass"
391
+ wrap_method :count, "sql, klass = nil"
392
+ wrap_method :delete, "obj_or_oid, klass = nil"
393
+ wrap_method :query, "sql"
394
+ wrap_method :exec, "sql"
395
+
396
+ def self.create_db!(config)
397
+ get_connection().db.create_db(config[:database], config[:user],
398
+ config[:password])
399
+ end
400
+
401
+ def self.drop_db!(config)
402
+ backend = config[:backend] || "psql"
403
+ require "og/backends/#{backend}"
404
+ eval %{
405
+ #{backend.capitalize}Backend.drop_db(config[:database], config[:user],
406
+ config[:password])
407
+ }
408
+ end
409
+ end
410
+
411
+ end # module