og 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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