narou 1.1.0.rc1 → 1.1.0.rc2
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.
Potentially problematic release.
This version of narou might be problematic. Click here for more details.
- data/.gitignore +1 -1
- data/ChangeLog.md +22 -0
- data/README.md +24 -288
- data/lib/command/convert.rb +7 -3
- data/lib/command/download.rb +1 -2
- data/lib/command/init.rb +3 -2
- data/lib/command/setting.rb +1 -0
- data/lib/command/update.rb +7 -5
- data/lib/converterbase.rb +89 -65
- data/lib/downloader.rb +60 -10
- data/lib/loadconverter.rb +5 -15
- data/lib/novelconverter.rb +11 -4
- data/lib/novelsetting.rb +4 -4
- data/lib/sitesetting.rb +3 -0
- data/lib/version.rb +1 -1
- data/preset/custom_chuki_tag.txt +1 -6
- data/preset/vertical_font.css +4 -2
- data/preset/xhtml_nav.vm +109 -0
- data/template/novel.txt.erb +1 -7
- data/webnovel/ncode.syosetu.com.yaml +1 -0
- data/webnovel/novel18.syosetu.com.yaml +1 -0
- metadata +4 -3
- data/ChangeLog.txt +0 -12
data/lib/converterbase.rb
CHANGED
@@ -41,6 +41,7 @@ class ConverterBase
|
|
41
41
|
@english_sentences = []
|
42
42
|
@url_list = []
|
43
43
|
@illust_chuki_list = []
|
44
|
+
@in_author_comment_block = nil
|
44
45
|
end
|
45
46
|
|
46
47
|
def outputs(data = "", force = false)
|
@@ -59,7 +60,21 @@ class ConverterBase
|
|
59
60
|
# すべての行の行末空白を削除
|
60
61
|
#
|
61
62
|
def rstrip_all_lines(data)
|
62
|
-
data.gsub(
|
63
|
+
data.gsub(/[ \t]+$/m, "")
|
64
|
+
end
|
65
|
+
|
66
|
+
#
|
67
|
+
# 数字の変換
|
68
|
+
#
|
69
|
+
def convert_numbers(data)
|
70
|
+
# 小数点を・に
|
71
|
+
data.gsub!(/([\d0-9#{KANJI_NUM}]+?)[\..]([\d0-9#{KANJI_NUM}]+?)/, "\\1・\\2")
|
72
|
+
if @setting.enable_convert_num_to_kanji
|
73
|
+
num_to_kanji(data)
|
74
|
+
else
|
75
|
+
hankaku_num_to_zenkaku_num(data)
|
76
|
+
end
|
77
|
+
data
|
63
78
|
end
|
64
79
|
|
65
80
|
#
|
@@ -68,8 +83,6 @@ class ConverterBase
|
|
68
83
|
# カンマ区切りの数字はアラビア数字のままにしておく
|
69
84
|
#
|
70
85
|
def num_to_kanji(data)
|
71
|
-
data.gsub!(/([\d0-9#{KANJI_NUM}]+?)[\..]([\d0-9#{KANJI_NUM}]+?)/, "\\1・\\2")
|
72
|
-
return unless @setting.enable_convert_num_to_kanji
|
73
86
|
data.gsub!(/[\d0-9,,]+/) do |match|
|
74
87
|
if match =~ /[,,]/
|
75
88
|
match
|
@@ -77,6 +90,7 @@ class ConverterBase
|
|
77
90
|
zenkaku_num_to_kanji(match.tr("0-9", KANJI_NUM))
|
78
91
|
end
|
79
92
|
end
|
93
|
+
data
|
80
94
|
end
|
81
95
|
|
82
96
|
#
|
@@ -86,16 +100,26 @@ class ConverterBase
|
|
86
100
|
str.tr("0-9", KANJI_NUM)
|
87
101
|
end
|
88
102
|
|
89
|
-
KANJI_NUM_UNITS = %w(万 億 兆 京
|
103
|
+
KANJI_NUM_UNITS = %w(万 億 兆 京).unshift("")
|
90
104
|
KANJI_KURAI = %w(十 百 千).unshift("")
|
105
|
+
KANJI_NUM_UNITS_DIGIT = {
|
106
|
+
"十" => 1, "百" => 2, "千" => 3, "万" => 4, "億" => 8, "兆" => 12, "京" => 16
|
107
|
+
}
|
91
108
|
#
|
92
109
|
# 漢数字を単位を使った表現に変換
|
93
110
|
#
|
111
|
+
# 800万1000 といったような表現は、内部一度で 8001000 に変換する。
|
112
|
+
# lower_digit_zero はこの最後の 000 に適用される
|
113
|
+
#
|
94
114
|
def convert_kanji_num_with_unit(data, lower_digit_zero = 0)
|
95
|
-
data.gsub!(/([#{KANJI_NUM}]+)([
|
96
|
-
|
97
|
-
|
98
|
-
|
115
|
+
#data.gsub!(/([#{KANJI_NUM}]+)([十百千万億兆京]?)/) do |match|
|
116
|
+
data.gsub!(/([#{KANJI_NUM}十百千万億兆京]+)/) do |match|
|
117
|
+
total = 0
|
118
|
+
$1.scan(/([#{KANJI_NUM}]+)([十百千万億兆京]*)/) do |num, units|
|
119
|
+
total += (num.tr(KANJI_NUM, "0-9") + units.each_char.map { |c| "0" * KANJI_NUM_UNITS_DIGIT[c] }.join).to_i
|
120
|
+
end
|
121
|
+
m1 = total.to_s.tr("0-9", KANJI_NUM)
|
122
|
+
if m1 =~ /〇{#{lower_digit_zero},}$/
|
99
123
|
digits = m1.reverse.scan(/.{1,4}/).map(&:reverse).reverse # 下の桁から4桁ずつ区切った配列を作成
|
100
124
|
keta = digits.count - 1
|
101
125
|
digits.map.with_index { |nums, keta_i|
|
@@ -162,9 +186,7 @@ class ConverterBase
|
|
162
186
|
rescue ArgumentError
|
163
187
|
match
|
164
188
|
else
|
165
|
-
|
166
|
-
num_to_kanji(buf)
|
167
|
-
buf
|
189
|
+
convert_numbers(date.strftime(@setting.date_format))
|
168
190
|
end
|
169
191
|
end
|
170
192
|
else
|
@@ -174,7 +196,7 @@ class ConverterBase
|
|
174
196
|
end
|
175
197
|
|
176
198
|
#
|
177
|
-
#
|
199
|
+
# 特定の記号の直後は全角アキを挿入する
|
178
200
|
#
|
179
201
|
def insert_separate_space(data)
|
180
202
|
data.gsub!(/([!?!?]+)([^!?!?])/) do
|
@@ -332,16 +354,14 @@ class ConverterBase
|
|
332
354
|
#
|
333
355
|
def rebuild_english_sentences(data)
|
334
356
|
@english_sentences.each_with_index do |sentence, id|
|
335
|
-
|
336
|
-
num_to_kanji(buf)
|
337
|
-
data.sub!("[#英文=#{buf}]", sentence)
|
357
|
+
data.sub!("[#英文=#{convert_numbers(id.to_s)}]", sentence)
|
338
358
|
end
|
339
359
|
end
|
340
360
|
|
341
361
|
#
|
342
362
|
# コメントブロックを検出する
|
343
363
|
#
|
344
|
-
# コメントブロックの定義は -
|
364
|
+
# コメントブロックの定義は - のみが50回以上連続された行に囲まれている間
|
345
365
|
#
|
346
366
|
def comments_block?(line)
|
347
367
|
if line =~ /^-{50,}$/
|
@@ -364,17 +384,27 @@ class ConverterBase
|
|
364
384
|
def zenkaku_num_to_hankaku_num(num)
|
365
385
|
num.tr("0-9#{KANJI_NUM}", "0-90-9")
|
366
386
|
end
|
387
|
+
|
388
|
+
#
|
389
|
+
# 3桁以上の半角数字を全角アラビア数字に
|
390
|
+
#
|
391
|
+
def hankaku_num_to_zenkaku_num(data)
|
392
|
+
data.gsub!(/\d{3,}/) do |num|
|
393
|
+
num.tr("0-9", "0-9")
|
394
|
+
end
|
395
|
+
data
|
396
|
+
end
|
367
397
|
|
368
398
|
HALF_INDENT_TARGET = /^[ \t]*([「『((【〈《≪])/
|
369
399
|
FULL_INDENT_TARGET = /^[ \t]*(――)/
|
370
400
|
#
|
371
|
-
#
|
401
|
+
# 行頭鍵カッコ(等)に二分アキを追加する
|
372
402
|
#
|
373
|
-
#
|
403
|
+
# 「や(などの前にカスタム注記([#二分アキ])を追加し、半文字分字下げする(二分アキ)。
|
374
404
|
# kindle paperwhite で鍵括弧のインデントがおかしいことへの対応
|
375
405
|
#
|
376
|
-
def
|
377
|
-
data.gsub!(HALF_INDENT_TARGET, "
|
406
|
+
def half_indent_bracket(data)
|
407
|
+
data.gsub!(HALF_INDENT_TARGET, "[#二分アキ]\\1") if @setting.enable_half_indent_bracket
|
378
408
|
end
|
379
409
|
|
380
410
|
#
|
@@ -444,7 +474,7 @@ class ConverterBase
|
|
444
474
|
#
|
445
475
|
# 改ページある?
|
446
476
|
#
|
447
|
-
def
|
477
|
+
def page_break?(line)
|
448
478
|
line =~ /[#改ページ]/
|
449
479
|
end
|
450
480
|
|
@@ -452,8 +482,8 @@ class ConverterBase
|
|
452
482
|
# 前書き・後書きの検出及び処理 ==============================
|
453
483
|
#
|
454
484
|
|
455
|
-
AUTHOR_INTRODUCTION_SPLITTER = /^[\**]{44}$/
|
456
|
-
AUTHOR_POSTSCRIPT_SPLITTER = /^[\**]{48}$/
|
485
|
+
AUTHOR_INTRODUCTION_SPLITTER = /^ *[\**]{44}$/
|
486
|
+
AUTHOR_POSTSCRIPT_SPLITTER = /^ *[\**]{48}$/
|
457
487
|
AUTHOR_COMMENT_CHUKI = {
|
458
488
|
introduction: {
|
459
489
|
open: "[#ここから前書き]", close: "[#ここで前書き終わり]"
|
@@ -465,17 +495,7 @@ class ConverterBase
|
|
465
495
|
|
466
496
|
def process_author_comment(line)
|
467
497
|
if @setting.enable_author_comments
|
468
|
-
|
469
|
-
if inclusion_author_comment_block?(line)
|
470
|
-
# outputs を使うと改ページより前に注記が入ってしまうため、
|
471
|
-
# delay_outputs を使って出力を line 出力の後に遅らせる
|
472
|
-
delay_outputs(AUTHOR_COMMENT_CHUKI[@in_author_comment_block][:open])
|
473
|
-
if @in_author_comment_block == :postscript
|
474
|
-
@request_skip_output_line = true
|
475
|
-
line.clear
|
476
|
-
end
|
477
|
-
end
|
478
|
-
else
|
498
|
+
if @in_author_comment_block
|
479
499
|
if leave_author_comment_block?(line)
|
480
500
|
outputs(AUTHOR_COMMENT_CHUKI[@in_author_comment_block][:close])
|
481
501
|
if @in_author_comment_block == :introduction
|
@@ -489,6 +509,16 @@ class ConverterBase
|
|
489
509
|
process_author_comment(line)
|
490
510
|
end
|
491
511
|
end
|
512
|
+
else
|
513
|
+
if inclusion_author_comment_block?(line)
|
514
|
+
# outputs を使うと改ページより前に注記が入ってしまうため、
|
515
|
+
# delay_outputs を使って出力を line 出力の後に遅らせる
|
516
|
+
delay_outputs(AUTHOR_COMMENT_CHUKI[@in_author_comment_block][:open])
|
517
|
+
if @in_author_comment_block == :postscript
|
518
|
+
@request_skip_output_line = true
|
519
|
+
line.clear
|
520
|
+
end
|
521
|
+
end
|
492
522
|
end
|
493
523
|
end
|
494
524
|
end
|
@@ -498,7 +528,7 @@ class ConverterBase
|
|
498
528
|
pos = @read_fp.pos
|
499
529
|
result = false
|
500
530
|
@read_fp.each do |line|
|
501
|
-
break if
|
531
|
+
break if page_break?(line)
|
502
532
|
if line =~ AUTHOR_INTRODUCTION_SPLITTER
|
503
533
|
result = true
|
504
534
|
break
|
@@ -510,7 +540,7 @@ class ConverterBase
|
|
510
540
|
|
511
541
|
def inclusion_author_comment_block?(line)
|
512
542
|
result = false
|
513
|
-
if
|
543
|
+
if page_break?(line)
|
514
544
|
if find_introduction?
|
515
545
|
@in_author_comment_block = :introduction
|
516
546
|
result = true
|
@@ -530,7 +560,7 @@ class ConverterBase
|
|
530
560
|
result = true
|
531
561
|
end
|
532
562
|
when :postscript
|
533
|
-
if
|
563
|
+
if page_break?(line)
|
534
564
|
result = true
|
535
565
|
end
|
536
566
|
end
|
@@ -686,7 +716,7 @@ class ConverterBase
|
|
686
716
|
# 全角版 String#rstrip!
|
687
717
|
#
|
688
718
|
def zenkaku_rstrip(line)
|
689
|
-
line.gsub!(/[ \s]
|
719
|
+
line.gsub!(/[ \s]+\z/, "")
|
690
720
|
end
|
691
721
|
|
692
722
|
#
|
@@ -701,10 +731,8 @@ class ConverterBase
|
|
701
731
|
|
702
732
|
def rebuild_url(data)
|
703
733
|
@url_list.each_with_index do |url, id|
|
704
|
-
|
705
|
-
|
706
|
-
data.sub!("[#URL=#{buf}]", "[#リンク開始]#{url}[#リンクアドレスここまで]#{url}[#リンク終了]")
|
707
|
-
|
734
|
+
data.sub!("[#URL=#{convert_numbers(id.to_s)}]",
|
735
|
+
"[#リンク開始]#{url}[#リンクアドレスここまで]#{url}[#リンク終了]")
|
708
736
|
end
|
709
737
|
end
|
710
738
|
|
@@ -727,9 +755,7 @@ class ConverterBase
|
|
727
755
|
|
728
756
|
def rebuild_illust(data)
|
729
757
|
@illust_chuki_list.each_with_index do |chuki, id|
|
730
|
-
|
731
|
-
num_to_kanji(buf)
|
732
|
-
data.sub!("[#挿絵=#{buf}]", chuki)
|
758
|
+
data.sub!("[#挿絵=#{convert_numbers(id.to_s)}]", chuki)
|
733
759
|
end
|
734
760
|
end
|
735
761
|
|
@@ -738,7 +764,7 @@ class ConverterBase
|
|
738
764
|
#
|
739
765
|
def enchant_midashi(data)
|
740
766
|
def midashi(str)
|
741
|
-
midashi_title = str.gsub("[#半字下げ]", "")
|
767
|
+
midashi_title = str.gsub("[#半字下げ]", "").gsub(/^[ \s]+/, "").gsub(/[ \s]+$/, "")
|
742
768
|
@inspector.subtitle = midashi_title
|
743
769
|
"[#3字下げ][#ここから中見出し]#{midashi_title}[#ここで中見出し終わり]"
|
744
770
|
end
|
@@ -801,7 +827,7 @@ class ConverterBase
|
|
801
827
|
replace_narou_tag(data)
|
802
828
|
convert_rome_numeric(data)
|
803
829
|
alphabet_to_zenkaku(data, @setting.enable_alphabet_force_zenkaku)
|
804
|
-
|
830
|
+
convert_numbers(data)
|
805
831
|
exception_reconvert_kanji_to_num(data)
|
806
832
|
if @setting.enable_kanji_num_with_units
|
807
833
|
convert_kanji_num_with_unit(data, @setting.kanji_num_with_units_lower_digit_zero)
|
@@ -810,7 +836,7 @@ class ConverterBase
|
|
810
836
|
convert_special_characters(data)
|
811
837
|
convert_fraction_and_date(data)
|
812
838
|
if text_type == "body" || text_type == "textfile"
|
813
|
-
|
839
|
+
half_indent_bracket(data)
|
814
840
|
auto_indent(data)
|
815
841
|
end
|
816
842
|
convert_dakuten_char_to_font(data)
|
@@ -861,23 +887,21 @@ class ConverterBase
|
|
861
887
|
progressbar.output(i) if text_type == "textfile"
|
862
888
|
@request_skip_output_line = false
|
863
889
|
zenkaku_rstrip(line)
|
864
|
-
if
|
865
|
-
|
866
|
-
|
867
|
-
|
868
|
-
|
869
|
-
|
870
|
-
|
871
|
-
|
872
|
-
|
873
|
-
|
874
|
-
|
875
|
-
|
876
|
-
|
877
|
-
|
878
|
-
|
879
|
-
@before_line = line
|
880
|
-
end
|
890
|
+
if @request_insert_blank_next_line
|
891
|
+
outputs unless blank_line?(line)
|
892
|
+
@request_insert_blank_next_line = false
|
893
|
+
end
|
894
|
+
process_author_comment(line) if text_type == "textfile"
|
895
|
+
insert_blank_line_to_border_symbol(line)
|
896
|
+
force_indent_special_chapter(line)
|
897
|
+
|
898
|
+
outputs(line)
|
899
|
+
unless @delay_outputs_buffer.empty?
|
900
|
+
@write_fp.write(@delay_outputs_buffer)
|
901
|
+
@before_line = @delay_outputs_buffer
|
902
|
+
@delay_outputs_buffer = ""
|
903
|
+
else
|
904
|
+
@before_line = line
|
881
905
|
end
|
882
906
|
end
|
883
907
|
author_comment_force_close if text_type == "textfile"
|
data/lib/downloader.rb
CHANGED
@@ -56,8 +56,8 @@ class Downloader
|
|
56
56
|
return false
|
57
57
|
end
|
58
58
|
end
|
59
|
-
downloader = Downloader.new(setting)
|
60
|
-
result = downloader.start_download
|
59
|
+
downloader = Downloader.new(setting, force)
|
60
|
+
result = downloader.start_download
|
61
61
|
setting.clear
|
62
62
|
result
|
63
63
|
end
|
@@ -267,29 +267,53 @@ class Downloader
|
|
267
267
|
#
|
268
268
|
# コンストラクタ
|
269
269
|
#
|
270
|
-
def initialize(site_setting)
|
270
|
+
def initialize(site_setting, force = false)
|
271
271
|
@setting = site_setting
|
272
|
+
@force = force
|
273
|
+
@cache_dir = nil
|
272
274
|
@id = @@database.get_id("toc_url", @setting["toc_url"]) || @@database.get_new_id
|
273
275
|
end
|
274
276
|
|
277
|
+
#
|
278
|
+
# 18歳以上か確認する
|
279
|
+
#
|
280
|
+
def confirm_over18?
|
281
|
+
global_setting = GlobalSetting.get["global_setting"]
|
282
|
+
if global_setting.include?("over18")
|
283
|
+
return global_setting["over18"]
|
284
|
+
end
|
285
|
+
if Helper.confirm("年齢認証:あなたは18歳以上ですか")
|
286
|
+
global_setting["over18"] = true
|
287
|
+
GlobalSetting.get.save_settings
|
288
|
+
return true
|
289
|
+
else
|
290
|
+
return false
|
291
|
+
end
|
292
|
+
end
|
293
|
+
|
275
294
|
#
|
276
295
|
# ダウンロード処理本体
|
277
296
|
#
|
278
|
-
# force が true なら全話強制ダウンロード
|
279
297
|
# 返り値:ダウンロードしたものが1話でもあったかどうか(Boolean)
|
280
298
|
#
|
281
|
-
def start_download
|
299
|
+
def start_download
|
282
300
|
latest_toc = get_latest_table_of_contents
|
283
301
|
unless latest_toc
|
284
302
|
warn "目次データが取得出来ませんでした"
|
285
303
|
exit 1
|
286
304
|
end
|
305
|
+
if @setting["confirm_over18"]
|
306
|
+
unless confirm_over18?
|
307
|
+
puts "18歳以上のみ閲覧出来る小説です。ダウンロードを中止しました"
|
308
|
+
return false
|
309
|
+
end
|
310
|
+
end
|
287
311
|
old_toc = load_novel_data(TOC_FILE_NAME)
|
288
312
|
unless old_toc
|
289
313
|
init_novel_dir
|
290
314
|
old_toc = {}
|
291
315
|
end
|
292
|
-
if force
|
316
|
+
if @force
|
293
317
|
update_subtitles = latest_toc["subtitles"]
|
294
318
|
else
|
295
319
|
update_subtitles = update_check(old_toc["subtitles"], latest_toc["subtitles"])
|
@@ -299,7 +323,7 @@ class Downloader
|
|
299
323
|
begin
|
300
324
|
sections_download_and_save(update_subtitles)
|
301
325
|
rescue Interrupt
|
302
|
-
|
326
|
+
remove_cache_dir
|
303
327
|
puts "ダウンロードを中断しました"
|
304
328
|
exit 1
|
305
329
|
end
|
@@ -322,6 +346,13 @@ class Downloader
|
|
322
346
|
cache_dir
|
323
347
|
end
|
324
348
|
|
349
|
+
#
|
350
|
+
# 差分用キャッシュ保存ディレクトリを削除
|
351
|
+
#
|
352
|
+
def remove_cache_dir
|
353
|
+
FileUtils.remove_entry_secure(@cache_dir) if @cache_dir
|
354
|
+
end
|
355
|
+
|
325
356
|
#
|
326
357
|
# データベース更新
|
327
358
|
#
|
@@ -433,6 +464,7 @@ class Downloader
|
|
433
464
|
puts "ID:#{@id} #{@title} のDL開始"
|
434
465
|
interval_sleep_time = LocalSetting.get["local_setting"]["download.interval"] || 0
|
435
466
|
interval_sleep_time = 0 if interval_sleep_time < 0
|
467
|
+
save_least_one = false
|
436
468
|
subtitles.each_with_index do |subtitle_info, i|
|
437
469
|
if @setting["domain"] =~ /syosetu.com/ && (i % 10 == 0 && i >= 10)
|
438
470
|
# MEMO:
|
@@ -444,14 +476,32 @@ class Downloader
|
|
444
476
|
sleep(interval_sleep_time) if i > 0
|
445
477
|
end
|
446
478
|
index, subtitle, file_subtitle = %w(index subtitle file_subtitle).map {|k| subtitle_info[k] }
|
447
|
-
|
479
|
+
print "第#{index}部分 #{subtitle} (#{i+1}/#{max})"
|
448
480
|
section_element = a_section_download(subtitle_info)
|
449
481
|
info = subtitle_info.dup
|
450
482
|
info["element"] = section_element
|
451
483
|
section_file_name = "#{index} #{file_subtitle}.yaml"
|
452
484
|
section_file_path = File.join(SECTION_SAVE_DIR_NAME, section_file_name)
|
453
|
-
|
454
|
-
|
485
|
+
if different_section?(section_file_path, info)
|
486
|
+
print " (更新あり)" if @force
|
487
|
+
move_to_cache_dir(section_file_path)
|
488
|
+
save_novel_data(section_file_path, info)
|
489
|
+
save_least_one = true
|
490
|
+
end
|
491
|
+
puts
|
492
|
+
end
|
493
|
+
remove_cache_dir unless save_least_one
|
494
|
+
end
|
495
|
+
|
496
|
+
#
|
497
|
+
# すでに保存されている内容とDLした内容が違うかどうか
|
498
|
+
#
|
499
|
+
def different_section?(relative_path, subtitle_info)
|
500
|
+
path = File.join(get_novel_data_dir, relative_path)
|
501
|
+
if File.exists?(path)
|
502
|
+
return YAML.load_file(path) != subtitle_info
|
503
|
+
else
|
504
|
+
return true
|
455
505
|
end
|
456
506
|
end
|
457
507
|
|