stringex 1.5.1 → 2.0.0

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