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.
Files changed (63) hide show
  1. checksums.yaml +15 -0
  2. data/Gemfile +16 -0
  3. data/Gemfile.lock +74 -0
  4. data/README.rdoc +22 -1
  5. data/Rakefile +46 -223
  6. data/VERSION +1 -0
  7. data/init.rb +1 -0
  8. data/lib/stringex.rb +11 -3
  9. data/lib/stringex/acts_as_url.rb +49 -97
  10. data/lib/stringex/acts_as_url/adapter.rb +26 -0
  11. data/lib/stringex/acts_as_url/adapter/active_record.rb +23 -0
  12. data/lib/stringex/acts_as_url/adapter/base.rb +188 -0
  13. data/lib/stringex/acts_as_url/adapter/data_mapper.rb +67 -0
  14. data/lib/stringex/acts_as_url/adapter/mongoid.rb +36 -0
  15. data/lib/stringex/configuration.rb +4 -0
  16. data/lib/stringex/configuration/acts_as_url.rb +44 -0
  17. data/lib/stringex/configuration/base.rb +58 -0
  18. data/lib/stringex/configuration/configurator.rb +25 -0
  19. data/lib/stringex/configuration/string_extensions.rb +19 -0
  20. data/lib/stringex/localization.rb +98 -0
  21. data/lib/stringex/localization/backend/i18n.rb +53 -0
  22. data/lib/stringex/localization/backend/internal.rb +51 -0
  23. data/lib/stringex/localization/conversion_expressions.rb +148 -0
  24. data/lib/stringex/localization/converter.rb +121 -0
  25. data/lib/stringex/localization/default_conversions.rb +88 -0
  26. data/lib/stringex/rails/railtie.rb +10 -0
  27. data/lib/stringex/string_extensions.rb +153 -208
  28. data/lib/stringex/unidecoder.rb +6 -101
  29. data/lib/stringex/unidecoder_data/x00.yml +1 -1
  30. data/lib/stringex/unidecoder_data/x02.yml +5 -5
  31. data/lib/stringex/unidecoder_data/x05.yml +1 -1
  32. data/lib/stringex/unidecoder_data/x06.yml +1 -1
  33. data/lib/stringex/unidecoder_data/x07.yml +3 -3
  34. data/lib/stringex/unidecoder_data/x09.yml +1 -1
  35. data/lib/stringex/unidecoder_data/x0e.yml +2 -2
  36. data/lib/stringex/unidecoder_data/x1f.yml +2 -2
  37. data/lib/stringex/unidecoder_data/x20.yml +1 -1
  38. data/lib/stringex/unidecoder_data/xfb.yml +1 -1
  39. data/lib/stringex/unidecoder_data/xff.yml +1 -1
  40. data/lib/stringex/version.rb +8 -0
  41. data/locales/da.yml +73 -0
  42. data/locales/en.yml +66 -0
  43. data/stringex.gemspec +77 -18
  44. data/test/acts_as_url/adapter/active_record.rb +72 -0
  45. data/test/acts_as_url/adapter/data_mapper.rb +82 -0
  46. data/test/acts_as_url/adapter/mongoid.rb +73 -0
  47. data/test/acts_as_url_configuration_test.rb +51 -0
  48. data/test/acts_as_url_integration_test.rb +271 -0
  49. data/test/localization/da_test.rb +117 -0
  50. data/test/localization/default_test.rb +113 -0
  51. data/test/localization/en_test.rb +117 -0
  52. data/test/localization_test.rb +123 -0
  53. data/test/redcloth_to_html_test.rb +37 -0
  54. data/test/string_extensions_test.rb +59 -91
  55. data/test/test_helper.rb +2 -0
  56. data/test/unicode_point_suite/basic_greek_test.rb +113 -0
  57. data/test/unicode_point_suite/basic_latin_test.rb +142 -0
  58. data/test/unicode_point_suite/codepoint_test_helper.rb +32 -0
  59. data/test/unidecoder/bad_localization.yml +1 -0
  60. data/test/unidecoder/localization.yml +4 -0
  61. data/test/unidecoder_test.rb +3 -5
  62. metadata +145 -37
  63. 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,4 @@
1
+ require "stringex/configuration/base"
2
+ require "stringex/configuration/configurator"
3
+ require "stringex/configuration/acts_as_url"
4
+ require "stringex/configuration/string_extensions"
@@ -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