glue 0.0.1 → 0.13.0

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