mattetti-multibyte 0.0.1

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/History.txt ADDED
@@ -0,0 +1,4 @@
1
+ == 0.0.1 2008-05-28
2
+
3
+ * 1 major enhancement:
4
+ * Initial release
data/License.txt ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2005-2008 David Heinemeier Hansson
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Manifest.txt ADDED
@@ -0,0 +1,28 @@
1
+ History.txt
2
+ License.txt
3
+ Manifest.txt
4
+ README.txt
5
+ Rakefile
6
+ config/hoe.rb
7
+ config/requirements.rb
8
+ lib/multibyte.rb
9
+ lib/multibyte/chars.rb
10
+ lib/multibyte/generators/generate_tables.rb
11
+ lib/multibyte/handlers/passthru_handler.rb
12
+ lib/multibyte/handlers/unicode_tables.dat
13
+ lib/multibyte/handlers/utf8_handler.rb
14
+ lib/multibyte/handlers/utf8_handler_proc.rb
15
+ lib/multibyte/version.rb
16
+ lib/unicode.rb
17
+ log/debug.log
18
+ script/destroy
19
+ script/generate
20
+ script/txt2html
21
+ setup.rb
22
+ spec/multibyte_spec.rb
23
+ spec/spec.opts
24
+ spec/spec_helper.rb
25
+ tasks/deployment.rake
26
+ tasks/environment.rake
27
+ tasks/rspec.rake
28
+ tasks/website.rake
data/README.txt ADDED
@@ -0,0 +1,19 @@
1
+ Multibyte
2
+ ==========
3
+
4
+ Multibyte is extracted directly from Rails's ActiveSupport and offers Multibyte safety to Ruby.
5
+
6
+ Usage
7
+ =====
8
+
9
+ >> require 'multibyte'
10
+ => true
11
+ >> "新通訊錄".chars[0..1]
12
+ => #<Multibyte::Chars:0x127e3d0 @string="新通">
13
+ >> "新通訊錄".chars[0..1].to_s
14
+ => "新通"
15
+
16
+ Installation:
17
+ =============
18
+
19
+ $ sudo gem install mattetti-multibyte --source=http://gems.github.com
data/Rakefile ADDED
@@ -0,0 +1,4 @@
1
+ require 'config/requirements'
2
+ require 'config/hoe' # setup Hoe + all gem configuration
3
+
4
+ Dir['tasks/**/*.rake'].each { |rake| load rake }
data/config/hoe.rb ADDED
@@ -0,0 +1,70 @@
1
+ require 'multibyte/version'
2
+
3
+ AUTHOR = 'FIXME full name' # can also be an array of Authors
4
+ EMAIL = "FIXME email"
5
+ DESCRIPTION = "description of gem"
6
+ GEM_NAME = 'multibyte' # what ppl will type to install your gem
7
+ RUBYFORGE_PROJECT = 'multibyte' # The unix name for your project
8
+ HOMEPATH = "http://#{RUBYFORGE_PROJECT}.rubyforge.org"
9
+ DOWNLOAD_PATH = "http://rubyforge.org/projects/#{RUBYFORGE_PROJECT}"
10
+
11
+ @config_file = "~/.rubyforge/user-config.yml"
12
+ @config = nil
13
+ RUBYFORGE_USERNAME = "unknown"
14
+ def rubyforge_username
15
+ unless @config
16
+ begin
17
+ @config = YAML.load(File.read(File.expand_path(@config_file)))
18
+ rescue
19
+ puts <<-EOS
20
+ ERROR: No rubyforge config file found: #{@config_file}
21
+ Run 'rubyforge setup' to prepare your env for access to Rubyforge
22
+ - See http://newgem.rubyforge.org/rubyforge.html for more details
23
+ EOS
24
+ exit
25
+ end
26
+ end
27
+ RUBYFORGE_USERNAME.replace @config["username"]
28
+ end
29
+
30
+
31
+ REV = nil
32
+ # UNCOMMENT IF REQUIRED:
33
+ # REV = `svn info`.each {|line| if line =~ /^Revision:/ then k,v = line.split(': '); break v.chomp; else next; end} rescue nil
34
+ VERS = Multibyte::VERSION::STRING + (REV ? ".#{REV}" : "")
35
+ RDOC_OPTS = ['--quiet', '--title', 'multibyte documentation',
36
+ "--opname", "index.html",
37
+ "--line-numbers",
38
+ "--main", "README",
39
+ "--inline-source"]
40
+
41
+ class Hoe
42
+ def extra_deps
43
+ @extra_deps.reject! { |x| Array(x).first == 'hoe' }
44
+ @extra_deps
45
+ end
46
+ end
47
+
48
+ # Generate all the Rake tasks
49
+ # Run 'rake -T' to see list of generated tasks (from gem root directory)
50
+ hoe = Hoe.new(GEM_NAME, VERS) do |p|
51
+ p.developer(AUTHOR, EMAIL)
52
+ p.description = DESCRIPTION
53
+ p.summary = DESCRIPTION
54
+ p.url = HOMEPATH
55
+ p.rubyforge_name = RUBYFORGE_PROJECT if RUBYFORGE_PROJECT
56
+ p.test_globs = ["test/**/test_*.rb"]
57
+ p.clean_globs |= ['**/.*.sw?', '*.gem', '.config', '**/.DS_Store'] #An array of file patterns to delete on clean.
58
+
59
+ # == Optional
60
+ p.changes = p.paragraphs_of("History.txt", 0..1).join("\n\n")
61
+ #p.extra_deps = [] # An array of rubygem dependencies [name, version], e.g. [ ['active_support', '>= 1.3.1'] ]
62
+
63
+ #p.spec_extras = {} # A hash of extra values to set in the gemspec.
64
+
65
+ end
66
+
67
+ CHANGES = hoe.paragraphs_of('History.txt', 0..1).join("\\n\\n")
68
+ PATH = (RUBYFORGE_PROJECT == GEM_NAME) ? RUBYFORGE_PROJECT : "#{RUBYFORGE_PROJECT}/#{GEM_NAME}"
69
+ hoe.remote_rdoc_dir = File.join(PATH.gsub(/^#{RUBYFORGE_PROJECT}\/?/,''), 'rdoc')
70
+ hoe.rsync_args = '-av --delete --ignore-errors'
@@ -0,0 +1,17 @@
1
+ require 'fileutils'
2
+ include FileUtils
3
+
4
+ require 'rubygems'
5
+ %w[rake hoe newgem rubigen].each do |req_gem|
6
+ begin
7
+ require req_gem
8
+ rescue LoadError
9
+ puts "This Rakefile requires the '#{req_gem}' RubyGem."
10
+ puts "Installation: gem install #{req_gem} -y"
11
+ exit
12
+ end
13
+ end
14
+
15
+ $:.unshift(File.join(File.dirname(__FILE__), %w[.. lib]))
16
+
17
+ require 'multibyte'
data/lib/multibyte.rb ADDED
@@ -0,0 +1,15 @@
1
+ $:.unshift File.dirname(__FILE__)
2
+ $KCODE = 'u'
3
+
4
+ module Multibyte #:nodoc:
5
+ DEFAULT_NORMALIZATION_FORM = :kc
6
+ NORMALIZATIONS_FORMS = [:c, :kc, :d, :kd]
7
+ UNICODE_VERSION = '5.0.0'
8
+ end
9
+
10
+ require 'multibyte/chars'
11
+ require 'unicode'
12
+
13
+ class String #:nodoc:
14
+ include CoreExtensions::String::Unicode
15
+ end
@@ -0,0 +1,135 @@
1
+ require 'multibyte/handlers/utf8_handler'
2
+ require 'multibyte/handlers/passthru_handler'
3
+
4
+ # Encapsulates all the functionality related to the Chars proxy.
5
+ module Multibyte #:nodoc:
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
+ # 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
+ # Make duck-typing with String possible
45
+ def respond_to?(method)
46
+ super || @string.respond_to?(method) || handler.respond_to?(method) ||
47
+ (method.to_s =~ /(.*)!/ && handler.respond_to?($1)) || false
48
+ end
49
+
50
+ # Create a new Chars instance.
51
+ def initialize(str)
52
+ @string = str.respond_to?(:string) ? str.string : str
53
+ end
54
+
55
+ # Returns -1, 0 or +1 depending on whether the Chars object is to be sorted before, equal or after the
56
+ # object on the right side of the operation. It accepts any object that implements +to_s+. See String.<=>
57
+ # for more details.
58
+ def <=>(other); @string <=> other.to_s; end
59
+
60
+ # Works just like String#split, with the exception that the items in the resulting list are Chars
61
+ # instances instead of String. This makes chaining methods easier.
62
+ def split(*args)
63
+ @string.split(*args).map { |i| i.chars }
64
+ end
65
+
66
+ # Gsub works exactly the same as gsub on a normal string.
67
+ def gsub(*a, &b); @string.gsub(*a, &b).chars; end
68
+
69
+ # Like String.=~ only it returns the character offset (in codepoints) instead of the byte offset.
70
+ def =~(other)
71
+ handler.translate_offset(@string, @string =~ other)
72
+ end
73
+
74
+ # Try to forward all undefined methods to the handler, when a method is not defined on the handler, send it to
75
+ # the contained string. Method_missing is also responsible for making the bang! methods destructive.
76
+ def method_missing(m, *a, &b)
77
+ begin
78
+ # Simulate methods with a ! at the end because we can't touch the enclosed string from the handlers.
79
+ if m.to_s =~ /^(.*)\!$/ && handler.respond_to?($1)
80
+ result = handler.send($1, @string, *a, &b)
81
+ if result == @string
82
+ result = nil
83
+ else
84
+ @string.replace result
85
+ end
86
+ elsif handler.respond_to?(m)
87
+ result = handler.send(m, @string, *a, &b)
88
+ else
89
+ result = @string.send(m, *a, &b)
90
+ end
91
+ rescue Handlers::EncodingError
92
+ @string.replace handler.tidy_bytes(@string)
93
+ retry
94
+ end
95
+
96
+ if result.kind_of?(String)
97
+ result.chars
98
+ else
99
+ result
100
+ end
101
+ end
102
+
103
+ # Set the handler class for the Char objects.
104
+ def self.handler=(klass)
105
+ @@handler = klass
106
+ end
107
+
108
+ # Returns the proper handler for the contained string depending on $KCODE and the encoding of the string. This
109
+ # method is used internally to always redirect messages to the proper classes depending on the context.
110
+ def handler
111
+ if utf8_pragma?
112
+ @@handler
113
+ else
114
+ Multibyte::Handlers::PassthruHandler
115
+ end
116
+ end
117
+
118
+ private
119
+
120
+ # +utf8_pragma+ checks if it can send this string to the handlers. It makes sure @string isn't nil and $KCODE is
121
+ # set to 'UTF8'.
122
+ def utf8_pragma?
123
+ !@string.nil? && ($KCODE == 'UTF8')
124
+ end
125
+ end
126
+ end
127
+
128
+ # When we can load the utf8proc library, override normalization with the faster methods
129
+ begin
130
+ require 'utf8proc_native'
131
+ require 'multibyte/handlers/utf8_handler_proc'
132
+ Multibyte::Chars.handler = Multibyte::Handlers::UTF8HandlerProc
133
+ rescue LoadError
134
+ Multibyte::Chars.handler = Multibyte::Handlers::UTF8Handler
135
+ end
@@ -0,0 +1,148 @@
1
+ #!/usr/bin/env ruby
2
+ require 'open-uri'
3
+ require 'tmpdir'
4
+
5
+ $:.unshift(File.expand_path(File.dirname(__FILE__) + '/../../../lib'))
6
+ require 'multibyte'
7
+
8
+ module Multibyte::Handlers #:nodoc:
9
+ class UnicodeDatabase #:nodoc:
10
+ def self.load
11
+ [Hash.new(Codepoint.new),[],{},{}]
12
+ end
13
+ end
14
+
15
+ class UnicodeTableGenerator #:nodoc:
16
+ BASE_URI = "http://www.unicode.org/Public/#{Multibyte::UNICODE_VERSION}/ucd/"
17
+ SOURCES = {
18
+ :codepoints => BASE_URI + 'UnicodeData.txt',
19
+ :composition_exclusion => BASE_URI + 'CompositionExclusions.txt',
20
+ :grapheme_break_property => BASE_URI + 'auxiliary/GraphemeBreakProperty.txt',
21
+ :cp1252 => 'http://unicode.org/Public/MAPPINGS/VENDORS/MICSFT/WINDOWS/CP1252.TXT'
22
+ }
23
+
24
+ def initialize
25
+ @ucd = UnicodeDatabase.new
26
+
27
+ default = Codepoint.new
28
+ default.combining_class = 0
29
+ default.uppercase_mapping = 0
30
+ default.lowercase_mapping = 0
31
+ @ucd.codepoints = Hash.new(default)
32
+
33
+ @ucd.composition_exclusion = []
34
+ @ucd.composition_map = {}
35
+ @ucd.boundary = {}
36
+ @ucd.cp1252 = {}
37
+ end
38
+
39
+ def parse_codepoints(line)
40
+ codepoint = Codepoint.new
41
+ raise "Could not parse input." unless line =~ /^
42
+ ([0-9A-F]+); # code
43
+ ([^;]+); # name
44
+ ([A-Z]+); # general category
45
+ ([0-9]+); # canonical combining class
46
+ ([A-Z]+); # bidi class
47
+ (<([A-Z]*)>)? # decomposition type
48
+ ((\ ?[0-9A-F]+)*); # decompomposition mapping
49
+ ([0-9]*); # decimal digit
50
+ ([0-9]*); # digit
51
+ ([^;]*); # numeric
52
+ ([YN]*); # bidi mirrored
53
+ ([^;]*); # unicode 1.0 name
54
+ ([^;]*); # iso comment
55
+ ([0-9A-F]*); # simple uppercase mapping
56
+ ([0-9A-F]*); # simple lowercase mapping
57
+ ([0-9A-F]*)$/ix # simple titlecase mapping
58
+ codepoint.code = $1.hex
59
+ #codepoint.name = $2
60
+ #codepoint.category = $3
61
+ codepoint.combining_class = Integer($4)
62
+ #codepoint.bidi_class = $5
63
+ codepoint.decomp_type = $7
64
+ codepoint.decomp_mapping = ($8=='') ? nil : $8.split.collect { |element| element.hex }
65
+ #codepoint.bidi_mirrored = ($13=='Y') ? true : false
66
+ codepoint.uppercase_mapping = ($16=='') ? 0 : $16.hex
67
+ codepoint.lowercase_mapping = ($17=='') ? 0 : $17.hex
68
+ #codepoint.titlecase_mapping = ($18=='') ? nil : $18.hex
69
+ @ucd.codepoints[codepoint.code] = codepoint
70
+ end
71
+
72
+ def parse_grapheme_break_property(line)
73
+ if line =~ /^([0-9A-F\.]+)\s*;\s*([\w]+)\s*#/
74
+ type = $2.downcase.intern
75
+ @ucd.boundary[type] ||= []
76
+ if $1.include? '..'
77
+ parts = $1.split '..'
78
+ @ucd.boundary[type] << (parts[0].hex..parts[1].hex)
79
+ else
80
+ @ucd.boundary[type] << $1.hex
81
+ end
82
+ end
83
+ end
84
+
85
+ def parse_composition_exclusion(line)
86
+ if line =~ /^([0-9A-F]+)/i
87
+ @ucd.composition_exclusion << $1.hex
88
+ end
89
+ end
90
+
91
+ def parse_cp1252(line)
92
+ if line =~ /^([0-9A-Fx]+)\s([0-9A-Fx]+)/i
93
+ @ucd.cp1252[$1.hex] = $2.hex
94
+ end
95
+ end
96
+
97
+ def create_composition_map
98
+ @ucd.codepoints.each do |_, cp|
99
+ 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)
100
+ @ucd.composition_map[cp.decomp_mapping[0]] ||= {}
101
+ @ucd.composition_map[cp.decomp_mapping[0]][cp.decomp_mapping[1]] = cp.code
102
+ end
103
+ end
104
+ end
105
+
106
+ def normalize_boundary_map
107
+ @ucd.boundary.each do |k,v|
108
+ if [:lf, :cr].include? k
109
+ @ucd.boundary[k] = v[0]
110
+ end
111
+ end
112
+ end
113
+
114
+ def parse
115
+ SOURCES.each do |type, url|
116
+ filename = File.join(Dir.tmpdir, "#{url.split('/').last}")
117
+ unless File.exist?(filename)
118
+ $stderr.puts "Downloading #{url.split('/').last}"
119
+ File.open(filename, 'wb') do |target|
120
+ open(url) do |source|
121
+ source.each_line { |line| target.write line }
122
+ end
123
+ end
124
+ end
125
+ File.open(filename) do |file|
126
+ file.each_line { |line| send "parse_#{type}".intern, line }
127
+ end
128
+ end
129
+ create_composition_map
130
+ normalize_boundary_map
131
+ end
132
+
133
+ def dump_to(filename)
134
+ File.open(filename, 'wb') do |f|
135
+ f.write Marshal.dump([@ucd.codepoints, @ucd.composition_exclusion, @ucd.composition_map, @ucd.boundary, @ucd.cp1252])
136
+ end
137
+ end
138
+ end
139
+ end
140
+
141
+ if __FILE__ == $0
142
+ filename = Multibyte::Handlers::UnicodeDatabase.filename
143
+ generator = Multibyte::Handlers::UnicodeTableGenerator.new
144
+ generator.parse
145
+ print "Writing to: #{filename}"
146
+ generator.dump_to filename
147
+ puts " (#{File.size(filename)} bytes)"
148
+ end