stringex 1.5.1 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +15 -0
- data/Gemfile +16 -0
- data/Gemfile.lock +74 -0
- data/README.rdoc +22 -1
- data/Rakefile +46 -223
- data/VERSION +1 -0
- data/init.rb +1 -0
- data/lib/stringex.rb +11 -3
- data/lib/stringex/acts_as_url.rb +49 -97
- data/lib/stringex/acts_as_url/adapter.rb +26 -0
- data/lib/stringex/acts_as_url/adapter/active_record.rb +23 -0
- data/lib/stringex/acts_as_url/adapter/base.rb +188 -0
- data/lib/stringex/acts_as_url/adapter/data_mapper.rb +67 -0
- data/lib/stringex/acts_as_url/adapter/mongoid.rb +36 -0
- data/lib/stringex/configuration.rb +4 -0
- data/lib/stringex/configuration/acts_as_url.rb +44 -0
- data/lib/stringex/configuration/base.rb +58 -0
- data/lib/stringex/configuration/configurator.rb +25 -0
- data/lib/stringex/configuration/string_extensions.rb +19 -0
- data/lib/stringex/localization.rb +98 -0
- data/lib/stringex/localization/backend/i18n.rb +53 -0
- data/lib/stringex/localization/backend/internal.rb +51 -0
- data/lib/stringex/localization/conversion_expressions.rb +148 -0
- data/lib/stringex/localization/converter.rb +121 -0
- data/lib/stringex/localization/default_conversions.rb +88 -0
- data/lib/stringex/rails/railtie.rb +10 -0
- data/lib/stringex/string_extensions.rb +153 -208
- data/lib/stringex/unidecoder.rb +6 -101
- data/lib/stringex/unidecoder_data/x00.yml +1 -1
- data/lib/stringex/unidecoder_data/x02.yml +5 -5
- data/lib/stringex/unidecoder_data/x05.yml +1 -1
- data/lib/stringex/unidecoder_data/x06.yml +1 -1
- data/lib/stringex/unidecoder_data/x07.yml +3 -3
- data/lib/stringex/unidecoder_data/x09.yml +1 -1
- data/lib/stringex/unidecoder_data/x0e.yml +2 -2
- data/lib/stringex/unidecoder_data/x1f.yml +2 -2
- data/lib/stringex/unidecoder_data/x20.yml +1 -1
- data/lib/stringex/unidecoder_data/xfb.yml +1 -1
- data/lib/stringex/unidecoder_data/xff.yml +1 -1
- data/lib/stringex/version.rb +8 -0
- data/locales/da.yml +73 -0
- data/locales/en.yml +66 -0
- data/stringex.gemspec +77 -18
- data/test/acts_as_url/adapter/active_record.rb +72 -0
- data/test/acts_as_url/adapter/data_mapper.rb +82 -0
- data/test/acts_as_url/adapter/mongoid.rb +73 -0
- data/test/acts_as_url_configuration_test.rb +51 -0
- data/test/acts_as_url_integration_test.rb +271 -0
- data/test/localization/da_test.rb +117 -0
- data/test/localization/default_test.rb +113 -0
- data/test/localization/en_test.rb +117 -0
- data/test/localization_test.rb +123 -0
- data/test/redcloth_to_html_test.rb +37 -0
- data/test/string_extensions_test.rb +59 -91
- data/test/test_helper.rb +2 -0
- data/test/unicode_point_suite/basic_greek_test.rb +113 -0
- data/test/unicode_point_suite/basic_latin_test.rb +142 -0
- data/test/unicode_point_suite/codepoint_test_helper.rb +32 -0
- data/test/unidecoder/bad_localization.yml +1 -0
- data/test/unidecoder/localization.yml +4 -0
- data/test/unidecoder_test.rb +3 -5
- metadata +145 -37
- data/test/acts_as_url_test.rb +0 -272
@@ -0,0 +1,67 @@
|
|
1
|
+
module Stringex
|
2
|
+
module ActsAsUrl
|
3
|
+
module Adapter
|
4
|
+
class DataMapper < Base
|
5
|
+
def self.load
|
6
|
+
ensure_loadable
|
7
|
+
orm_class.send :include, Stringex::ActsAsUrl::ActsAsUrlInstanceMethods
|
8
|
+
::DataMapper::Model.send :include, Stringex::ActsAsUrl::ActsAsUrlClassMethods
|
9
|
+
end
|
10
|
+
|
11
|
+
private
|
12
|
+
|
13
|
+
def create_callback
|
14
|
+
if settings.sync_url
|
15
|
+
klass.class_eval do
|
16
|
+
before :save, :ensure_unique_url
|
17
|
+
end
|
18
|
+
else
|
19
|
+
klass.class_eval do
|
20
|
+
before :create, :ensure_unique_url
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def instance_from_db
|
26
|
+
instance.class.get(instance.id)
|
27
|
+
end
|
28
|
+
|
29
|
+
def is_blank?(object)
|
30
|
+
object.nil? || object == '' || object == []
|
31
|
+
end
|
32
|
+
|
33
|
+
def is_new?(object)
|
34
|
+
object.new?
|
35
|
+
end
|
36
|
+
|
37
|
+
def is_present?(object)
|
38
|
+
!is_blank? object
|
39
|
+
end
|
40
|
+
|
41
|
+
def klass_previous_instances(&block)
|
42
|
+
klass.all(:conditions => {settings.url_attribute => nil}).each(&block)
|
43
|
+
end
|
44
|
+
|
45
|
+
def read_attribute(instance, attribute)
|
46
|
+
instance.attributes[attribute]
|
47
|
+
end
|
48
|
+
|
49
|
+
def url_owners
|
50
|
+
@url_owners ||= url_owners_class.all(:conditions => url_owner_conditions)
|
51
|
+
end
|
52
|
+
|
53
|
+
def read_attribute(instance, name)
|
54
|
+
instance.attribute_get name
|
55
|
+
end
|
56
|
+
|
57
|
+
def write_attribute(instance, name, value)
|
58
|
+
instance.attribute_set name, value
|
59
|
+
end
|
60
|
+
|
61
|
+
def self.orm_class
|
62
|
+
::DataMapper::Resource
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module Stringex
|
2
|
+
module ActsAsUrl
|
3
|
+
module Adapter
|
4
|
+
class Mongoid < Base
|
5
|
+
def self.load
|
6
|
+
ensure_loadable
|
7
|
+
orm_class.send :extend, Stringex::ActsAsUrl::ActsAsUrlClassMethods
|
8
|
+
end
|
9
|
+
|
10
|
+
private
|
11
|
+
|
12
|
+
def add_new_record_url_owner_conditions
|
13
|
+
return if instance.new_record?
|
14
|
+
@url_owner_conditions.merge! :id => {'$ne' => instance.id}
|
15
|
+
end
|
16
|
+
|
17
|
+
def add_scoped_url_owner_conditions
|
18
|
+
return unless settings.scope_for_url
|
19
|
+
@url_owner_conditions.merge! settings.scope_for_url => instance.send(settings.scope_for_url)
|
20
|
+
end
|
21
|
+
|
22
|
+
def get_base_url_owner_conditions
|
23
|
+
@url_owner_conditions = {settings.url_attribute => /^#{Regexp.escape(base_url)}/}
|
24
|
+
end
|
25
|
+
|
26
|
+
def klass_previous_instances(&block)
|
27
|
+
klass.where(settings.url_attribute => nil).to_a.each(&block)
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.orm_class
|
31
|
+
::Mongoid::Document
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module Stringex
|
2
|
+
module Configuration
|
3
|
+
class ActsAsUrl < Base
|
4
|
+
def initialize(options = {})
|
5
|
+
if options[:scope]
|
6
|
+
options[:scope_for_url] = options.delete(:scope)
|
7
|
+
end
|
8
|
+
super
|
9
|
+
end
|
10
|
+
|
11
|
+
def string_extensions_settings
|
12
|
+
[
|
13
|
+
:allow_slash,
|
14
|
+
:exclude,
|
15
|
+
:force_downcase,
|
16
|
+
:limit,
|
17
|
+
:replace_whitespace_with
|
18
|
+
].inject(Hash.new){|m, x| m[x] = settings.send(x); m}
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.settings
|
22
|
+
@settings
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def default_settings
|
28
|
+
self.class.default_settings
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.default_settings
|
32
|
+
@default_settings ||= {
|
33
|
+
:allow_duplicates => false,
|
34
|
+
:duplicate_count_separator => "-",
|
35
|
+
:enforce_uniqueness_on_sti_class => false,
|
36
|
+
:only_when_blank => false,
|
37
|
+
:scope_for_url => nil,
|
38
|
+
:sync_url => false,
|
39
|
+
:url_attribute => "url",
|
40
|
+
}.merge(Stringex::Configuration::StringExtensions.new.default_settings)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
module Stringex
|
2
|
+
module Configuration
|
3
|
+
class Base
|
4
|
+
attr_accessor :settings
|
5
|
+
|
6
|
+
def initialize(local_options = {})
|
7
|
+
current_settings = default_settings.merge(system_wide_customizations)
|
8
|
+
current_settings.merge! local_options
|
9
|
+
|
10
|
+
@settings = OpenStruct.new(current_settings)
|
11
|
+
end
|
12
|
+
|
13
|
+
# NOTE: This does not cache itself so that instance and class can be cached on the adapter
|
14
|
+
# without worrying about thread safety or race conditions
|
15
|
+
def adapter
|
16
|
+
adapter_name = settings.adapter || Stringex::ActsAsUrl::Adapter.first_available
|
17
|
+
case adapter_name
|
18
|
+
when Class
|
19
|
+
adapter_name.send :new, self
|
20
|
+
when :active_record
|
21
|
+
Stringex::ActsAsUrl::Adapter::ActiveRecord.new self
|
22
|
+
when :mongoid
|
23
|
+
Stringex::ActsAsUrl::Adapter::Mongoid.new self
|
24
|
+
else
|
25
|
+
raise ArgumentError, "#{adapter_name} is not a defined ActsAsUrl adapter. Please feel free to implement your own and submit it back upstream."
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.configure(&block)
|
30
|
+
configurator = Stringex::Configuration::Configurator.new(self)
|
31
|
+
yield configurator
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.system_wide_customizations
|
35
|
+
@system_wide_customizations ||= {}
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.unconfigure!
|
39
|
+
@system_wide_customizations = {}
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
def default_settings
|
45
|
+
raise ArgumentError, "You shouldn't have hit default_settings on Stringex::Configuration::Base. Check your code."
|
46
|
+
end
|
47
|
+
|
48
|
+
def system_wide_customizations
|
49
|
+
self.class.system_wide_customizations
|
50
|
+
end
|
51
|
+
|
52
|
+
def self.valid_configuration_details
|
53
|
+
default_settings.keys
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Stringex
|
2
|
+
module Configuration
|
3
|
+
class Configurator
|
4
|
+
attr_accessor :klass
|
5
|
+
|
6
|
+
def initialize(klass)
|
7
|
+
@klass = klass
|
8
|
+
|
9
|
+
self.klass.valid_configuration_details.each do |name|
|
10
|
+
define_instance_method_for_configuration_wrapper name
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def define_instance_method_for_configuration_wrapper(name)
|
15
|
+
name = name.respond_to?(:intern) ? name.intern : name
|
16
|
+
(class << self; self; end).instance_eval do
|
17
|
+
define_method("#{name}=") do |value|
|
18
|
+
customizations = klass.send(:system_wide_customizations)
|
19
|
+
customizations[name] = value
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Stringex
|
2
|
+
module Configuration
|
3
|
+
class StringExtensions < Base
|
4
|
+
def default_settings
|
5
|
+
self.class.default_settings
|
6
|
+
end
|
7
|
+
|
8
|
+
def self.default_settings
|
9
|
+
@default_settings ||= {
|
10
|
+
:allow_slash => false,
|
11
|
+
:exclude => [],
|
12
|
+
:force_downcase => true,
|
13
|
+
:limit => nil,
|
14
|
+
:replace_whitespace_with => "-"
|
15
|
+
}
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
require 'stringex/localization/converter'
|
4
|
+
require 'stringex/localization/default_conversions'
|
5
|
+
require 'stringex/localization/backend/internal'
|
6
|
+
require 'stringex/localization/backend/i18n'
|
7
|
+
|
8
|
+
module Stringex
|
9
|
+
module Localization
|
10
|
+
include DefaultConversions
|
11
|
+
|
12
|
+
class << self
|
13
|
+
def backend
|
14
|
+
@backend ||= defined?(I18n) ? Backend::I18n : Backend::Internal
|
15
|
+
end
|
16
|
+
|
17
|
+
def backend=(sym_or_class)
|
18
|
+
if sym_or_class.is_a?(Symbol)
|
19
|
+
@backend = case sym_or_class
|
20
|
+
when :internal then Backend::Internal
|
21
|
+
when :i18n then Backend::I18n
|
22
|
+
else raise "Invalid backend :#{sym_or_class}"
|
23
|
+
end
|
24
|
+
else
|
25
|
+
@backend = sym_or_class
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def store_translations(locale, scope, data)
|
30
|
+
backend.store_translations(locale, scope, data)
|
31
|
+
end
|
32
|
+
|
33
|
+
def translate(scope, key, options = {})
|
34
|
+
return if key == "." # I18n doesn't support dots as translation keys so we don't either
|
35
|
+
|
36
|
+
locale = options[:locale] || self.locale
|
37
|
+
|
38
|
+
translation = initial_translation(scope, key, locale)
|
39
|
+
|
40
|
+
return translation unless translation.nil?
|
41
|
+
|
42
|
+
if locale != default_locale
|
43
|
+
translate scope, key, options.merge(:locale => default_locale)
|
44
|
+
else
|
45
|
+
default_conversion(scope, key) || options[:default]
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def locale
|
50
|
+
backend.locale
|
51
|
+
end
|
52
|
+
|
53
|
+
def locale=(new_locale)
|
54
|
+
backend.locale = new_locale
|
55
|
+
end
|
56
|
+
|
57
|
+
def default_locale
|
58
|
+
backend.default_locale
|
59
|
+
end
|
60
|
+
|
61
|
+
def default_locale=(new_locale)
|
62
|
+
backend.default_locale = new_locale
|
63
|
+
end
|
64
|
+
|
65
|
+
def with_locale(new_locale, &block)
|
66
|
+
new_locale = default_locale if new_locale == :default
|
67
|
+
backend.with_locale new_locale, &block
|
68
|
+
end
|
69
|
+
|
70
|
+
def with_default_locale(&block)
|
71
|
+
with_locale default_locale, &block
|
72
|
+
end
|
73
|
+
|
74
|
+
def reset!
|
75
|
+
backend.reset!
|
76
|
+
@backend = nil
|
77
|
+
end
|
78
|
+
|
79
|
+
def convert(string, options = {}, &block)
|
80
|
+
converter = Converter.new(string)
|
81
|
+
converter.instance_exec &block
|
82
|
+
converter.smart_strip!
|
83
|
+
converter.string
|
84
|
+
end
|
85
|
+
|
86
|
+
private
|
87
|
+
|
88
|
+
def initial_translation(scope, key, locale)
|
89
|
+
backend.initial_translation(scope, key, locale)
|
90
|
+
end
|
91
|
+
|
92
|
+
def default_conversion(scope, key)
|
93
|
+
return unless DefaultConversions.respond_to?(scope)
|
94
|
+
DefaultConversions.send(scope)[key]
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module Stringex
|
2
|
+
module Localization
|
3
|
+
module Backend
|
4
|
+
module I18n
|
5
|
+
LOAD_PATH_BASE = File.join(File.expand_path(File.dirname(__FILE__)), '..', '..', '..', '..', 'locales')
|
6
|
+
|
7
|
+
class << self
|
8
|
+
def locale
|
9
|
+
::I18n.locale
|
10
|
+
end
|
11
|
+
|
12
|
+
def locale=(new_locale)
|
13
|
+
::I18n.locale = new_locale
|
14
|
+
end
|
15
|
+
|
16
|
+
def default_locale
|
17
|
+
::I18n.default_locale
|
18
|
+
end
|
19
|
+
|
20
|
+
def default_locale=(new_locale)
|
21
|
+
::I18n.default_locale = new_locale
|
22
|
+
end
|
23
|
+
|
24
|
+
def with_locale(new_locale, &block)
|
25
|
+
::I18n.with_locale new_locale, &block
|
26
|
+
end
|
27
|
+
|
28
|
+
def store_translations(locale, scope, data)
|
29
|
+
::I18n.backend.store_translations(locale, { :stringex => { scope => data } })
|
30
|
+
end
|
31
|
+
|
32
|
+
def initial_translation(scope, key, locale)
|
33
|
+
# I18n can't return a nil as default as this gets interpreted as if no default
|
34
|
+
# is specified, so we use a string instead.
|
35
|
+
translated = ::I18n.translate(key, :scope => [:stringex, scope], :locale => locale, :default => "__default__")
|
36
|
+
translated == "__default__" ? nil : translated
|
37
|
+
end
|
38
|
+
|
39
|
+
def load_translations(locale = nil)
|
40
|
+
locale ||= ::I18n.locale
|
41
|
+
path = Dir[File.join(LOAD_PATH_BASE, "#{locale}.yml")]
|
42
|
+
::I18n.load_path |= Dir[File.join(LOAD_PATH_BASE, "#{locale}.yml")]
|
43
|
+
::I18n.backend.load_translations
|
44
|
+
end
|
45
|
+
|
46
|
+
def reset!
|
47
|
+
# Can't reset I18n. Needed?
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module Stringex
|
2
|
+
module Localization
|
3
|
+
module Backend
|
4
|
+
module Internal
|
5
|
+
DEFAULT_LOCALE = :en
|
6
|
+
|
7
|
+
class << self
|
8
|
+
def locale
|
9
|
+
@locale || default_locale
|
10
|
+
end
|
11
|
+
|
12
|
+
def locale=(new_locale)
|
13
|
+
@locale = new_locale.to_sym
|
14
|
+
end
|
15
|
+
|
16
|
+
def default_locale
|
17
|
+
@default_locale || DEFAULT_LOCALE
|
18
|
+
end
|
19
|
+
|
20
|
+
def default_locale=(new_locale)
|
21
|
+
@default_locale = @locale = new_locale.to_sym
|
22
|
+
end
|
23
|
+
|
24
|
+
def with_locale(new_locale, &block)
|
25
|
+
original_locale = locale
|
26
|
+
self.locale = new_locale
|
27
|
+
yield
|
28
|
+
self.locale = original_locale
|
29
|
+
end
|
30
|
+
|
31
|
+
def translations
|
32
|
+
# Set up hash like translations[:en][:transliterations]["é"]
|
33
|
+
@translations ||= Hash.new { |k, v| k[v] = Hash.new({}) }
|
34
|
+
end
|
35
|
+
|
36
|
+
def store_translations(locale, scope, data)
|
37
|
+
self.translations[locale.to_sym][scope.to_sym] = Hash[data.map { |k, v| [k.to_sym, v] }] # Symbolize keys
|
38
|
+
end
|
39
|
+
|
40
|
+
def initial_translation(scope, key, locale)
|
41
|
+
translations[locale][scope][key.to_sym]
|
42
|
+
end
|
43
|
+
|
44
|
+
def reset!
|
45
|
+
@translations = @locale = @default_locale = nil
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|