bioinform 0.1.11 → 0.1.12

Sign up to get free protection for your applications and to get access to all the features.
Files changed (27) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +1 -1
  3. data/TODO.txt +2 -0
  4. data/bioinform.gemspec +0 -6
  5. data/lib/bioinform/data_models/motif.rb +1 -1
  6. data/lib/bioinform/data_models/pm.rb +2 -1
  7. data/lib/bioinform/data_models/pwm.rb +4 -5
  8. data/lib/bioinform/support/multiline_squish.rb +1 -1
  9. data/lib/bioinform/support/third_part/active_support/core_ext/array/extract_options.rb +29 -0
  10. data/lib/bioinform/support/third_part/active_support/core_ext/hash/indifferent_access.rb +23 -0
  11. data/lib/bioinform/support/third_part/active_support/core_ext/hash/keys.rb +54 -0
  12. data/lib/bioinform/support/third_part/active_support/core_ext/module/attribute_accessors.rb +64 -0
  13. data/lib/bioinform/support/third_part/active_support/core_ext/object/try.rb +57 -0
  14. data/lib/bioinform/support/third_part/active_support/core_ext/string/access.rb +99 -0
  15. data/lib/bioinform/support/third_part/active_support/core_ext/string/behavior.rb +6 -0
  16. data/lib/bioinform/support/third_part/active_support/core_ext/string/filters.rb +49 -0
  17. data/lib/bioinform/support/third_part/active_support/core_ext/string/multibyte.rb +72 -0
  18. data/lib/bioinform/support/third_part/active_support/hash_with_indifferent_access.rb +178 -0
  19. data/lib/bioinform/support/third_part/active_support/multibyte/chars.rb +476 -0
  20. data/lib/bioinform/support/third_part/active_support/multibyte/exceptions.rb +8 -0
  21. data/lib/bioinform/support/third_part/active_support/multibyte/unicode.rb +393 -0
  22. data/lib/bioinform/support/third_part/active_support/multibyte/utils.rb +60 -0
  23. data/lib/bioinform/support/third_part/active_support/multibyte.rb +44 -0
  24. data/lib/bioinform/support.rb +2 -2
  25. data/lib/bioinform/version.rb +1 -1
  26. data/spec/spec_helper.rb +10 -20
  27. metadata +21 -77
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 981be4ea2c5a07809303b9d4ac6a43f5d816e2f9
4
- data.tar.gz: 84aa6ce722f015b357c1ac87cbb0151c8f37a3d2
3
+ metadata.gz: bce1095da9311ea0d77479899064e74e5979c516
4
+ data.tar.gz: 3cdd8b774e7ad7cb930965bb5112c9046552f924
5
5
  SHA512:
6
- metadata.gz: 6b60eaa7651a2880354673d0e9f40e11407c76e76cb8ce526b60283049ca1e9e6dafdf16d258f77146987c507adfcc8d252485a38130278204015cbaaa31c4c3
7
- data.tar.gz: 45d806ad0a1befd381da35381f42d0cfcc1acd361bc35efa7ce9dacb203ab820e38564f8d93134ede8f5c380b1efc34ccc70ab1ee60ff7b3291c5ecc8d4e6c60
6
+ metadata.gz: 40c7a5191db1b2137de61d5e4b038fca9bb2ec14289ef3010a012a1fd1adb8f47d0b650bcd66b04ecfdac44104754a699ff8cf9b3e004e21834c8aeedddf521c
7
+ data.tar.gz: 9c2c7ce0367b030b318117f6fc7b597ed9886312e26c64f0bea32157485896f836cb7f30e91777b5f6df1944ac6cc2d53cf1b5c96019a236036fe306d4b86c9c
data/Gemfile CHANGED
@@ -13,4 +13,4 @@ group :development do
13
13
  gem 'fakefs', '~> 0.4.2'
14
14
  gem 'wdm', :require => false
15
15
  gem 'guard-rspec', '>=2.1.0'
16
- end
16
+ end
data/TODO.txt CHANGED
@@ -1,3 +1,5 @@
1
+ ! Make matrices immutable - it will allow more safe interface and better caching
2
+
1
3
  Collection contain Motif-s, each Motif can contain any of list: pcm,pwm,ppm.
2
4
  Name, background, tags and any other parameters should be removed from PM class to be placed in Motif
