unicode-multibyte 0.1.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.
data/.Rakefile.swp ADDED
Binary file
data/.project ADDED
@@ -0,0 +1,17 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <projectDescription>
3
+ <name>UnicodeChars</name>
4
+ <comment></comment>
5
+ <projects>
6
+ </projects>
7
+ <buildSpec>
8
+ <buildCommand>
9
+ <name>org.rubypeople.rdt.core.rubybuilder</name>
10
+ <arguments>
11
+ </arguments>
12
+ </buildCommand>
13
+ </buildSpec>
14
+ <natures>
15
+ <nature>org.rubypeople.rdt.core.rubynature</nature>
16
+ </natures>
17
+ </projectDescription>
Binary file
data/Rakefile ADDED
@@ -0,0 +1,18 @@
1
+ require "bundler"
2
+ require "rake/testtask"
3
+
4
+ Rake::TestTask.new do |t|
5
+ t.libs << "test"
6
+ t.test_files = Dir.glob("test/**/*test.rb")
7
+ t.verbose = true
8
+ end
9
+
10
+ desc 'Builds the gem'
11
+ task :build do
12
+ sh "gem build unicode-multibyte.gemspec"
13
+ end
14
+
15
+ desc 'Builds and installs the gem'
16
+ task :install => :build do
17
+ sh "gem install unicode-multibyte-0.1.0"
18
+ end
@@ -0,0 +1,155 @@
1
+ require File.join(File.dirname(__FILE__), "handlers", "utf8_handler")
2
+ require File.join(File.dirname(__FILE__), "handlers", "passthru_handler")
3
+
4
+ # Encapsulates all the functionality related to the Chars proxy.
5
+ module UnicodeMultibyte::Multibyte
6
+ # Chars enables you to work transparently with multibyte encodings in the Ruby String class without having extensive
7
+ # knowledge about the encoding. A Chars object accepts a string upon initialization and proxies String methods in an
8
+ # encoding safe manner. All the normal String methods are also implemented on the proxy.
9
+ #
10
+ # String methods are proxied through the Chars object, and can be accessed through the +chars+ method. Methods
11
+ # which would normally return a String object now return a Chars object so methods can be chained.
12
+ #
13
+ # "The Perfect String ".chars.downcase.strip.normalize #=> "the perfect string"
14
+ #
15
+ # Chars objects are perfectly interchangeable with String objects as long as no explicit class checks are made.
16
+ # If certain methods do explicitly check the class, call +to_s+ before you pass chars objects to them.
17
+ #
18
+ # bad.explicit_checking_method "T".chars.downcase.to_s
19
+ #
20
+ # The actual operations on the string are delegated to handlers. Theoretically handlers can be implemented for
21
+ # any encoding, but the default handler handles UTF-8. This handler is set during initialization, if you want to
22
+ # use you own handler, you can set it on the Chars class. Look at the UTF8Handler source for an example how to
23
+ # implement your own handler. If you your own handler to work on anything but UTF-8 you probably also
24
+ # want to override Chars#handler.
25
+ #
26
+ # UnicodeMultibyte::Multibyte::Chars.handler = MyHandler
27
+ #
28
+ # Note that a few methods are defined on Chars instead of the handler because they are defined on Object or Kernel
29
+ # and method_missing can't catch them.
30
+ class Chars
31
+
32
+ attr_reader :string # The contained string
33
+ alias_method :to_s, :string
34
+
35
+ include Comparable
36
+
37
+ # The magic method to make String and Chars comparable
38
+ def to_str
39
+ # Using any other ways of overriding the String itself will lead you all the way from infinite loops to
40
+ # core dumps. Don't go there.
41
+ @string
42
+ end
43
+
44
+ # Makes unicode string look like a string in the console
45
+ def inspect
46
+ @string.inspect
47
+ end
48
+
49
+ def is_a?(type)
50
+ if type == String
51
+ true
52
+ else
53
+ super
54
+ end
55
+ end
56
+
57
+ # Fix [] for single numbers
58
+ def [](num)
59
+ if num.is_a?(Fixnum)
60
+ self[num..num]
61
+ else
62
+ super
63
+ end
64
+ end
65
+
66
+ # Create a new Chars instance.
67
+ def initialize(str)
68
+ @string = (str.string rescue str)
69
+ end
70
+
71
+ def each &block
72
+ split(//).each(&block)
73
+ end
74
+
75
+ # Returns -1, 0 or +1 depending on whether the Chars object is to be sorted before, equal or after the
76
+ # object on the right side of the operation. It accepts any object that implements +to_s+. See String.<=>
77
+ # for more details.
78
+ def <=>(other); @string <=> other.to_s; end
79
+
80
+ # Works just like String#split, with the exception that the items in the resulting list are Chars
81
+ # instances instead of String. This makes chaining methods easier.
82
+ def split(*args)
83
+ @string.split(*args).map { |i| i.chars }
84
+ end
85
+
86
+ # Gsub works exactly the same as gsub on a normal string.
87
+ def gsub(*a, &b); @string.gsub(*a, &b).chars; end
88
+
89
+ # Like String.=~ only it returns the character offset (in codepoints) instead of the byte offset.
90
+ def =~(other)
91
+ handler.translate_offset(@string, @string =~ other)
92
+ end
93
+
94
+ # Try to forward all undefined methods to the handler, when a method is not defined on the handler, send it to
95
+ # the contained string. Method_missing is also responsible for making the bang! methods destructive.
96
+ def method_missing(m, *a, &b)
97
+ begin
98
+ # Simulate methods with a ! at the end because we can't touch the enclosed string from the handlers.
99
+ if m.to_s =~ /^(.*)\!$/
100
+ result = handler.send($1, @string, *a, &b)
101
+ if result == @string
102
+ result = nil
103
+ else
104
+ @string.replace result
105
+ end
106
+ else
107
+ result = handler.send(m, @string, *a, &b)
108
+ end
109
+ rescue NoMethodError
110
+ result = @string.send(m, *a, &b)
111
+ rescue Handlers::EncodingError
112
+ @string.replace handler.tidy_bytes(@string)
113
+ retry
114
+ end
115
+
116
+ if result.kind_of?(String)
117
+ result.chars
118
+ else
119
+ result
120
+ end
121
+ end
122
+
123
+ # Set the handler class for the Char objects.
124
+ def self.handler=(klass)
125
+ @@handler = klass
126
+ end
127
+
128
+ # Returns the proper handler for the contained string depending on $KCODE and the encoding of the string. This
129
+ # method is used internally to always redirect messages to the proper classes depending on the context.
130
+ def handler
131
+ if utf8_pragma?
132
+ @@handler
133
+ else
134
+ UnicodeMultibyte::Multibyte::Handlers::PassthruHandler
135
+ end
136
+ end
137
+
138
+ private
139
+
140
+ # +utf8_pragma+ checks if it can send this string to the handlers. It makes sure @string isn't nil and $KCODE is
141
+ # set to 'UTF8'.
142
+ def utf8_pragma?
143
+ !@string.nil? && ($KCODE == 'UTF8')
144
+ end
145
+ end
146
+ end
147
+
148
+ # When we can load the utf8proc library, override normalization with the faster methods
149
+ begin
150
+ require 'utf8proc_native'
151
+ require File.join(File.dirname(__FILE__), "handlers", "utf8_handler_proc")
152
+ UnicodeMultibyte::Multibyte::Chars.handler = UnicodeMultibyte::Multibyte::Handlers::UTF8HandlerProc
153
+ rescue LoadError
154
+ UnicodeMultibyte::Multibyte::Chars.handler = UnicodeMultibyte::Multibyte::Handlers::UTF8Handler
155
+ end
@@ -0,0 +1,146 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'open-uri'
4
+ require 'tmpdir'
5
+
6
+ module UnicodeMultibyte::Multibyte::Handlers #:nodoc:
7
+ class UnicodeDatabase #:nodoc:
8
+ def self.load
9
+ [Hash.new(Codepoint.new),[],{},{}]
10
+ end
11
+ end
12
+
13
+ class UnicodeTableGenerator #:nodoc:
14
+ BASE_URI = "http://www.unicode.org/Public/#{UnicodeMultibyte::Multibyte::UNICODE_VERSION}/ucd/"
15
+ SOURCES = {
16
+ :codepoints => BASE_URI + 'UnicodeData.txt',
17
+ :composition_exclusion => BASE_URI + 'CompositionExclusions.txt',
18
+ :grapheme_break_property => BASE_URI + 'auxiliary/GraphemeBreakProperty.txt',
19
+ :cp1252 => 'http://unicode.org/Public/MAPPINGS/VENDORS/MICSFT/WINDOWS/CP1252.TXT'
20
+ }
21
+
22
+ def initialize
23
+ @ucd = UnicodeDatabase.new
24
+
25
+ default = Codepoint.new
26
+ default.combining_class = 0
27
+ default.uppercase_mapping = 0
28
+ default.lowercase_mapping = 0
29
+ @ucd.codepoints = Hash.new(default)
30
+
31
+ @ucd.composition_exclusion = []
32
+ @ucd.composition_map = {}
33
+ @ucd.boundary = {}
34
+ @ucd.cp1252 = {}
35
+ end
36
+
37
+ def parse_codepoints(line)
38
+ codepoint = Codepoint.new
39
+ raise "Could not parse input." unless line =~ /^
40
+ ([0-9A-F]+); # code
41
+ ([^;]+); # name
42
+ ([A-Z]+); # general category
43
+ ([0-9]+); # canonical combining class
44
+ ([A-Z]+); # bidi class
45
+ (<([A-Z]*)>)? # decomposition type
46
+ ((\ ?[0-9A-F]+)*); # decompomposition mapping
47
+ ([0-9]*); # decimal digit
48
+ ([0-9]*); # digit
49
+ ([^;]*); # numeric
50
+ ([YN]*); # bidi mirrored
51
+ ([^;]*); # unicode 1.0 name
52
+ ([^;]*); # iso comment
53
+ ([0-9A-F]*); # simple uppercase mapping
54
+ ([0-9A-F]*); # simple lowercase mapping
55
+ ([0-9A-F]*)$/ix # simple titlecase mapping
56
+ codepoint.code = $1.hex
57
+ #codepoint.name = $2
58
+ #codepoint.category = $3
59
+ codepoint.combining_class = Integer($4)
60
+ #codepoint.bidi_class = $5
61
+ codepoint.decomp_type = $7
62
+ codepoint.decomp_mapping = ($8=='') ? nil : $8.split.collect { |element| element.hex }
63
+ #codepoint.bidi_mirrored = ($13=='Y') ? true : false
64
+ codepoint.uppercase_mapping = ($16=='') ? 0 : $16.hex
65
+ codepoint.lowercase_mapping = ($17=='') ? 0 : $17.hex
66
+ #codepoint.titlecase_mapping = ($18=='') ? nil : $18.hex
67
+ @ucd.codepoints[codepoint.code] = codepoint
68
+ end
69
+
70
+ def parse_grapheme_break_property(line)
71
+ if line =~ /^([0-9A-F\.]+)\s*;\s*([\w]+)\s*#/
72
+ type = $2.downcase.intern
73
+ @ucd.boundary[type] ||= []
74
+ if $1.include? '..'
75
+ parts = $1.split '..'
76
+ @ucd.boundary[type] << (parts[0].hex..parts[1].hex)
77
+ else
78
+ @ucd.boundary[type] << $1.hex
79
+ end
80
+ end
81
+ end
82
+
83
+ def parse_composition_exclusion(line)
84
+ if line =~ /^([0-9A-F]+)/i
85
+ @ucd.composition_exclusion << $1.hex
86
+ end
87
+ end
88
+
89
+ def parse_cp1252(line)
90
+ if line =~ /^([0-9A-Fx]+)\s([0-9A-Fx]+)/i
91
+ @ucd.cp1252[$1.hex] = $2.hex
92
+ end
93
+ end
94
+
95
+ def create_composition_map
96
+ @ucd.codepoints.each do |_, cp|
97
+ if !cp.nil? and cp.combining_class == 0 and cp.decomp_type.nil? and !cp.decomp_mapping.nil? and cp.decomp_mapping.length == 2 and @ucd[cp.decomp_mapping[0]].combining_class == 0 and !@ucd.composition_exclusion.include?(cp.code)
98
+ @ucd.composition_map[cp.decomp_mapping[0]] ||= {}
99
+ @ucd.composition_map[cp.decomp_mapping[0]][cp.decomp_mapping[1]] = cp.code
100
+ end
101
+ end
102
+ end
103
+
104
+ def normalize_boundary_map
105
+ @ucd.boundary.each do |k,v|
106
+ if [:lf, :cr].include? k
107
+ @ucd.boundary[k] = v[0]
108
+ end
109
+ end
110
+ end
111
+
112
+ def parse
113
+ SOURCES.each do |type, url|
114
+ filename = File.join(Dir.tmpdir, "#{url.split('/').last}")
115
+ unless File.exist?(filename)
116
+ $stderr.puts "Downloading #{url.split('/').last}"
117
+ File.open(filename, 'wb') do |target|
118
+ open(url) do |source|
119
+ source.each_line { |line| target.write line }
120
+ end
121
+ end
122
+ end
123
+ File.open(filename) do |file|
124
+ file.each_line { |line| send "parse_#{type}".intern, line }
125
+ end
126
+ end
127
+ create_composition_map
128
+ normalize_boundary_map
129
+ end
130
+
131
+ def dump_to(filename)
132
+ File.open(filename, 'wb') do |f|
133
+ f.write Marshal.dump([@ucd.codepoints, @ucd.composition_exclusion, @ucd.composition_map, @ucd.boundary, @ucd.cp1252])
134
+ end
135
+ end
136
+ end
137
+ end
138
+
139
+ if __FILE__ == $0
140
+ filename = UnicodeMultibyte::Multibyte::Handlers::UnicodeDatabase.filename
141
+ generator = UnicodeMultibyte::Multibyte::Handlers::UnicodeTableGenerator.new
142
+ generator.parse
143
+ print "Writing to: #{filename}"
144
+ generator.dump_to filename
145
+ puts " (#{File.size(filename)} bytes)"
146
+ end
@@ -0,0 +1,9 @@
1
+ # Chars uses this handler when $KCODE is not set to 'UTF8'. Because this handler doesn't define any methods all call
2
+ # will be forwarded to String.
3
+ class UnicodeMultibyte::Multibyte::Handlers::PassthruHandler
4
+
5
+ # Return the original byteoffset
6
+ def self.translate_offset(string, byte_offset) #:nodoc:
7
+ byte_offset
8
+ end
9
+ end