glue 0.0.1 → 0.13.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,162 @@
1
+ # * George Moschovitis <gm@navel.gr>
2
+ # * Anastasios Koutoumanos <ak@navel.gr>
3
+ # * Elias Karakoulakis <ekarak@ktismata.com>
4
+ # (c) 2004-2005 Navel, all rights reserved.
5
+ # $Id: string.rb 282 2005-03-10 12:24:53Z gmosx $
6
+
7
+ require "uri"
8
+
9
+ module N;
10
+
11
+ # General string utilities collection.
12
+ #
13
+ # === Design:
14
+ #
15
+ # Implement as a module to avoid class polution. You can
16
+ # still Ruby's advanced features to include the module in your
17
+ # class. Passing the object to act upon allows to check for nil,
18
+ # which isn't possible if you use self.
19
+ #
20
+ # === TODO:
21
+ #
22
+ # - implement a method that returns easy to remember
23
+ # pseudo-random strings
24
+ # - add aliases for those methods in Kernel.
25
+
26
+ module StringUtils
27
+
28
+ # Move this in String class?
29
+ #
30
+ # Tests a string for a valid value (non nil, not empty)
31
+ #
32
+ def self.valid?(string)
33
+ return (not ((nil == string) or (string.empty?)))
34
+ end
35
+
36
+ # returns short abstract of long strings (first 'count'
37
+ # characters, chopped at the nearest word, appended by '...')
38
+ # force_cutoff: break forcibly at 'count' chars. Does not accept
39
+ # count < 2.
40
+
41
+ def self.head(string, count = 128, force_cutoff = false, ellipsis="...")
42
+ return nil unless string
43
+ return nil if count < 2
44
+
45
+ if string.size > count
46
+ cut_at = force_cutoff ? count : (string.index(' ', count-1) || count)
47
+ xstring = string.slice(0, cut_at)
48
+ return xstring.chomp(" ") + ellipsis
49
+ else
50
+ return string
51
+ end
52
+ end
53
+
54
+ # Apply a set of rules (regular expression matches) to the
55
+ # string
56
+ #
57
+ # === Requirements:
58
+ # - the rules must be applied in order! So we cannot use a
59
+ # hash because the ordering is not guaranteed! we use an
60
+ # array instead.
61
+ #
62
+ # === Input:
63
+ # the string to rewrite
64
+ # the array containing rule-pairs (match, rewrite)
65
+ #
66
+ # === Output:
67
+ # the rewritten string
68
+
69
+ MATCH = 0
70
+ REWRITE = 1
71
+
72
+ def self.rewrite(string, rules)
73
+ return nil unless string
74
+
75
+ # gmosx: helps to find bugs
76
+ raise ArgumentError.new('The rules parameter is nil') unless rules
77
+
78
+ rewritten_string = string.dup
79
+
80
+ for rule in rules
81
+ rewritten_string.gsub!(rule[MATCH], rule[REWRITE])
82
+ end
83
+
84
+ return (rewritten_string or string)
85
+ end
86
+
87
+ # Enforces a maximum width of a string inside an
88
+ # html container. If the string exceeds this maximum width
89
+ # the string gets wraped.
90
+ #
91
+ # Not really useful, better use the CSS overflow: hidden
92
+ # functionality.
93
+ #
94
+ # === Input:
95
+ # the string to be wrapped
96
+ # the enforced width
97
+ # the separator used for wrapping
98
+ #
99
+ # === Output:
100
+ # the wrapped string
101
+ #
102
+ # === Example:
103
+ # text = "1111111111111111111111111111111111111111111"
104
+ # text = wrap(text, 10, " ")
105
+ # p text # => "1111111111 1111111111 1111111111"
106
+ #
107
+ # See the test cases to better understand the behaviour!
108
+
109
+ def self.wrap(string, width = 20, separator = " ")
110
+ return nil unless string
111
+
112
+ re = /([^#{separator}]{1,#{width}})/
113
+ wrapped_string = string.scan(re).join(separator)
114
+
115
+ return wrapped_string
116
+ end
117
+
118
+ # Replace dangerours chars in filenames
119
+ =begin
120
+ def self.rationalize_filename(filename)
121
+ return nil unless filename
122
+ # gmosx: rationalize a copy!!! (add unit test)
123
+ xfilename = filename.dup()
124
+ # gmosx: replace some dangerous chars!
125
+ xfilename.gsub!(/ /, "-")
126
+ xfilename.gsub!(/!/, "")
127
+ xfilename.gsub!(/'/, "")
128
+ xfilename.gsub!(/\(/, "")
129
+ xfilename.gsub!(/\)/, "")
130
+ # xfilename = self.to_greeklish(xfilename)
131
+ return xfilename
132
+ end
133
+ =end
134
+
135
+ # Returns a random string. one possible use is
136
+ # password initialization.
137
+ #
138
+ # === Input:
139
+ # the maximum length of the string
140
+ #
141
+ # === Output:
142
+ # the random string
143
+
144
+ def self.random(max_length = 8, char_re = /[\w\d]/)
145
+ # gmosx: this is a nice example of input parameter checking.
146
+ # this is NOT a real time called method so we can add this
147
+ # check. Congrats to the author.
148
+ raise ArgumentError.new('char_re must be a regular expression!') unless char_re.is_a?(Regexp)
149
+
150
+ string = ""
151
+
152
+ while string.length < max_length
153
+ ch = rand(255).chr
154
+ string << ch if ch =~ char_re
155
+ end
156
+
157
+ return string
158
+ end
159
+
160
+ end
161
+
162
+ end
data/lib/glue/time.rb ADDED
@@ -0,0 +1,85 @@
1
+ # * George Moschovitis <gm@navel.gr>
2
+ # (c) 2004-2005 Navel, all rights reserved.
3
+ # $Id: time.rb 282 2005-03-10 12:24:53Z gmosx $
4
+
5
+ require 'time.rb'
6
+
7
+ module N;
8
+
9
+ # General time utilities collection
10
+ #
11
+ # Implement as a module to avoid class polution. You can
12
+ # still Ruby's advanced features to include the module in your
13
+ # class. Passing the object to act upon allows to check for nil,
14
+ # which isn't possible if you use self.
15
+ #
16
+ # == TODO
17
+ #
18
+ # - SOS: add test units.
19
+ # - add aliases for those methods in Kernel ?
20
+
21
+ module TimeUtils
22
+
23
+ NOW = Time.now
24
+ NEVER = Time.mktime(2038)
25
+ ZERO = Time.mktime(1972)
26
+
27
+ # Convert the time to a nice String representation.
28
+
29
+ def self.date_time(time)
30
+ return nil unless time
31
+ return time.strftime("%d-%m-%Y %H:%M")
32
+ end
33
+
34
+ # This method calculates the days extrema given two time objects.
35
+ # start time is the given time1 at 00:00:00
36
+ # end time is the given time2 at 23:59:59:999
37
+ #
38
+ # Input:
39
+ # - the two times (if only time1 is provided then you get an extrema
40
+ # of exactly one day extrema.
41
+ #
42
+ # Output
43
+ # - the time range. you can get the start/end times using
44
+ # range methods.
45
+
46
+ def self.days_extrema(time1, time2=nil)
47
+ time2 = time1 if (not time2.valid? Time)
48
+ time2 = NEVER if (time2 <= time1)
49
+ start_time = Time.self.start_of_day(time1)
50
+ end_time = self.end_of_day(time2)
51
+ return (start_time..end_time)
52
+ end
53
+
54
+ # Set time to start of day
55
+
56
+ def self.start_of_day(time)
57
+ return Time.mktime(time.year, time.month, time.day, 0, 0, 0, 0)
58
+ end
59
+
60
+
61
+ # Set time to end of day
62
+
63
+ def self.end_of_day(time)
64
+ return Time.mktime(time.year, time.month, time.day, 23, 59, 59, 999)
65
+ end
66
+
67
+
68
+ # Returns true only if day of time is included in the
69
+ # range (stime..etime). Only year days are checked.
70
+
71
+ def self.time_in_day_range(time, stime=ZERO, etime=NEVER)
72
+ if (etime <= stime)
73
+ Logger.debug "Invalid end time (#{etime} < #{stime})" if $DBG
74
+ etime = NEVER
75
+ end
76
+
77
+ stime = start_of_day(stime)
78
+ etime = end_of_day(etime)
79
+
80
+ return (stime..etime).include?(time)
81
+ end
82
+
83
+ end
84
+
85
+ end
@@ -0,0 +1,461 @@
1
+ # * George Moschovitis <gm@navel.gr>
2
+ # (c) 2004-2005 Navel, all rights reserved.
3
+ # $Id$
4
+
5
+ module N
6
+
7
+ # Implements a meta-language for validating managed
8
+ # objects. Typically used in Validator objects but can be
9
+ # included in managed objects too.
10
+ #
11
+ # The following validation macros are available:
12
+ #
13
+ # * validate_value
14
+ # * validate_confirmation
15
+ # * validate_format
16
+ # * validate_length
17
+ # * validate_inclusion
18
+ #
19
+ # Og/Database specific validation methods are added in the
20
+ # file og/validation.rb
21
+ #
22
+ # === Example
23
+ #
24
+ # class User
25
+ # prop_accessor :name, String
26
+ # prop_accessor :level, Fixnum
27
+ #
28
+ # validate_length :name, :range => 2..6
29
+ # validate_unique :name, :msg => :name_allready_exists
30
+ # validate_format :name, :format => /[a-z]*/, :msg => 'invalid format', :on => :create
31
+ # end
32
+ #
33
+ # class N::CustomUserValidator
34
+ # include N::Validation
35
+ # validate_length :name, :range => 2..6, :msg_short => :name_too_short, :msg_long => :name_too_long
36
+ # end
37
+ #
38
+ # user = @request.fill(User.new)
39
+ # user.level = 15
40
+ #
41
+ # unless user.valid?
42
+ # user.save
43
+ # else
44
+ # p user.errors[:name]
45
+ # end
46
+ #
47
+ # unless user.save
48
+ # p user.errors.on(:name)
49
+ # end
50
+ #
51
+ # unless errors = N::CustomUserValidator.errors(user)
52
+ # user.save
53
+ # else
54
+ # p errors[:name]
55
+ # end
56
+ #
57
+ #--
58
+ # TODO: all validation methods should imply validate_value
59
+ # TODO: add validate_unique
60
+ #++
61
+
62
+ module Validation
63
+
64
+ # Encapsulates a list of validation errors.
65
+
66
+ class Errors
67
+ attr_accessor :errors
68
+
69
+ cattr_accessor :no_value, 'No value provided'
70
+ cattr_accessor :no_confirmation, 'Invalid confirmation'
71
+ cattr_accessor :invalid_format, 'Invalid format'
72
+ cattr_accessor :too_short, 'Too short, must be more than %d characters long'
73
+ cattr_accessor :too_long, 'Too long, must be less than %d characters long'
74
+ cattr_accessor :invalid_length, 'Must be %d characters long'
75
+ cattr_accessor :no_inclusion, 'The value is invalid'
76
+ cattr_accessor :no_numeric, 'The value must be numeric'
77
+ cattr_accessor :not_unique, 'The value is already used'
78
+
79
+ def initialize
80
+ @errors = {}
81
+ end
82
+
83
+ def add(attr, message)
84
+ (@errors[attr] ||= []) << message
85
+ end
86
+
87
+ def on(attr)
88
+ @errors[attr]
89
+ end
90
+ alias_method :[], :on
91
+
92
+ # Yields each attribute and associated message.
93
+
94
+ def each
95
+ @errors.each_key do |attr|
96
+ @errors[attr].each { |msg| yield attr, msg }
97
+ end
98
+ end
99
+
100
+ def size
101
+ @errors.size
102
+ end
103
+ alias_method :count, :size
104
+
105
+ def empty?
106
+ @errors.empty?
107
+ end
108
+
109
+ def clear
110
+ @errors.clear
111
+ end
112
+ end
113
+
114
+ # If the validate method returns true, this
115
+ # attributes holds the errors found.
116
+
117
+ attr_accessor :errors
118
+
119
+ # Call the #validate method for this object.
120
+ # If validation errors are found, sets the
121
+ # @errors attribute to the Errors object and
122
+ # returns true.
123
+
124
+ def valid?
125
+ begin
126
+ @errors = self.class.validate(self)
127
+ return @errors.empty?
128
+ rescue NoMethodError => e
129
+ # gmosx: hmm this is potentially dangerous.
130
+ N::Validation.eval_validate(self.class)
131
+ retry
132
+ end
133
+ end
134
+
135
+ # Evaluate the 'validate' method for the calling
136
+ # class.
137
+ #
138
+ # WARNING: for the moment only evaluates for
139
+ # on == :save
140
+
141
+ def self.eval_validate(klass)
142
+ code = %{
143
+ def self.validate(obj, on = :save)
144
+ errors = Errors.new
145
+ }
146
+
147
+ for val, on in klass.__meta[:validations]
148
+ code << %{
149
+ #{val}
150
+ }
151
+ end
152
+
153
+ code << %{
154
+ return errors
155
+ end
156
+ }
157
+
158
+ klass.module_eval(code)
159
+ end
160
+
161
+ def self.append_features(base)
162
+ super
163
+
164
+ base.module_eval <<-"end_eval", __FILE__, __LINE__
165
+ meta :validations, []
166
+ end_eval
167
+
168
+ base.extend(MetaLanguage)
169
+ end
170
+
171
+ # Implements the Validation meta-language.
172
+
173
+ module MetaLanguage
174
+
175
+ # the postfix attached to confirmation attributes.
176
+
177
+ mattr_accessor :confirmation_postfix, '_confirmation'
178
+
179
+ # Validates that the attributes have a values, ie they are
180
+ # neither nil or empty.
181
+ #
182
+ # === Example
183
+ #
184
+ # validate_value :param, :msg => 'No confirmation'
185
+
186
+ def validate_value(*params)
187
+ c = {
188
+ :msg => N::Validation::Errors.no_value,
189
+ :on => :save
190
+ }
191
+ c.update(params.pop) if params.last.is_a?(Hash)
192
+
193
+ idx = 0
194
+ for name in params
195
+ code = %{
196
+ if obj.#{name}.nil?
197
+ errors.add(:#{name}, '#{c[:msg]}')
198
+ elsif obj.#{name}.respond_to?(:empty?)
199
+ errors.add(:#{name}, '#{c[:msg]}') if obj.#{name}.empty?
200
+ end
201
+ }
202
+
203
+ __meta[:validations] << [code, c[:on]]
204
+ end
205
+ end
206
+
207
+ # Validates the confirmation of +String+ attributes.
208
+ #
209
+ # === Example
210
+ #
211
+ # validate_confirmation :password, :msg => 'No confirmation'
212
+
213
+ def validate_confirmation(*params)
214
+ c = {
215
+ :msg => N::Validation::Errors.no_confirmation,
216
+ :postfix => N::Validation::MetaLanguage.confirmation_postfix,
217
+ :on => :save
218
+ }
219
+ c.update(params.pop) if params.last.is_a?(Hash)
220
+
221
+
222
+ for name in params
223
+ confirm_name = "#{name}#{c[:postfix]}"
224
+ eval "attr_accessor :#{confirm_name}"
225
+
226
+ code = %{
227
+ if obj.#{confirm_name}.nil? or (obj.#{confirm_name} != obj.#{name})
228
+ errors.add(:#{name}, '#{c[:msg]}')
229
+ end
230
+ }
231
+
232
+ __meta[:validations] << [code, c[:on]]
233
+ end
234
+ end
235
+
236
+ # Validates the format of +String+ attributes.
237
+ # WARNING: regexp options are ignored.
238
+ #
239
+ # === Example
240
+ #
241
+ # validate_format :name, :format => /$A*/, :msg => 'My error', :on => :create
242
+ #
243
+ #--
244
+ # FIXME: how to get the Regexp options?
245
+ #++
246
+
247
+ def validate_format(*params)
248
+ c = {
249
+ :format => nil,
250
+ :msg_no_value => N::Validation::Errors.no_value,
251
+ :msg => N::Validation::Errors.invalid_format,
252
+ :on => :save
253
+ }
254
+ c.update(params.pop) if params.last.is_a?(Hash)
255
+
256
+ unless c[:format].is_a?(Regexp)
257
+ raise(ArgumentError,
258
+ 'A regular expression must be supplied as the :format option')
259
+ end
260
+
261
+ for name in params
262
+ code = %{
263
+ if obj.#{name}.nil?
264
+ errors.add(:#{name}, '#{c[:msg_no_value]}')
265
+ else
266
+ unless obj.#{name}.to_s.match(/#{c[:format].source}/)
267
+ errors.add(:#{name}, '#{c[:msg]}')
268
+ end
269
+ end;
270
+ }
271
+
272
+ __meta[:validations] << [code, c[:on]]
273
+ end
274
+ end
275
+
276
+ # Validates the length of +String+ attributes.
277
+ #
278
+ # === Example
279
+ #
280
+ # validate_length :name, :max => 30, :msg => 'Too long'
281
+ # validate_length :name, :min => 2, :msg => 'Too sort'
282
+ # validate_length :name, :range => 2..30
283
+ # validate_length :name, :length => 15, :msg => 'Name should be %d chars long'
284
+
285
+ def validate_length(*params)
286
+ c = {
287
+ :min => nil, :max => nil, :range => nil, :length => nil,
288
+ :msg => nil,
289
+ :msg_no_value => N::Validation::Errors.no_value,
290
+ :msg_short => N::Validation::Errors.too_short,
291
+ :msg_long => N::Validation::Errors.too_long,
292
+ :on => :save
293
+ }
294
+ c.update(params.pop) if params.last.is_a?(Hash)
295
+
296
+ min, max = c[:min], c[:max]
297
+ range, length = c[:range], c[:length]
298
+
299
+ count = 0
300
+ [min, max, range, length].each { |o| count += 1 if o }
301
+
302
+ if count == 0
303
+ raise(ArgumentError,
304
+ 'One of :min, :max, :range, :length must be provided!')
305
+ end
306
+
307
+ if count > 1
308
+ raise(ArgumentError,
309
+ 'The :min, :max, :range, :length options are mutually exclusive!')
310
+ end
311
+
312
+ for name in params
313
+ if min
314
+ c[:msg] ||= N::Validation::Errors.too_short
315
+ code = %{
316
+ if obj.#{name}.nil?
317
+ errors.add(:#{name}, '#{c[:msg_no_value]}')
318
+ elsif obj.#{name}.to_s.length < #{min}
319
+ msg = '#{c[:msg]}'
320
+ msg = (msg % #{min}) rescue msg
321
+ errors.add(:#{name}, msg)
322
+ end;
323
+ }
324
+ elsif max
325
+ c[:msg] ||= N::Validation::Errors.too_long
326
+ code = %{
327
+ if obj.#{name}.nil?
328
+ errors.add(:#{name}, '#{c[:msg_no_value]}')
329
+ elsif obj.#{name}.to_s.length > #{max}
330
+ msg = '#{c[:msg]}'
331
+ msg = (msg % #{max}) rescue msg
332
+ errors.add(:#{name}, msg)
333
+ end;
334
+ }
335
+ elsif range
336
+ code = %{
337
+ if obj.#{name}.nil?
338
+ errors.add(:#{name}, '#{c[:msg_no_value]}')
339
+ elsif obj.#{name}.to_s.length < #{range.first}
340
+ msg = '#{c[:msg_short]}'
341
+ msg = (msg % #{range.first}) rescue msg
342
+ errors.add(:#{name}, msg)
343
+ elsif obj.#{name}.to_s.length > #{range.last}
344
+ msg = '#{c[:msg_long]}'
345
+ msg = (msg % #{range.last}) rescue msg
346
+ errors.add(:#{name}, msg)
347
+ end;
348
+ }
349
+ elsif length
350
+ c[:msg] ||= N::Validation::Errors.invalid_length
351
+ code = %{
352
+ if obj.#{name}.nil?
353
+ errors.add(:#{name}, '#{c[:msg_no_value]}')
354
+ elsif obj.#{name}.to_s.length != #{length}
355
+ msg = '#{c[:msg]}'
356
+ msg = (msg % #{length}) rescue msg
357
+ errors.add(:#{name}, msg)
358
+ end;
359
+ }
360
+ end
361
+
362
+ __meta[:validations] << [code, c[:on]]
363
+ end
364
+ end
365
+
366
+ # Validates that the attributes are included in
367
+ # an enumeration.
368
+ #
369
+ # === Example
370
+ #
371
+ # validate_inclusion :sex, :in => %w{ Male Female }, :msg => 'huh??'
372
+ # validate_inclusion :age, :in => 5..99
373
+
374
+ def validate_inclusion(*params)
375
+ c = {
376
+ :in => nil,
377
+ :msg => N::Validation::Errors.no_inclusion,
378
+ :allow_nil => false,
379
+ :on => :save
380
+ }
381
+ c.update(params.pop) if params.last.is_a?(Hash)
382
+
383
+ unless c[:in].respond_to?('include?')
384
+ raise(ArgumentError,
385
+ 'An object that responds to #include? must be supplied as the :in option')
386
+ end
387
+
388
+ for name in params
389
+ if c[:allow_nil]
390
+ code = %{
391
+ unless obj.#{name}.nil? or (#{c[:in].inspect}).include?(obj.#{name})
392
+ errors.add(:#{name}, '#{c[:msg]}')
393
+ end;
394
+ }
395
+ else
396
+ code = %{
397
+ unless (#{c[:in].inspect}).include?(obj.#{name})
398
+ errors.add(:#{name}, '#{c[:msg]}')
399
+ end;
400
+ }
401
+ end
402
+
403
+ __meta[:validations] << [code, c[:on]]
404
+ end
405
+ end
406
+
407
+ # Validates that the attributes have numeric values.
408
+ #
409
+ # [+:integer+]
410
+ # Only accept integers
411
+ #
412
+ # [+:msg]
413
+ # Custom message
414
+ #
415
+ # [+:on:]
416
+ # When to validate
417
+ #
418
+ # === Example
419
+ #
420
+ # validate_numeric :age, :msg => 'Must be an integer'
421
+
422
+ def validate_numeric(*params)
423
+ c = {
424
+ :integer => false,
425
+ :msg => N::Validation::Errors.no_numeric,
426
+ :on => :save
427
+ }
428
+ c.update(params.pop) if params.last.is_a?(Hash)
429
+
430
+ for name in params
431
+ if c[:integer]
432
+ code = %{
433
+ unless obj.#{name}.to_s =~ /^[\\+\\-]?\\d+$/
434
+ errors.add(:#{name}, '#{c[:msg]}')
435
+ end;
436
+ }
437
+ else
438
+ code = %{
439
+ begin
440
+ Kernel.Float(obj.#{name})
441
+ rescue ArgumentError, TypeError
442
+ errors.add(:#{name}, '#{c[:msg]}')
443
+ end;
444
+ }
445
+ end
446
+
447
+ __meta[:validations] << [code, c[:on]]
448
+ end
449
+ end
450
+ alias_method :validate_numericality, :validate_numeric
451
+
452
+ end
453
+
454
+ end
455
+
456
+ end
457
+
458
+ class Module # :nodoc: all
459
+ include N::Validation::MetaLanguage
460
+ end
461
+