narou 3.2.5.1 → 3.3.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of narou might be problematic. Click here for more details.

Files changed (131) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +2 -5
  3. data/.haml-lint.yml +7 -0
  4. data/.rubocop.yml +23 -5
  5. data/.scss-lint.yml +9 -0
  6. data/ChangeLog.md +86 -0
  7. data/Gemfile.lock +35 -35
  8. data/README.md +80 -64
  9. data/lib/backtracer.rb +2 -2
  10. data/lib/color.rb +5 -1
  11. data/lib/command.rb +7 -2
  12. data/lib/command/alias.rb +3 -5
  13. data/lib/command/backup.rb +3 -5
  14. data/lib/command/browser.rb +3 -5
  15. data/lib/command/clean.rb +5 -1
  16. data/lib/command/console.rb +33 -0
  17. data/lib/command/convert.rb +143 -117
  18. data/lib/command/csv.rb +2 -1
  19. data/lib/command/diff.rb +20 -18
  20. data/lib/command/download.rb +25 -14
  21. data/lib/command/folder.rb +3 -5
  22. data/lib/command/freeze.rb +3 -5
  23. data/lib/command/help.rb +20 -18
  24. data/lib/command/init.rb +4 -3
  25. data/lib/command/inspect.rb +2 -1
  26. data/lib/command/list.rb +10 -8
  27. data/lib/command/list/novel_decorator.rb +2 -1
  28. data/lib/command/log.rb +100 -0
  29. data/lib/command/log/tail.rb +76 -0
  30. data/lib/command/mail.rb +20 -17
  31. data/lib/command/remove.rb +7 -6
  32. data/lib/command/send.rb +23 -20
  33. data/lib/command/setting.rb +74 -40
  34. data/lib/command/tag.rb +16 -15
  35. data/lib/command/trace.rb +2 -2
  36. data/lib/command/update.rb +78 -128
  37. data/lib/command/update/general_lastup_updater.rb +3 -2
  38. data/lib/command/update/hotentry_manager.rb +2 -1
  39. data/lib/command/update/interval.rb +2 -1
  40. data/lib/command/version.rb +2 -1
  41. data/lib/command/web.rb +17 -3
  42. data/lib/commandbase.rb +34 -7
  43. data/lib/commandline.rb +54 -35
  44. data/lib/converterbase.rb +21 -15
  45. data/lib/database.rb +3 -2
  46. data/lib/device.rb +5 -4
  47. data/lib/device/epub.rb +2 -1
  48. data/lib/device/ibooks.rb +2 -1
  49. data/lib/device/ibunko.rb +2 -1
  50. data/lib/device/kindle.rb +2 -1
  51. data/lib/device/kobo.rb +2 -1
  52. data/lib/device/library/cygwin.rb +2 -1
  53. data/lib/device/library/linux.rb +2 -1
  54. data/lib/device/library/mac.rb +2 -1
  55. data/lib/device/library/windows.rb +2 -1
  56. data/lib/device/library/windows/eject.rb +2 -1
  57. data/lib/device/reader.rb +2 -1
  58. data/lib/diffviewer.rb +8 -11
  59. data/lib/downloader.rb +159 -151
  60. data/lib/eventable.rb +2 -1
  61. data/lib/extension.rb +16 -14
  62. data/lib/extensions/jruby.rb +2 -1
  63. data/lib/extensions/monkey_patches.rb +7 -0
  64. data/lib/extensions/monkey_patches/pathname.rb +22 -0
  65. data/lib/extensions/windows.rb +2 -1
  66. data/lib/extensions/windows_write_color.rb +2 -1
  67. data/lib/helper.rb +35 -20
  68. data/lib/html.rb +2 -1
  69. data/lib/illustration.rb +2 -1
  70. data/lib/ini.rb +2 -1
  71. data/lib/input.rb +2 -1
  72. data/lib/inspector.rb +3 -2
  73. data/lib/inventory.rb +3 -3
  74. data/lib/mailer.rb +3 -2
  75. data/lib/mixin/all.rb +8 -0
  76. data/lib/mixin/locker.rb +40 -0
  77. data/lib/mixin/output_error.rb +28 -0
  78. data/lib/narou.rb +69 -51
  79. data/lib/narou/api.rb +2 -4
  80. data/lib/narou_logger.rb +236 -108
  81. data/lib/novelconverter.rb +77 -69
  82. data/lib/novelinfo.rb +4 -2
  83. data/lib/novelsetting.rb +15 -12
  84. data/lib/progressbar.rb +13 -9
  85. data/lib/sitesetting.rb +39 -18
  86. data/lib/template.rb +5 -4
  87. data/lib/version.rb +3 -2
  88. data/lib/web/all.rb +2 -1
  89. data/lib/web/appserver.rb +83 -65
  90. data/lib/web/helper4web.rb +10 -5
  91. data/lib/web/progressbar4web.rb +8 -4
  92. data/lib/web/public/resources/default-style.css +2 -3
  93. data/lib/web/public/resources/narou.library.js +86 -60
  94. data/lib/web/public/resources/narou.queue.js +24 -30
  95. data/lib/web/public/resources/narou.ui.js +22 -3
  96. data/lib/web/public/theme/Cerulean/style.css +5 -5
  97. data/lib/web/public/theme/Darkly/style.css +5 -5
  98. data/lib/web/public/theme/Readable/style.css +5 -5
  99. data/lib/web/public/theme/Slate/style.css +2 -3
  100. data/lib/web/public/theme/Superhero/style.css +2 -3
  101. data/lib/web/public/theme/United/style.css +5 -5
  102. data/lib/web/pushserver.rb +10 -7
  103. data/lib/web/server_helpers.rb +16 -1
  104. data/lib/web/settingmessages.rb +10 -7
  105. data/lib/web/streaminginput.rb +2 -1
  106. data/lib/web/streaminglogger.rb +45 -32
  107. data/lib/web/views/_about.haml +6 -3
  108. data/lib/web/views/_header.haml +2 -3
  109. data/lib/web/views/_move_to_top.haml +2 -0
  110. data/lib/web/views/_queue.haml +7 -0
  111. data/lib/web/views/bookmarklet/insert_button.js.erb +1 -1
  112. data/lib/web/views/index.haml +30 -27
  113. data/lib/web/views/layout.haml +2 -0
  114. data/lib/web/views/novels/setting.haml +3 -4
  115. data/lib/web/views/settings.haml +22 -8
  116. data/lib/web/views/style.scss +54 -3
  117. data/lib/web/views/widget/download.haml +9 -3
  118. data/lib/web/views/widget/drag_and_drop.haml +10 -4
  119. data/lib/web/web_worker.rb +132 -0
  120. data/lib/worker.rb +142 -0
  121. data/narou.gemspec +80 -45
  122. data/narou.rb +6 -4
  123. data/template/novel.txt.erb +1 -0
  124. data/webnovel/kakuyomu.jp.yaml +9 -13
  125. data/webnovel/ncode.syosetu.com.yaml +3 -1
  126. data/webnovel/novel18.syosetu.com.yaml +8 -1
  127. data/webnovel/syosetu.org.yaml +3 -1
  128. data/webnovel/www.akatsuki-novels.com.yaml +4 -2
  129. data/webnovel/www.mai-net.net.yaml +3 -1
  130. metadata +109 -48
  131. data/lib/web/worker.rb +0 -126