3
5
 
data/bioinform.gemspec CHANGED
@@ -15,11 +15,5 @@ Gem::Specification.new do |gem|
15
15
  gem.require_paths = ["lib"]
16
16
  gem.version = Bioinform::VERSION
17
17
 
18
- gem.add_dependency('activesupport', '>= 3.0.0')
19
18
  gem.add_dependency('docopt', '= 0.5.0')
20
-
21
- gem.add_development_dependency('fakefs', '~> 0.4.2')
22
- gem.add_development_dependency('fabrication', '~> 2.5.0')
23
- gem.add_development_dependency('rspec', '>= 2.0')
24
- gem.add_development_dependency('rspec-given', '>= 2.0.0')
25
19
  end
@@ -1,5 +1,5 @@
1
1
  require 'ostruct'
2
- require 'active_support/core_ext/object/try'
2
+ require_relative '../support/third_part/active_support/core_ext/object/try'
3
3
  require_relative '../support/parameters'
4
4
  module Bioinform
5
5
  class Motif
@@ -4,7 +4,8 @@ require_relative '../parsers'
4
4
  require_relative '../formatters'
5
5
 
6
6
  module Bioinform
7
- IndexByLetter = {'A' => 0, 'C' => 1, 'G' => 2, 'T' => 3, A: 0, C: 1, G: 2, T: 3}
7
+ IndexByLetter = { 'A' => 0, 'C' => 1, 'G' => 2, 'T' => 3, A: 0, C: 1, G: 2, T: 3,
8
+ 'a' => 0, 'c' => 1, 'g' => 2, 't' => 3, a: 0, c: 1, g: 2, t: 3}
8
9
  LetterByIndex = {0 => :A, 1 => :C, 2 => :G, 3 => :T}
9
10
 
10
11
  class PM
@@ -19,7 +19,6 @@ module Bioinform
19
19
  end
20
20
 
21
21
  def score(word)
22
- word = word.upcase
23
22
  raise ArgumentError, 'word in PWM#score(word) should have the same length as matrix' unless word.length == length
24
23
  #raise ArgumentError, 'word in PWM#score(word) should have only ACGT-letters' unless word.each_char.all?{|letter| %w{A C G T}.include? letter}
25
24
  (0...length).map do |pos|
@@ -27,11 +26,11 @@ module Bioinform
27
26
  if IndexByLetter[letter]
28
27
  matrix[pos][IndexByLetter[letter]]
29
28
  elsif letter == 'N'
30
- matrix[pos].zip(probability).map{|el, p| el * p}.inject(0.0, &:+)
29
+ matrix[pos].zip(probability).map{|el, p| el * p}.inject(0, &:+)
31
30
  else
32
31
  raise ArgumentError, "word in PWM#score(#{word}) should have only ACGT or N letters"
33
32
  end
34
- end.inject(0.0, &:+)
33
+ end.inject(0, &:+).to_f
35
34
  end
36
35
 
37
36
  def to_pwm
@@ -39,10 +38,10 @@ module Bioinform
39
38
  end
40
39
 
41
40
  def best_score
42
- @matrix.inject(0.0){|sum, col| sum + col.max}
41
+ best_suffix(0)
43
42
  end
44
43
  def worst_score
45
- @matrix.inject(0.0){|sum, col| sum + col.min}
44
+ worst_suffix(0)
46
45
  end
47
46
 
48
47
  # best score of suffix s[i..l]
@@ -1,4 +1,4 @@
1
- require 'active_support/core_ext/string/filters'
1
+ require_relative 'third_part/active_support/core_ext/string/filters'
2
2
  class String
3
3
  def multiline_squish
4
4
  split("\n").map(&:squish).join("\n").gsub(/\A\n+/,'').gsub(/\n+\z/,'')
