narou 2.6.1 → 2.7.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 (68) hide show
  1. checksums.yaml +4 -4
  2. data/ChangeLog.md +65 -0
  3. data/README.md +69 -22
  4. data/lib/command.rb +1 -0
  5. data/lib/command/alias.rb +2 -2
  6. data/lib/command/convert.rb +3 -3
  7. data/lib/command/csv.rb +108 -0
  8. data/lib/command/download.rb +13 -1
  9. data/lib/command/freeze.rb +1 -1
  10. data/lib/command/init.rb +5 -1
  11. data/lib/command/inspect.rb +1 -1
  12. data/lib/command/mail.rb +1 -1
  13. data/lib/command/send.rb +2 -2
  14. data/lib/command/setting.rb +17 -1
  15. data/lib/command/tag.rb +28 -9
  16. data/lib/command/update.rb +57 -30
  17. data/lib/commandbase.rb +3 -2
  18. data/lib/commandline.rb +2 -2
  19. data/lib/converterbase.rb +70 -7
  20. data/lib/database.rb +1 -1
  21. data/lib/device.rb +3 -0
  22. data/lib/device/ibooks.rb +2 -2
  23. data/lib/device/ibunko.rb +4 -0
  24. data/lib/diffviewer.rb +2 -2
  25. data/lib/downloader.rb +16 -9
  26. data/lib/helper.rb +53 -22
  27. data/lib/inventory.rb +6 -2
  28. data/lib/narou.rb +13 -6
  29. data/lib/novelconverter.rb +35 -13
  30. data/lib/novelsetting.rb +7 -1
  31. data/lib/version.rb +1 -1
  32. data/lib/web/appserver.rb +78 -8
  33. data/lib/web/public/resources/common.ui.js +0 -39
  34. data/lib/web/public/resources/dataTables.colVis.js +1 -1
  35. data/lib/web/public/resources/default-style.css +1 -1
  36. data/lib/web/public/resources/help/bookmarklet1.png +0 -0
  37. data/lib/web/public/resources/help/bookmarklet2.png +0 -0
  38. data/lib/web/public/resources/images/dl_button0.gif +0 -0
  39. data/lib/web/public/resources/images/dl_button1.gif +0 -0
  40. data/lib/web/public/resources/narou.library.js +268 -36
  41. data/lib/web/public/resources/narou.queue.js +51 -0
  42. data/lib/web/public/resources/narou.ui.js +156 -59
  43. data/lib/web/settingmessages.rb +2 -1
  44. data/lib/web/views/{js/widget.erb → bookmarklet/download.js.erb} +1 -1
  45. data/lib/web/views/bookmarklet/insert_button.js.erb +133 -0
  46. data/lib/web/views/help.haml +31 -14
  47. data/lib/web/views/index.haml +62 -0
  48. data/lib/web/views/layout.haml +8 -7
  49. data/lib/web/views/parts/csv_import.haml +68 -0
  50. data/lib/web/views/parts/download_form.haml +89 -0
  51. data/lib/web/views/settings.haml +8 -3
  52. data/lib/web/views/style.scss +200 -14
  53. data/lib/web/views/{widget.haml → widget/download.haml} +18 -4
  54. data/lib/web/views/widget/drag_and_drop.haml +97 -0
  55. data/lib/web/worker.rb +39 -1
  56. data/narou.gemspec +56 -56
  57. data/preset/custom_chuki_tag.txt +1 -1
  58. data/preset/doubledash.png +0 -0
  59. data/preset/singledash.png +0 -0
  60. data/preset/vertical_font.css +1 -1
  61. data/spec/convert_spec.rb +5 -1
  62. data/spec/data/convert_test/auto_join_line/correct_test_auto_join_line.txt +6 -0
  63. data/spec/data/convert_test/auto_join_line/test_auto_join_line.txt +8 -0
  64. data/spec/data/convert_test/double_dash_to_image/correct_test_double_dash_to_image.txt +14 -0
  65. data/spec/data/convert_test/double_dash_to_image/setting.ini +1 -0
  66. data/spec/data/convert_test/double_dash_to_image/test_double_dash_to_image.txt +12 -0
  67. data/spec/worker_spec.rb +21 -2
  68. metadata +97 -60