@@ -1,16 +1,27 @@
1
- # -*- coding: utf-8 -*-
1
+ # frozen_string_literal: true
2
+
2
3
  #
3
4
  # Copyright 2013 whiteleaf. All rights reserved.
4
5
  #
5
6
 
6
7
  require "optparse"
7
8
  require "termcolorlight"
8
- require_relative "web/worker"
9
+
10
+ # help をログに記録しないために STDOUT に直接出力する
11
+ OptionParser::Officious["help"] = proc do |parser|
12
+ OptionParser::Switch::NoArgument.new do |_arg|
13
+ STDOUT.puts parser.help
14
+ exit
15
+ end
16
+ end
9
17
 
10
18
  module Command
11
19
  class CommandBase
20
+ attr_accessor :stream_io
21
+
12
22
  # postfixies は改行で区切ることで2パターン以上記述できる
13
- def initialize(postfixies = "")
23
+ def initialize(postfixies = " ")
24
+ self.stream_io = $stdout
14
25
  @opt = OptionParser.new(nil, 20)
15
26
  command_name = self.class.to_s.scan(/::(.+)$/)[0][0].downcase
16
27
  banner = postfixies.split("\n").map.with_index { |postfix, i|
@@ -37,6 +48,11 @@ module Command
37
48
  end
38
49
  end
39
50
 
51
+ def display_help!
52
+ STDOUT.puts @opt.help
53
+ exit
54
+ end
55
+
40
56
  def execute(argv)
41
57
  @options.clear
42
58
  load_local_settings
@@ -103,7 +119,8 @@ module Command
103
119
  # コマンドを実行するが、アプリケーションは終了させない
104
120
  # (SystemExit を補足し、終了コードを返り値とする)
105
121
  #
106
- def execute!(*argv)
122
+ def execute!(*argv, io: $stdout)
123
+ self.stream_io = io
107
124
  argv.flatten!
108
125
  execute(argv)
109
126
  rescue SystemExit => e
@@ -112,12 +129,13 @@ module Command
112
129
  0
113
130
  end
114
131
 
115
- def self.execute!(*argv)
116
- self.new.execute!(*argv)
132
+ def self.execute!(*argv, io: $stdout)
133
+ cmd = new
134
+ cmd.execute!(*argv, io: io)
117
135
  end
118
136
 
119
137
  def self.oneline_help
120
- ""
138
+ raise "implement #{self}.oneline_help"
121
139
  end
122
140
 
123
141
  #
@@ -150,5 +168,14 @@ module Command
150
168
  end
151
169
  settings.save if modified
152
170
  end
171
+
172
+ #
173
+ # コマンド出力のログ保存を抑制する
174
+ #
175
+ # コマンドの中で、stream_io に対して出力している必要がある
176
+ #
177
+ def disable_logging
178
+ self.stream_io = stream_io.dup_with_disabled_logging
179
+ end
153
180
  end
154
181
  end
@@ -1,4 +1,5 @@
1
- # -*- coding: utf-8 -*-
1
+ # frozen_string_literal: true
2
+
2
3
  #
3
4
  # Copyright 2013 whiteleaf. All rights reserved.
4
5
  #
@@ -11,31 +12,11 @@ require_relative "inventory"
11
12
  module CommandLine
12
13
  module_function
13
14
 
14
- def run(*argv)
15
+ def run(*argv, catch_exit: false, io: $stdout)
15
16
  argv.flatten!
16
- if Helper.os_windows?
17
- argv.map! do |arg|
18
- arg&.encode(Encoding::UTF_8)
19
- end
20
- end
21
- argv.unshift("help") if argv.empty?
22
- arg = argv.shift.downcase
23
- arg = Command::Shortcuts[arg] || arg
24
- arg = "help" if arg == "-h" || arg == "--help"
25
- arg = "version" if arg == "-v" || arg == "--version"
26
- unless Narou.already_init?
27
- unless ["help", "version", "init"].include?(arg)
28
- arg = "help"
29
- end
30
- end
31
- unless Command.get_list.include?(arg)
32
- error "不明なコマンドです"
33
- puts
34
- arg = "help"
35
- end
36
- if argv.empty? && STDIN.tty?
37
- argv += load_default_arguments(arg)
38
- end
17
+ argv_for_windows(argv)
18
+ cmd_name = take_command_name(argv)
19
+ proc_default_arguments(argv, cmd_name)
39
20
  if argv.delete("--multiple")
40
21
  multiple_argument_extract(argv)
41
22
  end
@@ -43,22 +24,23 @@ module CommandLine
43
24
  # pipeで接続された場合、標準入力からIDリストを受け取って引数に繋げる
44
25
  argv += (STDIN.gets || "").split
45
26
  end
46
- Command.get_list[arg].new.execute(argv)
47
- ensure
48
- if Command::Convert.exists_sending_error_list?
49
- Command::Convert.display_sending_error_list
27
+ command = Command.get_list[cmd_name]
28
+ if catch_exit
29
+ command.execute!(argv, io: io)
30
+ else
31
+ cmd = command.new
32
+ cmd.stream_io = io
33
+ cmd.execute(argv)
50
34
  end
35
+ ensure
36
+ Command::Convert.display_sending_error_list
51
37
  end
52
38
 
53
39
  #
54
40
  # exit を捕捉して終了コードを返す
55
41
  #
56
- def run!(*argv)
57
- run(*argv)
58
- rescue SystemExit => e
59
- e.status
60
- else
61
- 0
42
+ def run!(*argv, io: $stdout)
43
+ run(*argv, catch_exit: true, io: io)
62
44
  end
63
45
 
64
46
  def load_default_arguments(cmd)
@@ -66,6 +48,43 @@ module CommandLine
66
48
  (default_arguments_list["default_args.#{cmd}"] || "").split
67
49
  end
68
50
 
51
+ def argv_for_windows(argv)
52
+ return unless Helper.os_windows?
53
+ argv.map! do |arg|
54
+ arg&.encode(Encoding::UTF_8)
55
+ end
56
+ end
57
+
58
+ def take_command_name(argv)
59
+ argv.unshift("help") if argv.empty?
60
+ name = argv.shift.downcase
61
+ name = Command::Shortcuts[name] || name
62
+ name = case name
63
+ when "-v", "--version"
64
+ "version"
65
+ when "-h", "--help"
66
+ "help"
67
+ else
68
+ name
69
+ end
70
+ unless Narou.already_init?
71
+ unless %w(help version init).include?(name)
72
+ name = "help"
73
+ end
74
+ end
75
+ unless Command.get_list.include?(name)
76
+ error "不明なコマンドです。narou help を確認してください"
77
+ exit Narou::EXIT_ERROR_CODE
78
+ end
79
+ name
80
+ end
81
+
82
+ def proc_default_arguments(argv, name)
83
+ if argv.empty? && STDIN.tty?
84
+ argv.concat(load_default_arguments(name))
85
+ end
86
+ end
87
+
69
88
  #
70
89
  # 引数をスペース以外による区切り文字で展開する
71
90
  #
@@ -1,4 +1,5 @@
1
- # -*- coding: utf-8 -*-
1
+ # frozen_string_literal: true
2
+
2
3
  #
3
4
  # Copyright 2013 whiteleaf. All rights reserved.
4
5
  #
@@ -55,7 +56,7 @@ class ConverterBase
55
56
  @request_insert_blank_next_line = false
56
57
  @request_skip_output_line = false
57
58
  @before_line = ""
58
- @delay_outputs_buffer = ""
59
+ @delay_outputs_buffer = +""
59
60
  @in_comment_block = false
60
61
  @english_sentences = []
61
62
  @url_list = []
@@ -500,6 +501,15 @@ class ConverterBase
500
501
  data.gsub!(/。 /, "。")
501
502
  end
502
503
 
504
+ def should_word_be_hankaku?(word)
505
+ (word.length >= ENGLISH_SENTENCES_MIN_LENGTH || @setting.disable_alphabet_word_to_zenkaku) &&
506
+ word.match(/[a-z]/i)
507
+ end
508
+
509
+ def sentence?(match)
510
+ match.split(" ").size >= 2
511
+ end
512
+
503
513
  #
504
514
  # 半角アルファベットを全角に変換する
505
515
  #
@@ -515,9 +525,7 @@ class ConverterBase
515
525
  end
516
526
  else
517
527
  data.gsub!(ENGLISH_SENTENCES_CHARACTERS) do |match|
518
- if match.split(" ").size >= 2 ||
519
- (match.length >= ENGLISH_SENTENCES_MIN_LENGTH && match.match(/[a-z]/i)) ||
520
- @setting.disable_alphabet_word_to_zenkaku
528
+ if sentence?(match) || should_word_be_hankaku?(match)
521
529
  @english_sentences << match
522
530
  "[#英文=#{@english_sentences.size - 1}]"
523
531
  else
@@ -639,7 +647,7 @@ class ConverterBase
639
647
  top = "― "
640
648
  bottom = " ―"
641
649
  end
642
- str = "   [#ゴシック体]#{top}"
650
+ str = +"   [#ゴシック体]#{top}"
643
651
  str += hankaku_num_to_zenkaku_num(chapter.tr("0-9", "0-9"))
644
652
  str += "#{bottom}[#ゴシック体終わり]"
645
653
  # 前後に空行を入れたいが、それは行処理ループ中に行う
@@ -657,7 +665,7 @@ class ConverterBase
657
665
  end
658
666
 
659
667
  def insert_blank_before_line_and_behind_to_special_chapter(line)
660
- result = ""
668
+ result = +""
661
669
  if line =~ /[#章見出しっぽい文=/
662
670
  unless blank_line?(@before_line)
663
671
  result << "\n"
@@ -675,9 +683,7 @@ class ConverterBase
675
683
  end
676
684
 
677
685
  def border_symbol?(line)
678
- @@symbols ||= open(File.join(Narou.get_preset_dir, "bordersymbols.txt"), "r:BOM|UTF-8") { |fp|
679
- fp.read.strip
680
- }
686
+ @@symbols ||= File.read(Narou.preset_dir.join("bordersymbols.txt"), encoding: "BOM|UTF-8")
681
687
  line =~ /^[  \t]*[#{@@symbols}]+$/
682
688
  end
683
689
 
@@ -689,7 +695,7 @@ class ConverterBase
689
695
  # ■などの区切りの前後には空行が必ず存在するようにする
690
696
  #
691
697
  def insert_blank_line_to_border_symbol(line)
692
- result = ""
698
+ result = +""
693
699
  if border_symbol?(line)
694
700
  unless blank_line?(@before_line)
695
701
  result << "\n"
@@ -818,8 +824,8 @@ class ConverterBase
818
824
  # 改行がひとつもなかった場合は nil を返す
819
825
  #
820
826
  def join_inner_bracket(str)
821
- joined_str = str.dup
822
827
  return nil if str.count("\n") == 0
828
+ joined_str = str.dup
823
829
  joined_str.gsub!(/([…―])\n/, "\\1。\n")
824
830
  joined_str = joined_str.split("\n").map { |s|
825
831
  s.sub(/^ +/, "")
@@ -1204,7 +1210,7 @@ class ConverterBase
1204
1210
  # 単語単位でzwsを挿入する
1205
1211
  #
1206
1212
  def insert_word_separator(str)
1207
- buffer = ""
1213
+ buffer = +""
1208
1214
  ss = StringScanner.new(str)
1209
1215
  before_symbol = false
1210
1216
 
@@ -1265,7 +1271,7 @@ class ConverterBase
1265
1271
  # 文字単位でzwsを挿入する
1266
1272
  #
1267
1273
  def insert_char_separator(str)
1268
- buffer = ""
1274
+ buffer = +""
1269
1275
  ss = StringScanner.new(str)
1270
1276
  before_symbol = false
1271
1277
  while char = ss.getch
@@ -1378,7 +1384,7 @@ class ConverterBase
1378
1384
  unless @delay_outputs_buffer.empty?
1379
1385
  @write_fp.write(@delay_outputs_buffer)
1380
1386
  @before_line = @delay_outputs_buffer
1381
- @delay_outputs_buffer = ""
1387
+ @delay_outputs_buffer.clear
1382
1388
  else
1383
1389
  @before_line = line
1384
1390
  end
@@ -1,4 +1,5 @@
1
- # -*- coding: utf-8 -*-
1
+ # frozen_string_literal: true
2
+
2
3
  #
3
4
  # Copyright 2013 whiteleaf. All rights reserved.
4
5
  #
@@ -42,7 +43,7 @@ class Database
42
43
  # 小説格納用のルートディレクトリを取得
43
44
  #
44
45
  def self.archive_root_path
45
- @archive_root_path ||= File.expand_path(File.join(Narou.get_root_dir, ARCHIVE_ROOT_DIR_PATH))
46
+ @archive_root_path ||= Narou.root_dir.join(ARCHIVE_ROOT_DIR_PATH).expand_path
46
47
  end
47
48
 
48
49
  def save_database
@@ -1,4 +1,5 @@
1
- # -*- coding: utf-8 -*-
1
+ # frozen_string_literal: true
2
+
2
3
  #
3
4
  # Copyright 2013 whiteleaf. All rights reserved.
4
5
  #
@@ -17,7 +18,7 @@ class Device
17
18
 
18
19
  # 栞データを管理するディレクトリのパスを取得
19
20
  def get_storage_path
20
- File.join(Narou.get_misc_dir, "bookmark", @name)
21
+ File.join(Narou.misc_dir, "bookmark", @name)
21
22
  end
22
23
  memoize :get_storage_path
23
24
  end
@@ -40,8 +41,8 @@ class Device
40
41
  attr_reader :name, :ebook_file_ext, :display_name
41
42
 
42
43
  DEVICES = {}.tap do |h|
43
- [Narou.get_root_dir, File.dirname(__FILE__)].each do |dir|
44
- next unless dir # narou init 前だと get_root_dir は nil
44
+ [Narou.root_dir, __dir__].each do |dir|
45
+ next unless dir # narou init 前だと root_dir は nil
45
46
  Dir.glob(File.join(dir, "device", "*.rb")).each do |path|
46
47
  name = File.basename(path, ".rb")
47
48
  unless h[name]
@@ -1,4 +1,5 @@
1
- # -*- coding: utf-8 -*-
1
+ # frozen_string_literal: true
2
+
2
3
  #
3
4
  # Copyright 2013 whiteleaf. All rights reserved.
4
5
  #
@@ -1,4 +1,5 @@
1
- # -*- coding: utf-8 -*-
1
+ # frozen_string_literal: true
2
+
2
3
  #
3
4
  # Copyright 2013 whiteleaf. All rights reserved.
4
5
  #
@@ -1,4 +1,5 @@
1
- # -*- coding: utf-8 -*-
1
+ # frozen_string_literal: true
2
+
2
3
  #
3
4
  # Copyright 2013 whiteleaf. All rights reserved.
4
5
  #
@@ -1,4 +1,5 @@
1
- # -*- coding: utf-8 -*-
1
+ # frozen_string_literal: true
2
+
2
3
  #
3
4
  # Copyright 2013 whiteleaf. All rights reserved.
4
5
  #
@@ -1,4 +1,5 @@
1
- # -*- coding: utf-8 -*-
1
+ # frozen_string_literal: true
2
+
2
3
  #
3
4
  # Copyright 2013 whiteleaf. All rights reserved.
4
5
  #
@@ -1,4 +1,5 @@
1
- # -*- coding: utf-8 -*-
1
+ # frozen_string_literal: true
2
+
2
3
  #
3
4
  # Copyright 2013 whiteleaf. All rights reserved.
4
5
  #
@@ -1,4 +1,5 @@
1
- # -*- coding: utf-8 -*-
1
+ # frozen_string_literal: true
2
+
2
3
  #
3
4
  # Copyright 2013 whiteleaf. All rights reserved.
4
5
  #
@@ -1,4 +1,5 @@
1
- # -*- coding: utf-8 -*-
1
+ # frozen_string_literal: true
2
+
2
3
  #
3
4
  # Copyright 2013 whiteleaf. All rights reserved.
4
5
  #
@@ -1,4 +1,5 @@
1
- # -*- coding: utf-8 -*-
1
+ # frozen_string_literal: true
2
+
2
3
  #
3
4
  # Copyright 2013 whiteleaf. All rights reserved.
4
5
  #
@@ -1,4 +1,5 @@
1
- # -*- coding: utf-8 -*-
1
+ # frozen_string_literal: true
2
+
2
3
  #
3
4
  # Copyright 2013 whiteleaf. All rights reserved.
4
5
  #
@@ -1,4 +1,5 @@
1
- # -*- coding: utf-8 -*-
1
+ # frozen_string_literal: true
2
+
2
3
  #
3
4
  # Copyright 2013 whiteleaf. All rights reserved.
4
5
  #
@@ -1,4 +1,5 @@
1
- # -*- coding: utf-8 -*-
1
+ # frozen_string_literal: true
2
+
2
3
  #
3
4
  # Copyright 2013 whiteleaf. All rights reserved.
4
5
  #
@@ -26,19 +27,15 @@ class DiffViewer
26
27
  end
27
28
  old_strings.gsub!("\r", "")
28
29
  new_strings.gsub!("\r", "")
29
- @builded_buffer = ""
30
+ @builded_buffer = +""
30
31
  @events = Diff::LCS.sdiff(old_strings.split("\n"), new_strings.split("\n"))
31
32
  build
32
33
  end
33
34
 
34
- def to_s
35
+ def result
35
36
  @builded_buffer
36
37
  end
37
38
 
38
- def view
39
- puts @builded_buffer
40
- end
41
-
42
39
  private
43
40
 
44
41
  def build
@@ -54,7 +51,7 @@ class DiffViewer
54
51
  index
55
52
  }.map { |(index, (event, str))|
56
53
  # index が途切れたら、ポジション情報を付与する
57
- result = ""
54
+ result = +""
58
55
  if index - before_index >= 2
59
56
  result += "<bold><cyan>@@ -#{event.old_position+1}, " \
60
57
  "+#{event.new_position+1} @@</cyan></bold>\n".termcolor
@@ -97,13 +94,13 @@ class DiffViewer
97
94
  # event.action を見てラインを装飾
98
95
  #
99
96
  def decorate_event(event)
100
- result = ""
97
+ result = +""
101
98
  old_element = event.old_element
102
99
  new_element = event.new_element
103
100
  case event.action
104
101
  when "!"
105
- old_str = ""
106
- new_str = ""
102
+ old_str = +""
103
+ new_str = +""
107
104
  line_events = Diff::LCS.sdiff(old_element, new_element)
108
105
  distance = calc_levenshtein_distance(line_events)
109
106
  # レーベンシュタイン距離を正規化する