@@ -0,0 +1,29 @@
1
+ class Hash
2
+ # By default, only instances of Hash itself are extractable.
3
+ # Subclasses of Hash may implement this method and return
4
+ # true to declare themselves as extractable. If a Hash
5
+ # is extractable, Array#extract_options! pops it from
6
+ # the Array when it is the last element of the Array.
7
+ def extractable_options?
8
+ instance_of?(Hash)
9
+ end
10
+ end
11
+
12
+ class Array
13
+ # Extracts options from a set of arguments. Removes and returns the last
14
+ # element in the array if it's a hash, otherwise returns a blank hash.
15
+ #
16
+ # def options(*args)
17
+ # args.extract_options!
18
+ # end
19
+ #
20
+ # options(1, 2) # => {}
21
+ # options(1, 2, :a => :b) # => {:a=>:b}
22
+ def extract_options!
23
+ if last.is_a?(Hash) && last.extractable_options?
24
+ pop
25
+ else
26
+ {}
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,23 @@
1
+ require_relative '../../hash_with_indifferent_access'
2
+
3
+ class Hash
4
+ # Returns an <tt>ActiveSupport::HashWithIndifferentAccess</tt> out of its receiver:
5
+ #
6
+ # {:a => 1}.with_indifferent_access["a"] # => 1
7
+ #
8
+ def with_indifferent_access
9
+ ActiveSupport::HashWithIndifferentAccess.new_from_hash_copying_default(self)
10
+ end
11
+
12
+ # Called when object is nested under an object that receives
13
+ # #with_indifferent_access. This method will be called on the current object
14
+ # by the enclosing object and is aliased to #with_indifferent_access by
15
+ # default. Subclasses of Hash may overwrite this method to return +self+ if
16
+ # converting to an <tt>ActiveSupport::HashWithIndifferentAccess</tt> would not be
17
+ # desirable.
18
+ #
19
+ # b = {:b => 1}
20
+ # {:a => b}.with_indifferent_access["a"] # calls b.nested_under_indifferent_access
21
+ #
22
+ alias nested_under_indifferent_access with_indifferent_access
23
+ end
@@ -0,0 +1,54 @@
1
+ class Hash
2
+ # Return a new hash with all keys converted to strings.
3
+ #
4
+ # { :name => 'Rob', :years => '28' }.stringify_keys
5
+ # #=> { "name" => "Rob", "years" => "28" }
6
+ def stringify_keys
7
+ dup.stringify_keys!
8
+ end
9
+
10
+ # Destructively convert all keys to strings. Same as
11
+ # +stringify_keys+, but modifies +self+.
12
+ def stringify_keys!
13
+ keys.each do |key|
14
+ self[key.to_s] = delete(key)
15
+ end
16
+ self
17
+ end
18
+
19
+ # Return a new hash with all keys converted to symbols, as long as
20
+ # they respond to +to_sym+.
21
+ #
22
+ # { 'name' => 'Rob', 'years' => '28' }.symbolize_keys
23
+ # #=> { :name => "Rob", :years => "28" }
24
+ def symbolize_keys
25
+ dup.symbolize_keys!
26
+ end
27
+
28
+ # Destructively convert all keys to symbols, as long as they respond
29
+ # to +to_sym+. Same as +symbolize_keys+, but modifies +self+.
30
+ def symbolize_keys!
31
+ keys.each do |key|
32
+ self[(key.to_sym rescue key) || key] = delete(key)
33
+ end
34
+ self
35
+ end
36
+
37
+ alias_method :to_options, :symbolize_keys
38
+ alias_method :to_options!, :symbolize_keys!
39
+
40
+ # Validate all keys in a hash match *valid keys, raising ArgumentError on a mismatch.
41
+ # Note that keys are NOT treated indifferently, meaning if you use strings for keys but assert symbols
42
+ # as keys, this will fail.
43
+ #
44
+ # ==== Examples
45
+ # { :name => "Rob", :years => "28" }.assert_valid_keys(:name, :age) # => raises "ArgumentError: Unknown key: years"
46
+ # { :name => "Rob", :age => "28" }.assert_valid_keys("name", "age") # => raises "ArgumentError: Unknown key: name"
47
+ # { :name => "Rob", :age => "28" }.assert_valid_keys(:name, :age) # => passes, raises nothing
48
+ def assert_valid_keys(*valid_keys)
49
+ valid_keys.flatten!
50
+ each_key do |k|
51
+ raise(ArgumentError, "Unknown key: #{k}") unless valid_keys.include?(k)
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,64 @@
1
+ require_relative '../array/extract_options'
2
+
3
+ class Module
4
+ def mattr_reader(*syms)
5
+ options = syms.extract_options!
6
+ syms.each do |sym|
7
+ class_eval(<<-EOS, __FILE__, __LINE__ + 1)
8
+ @@#{sym} = nil unless defined? @@#{sym}
9
+
10
+ def self.#{sym}
11
+ @@#{sym}
12
+ end
13
+ EOS
14
+
15
+ unless options[:instance_reader] == false || options[:instance_accessor] == false
16
+ class_eval(<<-EOS, __FILE__, __LINE__ + 1)
17
+ def #{sym}
18
+ @@#{sym}
19
+ end
20
+ EOS
21
+ end
22
+ end
23
+ end
24
+
25
+ def mattr_writer(*syms)
26
+ options = syms.extract_options!
27
+ syms.each do |sym|
28
+ class_eval(<<-EOS, __FILE__, __LINE__ + 1)
29
+ def self.#{sym}=(obj)
30
+ @@#{sym} = obj
31
+ end
32
+ EOS
33
+
34
+ unless options[:instance_writer] == false || options[:instance_accessor] == false
35
+ class_eval(<<-EOS, __FILE__, __LINE__ + 1)
36
+ def #{sym}=(obj)
37
+ @@#{sym} = obj
38
+ end
39
+ EOS
40
+ end
41
+ end
42
+ end
43
+
44
+ # Extends the module object with module and instance accessors for class attributes,
45
+ # just like the native attr* accessors for instance attributes.
46
+ #
47
+ # module AppConfiguration
48
+ # mattr_accessor :google_api_key
49
+ # self.google_api_key = "123456789"
50
+ #
51
+ # mattr_accessor :paypal_url
52
+ # self.paypal_url = "www.sandbox.paypal.com"
53
+ # end
54
+ #
55
+ # AppConfiguration.google_api_key = "overriding the api key!"
56
+ #
57
+ # To opt out of the instance writer method, pass :instance_writer => false.
58
+ # To opt out of the instance reader method, pass :instance_reader => false.
59
+ # To opt out of both instance methods, pass :instance_accessor => false.
60
+ def mattr_accessor(*syms)
61
+ mattr_reader(*syms)
62
+ mattr_writer(*syms)
63
+ end
64
+ end
@@ -0,0 +1,57 @@
1
+ class Object
2
+ # Invokes the method identified by the symbol +method+, passing it any arguments
3
+ # and/or the block specified, just like the regular Ruby <tt>Object#send</tt> does.
4
+ #
5
+ # *Unlike* that method however, a +NoMethodError+ exception will *not* be raised
6
+ # and +nil+ will be returned instead, if the receiving object is a +nil+ object or NilClass.
7
+ #
8
+ # If try is called without a method to call, it will yield any given block with the object.
9
+ #
10
+ # Please also note that +try+ is defined on +Object+, therefore it won't work with
11
+ # subclasses of +BasicObject+. For example, using try with +SimpleDelegator+ will
12
+ # delegate +try+ to target instead of calling it on delegator itself.
13
+ #
14
+ # ==== Examples
15
+ #
16
+ # Without +try+
17
+ # @person && @person.name
18
+ # or
19
+ # @person ? @person.name : nil
20
+ #
21
+ # With +try+
22
+ # @person.try(:name)
23
+ #
24
+ # +try+ also accepts arguments and/or a block, for the method it is trying
25
+ # Person.try(:find, 1)
26
+ # @people.try(:collect) {|p| p.name}
27
+ #
28
+ # Without a method argument try will yield to the block unless the receiver is nil.
29
+ # @person.try { |p| "#{p.first_name} #{p.last_name}" }
30
+ #--
31
+ # +try+ behaves like +Object#send+, unless called on +NilClass+.
32
+ def try(*a, &b)
33
+ if a.empty? && block_given?
34
+ yield self
35
+ else
36
+ __send__(*a, &b)
37
+ end
38
+ end
39
+ end
40
+
41
+ class NilClass
42
+ # Calling +try+ on +nil+ always returns +nil+.
43
+ # It becomes specially helpful when navigating through associations that may return +nil+.
44
+ #
45
+ # === Examples
46
+ #
47
+ # nil.try(:name) # => nil
48
+ #
49
+ # Without +try+
50
+ # @person && !@person.children.blank? && @person.children.first.name
51
+ #
52
+ # With +try+
53
+ # @person.try(:children).try(:first).try(:name)
54
+ def try(*args)
55
+ nil
56
+ end
57
+ end
@@ -0,0 +1,99 @@
1
+ require_relative "../../multibyte"
2
+
3
+ class String
4
+ unless '1.9'.respond_to?(:force_encoding)
5
+ # Returns the character at the +position+ treating the string as an array (where 0 is the first character).
6
+ #
7
+ # Examples:
8
+ # "hello".at(0) # => "h"
9
+ # "hello".at(4) # => "o"
10
+ # "hello".at(10) # => ERROR if < 1.9, nil in 1.9
11
+ def at(position)
12
+ mb_chars[position, 1].to_s
13
+ end
14
+
15
+ # Returns the remaining of the string from the +position+ treating the string as an array (where 0 is the first character).
16
+ #
17
+ # Examples:
18
+ # "hello".from(0) # => "hello"
19
+ # "hello".from(2) # => "llo"
20
+ # "hello".from(10) # => "" if < 1.9, nil in 1.9
21
+ def from(position)
22
+ mb_chars[position..-1].to_s
23
+ end
24
+
25
+ # Returns the beginning of the string up to the +position+ treating the string as an array (where 0 is the first character).
26
+ #
27
+ # Examples:
28
+ # "hello".to(0) # => "h"
29
+ # "hello".to(2) # => "hel"
30
+ # "hello".to(10) # => "hello"
31
+ def to(position)
32
+ mb_chars[0..position].to_s
33
+ end
34
+
35
+ # Returns the first character of the string or the first +limit+ characters.
36
+ #
37
+ # Examples:
38
+ # "hello".first # => "h"
39
+ # "hello".first(2) # => "he"
40
+ # "hello".first(10) # => "hello"
41
+ def first(limit = 1)
42
+ if limit == 0
43
+ ''
44
+ elsif limit >= size
45
+ self
46
+ else
47
+ mb_chars[0...limit].to_s
48
+ end
49
+ end
50
+
51
+ # Returns the last character of the string or the last +limit+ characters.
52
+ #
53
+ # Examples:
54
+ # "hello".last # => "o"
55
+ # "hello".last(2) # => "lo"
56
+ # "hello".last(10) # => "hello"
57
+ def last(limit = 1)
58
+ if limit == 0
59
+ ''
60
+ elsif limit >= size
61
+ self
62
+ else
63
+ mb_chars[(-limit)..-1].to_s
64
+ end
65
+ end
66
+ else
67
+ def at(position)
68
+ self[position]
69
+ end
70
+
71
+ def from(position)
72
+ self[position..-1]
73
+ end
74
+
75
+ def to(position)
76
+ self[0..position]
77
+ end
78
+
79
+ def first(limit = 1)
80
+ if limit == 0
81
+ ''
82
+ elsif limit >= size
83
+ self
84
+ else
85
+ to(limit - 1)
86
+ end
87
+ end
88
+
89
+ def last(limit = 1)
90
+ if limit == 0
91
+ ''
92
+ elsif limit >= size
93
+ self
94
+ else
95
+ from(-limit)
96
+ end
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,6 @@
1
+ class String
2
+ # Enable more predictable duck-typing on String-like classes. See <tt>Object#acts_like?</tt>.
3
+ def acts_like_string?
4
+ true
5
+ end
6
+ end
@@ -0,0 +1,49 @@
1
+ require_relative 'multibyte'
2
+
3
+ class String
4
+ # Returns the string, first removing all whitespace on both ends of
5
+ # the string, and then changing remaining consecutive whitespace
6
+ # groups into one space each.
7
+ #
8
+ # Examples:
9
+ # %{ Multi-line
10
+ # string }.squish # => "Multi-line string"
11
+ # " foo bar \n \t boo".squish # => "foo bar boo"
12
+ def squish
13
+ dup.squish!
14
+ end
15
+
16
+ # Performs a destructive squish. See String#squish.
17
+ def squish!
18
+ strip!
19
+ gsub!(/\s+/, ' ')
20
+ self
21
+ end
22
+
23
+ # Truncates a given +text+ after a given <tt>length</tt> if +text+ is longer than <tt>length</tt>:
24
+ #
25
+ # "Once upon a time in a world far far away".truncate(27)
26
+ # # => "Once upon a time in a wo..."
27
+ #
28
+ # Pass a <tt>:separator</tt> to truncate +text+ at a natural break:
29
+ #
30
+ # "Once upon a time in a world far far away".truncate(27, :separator => ' ')
31
+ # # => "Once upon a time in a..."
32
+ #
33
+ # The last characters will be replaced with the <tt>:omission</tt> string (defaults to "...")
34
+ # for a total length not exceeding <tt>:length</tt>:
35
+ #
36
+ # "And they found that many people were sleeping better.".truncate(25, :omission => "... (continued)")
37
+ # # => "And they f... (continued)"
38
+ def truncate(length, options = {})
39
+ text = self.dup
40
+ options[:omission] ||= "..."
41
+
42
+ length_with_room_for_omission = length - options[:omission].mb_chars.length
43
+ chars = text.mb_chars
44
+ stop = options[:separator] ?
45
+ (chars.rindex(options[:separator].mb_chars, length_with_room_for_omission) || length_with_room_for_omission) : length_with_room_for_omission
46
+
47
+ (chars.length > length ? chars[0...stop] + options[:omission] : text).to_s
48
+ end
49
+ end
@@ -0,0 +1,72 @@
1
+ # encoding: utf-8
2
+ require_relative '../../multibyte'
3
+
4
+ class String
5
+ if RUBY_VERSION >= "1.9"
6
+ # == Multibyte proxy
7
+ #
8
+ # +mb_chars+ is a multibyte safe proxy for string methods.
9
+ #
10
+ # In Ruby 1.8 and older it creates and returns an instance of the ActiveSupport::Multibyte::Chars class which
11
+ # encapsulates the original string. A Unicode safe version of all the String methods are defined on this proxy
12
+ # class. If the proxy class doesn't respond to a certain method, it's forwarded to the encapsulated string.
13
+ #
14
+ # name = 'Claus Müller'
15
+ # name.reverse # => "rell??M sualC"
16
+ # name.length # => 13
17
+ #
18
+ # name.mb_chars.reverse.to_s # => "rellüM sualC"
19
+ # name.mb_chars.length # => 12
20
+ #
21
+ # In Ruby 1.9 and newer +mb_chars+ returns +self+ because String is (mostly) encoding aware. This means that
22
+ # it becomes easy to run one version of your code on multiple Ruby versions.
23
+ #
24
+ # == Method chaining
25
+ #
26
+ # All the methods on the Chars proxy which normally return a string will return a Chars object. This allows
27
+ # method chaining on the result of any of these methods.
28
+ #
29
+ # name.mb_chars.reverse.length # => 12
30
+ #
31
+ # == Interoperability and configuration
32
+ #
33
+ # The Chars object tries to be as interchangeable with String objects as possible: sorting and comparing between
34
+ # String and Char work like expected. The bang! methods change the internal string representation in the Chars
35
+ # object. Interoperability problems can be resolved easily with a +to_s+ call.
36
+ #
37
+ # For more information about the methods defined on the Chars proxy see ActiveSupport::Multibyte::Chars. For
38
+ # information about how to change the default Multibyte behavior see ActiveSupport::Multibyte.
39
+ def mb_chars
40
+ if ActiveSupport::Multibyte.proxy_class.consumes?(self)
41
+ ActiveSupport::Multibyte.proxy_class.new(self)
42
+ else
43
+ self
44
+ end
45
+ end
46
+
47
+ def is_utf8?
48
+ case encoding
49
+ when Encoding::UTF_8
50
+ valid_encoding?
51
+ when Encoding::ASCII_8BIT, Encoding::US_ASCII
52
+ dup.force_encoding(Encoding::UTF_8).valid_encoding?
53
+ else
54
+ false
55
+ end
56
+ end
57
+ else
58
+ def mb_chars
59
+ if ActiveSupport::Multibyte.proxy_class.wants?(self)
60
+ ActiveSupport::Multibyte.proxy_class.new(self)
61
+ else
62
+ self
63
+ end
64
+ end
65
+
66
+ # Returns true if the string has UTF-8 semantics (a String used for purely byte resources is unlikely to have
67
+ # them), returns false otherwise.
68
+ def is_utf8?
69
+ ActiveSupport::Multibyte::Chars.consumes?(self)
70
+ end
71
+ end
72
+ end