activegroonga 0.0.7 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (122) hide show
  1. data/README.ja.rdoc +4 -1
  2. data/README.rdoc +4 -1
  3. data/Rakefile +18 -4
  4. data/lib/active_groonga.rb +25 -52
  5. data/lib/active_groonga/base.rb +164 -1480
  6. data/lib/active_groonga/callbacks.rb +26 -7
  7. data/lib/active_groonga/database.rb +55 -0
  8. data/lib/active_groonga/{dirty.rb → error.rb} +20 -10
  9. data/lib/active_groonga/locale/en.yml +5 -0
  10. data/lib/active_groonga/migration.rb +44 -113
  11. data/lib/active_groonga/migrator.rb +172 -0
  12. data/lib/active_groonga/persistence.rb +172 -0
  13. data/lib/active_groonga/railtie.rb +66 -0
  14. data/lib/active_groonga/railties/configurable.rb +47 -0
  15. data/lib/active_groonga/railties/groonga.rake +167 -0
  16. data/lib/active_groonga/result_set.rb +89 -0
  17. data/lib/active_groonga/schema.rb +54 -188
  18. data/lib/active_groonga/validations.rb +54 -5
  19. data/lib/active_groonga/vector.rb +64 -0
  20. data/lib/active_groonga/version.rb +3 -3
  21. data/lib/{active_groonga/aggregations.rb → rails/generators/active_groonga.rb} +17 -10
  22. data/lib/rails/generators/active_groonga/migration/column.rb +101 -0
  23. data/lib/rails/generators/active_groonga/migration/migration_generator.rb +53 -0
  24. data/lib/rails/generators/active_groonga/migration/templates/migration.rb +29 -0
  25. data/lib/rails/generators/active_groonga/model/model_generator.rb +60 -0
  26. data/lib/rails/generators/active_groonga/model/templates/migration.rb +16 -0
  27. data/lib/rails/generators/active_groonga/model/templates/model.rb +2 -0
  28. data/lib/rails/generators/active_groonga/model/templates/module.rb +5 -0
  29. data/test-unit-notify/COPYING +502 -0
  30. data/test-unit-notify/Rakefile +47 -0
  31. data/test-unit-notify/lib/test/unit/notify.rb +127 -0
  32. data/test-unit/COPYING +56 -0
  33. data/test-unit/GPL +340 -0
  34. data/test-unit/PSFL +271 -0
  35. data/test-unit/Rakefile +18 -5
  36. data/test-unit/html/bar.svg +153 -0
  37. data/test-unit/html/developer.svg +469 -0
  38. data/test-unit/html/favicon.ico +0 -0
  39. data/test-unit/html/favicon.svg +82 -0
  40. data/test-unit/html/heading-mark.svg +393 -0
  41. data/test-unit/html/index.html +235 -13
  42. data/test-unit/html/index.html.ja +258 -15
  43. data/test-unit/html/install.svg +636 -0
  44. data/test-unit/html/logo.svg +483 -0
  45. data/test-unit/html/test-unit.css +339 -0
  46. data/test-unit/html/tutorial.svg +559 -0
  47. data/test-unit/lib/test/unit.rb +29 -43
  48. data/test-unit/lib/test/unit/assertionfailederror.rb +11 -0
  49. data/test-unit/lib/test/unit/assertions.rb +202 -20
  50. data/test-unit/lib/test/unit/autorunner.rb +51 -20
  51. data/test-unit/lib/test/unit/collector.rb +1 -8
  52. data/test-unit/lib/test/unit/collector/dir.rb +1 -1
  53. data/test-unit/lib/test/unit/collector/load.rb +16 -9
  54. data/test-unit/lib/test/unit/color-scheme.rb +19 -3
  55. data/test-unit/lib/test/unit/diff.rb +240 -38
  56. data/test-unit/lib/test/unit/error.rb +4 -0
  57. data/test-unit/lib/test/unit/failure.rb +31 -5
  58. data/test-unit/lib/test/unit/notification.rb +8 -4
  59. data/test-unit/lib/test/unit/omission.rb +51 -3
  60. data/test-unit/lib/test/unit/pending.rb +4 -0
  61. data/test-unit/lib/test/unit/priority.rb +2 -3
  62. data/test-unit/lib/test/unit/testcase.rb +65 -7
  63. data/test-unit/lib/test/unit/testresult.rb +34 -2
  64. data/test-unit/lib/test/unit/ui/console/testrunner.rb +197 -45
  65. data/test-unit/lib/test/unit/ui/emacs/testrunner.rb +14 -0
  66. data/test-unit/lib/test/unit/ui/tap/testrunner.rb +2 -12
  67. data/test-unit/lib/test/unit/ui/testrunner.rb +33 -0
  68. data/test-unit/lib/test/unit/util/backtracefilter.rb +1 -0
  69. data/test-unit/lib/test/unit/util/output.rb +31 -0
  70. data/test-unit/lib/test/unit/version.rb +1 -1
  71. data/test-unit/sample/{tc_adder.rb → test_adder.rb} +3 -1
  72. data/test-unit/sample/{tc_subtracter.rb → test_subtracter.rb} +3 -1
  73. data/test-unit/sample/test_user.rb +1 -0
  74. data/test-unit/test/collector/test-descendant.rb +2 -4
  75. data/test-unit/test/collector/test-load.rb +121 -8
  76. data/test-unit/test/collector/test_objectspace.rb +7 -5
  77. data/test-unit/test/run-test.rb +2 -0
  78. data/test-unit/test/test-color-scheme.rb +11 -2
  79. data/test-unit/test/test-diff.rb +48 -7
  80. data/test-unit/test/test-omission.rb +1 -1
  81. data/test-unit/test/test-testcase.rb +57 -20
  82. data/test-unit/test/test_assertions.rb +128 -13
  83. data/test-unit/test/ui/test_tap.rb +33 -0
  84. data/test-unit/test/util/test-output.rb +11 -0
  85. data/test/active-groonga-test-utils.rb +50 -36
  86. data/test/fixtures/site.rb +2 -0
  87. data/test/run-test.rb +36 -9
  88. data/test/test-associations.rb +5 -2
  89. data/test/test-base.rb +39 -31
  90. data/test/test-callbacks.rb +13 -3
  91. data/test/test-persistence.rb +53 -0
  92. data/{lib/active_groonga/observer.rb → test/test-result-set.rb} +14 -12
  93. data/test/test-schema.rb +85 -22
  94. data/{lib/active_groonga/rails_support.rb → test/test-validations.rb} +15 -13
  95. metadata +85 -52
  96. data/lib/active_groonga/associations.rb +0 -93
  97. data/lib/active_groonga/associations/belongs_to_association.rb +0 -25
  98. data/lib/active_groonga/attribute_methods.rb +0 -36
  99. data/lib/active_groonga/column.rb +0 -137
  100. data/lib/active_groonga/dynamic_record_expression_builder.rb +0 -40
  101. data/lib/active_groonga/reflection.rb +0 -30
  102. data/lib/active_groonga/schema_dumper.rb +0 -163
  103. data/lib/active_groonga/tasks.rb +0 -16
  104. data/lib/active_groonga/tasks/groonga.rake +0 -164
  105. data/lib/active_groonga/timestamp.rb +0 -30
  106. data/rails/README +0 -28
  107. data/rails/init.rb +0 -70
  108. data/rails_generators/index_table_groonga/USAGE +0 -23
  109. data/rails_generators/index_table_groonga/index_table_groonga_generator.rb +0 -44
  110. data/rails_generators/index_table_groonga/templates/migration.rb +0 -12
  111. data/rails_generators/migration_groonga/USAGE +0 -29
  112. data/rails_generators/migration_groonga/migration_groonga_generator.rb +0 -19
  113. data/rails_generators/migration_groonga/templates/migration.rb +0 -11
  114. data/rails_generators/model_groonga/USAGE +0 -28
  115. data/rails_generators/model_groonga/model_groonga_generator.rb +0 -45
  116. data/rails_generators/model_groonga/templates/fixtures.yml +0 -17
  117. data/rails_generators/model_groonga/templates/migration.rb +0 -16
  118. data/rails_generators/model_groonga/templates/model.rb +0 -2
  119. data/rails_generators/model_groonga/templates/unit_test.rb +0 -8
  120. data/test-unit/html/classic.html +0 -15
  121. data/test-unit/sample/ts_examples.rb +0 -7
  122. data/test/test-schema-dumper.rb +0 -48