@@ -333,7 +333,7 @@ module Command
333
333
  tab: :general
334
334
  },
335
335
  "hotentry.auto-mail" => {
336
- type: :boolean, help: "新着投稿作成時にメールを送る(mail設定済みの場合)",
336
+ type: :boolean, help: "hotentryをメールで送る(mail設定済みの場合)",
337
337
  tab: :detail
338
338
  },
339
339
  "update.strong" => {
@@ -431,8 +431,24 @@ module Command
431
431
  type: :string, help: "--multiple指定時の区切り文字",
432
432
  tab: :detail
433
433
  },
434
+ "economy" => {
435
+ type: :multiple,
436
+ help: "容量節約に関する設定。カンマ区切りで設定\n" \
437
+ "(cleanup_temp:変換後に作業ファイルを削除 send_delete:送信後に書籍ファイルを削除 " \
438
+ "nosave_diff:差分ファイルを保存しない nosave_raw:rawデータを保存しない)",
439
+ select_keys: %w(cleanup_temp send_delete nosave_diff nosave_raw),
440
+ select_summaries: %w(変換後に作業ファイルを削除 送信後に書籍ファイルを削除
441
+ 差分ファイルを保存しない rawデータを保存しない),
442
+ tab: :detail
443
+ },
444
+ "guard-spoiler" => {
445
+ type: :boolean,
446
+ help: "ネタバレ防止機能。ダウンロード時の各話タイトルを伏せ字で表示する",
447
+ tab: :detail
448
+ },
434
449
  "theme" => {
435
450
  type: :select, help: "WEB UI 用テーマ選択",
451
+ invisible: true,
436
452
  select_keys: Narou.get_theme_names,
437
453
  select_summaries: Narou.get_theme_names,
438
454
  tab: :general
@@ -114,15 +114,25 @@ module Command
114
114
  end
115
115
 
116
116
  def execute(argv)
117
+ # タグの色がすでに決まっていた場合は変更しない隠しオプション
118
+ no_overwrite_color = !!argv.delete("--no-overwrite-color")
117
119
  super
120
+ @options["no-overwrite-color"] = no_overwrite_color # super で @options.clear されるのでここで代入
121
+ color_changed = change_colors
118
122
  @options["mode"] ||= :list
119
123
  if argv.empty?
120
124
  if @options["mode"] == :list
121
125
  display_taglist
122
126
  return
123
127
  end
124
- error "対象の小説を指定して下さい"
125
- exit Narou::EXIT_ERROR_CODE
128
+ if color_changed
129
+ puts "タグの色を変更しました"
130
+ display_taglist
131
+ return
132
+ else
133
+ error "対象の小説を指定して下さい"
134
+ exit Narou::EXIT_ERROR_CODE
135
+ end
126
136
  else
127
137
  if @options["mode"] == :list
128
138
  search_novel_by_tag(argv)
@@ -146,6 +156,17 @@ module Command
146
156
  List.execute!(["--tag", argv.join(" ")])
147
157
  end
148
158
 
159
+ def change_colors
160
+ changed = false
161
+ if @options["color"] && @options["tags"]
162
+ @options["tags"].each do |tag|
163
+ set_color(tag, @options["color"])
164
+ end
165
+ changed = true
166
+ end
167
+ changed
168
+ end
169
+
149
170
  def edit_tags(argv)
150
171
  database = Database.instance
151
172
  tagname_to_ids(argv)
@@ -168,11 +189,6 @@ module Command
168
189
  tags.clear
169
190
  puts "#{title} のタグをすべて外しました"
170
191
  end
171
- if @options["color"] && @options["tags"]
172
- @options["tags"].each do |tag|
173
- set_color(tag, @options["color"])
174
- end
175
- end
176
192
  if tags.size > 0
177
193
  print "現在のタグは "
178
194
  print tags.map { |tagname|
@@ -187,7 +203,7 @@ module Command
187
203
  end
188
204
 
189
205
  def self.get_color(tagname)
190
- tag_colors = Inventory.load("tag_colors", :local)
206
+ tag_colors = Inventory.load("tag_colors")
191
207
  color = tag_colors[tagname]
192
208
  return color if color
193
209
  last_color = tag_colors.values.last || COLORS.last
@@ -199,7 +215,10 @@ module Command
199
215
  end
200
216
 
201
217
  def set_color(tagname, color)
202
- tag_colors = Inventory.load("tag_colors", :local)
218
+ tag_colors = Inventory.load("tag_colors")
219
+ if @options["no-overwrite-color"]
220
+ return if tag_colors.include?(tagname)
221
+ end
203
222
  tag_colors[tagname] = color
204
223
  tag_colors.save
205
224
  end
@@ -114,12 +114,12 @@ module Command
114
114
  super
115
115
  mistook_count = 0
116
116
  update_target_list = argv.dup
117
- no_open = false
117
+ @options["no-open"] = false
118
118
  if update_target_list.empty?
119
119
  Database.instance.each_key do |id|
120
120
  update_target_list << id
121
121
  end
122
- no_open = true
122
+ @options["no-open"] = true
123
123
  end
124
124
  tagname_to_ids(update_target_list)
125
125
 
@@ -134,7 +134,7 @@ module Command
134
134
  end
135
135
  flush_cache # memoist のキャッシュ削除
136
136
 
137
- inv = Inventory.load("local_setting", :local)
137
+ inv = Inventory.load("local_setting")
138
138
  @options["hotentry"] = inv["hotentry"]
139
139
  @options["hotentry.auto-mail"] = inv["hotentry.auto-mail"]
140
140
  hotentry = {}
@@ -170,31 +170,50 @@ module Command
170
170
  result = downloader.start_download
171
171
  case result.status
172
172
  when :ok
173
- unless @options["no-convert"] ||
174
- (@options["convert-only-new-arrival"] && !result.new_arrivals)
175
- convert_argv = [target]
176
- convert_argv << "--no-open" if no_open
177
- Convert.execute!(convert_argv)
173
+ if @options["no-convert"] ||
174
+ (@options["convert-only-new-arrival"] && !result.new_arrivals)
175
+ next
178
176
  end
179
177
  when :failed
180
178
  puts "ID:#{data["id"]} #{data["title"]} の更新は失敗しました"
181
179
  mistook_count += 1
180
+ next
182
181
  when :canceled
183
182
  puts "ID:#{data["id"]} #{data["title"]} の更新はキャンセルされました"
184
183
  mistook_count += 1
184
+ next
185
185
  when :none
186
186
  puts "#{data["title"]} に更新はありません"
187
+ next unless data["_convert_failure"]
188
+ end
189
+
190
+ if data["_convert_failure"]
191
+ puts "<yellow>前回変換できなかったので再変換します</yellow>".termcolor
192
+ end
193
+ convert_argv = [target]
194
+ convert_argv << "--no-open" if @options["no-open"]
195
+ convert_status = Convert.execute!(convert_argv)
196
+ if convert_status > 0
197
+ # 変換が失敗したか、中断された
198
+ data["_convert_failure"] = true
199
+ # 中断された場合には残りのアップデートも中止する
200
+ raise Interrupt if convert_status == Narou::EXIT_INTERRUPT
201
+ else
202
+ # 変換に成功した
203
+ data.delete("_convert_failure")
187
204
  end
188
205
  end
189
- end
190
206
 
191
- process_hotentry(hotentry)
192
- save_log(update_log)
207
+ process_hotentry(hotentry)
208
+ end
193
209
 
194
210
  exit mistook_count if mistook_count > 0
195
211
  rescue Interrupt
196
212
  puts "アップデートを中断しました"
197
- exit Narou::EXIT_ERROR_CODE
213
+ exit Narou::EXIT_INTERRUPT
214
+ ensure
215
+ save_log(update_log)
216
+ Database.instance.save_database
198
217
  end
199
218
 
200
219
  def get_log_paths
@@ -241,6 +260,7 @@ module Command
241
260
  end
242
261
 
243
262
  def update_general_lastup(through_frozen_novel: true)
263
+ completed = false
244
264
  database = Database.instance
245
265
  puts "最新話掲載日を更新しています..."
246
266
  progressbar = ProgressBar.new(database.get_object.size - 1)
@@ -265,7 +285,7 @@ module Command
265
285
  else
266
286
  # 小説情報ページがない場合は目次から取得する
267
287
  begin
268
- dates = get_latest_dates(setting)
288
+ dates = get_latest_dates(id)
269
289
  rescue OpenURI::HTTPError, Errno::ECONNRESET => e
270
290
  setting.clear
271
291
  next
@@ -275,8 +295,10 @@ module Command
275
295
  setting.clear
276
296
  end
277
297
  database.save_database
278
- progressbar.clear
279
- puts "更新が完了しました"
298
+ completed = true
299
+ ensure
300
+ progressbar.clear if progressbar
301
+ puts "更新が完了しました" if completed
280
302
  end
281
303
 
282
304
  # オンラインの目次からgeneral_lastupを取得する
@@ -322,23 +344,28 @@ module Command
322
344
  progressbar = ProgressBar.new(subtitles_size)
323
345
  total_progress = 0
324
346
 
325
- hotentry.each do |id, subtitles|
326
- setting = NovelSetting.load(id, ignore_force, ignore_default)
327
- novel_converter = NovelConverter.new(setting, output_filename, display_inspector)
328
- last_num = 0
329
- novel_converter.on(:"convert_main.loop") do |i|
330
- progressbar.output(total_progress + i)
331
- last_num = i
332
- end
333
- converted_text_array << {
334
- setting: setting,
335
- text: novel_converter.convert_main_for_novel(subtitles, true)
336
- }
337
- use_dakuten_font |= novel_converter.use_dakuten_font
347
+ begin
348
+ hotentry.each do |id, subtitles|
349
+ setting = NovelSetting.load(id, ignore_force, ignore_default)
350
+ setting.enable_illust = false # 挿絵はパス解決が煩雑なので強制無効
351
+ novel_converter = NovelConverter.new(setting, output_filename,
352
+ display_inspector, Update.hotentry_dirname)
353
+ last_num = 0
354
+ novel_converter.on(:"convert_main.loop") do |i|
355
+ progressbar.output(total_progress + i)
356
+ last_num = i
357
+ end
358
+ converted_text_array << {
359
+ setting: setting,
360
+ text: novel_converter.convert_main_for_novel(subtitles, true)
361
+ }
362
+ use_dakuten_font |= novel_converter.use_dakuten_font
338
363
 
339
- total_progress += last_num + 1
364
+ total_progress += last_num + 1
365
+ end
366
+ ensure
367
+ progressbar.clear
340
368
  end
341
- progressbar.clear
342
369
  puts "縦書用の変換が終了しました"
343
370
 
344
371
  device = Narou.get_device
@@ -4,6 +4,7 @@
4
4
  #
5
5
 
6
6
  require "optparse"
7
+ require_relative "web/worker"
7
8
 
8
9
  module Command
9
10
  class CommandBase
@@ -55,7 +56,7 @@ module Command
55
56
 
56
57
  def load_local_settings
57
58
  command_prefix = self.class.to_s.scan(/[^:]+$/)[0].downcase
58
- local_settings = Inventory.load("local_setting", :local)
59
+ local_settings = Inventory.load("local_setting")
59
60
  local_settings.each do |name, value|
60
61
  if name =~ /^#{command_prefix}\.(.+)$/
61
62
  @options[$1] = value
@@ -126,7 +127,7 @@ module Command
126
127
  # 設定の強制設定
127
128
  #
128
129
  def force_change_settings_function(pairs)
129
- settings = Inventory.load("local_setting", :local)
130
+ settings = Inventory.load("local_setting")
130
131
  modified = false
131
132
  pairs.each do |name, value|
132
133
  if settings[name].nil? || settings[name] != value
@@ -62,7 +62,7 @@ module CommandLine
62
62
  end
63
63
 
64
64
  def load_default_arguments(cmd)
65
- default_arguments_list = Inventory.load("local_setting", :local)
65
+ default_arguments_list = Inventory.load("local_setting")
66
66
  (default_arguments_list["default_args.#{cmd}"] || "").split
67
67
  end
68
68
 
@@ -70,7 +70,7 @@ module CommandLine
70
70
  # 引数をスペース以外による区切り文字で展開する
71
71
  #
72
72
  def multiple_argument_extract(argv)
73
- delimiter = Inventory.load("local_setting", :local)["multiple-delimiter"] || ","
73
+ delimiter = Inventory.load("local_setting")["multiple-delimiter"] || ","
74
74
  argv.map! { |arg|
75
75
  arg.split(delimiter)
76
76
  }.flatten!
@@ -7,6 +7,7 @@ require "stringio"
7
7
  require "date"
8
8
  require "uri"
9
9
  require "nkf"
10
+ require "pathname"
10
11
  require_relative "narou"
11
12
  require_relative "progressbar"
12
13
  require_relative "inspector"
@@ -17,6 +18,8 @@ class ConverterBase
17
18
  ENGLISH_SENTENCES_MIN_LENGTH = 8 # この文字数以上アルファベットが続くと半角のまま
18
19
 
19
20
  attr_reader :use_dakuten_font
21
+ attr_accessor :output_text_dir, :subtitles
22
+ attr_accessor :current_index # 現在処理してる subtitles 内でのインデックス
20
23
 
21
24
  def before(io, text_type)
22
25
  data = io.string
@@ -37,10 +40,17 @@ class ConverterBase
37
40
  @inspector = inspector
38
41
  @illustration = illustration
39
42
  @use_dakuten_font = false
40
- initialize_member_values
43
+ @output_text_dir = nil
44
+ @subtitles = nil
45
+ @current_index = 0
46
+ reset_member_values
41
47
  end
42
48
 
43
- def initialize_member_values
49
+ #
50
+ # .convert が実行されるたびに呼ばれるメンバ変数リセット用メソッド
51
+ # インスタンス作成時に一度だけ初期化したい場合は initialize で初期化する
52
+ #
53
+ def reset_member_values
44
54
  @request_insert_blank_next_line = false
45
55
  @request_skip_output_line = false
46
56
  @before_line = ""
@@ -832,7 +842,8 @@ class ConverterBase
832
842
  #
833
843
  def auto_join_line(data)
834
844
  # 次の行の冒頭が開き記号だったら意図的な改行だと判断して連結しない
835
- data.gsub!(/([^、])、\n ([^「『((【<<〈《≪…‥―])/, "\\1、\\2")
845
+ # 行頭の全角スペースが2個以上の場合も連結しない
846
+ data.gsub!(/([^、])、\n ([^「『((【<<〈《≪…‥― ])/, "\\1、\\2")
836
847
  end
837
848
 
838
849
  CHARACTER_OF_RUBY = "一-龠A-Za-zA-Za-z"
@@ -1267,12 +1278,16 @@ class ConverterBase
1267
1278
 
1268
1279
  def convert(text, text_type)
1269
1280
  return "" if text == ""
1281
+ output_text_dir = @output_text_dir || @setting.archive_path
1270
1282
  @text_type = text_type
1271
1283
  io = StringIO.new(rstrip_all_lines(text))
1272
1284
  (io = before_convert(io)).rewind
1273
1285
  (io = convert_main(io)).rewind
1274
1286
  (io = after_convert(io)).rewind
1275
- return insert_separator_for_selection(replace_by_replace_txt(io.read))
1287
+ data = replace_by_replace_txt(io.read)
1288
+ data = insert_separator_for_selection(data)
1289
+ data = double_dash_to_image(data, output_text_dir)
1290
+ return data
1276
1291
  end
1277
1292
 
1278
1293
  #
@@ -1297,8 +1312,9 @@ class ConverterBase
1297
1312
  else
1298
1313
  data = io.read
1299
1314
  end
1300
- initialize_member_values
1315
+ reset_member_values
1301
1316
  convert_for_all_data(data)
1317
+ progressbar = nil
1302
1318
  if @text_type == "textfile"
1303
1319
  # convert_for_all_data -> replace_narou_tag
1304
1320
  # で改行化を行わないと正確な改行数は分からない
@@ -1310,7 +1326,7 @@ class ConverterBase
1310
1326
  @write_fp.write(data)
1311
1327
  else
1312
1328
  @read_fp.each_with_index do |line, i|
1313
- progressbar.output(i) if @text_type == "textfile"
1329
+ progressbar.output(i) if progressbar
1314
1330
  @request_skip_output_line = false
1315
1331
  zenkaku_rstrip(line)
1316
1332
  if @request_insert_blank_next_line
@@ -1367,8 +1383,11 @@ class ConverterBase
1367
1383
  data.replace(title_and_author + data)
1368
1384
  end
1369
1385
  data.rstrip!
1370
- progressbar.clear if @text_type == "textfile"
1371
1386
  @write_fp
1387
+ ensure
1388
+ if @text_type == "textfile" && progressbar
1389
+ progressbar.clear
1390
+ end
1372
1391
  end
1373
1392
 
1374
1393
  #
@@ -1382,4 +1401,48 @@ class ConverterBase
1382
1401
  end
1383
1402
  result
1384
1403
  end
1404
+
1405
+ DASH_FILES = %w(singledash.png doubledash.png)
1406
+
1407
+ def double_dash_to_image(text, output_text_dir)
1408
+ return text unless @setting.enable_double_dash_to_image
1409
+ begin
1410
+ # AozoraEpub3 は相対パスじゃないとエラーになるので相対パスに変換
1411
+ dash_paths = dash_image_relative_paths(Narou.get_preset_dir, output_text_dir)
1412
+ rescue ArgumentError => e
1413
+ if e.message =~ /^different prefix/
1414
+ # Windowsにおいて、スクリプト本体のあるドライブと小説フォルダがあるドライブが
1415
+ # 違う場合、相対パスを計算できなくなる。そのための対処として、.narou ディレクトリ
1416
+ # に画像データをコピーし、同一ドライブ内で相対パスを取れるようにする
1417
+ copy_dash_images_to_local_setting_dir
1418
+ dash_paths = dash_image_relative_paths(Narou.get_local_setting_dir, output_text_dir)
1419
+ else
1420
+ raise
1421
+ end
1422
+ end
1423
+ text.gsub(/―{2,}/) do |match|
1424
+ len = match.length
1425
+ result = "※[#(#{dash_paths[1]})]" * (len / 2)
1426
+ if len.odd?
1427
+ result += "※[#(#{dash_paths[0]})]"
1428
+ end
1429
+ result
1430
+ end
1431
+ end
1432
+
1433
+ def dash_image_relative_paths(base_dir, output_text_dir)
1434
+ DASH_FILES.map do |name|
1435
+ pathname = Pathname(File.join(base_dir, name))
1436
+ pathname.relative_path_from(Pathname(output_text_dir)).to_s
1437
+ end
1438
+ end
1439
+
1440
+ def copy_dash_images_to_local_setting_dir
1441
+ DASH_FILES.each do |name|
1442
+ path = File.join(Narou.get_local_setting_dir, name)
1443
+ unless File.exist?(path)
1444
+ FileUtils.copy(File.join(Narou.get_preset_dir, name), path)
1445
+ end
1446
+ end
1447
+ end
1385
1448
  end