stringex 1.5.1 → 2.0.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.
- 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
data/lib/stringex.rb
CHANGED
@@ -1,9 +1,17 @@
|
|
1
1
|
# encoding: UTF-8
|
2
|
+
|
3
|
+
require 'ostruct'
|
4
|
+
require 'stringex/configuration'
|
5
|
+
require 'stringex/localization'
|
2
6
|
require 'stringex/string_extensions'
|
3
7
|
require 'stringex/unidecoder'
|
8
|
+
require 'stringex/acts_as_url'
|
4
9
|
|
5
|
-
String.send :include, Stringex::StringExtensions
|
10
|
+
String.send :include, Stringex::StringExtensions::PublicInstanceMethods
|
11
|
+
String.send :extend, Stringex::StringExtensions::PublicClassMethods
|
6
12
|
|
7
|
-
|
13
|
+
Stringex::ActsAsUrl::Adapter.load_available
|
8
14
|
|
9
|
-
|
15
|
+
if defined?(Rails::Railtie)
|
16
|
+
require 'stringex/rails/railtie'
|
17
|
+
end
|
data/lib/stringex/acts_as_url.rb
CHANGED
@@ -1,66 +1,17 @@
|
|
1
1
|
# encoding: UTF-8
|
2
|
+
require "stringex/acts_as_url/adapter"
|
3
|
+
|
2
4
|
module Stringex
|
3
5
|
module ActsAsUrl # :nodoc:
|
4
|
-
def self.
|
5
|
-
|
6
|
+
def self.configure(&block)
|
7
|
+
Stringex::Configuration::ActsAsUrl.configure &block
|
6
8
|
end
|
7
9
|
|
8
|
-
|
9
|
-
|
10
|
-
:exclude, :only_when_blank, :scope_for_url, :sync_url, :url_attribute, :url_limit
|
11
|
-
|
12
|
-
def initialize(klass, options = {})
|
13
|
-
self.allow_slash = options[:allow_slash]
|
14
|
-
self.allow_duplicates = options[:allow_duplicates]
|
15
|
-
self.attribute_to_urlify = options[:attribute]
|
16
|
-
self.duplicate_count_separator = options[:duplicate_count_separator] || "-"
|
17
|
-
self.exclude = options[:exclude] || []
|
18
|
-
self.only_when_blank = options[:only_when_blank]
|
19
|
-
self.scope_for_url = options[:scope]
|
20
|
-
self.sync_url = options[:sync_url]
|
21
|
-
self.url_attribute = options[:url_attribute] || "url"
|
22
|
-
self.url_limit = options[:limit]
|
23
|
-
end
|
24
|
-
|
25
|
-
def get_base_url!(instance)
|
26
|
-
base_url = instance.send(url_attribute)
|
27
|
-
if base_url.blank? || !only_when_blank
|
28
|
-
root = instance.send(attribute_to_urlify).to_s
|
29
|
-
base_url = root.to_url(:allow_slash => allow_slash, :limit => url_limit, :exclude => exclude)
|
30
|
-
end
|
31
|
-
instance.instance_variable_set "@acts_as_url_base_url", base_url
|
32
|
-
end
|
33
|
-
|
34
|
-
def get_conditions!(instance)
|
35
|
-
conditions = ["#{url_attribute} LIKE ?", instance.instance_variable_get("@acts_as_url_base_url") + '%']
|
36
|
-
unless instance.new_record?
|
37
|
-
conditions.first << " and id != ?"
|
38
|
-
conditions << instance.id
|
39
|
-
end
|
40
|
-
if scope_for_url
|
41
|
-
conditions.first << " and #{scope_for_url} = ?"
|
42
|
-
conditions << instance.send(scope_for_url)
|
43
|
-
end
|
44
|
-
conditions
|
45
|
-
end
|
46
|
-
|
47
|
-
def handle_duplicate_urls!(instance)
|
48
|
-
return if allow_duplicates
|
49
|
-
|
50
|
-
base_url = instance.instance_variable_get("@acts_as_url_base_url")
|
51
|
-
url_owners = instance.class.unscoped.find(:all, :conditions => get_conditions!(instance))
|
52
|
-
if url_owners.any?{|owner| owner.send(url_attribute) == base_url}
|
53
|
-
separator = duplicate_count_separator
|
54
|
-
n = 1
|
55
|
-
while url_owners.any?{|owner| owner.send(url_attribute) == "#{base_url}#{separator}#{n}"}
|
56
|
-
n = n.succ
|
57
|
-
end
|
58
|
-
instance.send :write_attribute, url_attribute, "#{base_url}#{separator}#{n}"
|
59
|
-
end
|
60
|
-
end
|
10
|
+
def self.unconfigure!
|
11
|
+
Stringex::Configuration::ActsAsUrl.unconfigure!
|
61
12
|
end
|
62
13
|
|
63
|
-
module
|
14
|
+
module ActsAsUrlClassMethods # :doc:
|
64
15
|
# Creates a callback to automatically create an url-friendly representation
|
65
16
|
# of the <tt>attribute</tt> argument. Example:
|
66
17
|
#
|
@@ -74,48 +25,58 @@ module Stringex
|
|
74
25
|
# The default attribute <tt>acts_as_url</tt> uses to save the permalink is <tt>url</tt>
|
75
26
|
# but this can be changed in the options hash. Available options are:
|
76
27
|
#
|
77
|
-
# <tt>:
|
78
|
-
#
|
79
|
-
#
|
28
|
+
# <tt>:adapter</tt>:: If specified, will indicate what ORM adapter to use. Default functionality
|
29
|
+
# is to use the first available adapter. This should work for most cases
|
30
|
+
# unless you are using multiple ORMs in a single project.
|
31
|
+
# <tt>:allow_slash</tt>:: If true, allows the generated url to contain slashes. Default is false[y].
|
32
|
+
# <tt>:allow_duplicates</tt>:: If true, allows duplicate urls instead of appending numbers to
|
33
|
+
# differentiate between urls. Default is false[y]. See note on <tt>:scope</tt>.
|
80
34
|
# <tt>:duplicate_count_separator</tt>:: String to use when forcing unique urls from non-unique strings.
|
81
35
|
# Default is "-".
|
36
|
+
# <tt>:force_downcase</tt>:: If false, allows generated url to contain uppercased letters. Default is false.
|
82
37
|
# <tt>:exclude_list</tt>:: List of complete strings that should not be transformed by <tt>acts_as_url</tt>.
|
83
38
|
# Default is empty.
|
84
39
|
# <tt>:only_when_blank</tt>:: If true, the url generation will only happen when <tt>:url_attribute</tt> is
|
85
40
|
# blank. Default is false[y] (meaning url generation will happen always).
|
86
41
|
# <tt>:scope</tt>:: The name of model attribute to scope unique urls to. There is no default here.
|
42
|
+
# <strong>Note:</strong> this will automatically act as if <tt>:allow_duplicates</tt>
|
43
|
+
# is set to true.
|
87
44
|
# <tt>:sync_url</tt>:: If set to true, the url field will be updated when changes are made to the
|
88
|
-
# attribute it is based on. Default is false
|
45
|
+
# attribute it is based on. Default is false.
|
89
46
|
# <tt>:url_attribute</tt>:: The name of the attribute to use for storing the generated url string.
|
90
47
|
# Default is <tt>:url</tt>.
|
91
|
-
# <tt>:
|
92
|
-
#
|
93
|
-
#
|
48
|
+
# <tt>:limit</tt>:: The maximum size a generated url should be. <strong>Note:</strong> this does not
|
49
|
+
# include the characters needed to enforce uniqueness on duplicate urls.
|
50
|
+
# Default is nil.
|
94
51
|
def acts_as_url(attribute, options = {})
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
self.acts_as_url_configuration = ActsAsUrl::Configuration.new(self, options)
|
99
|
-
|
100
|
-
if acts_as_url_configuration.sync_url
|
101
|
-
before_validation(:ensure_unique_url)
|
102
|
-
else
|
103
|
-
if defined?(ActiveModel::Callbacks)
|
104
|
-
before_validation(:ensure_unique_url, :on => :create)
|
105
|
-
else
|
106
|
-
before_validation_on_create(:ensure_unique_url)
|
52
|
+
class_eval do
|
53
|
+
class << self
|
54
|
+
attr_accessor :acts_as_url_configuration
|
107
55
|
end
|
108
|
-
end
|
109
56
|
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
else
|
115
|
-
read_attribute(acts_as_url_configuration.url_attribute)
|
57
|
+
define_method :acts_as_url_configuration do
|
58
|
+
klass = self.class
|
59
|
+
while klass.acts_as_url_configuration.nil?
|
60
|
+
klass = klass.superclass
|
116
61
|
end
|
62
|
+
klass.acts_as_url_configuration
|
117
63
|
end
|
118
|
-
|
64
|
+
end
|
65
|
+
|
66
|
+
options[:attribute_to_urlify] = attribute
|
67
|
+
self.acts_as_url_configuration = Stringex::Configuration::ActsAsUrl.new(options)
|
68
|
+
|
69
|
+
acts_as_url_configuration.adapter.create_callbacks! self
|
70
|
+
end
|
71
|
+
|
72
|
+
|
73
|
+
# Some ORMs function as mixins not base classes and need to have a hook to reinclude
|
74
|
+
# and re-extend ActsAsUrl methods
|
75
|
+
def included(base)
|
76
|
+
super
|
77
|
+
|
78
|
+
base.send :include, Stringex::ActsAsUrl::ActsAsUrlInstanceMethods
|
79
|
+
base.send :extend, Stringex::ActsAsUrl::ActsAsUrlClassMethods
|
119
80
|
end
|
120
81
|
|
121
82
|
# Initialize the url fields for the records that need it. Designed for people who add
|
@@ -126,23 +87,14 @@ module Stringex
|
|
126
87
|
# on a large selection, you will get much better results writing your own version with
|
127
88
|
# using pagination.
|
128
89
|
def initialize_urls
|
129
|
-
|
130
|
-
instance.send :ensure_unique_url
|
131
|
-
instance.save
|
132
|
-
end
|
90
|
+
acts_as_url_configuration.adapter.initialize_urls! self
|
133
91
|
end
|
134
92
|
end
|
135
93
|
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
config = acts_as_url_configuration
|
141
|
-
url_attribute = config.url_attribute
|
142
|
-
|
143
|
-
config.get_base_url! self
|
144
|
-
write_attribute url_attribute, @acts_as_url_base_url
|
145
|
-
config.handle_duplicate_urls!(self) unless config.allow_duplicates
|
94
|
+
module ActsAsUrlInstanceMethods
|
95
|
+
def ensure_unique_url
|
96
|
+
acts_as_url_configuration.adapter.ensure_unique_url! self
|
97
|
+
end
|
146
98
|
end
|
147
99
|
end
|
148
100
|
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require "stringex/acts_as_url/adapter/base"
|
2
|
+
require "stringex/acts_as_url/adapter/active_record"
|
3
|
+
require "stringex/acts_as_url/adapter/data_mapper"
|
4
|
+
require "stringex/acts_as_url/adapter/mongoid"
|
5
|
+
|
6
|
+
module Stringex
|
7
|
+
module ActsAsUrl
|
8
|
+
module Adapter
|
9
|
+
def self.add_loaded_adapter(adapter)
|
10
|
+
@loaded_adapters << adapter
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.load_available
|
14
|
+
@loaded_adapters = []
|
15
|
+
constants.each do |name|
|
16
|
+
adapter = const_get(name)
|
17
|
+
adapter.load if adapter.loadable?
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.first_available
|
22
|
+
@loaded_adapters[0]
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Stringex
|
2
|
+
module ActsAsUrl
|
3
|
+
module Adapter
|
4
|
+
class ActiveRecord < Base
|
5
|
+
def self.load
|
6
|
+
ensure_loadable
|
7
|
+
orm_class.send :include, ActsAsUrlInstanceMethods
|
8
|
+
orm_class.send :extend, ActsAsUrlClassMethods
|
9
|
+
end
|
10
|
+
|
11
|
+
private
|
12
|
+
|
13
|
+
def klass_previous_instances(&block)
|
14
|
+
klass.find_each(:conditions => {settings.url_attribute => nil}, &block)
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.orm_class
|
18
|
+
::ActiveRecord::Base
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,188 @@
|
|
1
|
+
module Stringex
|
2
|
+
module ActsAsUrl
|
3
|
+
module Adapter
|
4
|
+
class Base
|
5
|
+
attr_accessor :base_url, :configuration, :instance, :klass, :settings
|
6
|
+
|
7
|
+
def initialize(configuration)
|
8
|
+
ensure_loadable
|
9
|
+
self.configuration = configuration
|
10
|
+
self.settings = configuration.settings
|
11
|
+
end
|
12
|
+
|
13
|
+
def create_callbacks!(klass)
|
14
|
+
self.klass = klass
|
15
|
+
create_method_to_callback
|
16
|
+
create_callback
|
17
|
+
end
|
18
|
+
|
19
|
+
def ensure_unique_url!(instance)
|
20
|
+
@url_owners = nil
|
21
|
+
self.instance = instance
|
22
|
+
|
23
|
+
handle_url!
|
24
|
+
handle_duplicate_url! unless settings.allow_duplicates
|
25
|
+
end
|
26
|
+
|
27
|
+
def initialize_urls!(klass)
|
28
|
+
self.klass = klass
|
29
|
+
klass_previous_instances do |instance|
|
30
|
+
ensure_unique_url_for! instance
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def url_attribute(instance)
|
35
|
+
# Retrieve from database record if there are errors on attribute_to_urlify
|
36
|
+
if !is_new?(instance) && is_present?(instance.errors[settings.attribute_to_urlify])
|
37
|
+
self.instance = instance
|
38
|
+
read_attribute instance_from_db, settings.url_attribute
|
39
|
+
else
|
40
|
+
read_attribute instance, settings.url_attribute
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def self.ensure_loadable
|
45
|
+
raise "The #{self} adapter cannot be loaded" unless loadable?
|
46
|
+
Stringex::ActsAsUrl::Adapter.add_loaded_adapter self
|
47
|
+
end
|
48
|
+
|
49
|
+
def self.loadable?
|
50
|
+
orm_class
|
51
|
+
rescue NameError
|
52
|
+
false
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
def add_new_record_url_owner_conditions
|
58
|
+
return if is_new?(instance)
|
59
|
+
@url_owner_conditions.first << " and #{instance.class.primary_key} != ?"
|
60
|
+
@url_owner_conditions << instance.id
|
61
|
+
end
|
62
|
+
|
63
|
+
def add_scoped_url_owner_conditions
|
64
|
+
return unless settings.scope_for_url
|
65
|
+
@url_owner_conditions.first << " and #{settings.scope_for_url} = ?"
|
66
|
+
@url_owner_conditions << instance.send(settings.scope_for_url)
|
67
|
+
end
|
68
|
+
|
69
|
+
def create_callback
|
70
|
+
if settings.sync_url
|
71
|
+
klass.before_validation :ensure_unique_url
|
72
|
+
else
|
73
|
+
klass.before_validation :ensure_unique_url, :on => :create
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def create_method_to_callback
|
78
|
+
klass.class_eval <<-"END"
|
79
|
+
def #{settings.url_attribute}
|
80
|
+
acts_as_url_configuration.adapter.url_attribute self
|
81
|
+
end
|
82
|
+
END
|
83
|
+
end
|
84
|
+
|
85
|
+
def duplicate_for_base_url(n)
|
86
|
+
"#{base_url}#{settings.duplicate_count_separator}#{n}"
|
87
|
+
end
|
88
|
+
|
89
|
+
def ensure_loadable
|
90
|
+
self.class.ensure_loadable
|
91
|
+
end
|
92
|
+
|
93
|
+
# NOTE: The <tt>instance</tt> here is not the cached instance but a block variable
|
94
|
+
# passed from <tt>klass_previous_instances</tt>, just to be clear
|
95
|
+
def ensure_unique_url_for!(instance)
|
96
|
+
instance.send :ensure_unique_url
|
97
|
+
instance.save
|
98
|
+
end
|
99
|
+
|
100
|
+
def get_base_url_owner_conditions
|
101
|
+
@url_owner_conditions = ["#{settings.url_attribute} LIKE ?", base_url + '%']
|
102
|
+
end
|
103
|
+
|
104
|
+
def handle_duplicate_url!
|
105
|
+
return if url_owners.none?{|owner| url_attribute_for(owner) == base_url}
|
106
|
+
n = 1
|
107
|
+
while url_owners.any?{|owner| url_attribute_for(owner) == duplicate_for_base_url(n)}
|
108
|
+
n = n.succ
|
109
|
+
end
|
110
|
+
write_url_attribute duplicate_for_base_url(n)
|
111
|
+
end
|
112
|
+
|
113
|
+
def handle_url!
|
114
|
+
self.base_url = instance.send(settings.url_attribute)
|
115
|
+
modify_base_url if is_blank?(base_url) || !settings.only_when_blank
|
116
|
+
write_url_attribute base_url
|
117
|
+
end
|
118
|
+
|
119
|
+
def instance_from_db
|
120
|
+
instance.class.find(instance.id)
|
121
|
+
end
|
122
|
+
|
123
|
+
def is_blank?(object)
|
124
|
+
object.blank?
|
125
|
+
end
|
126
|
+
|
127
|
+
def is_new?(object)
|
128
|
+
object.new_record?
|
129
|
+
end
|
130
|
+
|
131
|
+
def is_present?(object)
|
132
|
+
object.present?
|
133
|
+
end
|
134
|
+
|
135
|
+
def loadable?
|
136
|
+
self.class.loadable?
|
137
|
+
end
|
138
|
+
|
139
|
+
def modify_base_url
|
140
|
+
root = instance.send(settings.attribute_to_urlify).to_s
|
141
|
+
self.base_url = root.to_url(configuration.string_extensions_settings)
|
142
|
+
end
|
143
|
+
|
144
|
+
def orm_class
|
145
|
+
self.class.orm_class
|
146
|
+
end
|
147
|
+
|
148
|
+
def read_attribute(instance, attribute)
|
149
|
+
instance.read_attribute attribute
|
150
|
+
end
|
151
|
+
|
152
|
+
def url_attribute_for(object)
|
153
|
+
object.send settings.url_attribute
|
154
|
+
end
|
155
|
+
|
156
|
+
def url_owner_conditions
|
157
|
+
get_base_url_owner_conditions
|
158
|
+
add_new_record_url_owner_conditions
|
159
|
+
add_scoped_url_owner_conditions
|
160
|
+
|
161
|
+
@url_owner_conditions
|
162
|
+
end
|
163
|
+
|
164
|
+
def url_owners
|
165
|
+
@url_owners ||= url_owners_class.unscoped.where(url_owner_conditions).to_a
|
166
|
+
end
|
167
|
+
|
168
|
+
def url_owners_class
|
169
|
+
return instance.class unless settings.enforce_uniqueness_on_sti_base_class
|
170
|
+
|
171
|
+
klass = instance.class
|
172
|
+
while klass.superclass < orm_class
|
173
|
+
klass = klass.superclass
|
174
|
+
end
|
175
|
+
klass
|
176
|
+
end
|
177
|
+
|
178
|
+
def write_attribute(instance, attribute, value)
|
179
|
+
instance.send :write_attribute, attribute, value
|
180
|
+
end
|
181
|
+
|
182
|
+
def write_url_attribute(value)
|
183
|
+
write_attribute instance, settings.url_attribute, value
|
184
|
+
end
|
185
|
+
end
|
186
|
+
end
|
187
|
+
end
|
188
|
+
end
|