vex 0.2
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.
- data/Manifest +112 -0
- data/Rakefile +53 -0
- data/VERSION +1 -0
- data/config/README +2 -0
- data/config/dependencies.rb +10 -0
- data/config/gem.yml +7 -0
- data/init.rb +36 -0
- data/lib/nokogiri/nokogiri_ext.rb +96 -0
- data/lib/vex.rb +5 -0
- data/lib/vex/action_controller.rb +4 -0
- data/lib/vex/action_controller/verify_action.rb +97 -0
- data/lib/vex/action_controller/whitelisted_actions.rb +45 -0
- data/lib/vex/active_record.rb +3 -0
- data/lib/vex/active_record/__init__.rb +0 -0
- data/lib/vex/active_record/advisory_lock.rb +11 -0
- data/lib/vex/active_record/advisory_lock/mysql_adapter.rb +16 -0
- data/lib/vex/active_record/advisory_lock/sqlite_adapter.rb +78 -0
- data/lib/vex/active_record/belongs_to_many.rb +143 -0
- data/lib/vex/active_record/find_by_extension.rb +70 -0
- data/lib/vex/active_record/gem.rb +8 -0
- data/lib/vex/active_record/lite_table.rb +139 -0
- data/lib/vex/active_record/lite_view.rb +140 -0
- data/lib/vex/active_record/mass_load.rb +65 -0
- data/lib/vex/active_record/mysql_backup.rb +21 -0
- data/lib/vex/active_record/plugins/default_value_for/LICENSE.TXT +19 -0
- data/lib/vex/active_record/plugins/default_value_for/README.rdoc +421 -0
- data/lib/vex/active_record/plugins/default_value_for/Rakefile +6 -0
- data/lib/vex/active_record/plugins/default_value_for/init.rb +91 -0
- data/lib/vex/active_record/plugins/default_value_for/test.rb +279 -0
- data/lib/vex/active_record/plugins/default_value_for/test.sqlite3 +0 -0
- data/lib/vex/active_record/random_id.rb +56 -0
- data/lib/vex/active_record/resolver.rb +49 -0
- data/lib/vex/active_record/serialize_hash.rb +125 -0
- data/lib/vex/active_record/to_html.rb +53 -0
- data/lib/vex/active_record/validate.rb +76 -0
- data/lib/vex/active_record/validation_error_ext.rb +68 -0
- data/lib/vex/base.rb +2 -0
- data/lib/vex/base/app.rb +75 -0
- data/lib/vex/base/array/at_random.rb +17 -0
- data/lib/vex/base/array/cross.rb +26 -0
- data/lib/vex/base/array/each_batch.rb +32 -0
- data/lib/vex/base/array/parallel_map.rb +98 -0
- data/lib/vex/base/deprecation.rb +41 -0
- data/lib/vex/base/enumerable/deep.rb +95 -0
- data/lib/vex/base/enumerable/enumerable_ext.rb +59 -0
- data/lib/vex/base/enumerable/progress.rb +71 -0
- data/lib/vex/base/filesystem/fast_copy.rb +61 -0
- data/lib/vex/base/filesystem/grep.rb +34 -0
- data/lib/vex/base/filesystem/lock.rb +43 -0
- data/lib/vex/base/filesystem/lock.rb.test.lck +0 -0
- data/lib/vex/base/filesystem/lock.rb.test.pid +1 -0
- data/lib/vex/base/filesystem/make_dirs.rb +94 -0
- data/lib/vex/base/filesystem/parse_filename.rb +36 -0
- data/lib/vex/base/filesystem/tmp_file.rb +87 -0
- data/lib/vex/base/filesystem/write.rb +43 -0
- data/lib/vex/base/hash/compact.rb +38 -0
- data/lib/vex/base/hash/cross.rb +117 -0
- data/lib/vex/base/hash/easy_access.rb +141 -0
- data/lib/vex/base/hash/ensure_keys.rb +18 -0
- data/lib/vex/base/hash/extract.rb +71 -0
- data/lib/vex/base/hash/extras.rb +62 -0
- data/lib/vex/base/hash/inspect.rb +17 -0
- data/lib/vex/base/hash/simple_access_methods.rb +74 -0
- data/lib/vex/base/invalid_argument/invalid_argument.rb +97 -0
- data/lib/vex/base/local_conf.rb +35 -0
- data/lib/vex/base/net/http_ext.rb +227 -0
- data/lib/vex/base/net/socket_ext.rb +43 -0
- data/lib/vex/base/object/insp.rb +123 -0
- data/lib/vex/base/object/multiple_attributes.rb +58 -0
- data/lib/vex/base/object/singleton_methods.rb +23 -0
- data/lib/vex/base/object/with_benchmark.rb +110 -0
- data/lib/vex/base/range_array.rb +40 -0
- data/lib/vex/base/range_ext.rb +28 -0
- data/lib/vex/base/safe_token.rb +156 -0
- data/lib/vex/base/string/string_ext.rb +136 -0
- data/lib/vex/base/thread/deferred.rb +52 -0
- data/lib/vex/base/thread/sleep.rb +11 -0
- data/lib/vex/base/time/date_ext.rb +12 -0
- data/lib/vex/boot.rb +40 -0
- data/lib/vex/boot/array.rb +22 -0
- data/lib/vex/boot/blank.rb +41 -0
- data/lib/vex/boot/string.rb +60 -0
- data/migration/create_request_log.rb +28 -0
- data/r.rb +35 -0
- data/script/console +19 -0
- data/script/rebuild +7 -0
- data/tasks/echoe.rake +52 -0
- data/tasks/validate_db.rake +14 -0
- data/test/ar.rb +30 -0
- data/test/auto.rb +31 -0
- data/test/base-tests/local_conf.rb +25 -0
- data/test/base.rb +2 -0
- data/test/boot.rb +3 -0
- data/test/config/local.defaults.yml +4 -0
- data/test/config/local.yml +8 -0
- data/test/test.sqlite3 +0 -0
- data/test/test.sqlite3.Class#create.lck +0 -0
- data/test/test.sqlite3.Class#create.lck.lck +0 -0
- data/test/test.sqlite3.Class#create.lck.pid +1 -0
- data/test/test.sqlite3.Class#create.pid +1 -0
- data/test/test.sqlite3.LiteView.make.holders__view_dummy.lck +0 -0
- data/test/test.sqlite3.LiteView.make.holders__view_dummy.lck.lck +0 -0
- data/test/test.sqlite3.LiteView.make.holders__view_dummy.lck.pid +1 -0
- data/test/test.sqlite3.LiteView.make.holders__view_dummy.pid +1 -0
- data/test/test.sqlite3.vex.lck +0 -0
- data/test/test_helper.rb +49 -0
- data/test/tmp/copy.dat +1 -0
- data/test/tmp/lock.sqlite3 +0 -0
- data/test/tmp/lock.sqlite3.etest.lck +0 -0
- data/test/tmp/lock.sqlite3.etest.pid +1 -0
- data/test/tmp/somedata.dat +61 -0
- data/vex.gemspec +49 -0
- data/vex.tmproj +186 -0
- metadata +305 -0
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
class Object
|
|
2
|
+
module SingletonMethods
|
|
3
|
+
# returns the singleton class of an object
|
|
4
|
+
def singleton_class
|
|
5
|
+
class << self; self; end
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
# defines a method on an object
|
|
9
|
+
def define_object_method(name, &block)
|
|
10
|
+
singleton_class.send :define_method, name, &block
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
include SingletonMethods
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
module SingletonMethods::Etest
|
|
18
|
+
def test_singleton_methods
|
|
19
|
+
s = "s"
|
|
20
|
+
s.define_object_method :bla do "blabla" end
|
|
21
|
+
assert_equal("blabla", s.bla)
|
|
22
|
+
end
|
|
23
|
+
end if VEX_TEST == "base"
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
require "benchmark"
|
|
2
|
+
|
|
3
|
+
module Object::WithBenchmark
|
|
4
|
+
|
|
5
|
+
class BenchmarkProxy
|
|
6
|
+
def initialize(host, *args)
|
|
7
|
+
@host = host
|
|
8
|
+
|
|
9
|
+
if args.length == 1 && args.first.is_a?(String)
|
|
10
|
+
@out, @label = nil, args.first
|
|
11
|
+
else
|
|
12
|
+
@out, @label = *args
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
private
|
|
17
|
+
|
|
18
|
+
def report(msg)
|
|
19
|
+
if !@out
|
|
20
|
+
logger = @host.respond_to?(:logger) && @host.logger || App.logger
|
|
21
|
+
|
|
22
|
+
logger.warn(msg)
|
|
23
|
+
STDERR.puts(msg) if App.env == "development"
|
|
24
|
+
elsif @out.respond_to?(:warn)
|
|
25
|
+
@out.warn(msg)
|
|
26
|
+
elsif @out.respond_to?(:<<)
|
|
27
|
+
@out << "#{msg}\n"
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def method_missing(*args, &block)
|
|
32
|
+
result = ex = nil
|
|
33
|
+
|
|
34
|
+
realtime = Benchmark.realtime do
|
|
35
|
+
begin
|
|
36
|
+
result = @host.__send__(*args, &block)
|
|
37
|
+
rescue
|
|
38
|
+
ex = $!
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
msg = "#{@label || "benchmarked"}: #{ex && "EXCEPTION #{ex} after "}#{"%.2f secs" % realtime}"
|
|
43
|
+
|
|
44
|
+
report(msg)
|
|
45
|
+
|
|
46
|
+
raise ex if ex
|
|
47
|
+
result
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def no_benchmark(*args, &block)
|
|
52
|
+
block_given? ? yield : self
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def benchmark(*args, &block)
|
|
56
|
+
count = args.last.is_a?(Fixnum) ? args.pop : 1
|
|
57
|
+
|
|
58
|
+
proxy = BenchmarkProxy.new self, *args, &block
|
|
59
|
+
return proxy unless block_given?
|
|
60
|
+
return proxy.yield(&block) if count <= 1
|
|
61
|
+
return proxy.yield do
|
|
62
|
+
count.times(&block)
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def yield(&block); yield; end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
Object.send :include, Object::WithBenchmark
|
|
70
|
+
|
|
71
|
+
module Object::WithBenchmark::Etest
|
|
72
|
+
def test_results
|
|
73
|
+
App.logger.stubs(:warn).returns(nil)
|
|
74
|
+
|
|
75
|
+
assert_equal 6, "string".benchmark.length
|
|
76
|
+
assert_raise(NoMethodError) {
|
|
77
|
+
"string".benchmark.you_dont_know_me
|
|
78
|
+
}
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def test_benchmark
|
|
82
|
+
s = ""
|
|
83
|
+
assert_equal 6, "string".benchmark(s, "").length
|
|
84
|
+
assert s.length > 0
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def test_label
|
|
88
|
+
App.logger.stubs(:warn).returns(nil)
|
|
89
|
+
|
|
90
|
+
assert_equal 6, "string".benchmark("oh! a label!").length
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
class DL
|
|
94
|
+
attr :msg
|
|
95
|
+
def warn(s);
|
|
96
|
+
@msg = s
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def test_logger
|
|
101
|
+
s = "string"
|
|
102
|
+
|
|
103
|
+
def s.logger; @logger ||= DL.new; end
|
|
104
|
+
assert s.respond_to?(:logger)
|
|
105
|
+
assert s.logger.msg.blank?
|
|
106
|
+
|
|
107
|
+
assert_equal 6, s.benchmark.length
|
|
108
|
+
assert !s.logger.msg.blank?
|
|
109
|
+
end
|
|
110
|
+
end if VEX_TEST == "base"
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
class RangeArray < Array
|
|
2
|
+
def initialize(array)
|
|
3
|
+
min = max = nil
|
|
4
|
+
|
|
5
|
+
array.each do |i|
|
|
6
|
+
if min
|
|
7
|
+
if i >= min && i <= max then next
|
|
8
|
+
elsif i == min-1 then min = i
|
|
9
|
+
elsif i == max+1 then max = i
|
|
10
|
+
else
|
|
11
|
+
push min, max
|
|
12
|
+
min = max = i
|
|
13
|
+
end
|
|
14
|
+
else
|
|
15
|
+
min = max = i
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
push min, max if min
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def push(min, max)
|
|
23
|
+
super min == max ? min : min..max
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
module RangeArray::Etest
|
|
28
|
+
def ra(*array)
|
|
29
|
+
RangeArray.new array
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def test_range_array
|
|
33
|
+
assert_equal [1..3], ra(1, 2, 3)
|
|
34
|
+
assert_equal [1..3, 5..6], ra(1, 2, 3, 5, 6)
|
|
35
|
+
assert_equal [1..3, 5..6, 8], ra(1, 2, 3, 5, 6, 8)
|
|
36
|
+
assert_equal [1..2, 7, 3, 5..6, 8], ra(1, 2, 7, 3, 5, 6, 8)
|
|
37
|
+
assert_equal [1..2, 7, 3, 5..6, 8, 4], ra(1, 2, 7, 3, 5, 6, 8, 4)
|
|
38
|
+
assert_equal [1..2, 7, 3, 5..6, 8, 4, -3], ra(1, 2, 7, 3, 5, 6, 8, 4, -3)
|
|
39
|
+
end
|
|
40
|
+
end if VEX_TEST == "base"
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
class Range
|
|
2
|
+
def limit(value)
|
|
3
|
+
if value < first then first
|
|
4
|
+
elsif value > last then last
|
|
5
|
+
else value
|
|
6
|
+
end
|
|
7
|
+
end
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
class Numeric
|
|
11
|
+
def limit(range)
|
|
12
|
+
range.limit(self)
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
module Range::Etest
|
|
17
|
+
def test_range
|
|
18
|
+
assert_equal 1, (1..3).limit(-1)
|
|
19
|
+
assert_equal 1, (1..3).limit(1)
|
|
20
|
+
assert_equal 2, (1..3).limit(2)
|
|
21
|
+
assert_equal 3, (1..3).limit(3)
|
|
22
|
+
assert_equal 3, (1..3).limit(4)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def test_numeric
|
|
26
|
+
assert_equal 1, -1.limit(1..3)
|
|
27
|
+
end
|
|
28
|
+
end if VEX_TEST == "base"
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
require 'digest/sha1'
|
|
2
|
+
require 'json'
|
|
3
|
+
require 'base64'
|
|
4
|
+
|
|
5
|
+
module SafeToken
|
|
6
|
+
CipherError = if defined?(OpenSSL::Cipher::CipherError)
|
|
7
|
+
OpenSSL::Cipher::CipherError
|
|
8
|
+
else
|
|
9
|
+
OpenSSL::CipherError
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
class InvalidToken < RuntimeError; end
|
|
13
|
+
class TokenTooLong < InvalidToken; end
|
|
14
|
+
class TokenExpired < InvalidToken
|
|
15
|
+
attr :expires
|
|
16
|
+
def initialize(expires)
|
|
17
|
+
@expires = expires
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def to_s
|
|
21
|
+
"token expired at #{expires}"
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
private
|
|
25
|
+
|
|
26
|
+
DEFAULTS = {
|
|
27
|
+
:secret => "46a348efe02807c999d69709abdbcd1b",
|
|
28
|
+
:limit => 800, # This should be safe for an URL
|
|
29
|
+
:crypt => false
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
def self.hash!(opts, data)
|
|
33
|
+
Digest::SHA1.hexdigest data
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def self.aes(encrypt_or_decrypt, data, opts)
|
|
37
|
+
return data if opts[:crypt] == false
|
|
38
|
+
|
|
39
|
+
# get the password and make sure it is long enough for the algorithm to work
|
|
40
|
+
secret = opts[:crypt].is_a?(String) ? opts[:crypt] : opts[:secret]
|
|
41
|
+
secret = Digest::SHA1.hexdigest(secret)
|
|
42
|
+
|
|
43
|
+
c = OpenSSL::Cipher::Cipher.new("aes-256-cbc")
|
|
44
|
+
c.send encrypt_or_decrypt
|
|
45
|
+
c.key = secret
|
|
46
|
+
s = c.update(data)
|
|
47
|
+
s << c.final
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def self.encode64(s)
|
|
51
|
+
Base64.encode64(s).gsub("\n", '').gsub("=", '-')
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def self.decode64(s)
|
|
55
|
+
Base64.decode64(s.gsub("-", '='))
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def self.limit!(s, opts)
|
|
59
|
+
if opts[:limit] && s.length > opts[:limit]
|
|
60
|
+
raise TokenTooLong, "Token too long: (#{s.length} bytes, allowed are #{opts[:limit]})"
|
|
61
|
+
end
|
|
62
|
+
s
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
public
|
|
66
|
+
|
|
67
|
+
def self.generate(data, opts = {})
|
|
68
|
+
opts = DEFAULTS.dup.update(opts)
|
|
69
|
+
expires = opts[:expires].to_i if opts[:expires]
|
|
70
|
+
|
|
71
|
+
data = data.to_json
|
|
72
|
+
|
|
73
|
+
hash = hash!(opts, "#{opts[:secret]}-#{expires}:#{data}")
|
|
74
|
+
s = "sha1:#{hash}:#{expires}:#{data}"
|
|
75
|
+
|
|
76
|
+
s = aes(:encrypt, s, opts)
|
|
77
|
+
s = encode64(s)
|
|
78
|
+
limit!(s, opts)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def self.validate(s, opts = {})
|
|
82
|
+
opts = DEFAULTS.dup.update(opts)
|
|
83
|
+
|
|
84
|
+
begin
|
|
85
|
+
s = decode64(s)
|
|
86
|
+
s = aes(:decrypt, s, opts)
|
|
87
|
+
rescue SafeToken::CipherError
|
|
88
|
+
raise InvalidToken, "Invalid token encryption: #{$!}"
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
raise InvalidToken, "Invalid token syntax" unless s =~ /^([^:]*):([^:]*):([^:]*):(.*)/
|
|
92
|
+
|
|
93
|
+
method, hash, expires, data = $1, $2, $3, $4
|
|
94
|
+
|
|
95
|
+
unless hash!(opts, "#{opts[:secret]}-#{expires}:#{data}") == hash
|
|
96
|
+
raise InvalidToken, "Invalid token #{s}"
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
if !expires.empty?
|
|
100
|
+
expires = Time.at(expires.to_i)
|
|
101
|
+
raise TokenExpired, expires if expires < Time.now
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
JSON.parse(data)
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
module SafeToken::Etest
|
|
109
|
+
def data
|
|
110
|
+
{"a" => "123232", "b" => { "c" => [ 1, 2, "d" ]}}
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def test_expiration
|
|
114
|
+
token = SafeToken.generate(data, :crypt => false, :expires => Time.now-10)
|
|
115
|
+
assert_raise(SafeToken::TokenExpired) {
|
|
116
|
+
begin
|
|
117
|
+
SafeToken.validate(token)
|
|
118
|
+
rescue
|
|
119
|
+
assert $!.to_s =~ /expired/
|
|
120
|
+
raise
|
|
121
|
+
end
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
token = SafeToken.generate(data, :crypt => false, :expires => Time.now+10)
|
|
125
|
+
assert_equal data, SafeToken.validate(token)
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def test_token
|
|
129
|
+
token = SafeToken.generate(data, :crypt => false)
|
|
130
|
+
assert_equal data, SafeToken.validate(token)
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def test_token_w_crypt
|
|
134
|
+
token = SafeToken.generate(data, :crypt => true)
|
|
135
|
+
assert_equal data, SafeToken.validate(token, :crypt => true)
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def test_token_w_crypt2
|
|
139
|
+
token1 = SafeToken.generate(data, :crypt => "secret")
|
|
140
|
+
token2 = SafeToken.generate(data, :crypt => true)
|
|
141
|
+
token3 = SafeToken.generate(data, :crypt => false)
|
|
142
|
+
assert_not_equal(token1, token2)
|
|
143
|
+
assert_not_equal(token1, token3)
|
|
144
|
+
assert_not_equal(token2, token3)
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def test_invalid_enc_parameters
|
|
148
|
+
token = SafeToken.generate(data, :crypt => true)
|
|
149
|
+
|
|
150
|
+
OpenSSL::Cipher::Cipher.any_instance.stubs(:update).raises(SafeToken::CipherError)
|
|
151
|
+
assert_raise(SafeToken::InvalidToken) {
|
|
152
|
+
assert_equal data, SafeToken.validate(token, :crypt => true)
|
|
153
|
+
}
|
|
154
|
+
end
|
|
155
|
+
end if VEX_TEST == "base"
|
|
156
|
+
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
module StringExt
|
|
2
|
+
def constantize?
|
|
3
|
+
constantize
|
|
4
|
+
rescue LoadError, NameError
|
|
5
|
+
STDERR.puts $!.to_s
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def uri?
|
|
9
|
+
!!(self =~ /^[a-z][a-z]+:/)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def unhtml
|
|
13
|
+
return self if blank?
|
|
14
|
+
s = Sanitize.clean(self)
|
|
15
|
+
HTMLEntities.new.decode(s)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# truncate :length => 30, :omission => "…"
|
|
19
|
+
def truncate(*args)
|
|
20
|
+
opts = if args.length == 1 && args.first.is_a?(Hash)
|
|
21
|
+
args.first
|
|
22
|
+
else
|
|
23
|
+
args.inject({}) do |hash, arg|
|
|
24
|
+
key = case arg
|
|
25
|
+
when Fixnum then :length
|
|
26
|
+
when String then :omission
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
invalid_argument!(arg) if key.nil? || hash[key]
|
|
30
|
+
|
|
31
|
+
hash.update key => arg
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
_truncate(opts)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def _truncate(opts)
|
|
39
|
+
opts = { :length => opts, :omission => "…" } unless opts.is_a?(Hash)
|
|
40
|
+
max_length = opts[:length] || 30
|
|
41
|
+
omission = opts[:omission] || "…"
|
|
42
|
+
|
|
43
|
+
#
|
|
44
|
+
# Treat multibytes differently
|
|
45
|
+
if respond_to?(:mb_chars)
|
|
46
|
+
l = max_length - omission.mb_chars.length
|
|
47
|
+
if mb_chars.length > max_length
|
|
48
|
+
return (mb_chars[0...l] + omission).to_s
|
|
49
|
+
end
|
|
50
|
+
else
|
|
51
|
+
l = max_length - omission.length
|
|
52
|
+
if length > max_length
|
|
53
|
+
return self[0...l] + omission
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
self
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def truncate!(opts = {})
|
|
61
|
+
replace truncate(opts)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def word_wrap(line_len=100)
|
|
65
|
+
lines = split("\n")
|
|
66
|
+
lines.map { |line| StringExt.word_wrap(line, line_len) }.join("\n")
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def self.word_wrap(line, line_len)
|
|
70
|
+
r = []
|
|
71
|
+
while line.length > line_len
|
|
72
|
+
# find last space in the first line_len characters. Failing that we
|
|
73
|
+
# take the *first* (sic!) space in the entire line.
|
|
74
|
+
firstline = line[0..line_len]
|
|
75
|
+
space_idx = firstline.rindex(/\s/) || line.index(/\s/, line_len)
|
|
76
|
+
|
|
77
|
+
if !space_idx
|
|
78
|
+
r << line
|
|
79
|
+
line = ""
|
|
80
|
+
else
|
|
81
|
+
r << line[0...space_idx]
|
|
82
|
+
line = line[space_idx+1..-1]
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
r << line unless line.blank?
|
|
87
|
+
r.join("\n")
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
String.send :include, StringExt
|
|
92
|
+
|
|
93
|
+
module StringExt::Etest
|
|
94
|
+
def test_unhtml
|
|
95
|
+
assert_equal("", "".unhtml)
|
|
96
|
+
assert_equal("hjghjg", "<p>hjghjg</p>".unhtml)
|
|
97
|
+
assert_equal("ä", "ä".unhtml)
|
|
98
|
+
assert_equal("ä", "ä".unhtml)
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# truncate :length => 30, :omission => "..."
|
|
102
|
+
def test_truncate
|
|
103
|
+
assert_equal("", "".truncate)
|
|
104
|
+
assert_equal("123456", "123456".truncate)
|
|
105
|
+
assert_equal("123…", "1234567".truncate(:length => 6))
|
|
106
|
+
assert_equal("123…", "1234567".truncate(6))
|
|
107
|
+
assert_equal("12345~", "1234567".truncate(6, "~"))
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def test_truncate!
|
|
111
|
+
s = "1234567"
|
|
112
|
+
|
|
113
|
+
assert_equal "123…", s.truncate(6)
|
|
114
|
+
assert_equal "1234567", s
|
|
115
|
+
|
|
116
|
+
assert_equal "123…", s.truncate!(6)
|
|
117
|
+
assert_equal "123…", s
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def test_word_wrap
|
|
121
|
+
assert_equal "abcdef\nghijkl", "abcdef ghijkl".word_wrap(8)
|
|
122
|
+
assert_equal "abc def\nghijkl", "abc def ghijkl".word_wrap(8)
|
|
123
|
+
assert_equal "abcdefghijkl", "abcdefghijkl".word_wrap(8)
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def test_uri
|
|
127
|
+
assert_equal true, "http://".uri?
|
|
128
|
+
assert_equal false, "//".uri?
|
|
129
|
+
assert_equal false, "c:\\x\\y".uri?
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def test_constantize
|
|
133
|
+
assert_equal String, "String".constantize?
|
|
134
|
+
assert_equal nil, "I::Dont::Know::This".constantize?
|
|
135
|
+
end
|
|
136
|
+
end if VEX_TEST == "base"
|