@@ -23,14 +23,7 @@ module Test
23
23
  def include?(test)
24
24
  return true if(@filters.empty?)
25
25
  @filters.each do |filter|
26
- result = filter[test]
27
- if(result.nil?)
28
- next
29
- elsif(!result)
30
- return false
31
- else
32
- return true
33
- end
26
+ return false if filter[test] == false
34
27
  end
35
28
  true
36
29
  end
@@ -60,7 +60,7 @@ module Test
60
60
  next if(e == '.' || e == '..')
61
61
  e_name = dir_name ? @file.join(dir_name, e) : e
62
62
  if @file.directory?(realdir(e_name))
63
- next if /\A(?:CVS|\.svn)\z/ =~ e
63
+ next if /\A(?:CVS|\.svn|\.git)\z/ =~ e
64
64
  sub_suite = recursive_collect(e_name, already_gathered)
65
65
  sub_suites << sub_suite unless(sub_suite.empty?)
66
66
  else
@@ -14,8 +14,8 @@ module Test
14
14
  def initialize
15
15
  super
16
16
  @system_excludes = [/~\z/, /\A\.\#/]
17
- @system_directory_excludes = [/\A(?:CVS|\.svn)\z/]
18
- @patterns = [/\Atest[_\-].+\.rb\z/m]
17
+ @system_directory_excludes = [/\A(?:CVS|\.svn|\.git)\z/]
18
+ @patterns = [/\Atest[_\-].+\.rb\z/m, /[_\-]test\.rb\z/]
19
19
  @excludes = []
20
20
  @base = nil
21
21
  end
@@ -28,11 +28,17 @@ module Test
28
28
  def collect(*froms)
29
29
  add_load_path(@base) do
30
30
  froms = ["."] if froms.empty?
31
- test_suites = froms.collect do |from|
32
- test_suite = collect_recursive(from, find_test_cases)
33
- test_suite = nil if test_suite.tests.empty?
34
- test_suite
35
- end.compact
31
+ test_suites = []
32
+ already_gathered = find_test_cases
33
+ froms.each do |from|
34
+ from = resolve_path(from)
35
+ if from.directory?
36
+ test_suite = collect_recursive(from, already_gathered)
37
+ test_suites << test_suite unless test_suite.tests.empty?
38
+ else
39
+ collect_file(from, test_suites, already_gathered)
40
+ end
41
+ end
36
42
 
37
43
  if test_suites.size > 1
38
44
  test_suite = TestSuite.new("[#{froms.join(', ')}]")
@@ -56,10 +62,9 @@ module Test
56
62
  end
57
63
 
58
64
  private
59
- def collect_recursive(name, already_gathered)
65
+ def collect_recursive(path, already_gathered)
60
66
  sub_test_suites = []
61
67
 
62
- path = resolve_path(name)
63
68
  if path.directory?
64
69
  directories, files = path.children.partition do |child|
65
70
  child.directory?
@@ -89,6 +94,8 @@ module Test
89
94
  end
90
95
 
91
96
  def collect_file(path, test_suites, already_gathered)
97
+ @program_file ||= File.expand_path($0)
98
+ return if @program_file == path.expand_path.to_s
92
99
  add_load_path(path.expand_path.dirname) do
93
100
  require(path.to_s)
94
101
  find_test_cases(already_gathered).each do |test_case|
@@ -8,8 +8,12 @@ module Test
8
8
  class << self
9
9
  @@default = nil
10
10
  def default
11
- @@default ||= new("success" => Color.new("green", :bold => true),
12
- "failure" => Color.new("red", :bold => true),
11
+ @@default ||= new("pass" =>
12
+ Color.new("green", :foreground => false) +
13
+ Color.new("white", :bold => true),
14
+ "failure" =>
15
+ Color.new("red", :foreground => false) +
16
+ Color.new("white", :bold => true),
13
17
  "pending" => Color.new("magenta", :bold => true),
14
18
  "omission" => Color.new("blue", :bold => true),
15
19
  "notification" => Color.new("cyan", :bold => true),
@@ -18,7 +22,19 @@ module Test
18
22
  "case" => Color.new("white", :bold => true) +
19
23
  Color.new("blue", :foreground => false),
20
24
  "suite" => Color.new("white", :bold => true) +
21
- Color.new("green", :foreground => false))
25
+ Color.new("green", :foreground => false),
26
+ "diff-inserted-tag" =>
27
+ Color.new("red", :bold => true),
28
+ "diff-deleted-tag" =>
29
+ Color.new("green", :bold => true),
30
+ "diff-difference-tag" =>
31
+ Color.new("cyan", :bold => true),
32
+ "diff-inserted" =>
33
+ Color.new("red", :foreground => false) +
34
+ Color.new("white", :bold => true),
35
+ "diff-deleted" =>
36
+ Color.new("green", :foreground => false) +
37
+ Color.new("white", :bold => true))
22
38
  end
23
39
 
24
40
  @@schemes = {}
@@ -1,4 +1,11 @@
1
1
  # port of Python's difflib.
2
+ #
3
+ # Copyright (c) 2001-2008 Python Software Foundation; All Rights Reserved
4
+ # Copyright (c) 2008-2010 Kouhei Sutou; All Rights Reserved
5
+ #
6
+ # It is free software, and is distributed under the Ruby
7
+ # license and/or the PSF license. See the COPYING file and
8
+ # PSFL file.
2
9
 
3
10
  module Test
4
11
  module Unit
@@ -35,7 +42,7 @@ module Test
35
42
 
36
43
  def grouped_operations(context_size=nil)
37
44
  context_size ||= 3
38
- _operations = operations
45
+ _operations = operations.dup
39
46
  _operations = [[:equal, 0, 0, 0, 0]] if _operations.empty?
40
47
  expand_edge_equal_operations!(_operations, context_size)
41
48
 
@@ -266,29 +273,187 @@ module Test
266
273
  end
267
274
  end
268
275
 
276
+ class UTF8Line
277
+ class << self
278
+ # from http://unicode.org/reports/tr11/
279
+ WIDE_CHARACTERS =
280
+ [0x1100..0x1159, 0x115F..0x115F, 0x2329..0x232A,
281
+ 0x2E80..0x2E99, 0x2E9B..0x2EF3, 0x2F00..0x2FD5,
282
+ 0x2FF0..0x2FFB, 0x3000..0x303E, 0x3041..0x3096,
283
+ 0x3099..0x30FF, 0x3105..0x312D, 0x3131..0x318E,
284
+ 0x3190..0x31B7, 0x31C0..0x31E3, 0x31F0..0x321E,
285
+ 0x3220..0x3243, 0x3250..0x32FE, 0x3300..0x4DB5,
286
+ 0x4E00..0x9FC3, 0xA000..0xA48C, 0xA490..0xA4C6,
287
+ 0xAC00..0xD7A3, 0xF900..0xFA2D, 0xFA30..0xFA6A,
288
+ 0xFA70..0xFAD9, 0xFE10..0xFE19, 0xFE30..0xFE52,
289
+ 0xFE54..0xFE66, 0xFE68..0xFE6B, 0xFF01..0xFF60,
290
+ 0xFFE0..0xFFE6, 0x20000..0x2FFFD, 0x30000..0x3FFFD,
291
+ ]
292
+
293
+ AMBIGUOUS =
294
+ [0x00A1..0x00A1, 0x00A4..0x00A4, 0x00A7..0x00A8,
295
+ 0x00AA..0x00AA, 0x00AD..0x00AE, 0x00B0..0x00B4,
296
+ 0x00B6..0x00BA, 0x00BC..0x00BF, 0x00C6..0x00C6,
297
+ 0x00D0..0x00D0, 0x00D7..0x00D8, 0x00DE..0x00E1,
298
+ 0x00E6..0x00E6, 0x00E8..0x00EA, 0x00EC..0x00ED,
299
+ 0x00F0..0x00F0, 0x00F2..0x00F3, 0x00F7..0x00FA,
300
+ 0x00FC..0x00FC, 0x00FE..0x00FE, 0x0101..0x0101,
301
+ 0x0111..0x0111, 0x0113..0x0113, 0x011B..0x011B,
302
+ 0x0126..0x0127, 0x012B..0x012B, 0x0131..0x0133,
303
+ 0x0138..0x0138, 0x013F..0x0142, 0x0144..0x0144,
304
+ 0x0148..0x014B, 0x014D..0x014D, 0x0152..0x0153,
305
+ 0x0166..0x0167, 0x016B..0x016B, 0x01CE..0x01CE,
306
+ 0x01D0..0x01D0, 0x01D2..0x01D2, 0x01D4..0x01D4,
307
+ 0x01D6..0x01D6, 0x01D8..0x01D8, 0x01DA..0x01DA,
308
+ 0x01DC..0x01DC, 0x0251..0x0251, 0x0261..0x0261,
309
+ 0x02C4..0x02C4, 0x02C7..0x02C7, 0x02C9..0x02CB,
310
+ 0x02CD..0x02CD, 0x02D0..0x02D0, 0x02D8..0x02DB,
311
+ 0x02DD..0x02DD, 0x02DF..0x02DF, 0x0300..0x036F,
312
+ 0x0391..0x03A1, 0x03A3..0x03A9, 0x03B1..0x03C1,
313
+ 0x03C3..0x03C9, 0x0401..0x0401, 0x0410..0x044F,
314
+ 0x0451..0x0451, 0x2010..0x2010, 0x2013..0x2016,
315
+ 0x2018..0x2019, 0x201C..0x201D, 0x2020..0x2022,
316
+ 0x2024..0x2027, 0x2030..0x2030, 0x2032..0x2033,
317
+ 0x2035..0x2035, 0x203B..0x203B, 0x203E..0x203E,
318
+ 0x2074..0x2074, 0x207F..0x207F, 0x2081..0x2084,
319
+ 0x20AC..0x20AC, 0x2103..0x2103, 0x2105..0x2105,
320
+ 0x2109..0x2109, 0x2113..0x2113, 0x2116..0x2116,
321
+ 0x2121..0x2122, 0x2126..0x2126, 0x212B..0x212B,
322
+ 0x2153..0x2154, 0x215B..0x215E, 0x2160..0x216B,
323
+ 0x2170..0x2179, 0x2190..0x2199, 0x21B8..0x21B9,
324
+ 0x21D2..0x21D2, 0x21D4..0x21D4, 0x21E7..0x21E7,
325
+ 0x2200..0x2200, 0x2202..0x2203, 0x2207..0x2208,
326
+ 0x220B..0x220B, 0x220F..0x220F, 0x2211..0x2211,
327
+ 0x2215..0x2215, 0x221A..0x221A, 0x221D..0x2220,
328
+ 0x2223..0x2223, 0x2225..0x2225, 0x2227..0x222C,
329
+ 0x222E..0x222E, 0x2234..0x2237, 0x223C..0x223D,
330
+ 0x2248..0x2248, 0x224C..0x224C, 0x2252..0x2252,
331
+ 0x2260..0x2261, 0x2264..0x2267, 0x226A..0x226B,
332
+ 0x226E..0x226F, 0x2282..0x2283, 0x2286..0x2287,
333
+ 0x2295..0x2295, 0x2299..0x2299, 0x22A5..0x22A5,
334
+ 0x22BF..0x22BF, 0x2312..0x2312, 0x2460..0x24E9,
335
+ 0x24EB..0x254B, 0x2550..0x2573, 0x2580..0x258F,
336
+ 0x2592..0x2595, 0x25A0..0x25A1, 0x25A3..0x25A9,
337
+ 0x25B2..0x25B3, 0x25B6..0x25B7, 0x25BC..0x25BD,
338
+ 0x25C0..0x25C1, 0x25C6..0x25C8, 0x25CB..0x25CB,
339
+ 0x25CE..0x25D1, 0x25E2..0x25E5, 0x25EF..0x25EF,
340
+ 0x2605..0x2606, 0x2609..0x2609, 0x260E..0x260F,
341
+ 0x2614..0x2615, 0x261C..0x261C, 0x261E..0x261E,
342
+ 0x2640..0x2640, 0x2642..0x2642, 0x2660..0x2661,
343
+ 0x2663..0x2665, 0x2667..0x266A, 0x266C..0x266D,
344
+ 0x266F..0x266F, 0x273D..0x273D, 0x2776..0x277F,
345
+ 0xE000..0xF8FF, 0xFE00..0xFE0F, 0xFFFD..0xFFFD,
346
+ 0xE0100..0xE01EF, 0xF0000..0xFFFFD, 0x100000..0x10FFFD,
347
+ ]
348
+
349
+ def wide_character?(character)
350
+ binary_search_ranges(character, WIDE_CHARACTERS) or
351
+ binary_search_ranges(character, AMBIGUOUS)
352
+ end
353
+
354
+ private
355
+ def binary_search_ranges(character, ranges)
356
+ if ranges.size.zero?
357
+ false
358
+ elsif ranges.size == 1
359
+ ranges[0].include?(character)
360
+ else
361
+ half = ranges.size / 2
362
+ range = ranges[half]
363
+ if range.include?(character)
364
+ true
365
+ elsif character < range.begin
366
+ binary_search_ranges(character, ranges[0...half])
367
+ else
368
+ binary_search_ranges(character, ranges[(half + 1)..-1])
369
+ end
370
+ end
371
+ end
372
+ end
373
+
374
+ def initialize(line)
375
+ @line = line
376
+ @characters = @line.unpack("U*")
377
+ end
378
+
379
+ def [](*args)
380
+ result = @characters[*args]
381
+ if result.respond_to?(:pack)
382
+ result.pack("U*")
383
+ else
384
+ result
385
+ end
386
+ end
387
+
388
+ def each(&block)
389
+ @characters.each(&block)
390
+ end
391
+
392
+ def size
393
+ @characters.size
394
+ end
395
+
396
+ def to_s
397
+ @line
398
+ end
399
+
400
+ def compute_width(start, _end)
401
+ width = 0
402
+ start.upto(_end - 1) do |i|
403
+ if self.class.wide_character?(@characters[i])
404
+ width += 2
405
+ else
406
+ width += 1
407
+ end
408
+ end
409
+ width
410
+ end
411
+ end
412
+
269
413
  class ReadableDiffer < Differ
270
414
  def diff(options={})
271
- result = []
272
- matcher = SequenceMatcher.new(@from, @to)
273
- matcher.operations.each do |args|
274
- tag, from_start, from_end, to_start, to_end = args
415
+ @result = []
416
+ operations.each do |tag, from_start, from_end, to_start, to_end|
275
417
  case tag
276
418
  when :replace
277
- result.concat(diff_lines(from_start, from_end, to_start, to_end))
419
+ diff_lines(from_start, from_end, to_start, to_end)
278
420
  when :delete
279
- result.concat(tag_deleted(@from[from_start...from_end]))
421
+ tag_deleted(@from[from_start...from_end])
280
422
  when :insert
281
- result.concat(tag_inserted(@to[to_start...to_end]))
423
+ tag_inserted(@to[to_start...to_end])
282
424
  when :equal
283
- result.concat(tag_equal(@from[from_start...from_end]))
425
+ tag_equal(@from[from_start...from_end])
284
426
  else
285
427
  raise "unknown tag: #{tag}"
286
428
  end
287
429
  end
288
- result
430
+ @result
289
431
  end
290
432
 
291
433
  private
434
+ def operations
435
+ @operations ||= nil
436
+ if @operations.nil?
437
+ matcher = SequenceMatcher.new(@from, @to)
438
+ @operations = matcher.operations
439
+ end
440
+ @operations
441
+ end
442
+
443
+ def default_ratio
444
+ 0.74
445
+ end
446
+
447
+ def cut_off_ratio
448
+ 0.75
449
+ end
450
+
451
+ def tag(mark, contents)
452
+ contents.each do |content|
453
+ @result << "#{mark}#{content}"
454
+ end
455
+ end
456
+
292
457
  def tag_deleted(contents)
293
458
  tag("- ", contents)
294
459
  end
@@ -306,7 +471,7 @@ module Test
306
471
  end
307
472
 
308
473
  def find_diff_line_info(from_start, from_end, to_start, to_end)
309
- best_ratio = 0.74
474
+ best_ratio = default_ratio
310
475
  from_equal_index = to_equal_index = nil
311
476
  from_best_index = to_best_index = nil
312
477
 
@@ -334,30 +499,31 @@ module Test
334
499
  end
335
500
 
336
501
  def diff_lines(from_start, from_end, to_start, to_end)
337
- cut_off = 0.75
338
-
339
502
  info = find_diff_line_info(from_start, from_end, to_start, to_end)
340
503
  best_ratio, from_equal_index, to_equal_index, *info = info
341
504
  from_best_index, to_best_index = info
505
+ from_best_index ||= from_start
506
+ to_best_index ||= to_start
342
507
 
343
- if best_ratio < cut_off
508
+ if best_ratio < cut_off_ratio
344
509
  if from_equal_index.nil?
345
- tagged_from = tag_deleted(@from[from_start...from_end])
346
- tagged_to = tag_inserted(@to[to_start...to_end])
347
510
  if to_end - to_start < from_end - from_start
348
- return tagged_to + tagged_from
511
+ tag_inserted(@to[to_start...to_end])
512
+ tag_deleted(@from[from_start...from_end])
349
513
  else
350
- return tagged_from + tagged_to
514
+ tag_deleted(@from[from_start...from_end])
515
+ tag_inserted(@to[to_start...to_end])
351
516
  end
517
+ return
352
518
  end
353
519
  from_best_index = from_equal_index
354
520
  to_best_index = to_equal_index
355
521
  best_ratio = 1.0
356
522
  end
357
523
 
358
- _diff_lines(from_start, from_best_index, to_start, to_best_index) +
359
- diff_line(@from[from_best_index], @to[to_best_index]) +
360
- _diff_lines(from_best_index + 1, from_end, to_best_index + 1, to_end)
524
+ _diff_lines(from_start, from_best_index, to_start, to_best_index)
525
+ diff_line(@from[from_best_index], @to[to_best_index])
526
+ _diff_lines(from_best_index + 1, from_end, to_best_index + 1, to_end)
361
527
  end
362
528
 
363
529
  def _diff_lines(from_start, from_end, to_start, to_end)
@@ -372,26 +538,54 @@ module Test
372
538
  end
373
539
  end
374
540
 
541
+ def line_operations(from_line, to_line)
542
+ if !from_line.respond_to?(:force_encoding) and $KCODE == "UTF8"
543
+ from_line = UTF8Line.new(from_line)
544
+ to_line = UTF8Line.new(to_line)
545
+ end
546
+ matcher = SequenceMatcher.new(from_line, to_line,
547
+ &method(:space_character?))
548
+ [from_line, to_line, matcher.operations]
549
+ end
550
+
551
+ def compute_width(line, start, _end)
552
+ if line.respond_to?(:encoding) and
553
+ Encoding.compatible?(Encoding::UTF_8, line.encoding)
554
+ utf8_line = line[start..._end].encode(Encoding::UTF_8)
555
+ width = 0
556
+ utf8_line.each_codepoint do |unicode_codepoint|
557
+ if UTF8Line.wide_character?(unicode_codepoint)
558
+ width += 2
559
+ else
560
+ width += 1
561
+ end
562
+ end
563
+ width
564
+ elsif line.is_a?(UTF8Line)
565
+ line.compute_width(start, _end)
566
+ else
567
+ _end - start
568
+ end
569
+ end
570
+
375
571
  def diff_line(from_line, to_line)
376
572
  from_tags = ""
377
573
  to_tags = ""
378
- matcher = SequenceMatcher.new(from_line, to_line,
379
- &method(:space_character?))
380
- operations = matcher.operations
381
- operations.each do |tag, from_start, from_end, to_start, to_end|
382
- from_length = from_end - from_start
383
- to_length = to_end - to_start
574
+ from_line, to_line, _operations = line_operations(from_line, to_line)
575
+ _operations.each do |tag, from_start, from_end, to_start, to_end|
576
+ from_width = compute_width(from_line, from_start, from_end)
577
+ to_width = compute_width(to_line, to_start, to_end)
384
578
  case tag
385
579
  when :replace
386
- from_tags << "^" * from_length
387
- to_tags << "^" * to_length
580
+ from_tags << "^" * from_width
581
+ to_tags << "^" * to_width
388
582
  when :delete
389
- from_tags << "-" * from_length
583
+ from_tags << "-" * from_width
390
584
  when :insert
391
- to_tags << "+" * to_length
585
+ to_tags << "+" * to_width
392
586
  when :equal
393
- from_tags << " " * from_length
394
- to_tags << " " * to_length
587
+ from_tags << " " * from_width
588
+ to_tags << " " * to_width
395
589
  else
396
590
  raise "unknown tag: #{tag}"
397
591
  end
@@ -409,13 +603,12 @@ module Test
409
603
 
410
604
  result = tag_deleted([from_line])
411
605
  unless from_tags.empty?
412
- result.concat(tag_difference(["#{"\t" * common}#{from_tags}"]))
606
+ tag_difference(["#{"\t" * common}#{from_tags}"])
413
607
  end
414
- result.concat(tag_inserted([to_line]))
608
+ tag_inserted([to_line])
415
609
  unless to_tags.empty?
416
- result.concat(tag_difference(["#{"\t" * common}#{to_tags}"]))
610
+ tag_difference(["#{"\t" * common}#{to_tags}"])
417
611
  end
418
- result
419
612
  end
420
613
 
421
614
  def n_leading_characters(string, character)
@@ -531,7 +724,16 @@ module Test
531
724
 
532
725
  def diff(differ_class, from, to, options={})
533
726
  differ = differ_class.new(from.split(/\r?\n/), to.split(/\r?\n/))
534
- differ.diff(options).join("\n")
727
+ lines = differ.diff(options)
728
+ if Object.const_defined?(:EncodingError)
729
+ begin
730
+ lines.join("\n")
731
+ rescue EncodingError
732
+ lines.collect {|line| line.force_encoding("ASCII-8BIT")}.join("\n")
733
+ end
734
+ else
735
+ lines.join("\n")
736
+ end
535
737
  end
536
738
  end
537
739
  end