narou 2.4.2 → 2.5.1
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.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/ChangeLog.md +56 -0
- data/README.md +56 -29
- data/lib/command/convert.rb +39 -7
- data/lib/command/diff.rb +42 -11
- data/lib/command/inspect.rb +1 -1
- data/lib/command/setting.rb +150 -45
- data/lib/command/tag.rb +8 -8
- data/lib/command/update.rb +57 -1
- data/lib/commandbase.rb +3 -0
- data/lib/converterbase.rb +17 -9
- data/lib/database.rb +4 -0
- data/lib/device/epub.rb +1 -1
- data/lib/device/ibooks.rb +1 -1
- data/lib/device/ibunko.rb +13 -6
- data/lib/device/kindle.rb +1 -1
- data/lib/device/kobo.rb +1 -1
- data/lib/device/reader.rb +1 -1
- data/lib/downloader.rb +10 -5
- data/lib/helper.rb +114 -3
- data/lib/ini.rb +3 -1
- data/lib/inventory.rb +3 -1
- data/lib/loadconverter.rb +1 -11
- data/lib/mailer.rb +1 -0
- data/lib/narou.rb +56 -5
- data/lib/novelconverter.rb +7 -5
- data/lib/novelsetting.rb +116 -63
- data/lib/template.rb +4 -4
- data/lib/version.rb +1 -1
- data/lib/web/appserver.rb +40 -9
- data/lib/web/public/resources/narou.library.js +35 -3
- data/lib/web/public/resources/narou.ui.js +16 -1
- data/lib/web/pushserver.rb +1 -0
- data/lib/web/settingmessages.rb +6 -3
- data/lib/web/views/diff_list.haml +11 -0
- data/lib/web/views/edit_replace_txt.haml +59 -0
- data/lib/web/views/help.haml +2 -2
- data/lib/web/views/index.haml +6 -4
- data/lib/web/views/layout.haml +11 -2
- data/lib/web/views/novels/setting.haml +51 -66
- data/lib/web/views/settings.haml +52 -15
- data/lib/web/views/style.scss +44 -2
- data/lib/web/views/widget.haml +6 -8
- data/narou.gemspec +45 -6
- data/spec/convert_spec.rb +1 -1
- data/spec/converterbase_spec.rb +25 -1
- data/spec/generator/convert_spec_gen.rb +1 -1
- data/spec/helper_spec.rb +8 -0
- data/spec/novelsetting_spec.rb +1 -1
- data/template/novel.txt.erb +1 -1
- data/template/setting.ini.erb +15 -3
- metadata +49 -8
data/lib/ini.rb
CHANGED
@@ -3,6 +3,8 @@
|
|
3
3
|
# Copyright 2013 whiteleaf. All rights reserved.
|
4
4
|
#
|
5
5
|
|
6
|
+
require_relative "helper"
|
7
|
+
|
6
8
|
class Ini
|
7
9
|
DELIMITER = "="
|
8
10
|
GLOBAL_SECTION = "global"
|
@@ -24,7 +26,7 @@ class Ini
|
|
24
26
|
def self.load_file(file)
|
25
27
|
case
|
26
28
|
when file.kind_of?(String)
|
27
|
-
text =
|
29
|
+
text = File.read(file, mode: "r:BOM|UTF-8")
|
28
30
|
ini = new(text)
|
29
31
|
ini.filename = file
|
30
32
|
return ini.object
|
data/lib/inventory.rb
CHANGED
@@ -38,7 +38,9 @@ module Inventory
|
|
38
38
|
return nil unless dir
|
39
39
|
@inventory_file_path = File.join(dir, name + ".yaml")
|
40
40
|
if File.exist?(@inventory_file_path)
|
41
|
-
self.merge!(
|
41
|
+
self.merge!(Helper::CacheLoader.memo(@inventory_file_path) { |yaml|
|
42
|
+
YAML.load(yaml)
|
43
|
+
})
|
42
44
|
end
|
43
45
|
end
|
44
46
|
|
data/lib/loadconverter.rb
CHANGED
@@ -9,7 +9,6 @@ require_relative "helper"
|
|
9
9
|
BlankConverter = Class.new(ConverterBase) {}
|
10
10
|
|
11
11
|
$converter_container = {}
|
12
|
-
$converter_load_once = {}
|
13
12
|
|
14
13
|
def converter(title, &block)
|
15
14
|
$converter_container[title] = Class.new(ConverterBase, &block)
|
@@ -62,16 +61,7 @@ end
|
|
62
61
|
def load_converter(title, archive_path)
|
63
62
|
converter_path = File.join(archive_path, "converter.rb")
|
64
63
|
if File.exist?(converter_path)
|
65
|
-
|
66
|
-
# TODO: RubyのバグでUTF-8なパスをrequireが見えてない。修正されたら消す
|
67
|
-
unless $converter_load_once[archive_path]
|
68
|
-
eval(File.read(converter_path, encoding: Encoding::UTF_8),
|
69
|
-
binding, converter_path)
|
70
|
-
$converter_load_once[archive_path] = true
|
71
|
-
end
|
72
|
-
else
|
73
|
-
require converter_path
|
74
|
-
end
|
64
|
+
eval(File.read(converter_path, mode: "r:BOM|UTF-8"), binding, converter_path)
|
75
65
|
else
|
76
66
|
return BlankConverter
|
77
67
|
end
|
data/lib/mailer.rb
CHANGED
data/lib/narou.rb
CHANGED
@@ -12,10 +12,6 @@ if Helper.engine_jruby?
|
|
12
12
|
end
|
13
13
|
|
14
14
|
module Narou
|
15
|
-
extend Memoist
|
16
|
-
|
17
|
-
module_function
|
18
|
-
|
19
15
|
LOCAL_SETTING_DIR = ".narou"
|
20
16
|
GLOBAL_SETTING_DIR = ".narousetting"
|
21
17
|
AOZORAEPUB3_JAR_NAME = "AozoraEpub3.jar"
|
@@ -23,6 +19,15 @@ module Narou
|
|
23
19
|
PRESET_DIR = "preset"
|
24
20
|
MISC_DIR = "misc"
|
25
21
|
EXIT_ERROR_CODE = 127
|
22
|
+
GLOBAL_REPLACE_NAME = "replace.txt"
|
23
|
+
|
24
|
+
UPDATE_SORT_KEYS = {
|
25
|
+
"id" => "ID", "last_update" => "更新日", "title" => "タイトル", "author" => "作者名",
|
26
|
+
"new_arrivals_date" => "新着日", "general_lastup" => "最新話掲載日"
|
27
|
+
}
|
28
|
+
|
29
|
+
class << self
|
30
|
+
extend Memoist
|
26
31
|
|
27
32
|
@@is_web = false
|
28
33
|
|
@@ -106,6 +111,46 @@ module Narou
|
|
106
111
|
File.exist?(create_aozoraepub3_jar_path(path))
|
107
112
|
end
|
108
113
|
|
114
|
+
def parse_replace_txt(text)
|
115
|
+
pattern = []
|
116
|
+
text.each_line do |line|
|
117
|
+
line.sub!(/[\r\n]+\z/, "")
|
118
|
+
next if line[0] == ";" # コメント記号
|
119
|
+
pair = line.split("\t", 2)
|
120
|
+
if pair.length == 2 && pair[0]
|
121
|
+
pattern << pair
|
122
|
+
end
|
123
|
+
end
|
124
|
+
pattern
|
125
|
+
end
|
126
|
+
|
127
|
+
def write_replace_txt(path, pairs)
|
128
|
+
buffer = pairs.each_with_object("\t").map(&:join).join("\n")
|
129
|
+
File.write(path, buffer)
|
130
|
+
end
|
131
|
+
|
132
|
+
def load_global_replace_pattern
|
133
|
+
path = File.join(get_root_dir, GLOBAL_REPLACE_NAME)
|
134
|
+
if File.exist?(path)
|
135
|
+
pairs = Helper::CacheLoader.memo(path) do |text|
|
136
|
+
parse_replace_txt(text)
|
137
|
+
end
|
138
|
+
else
|
139
|
+
pairs = []
|
140
|
+
end
|
141
|
+
@@global_replace_pattern_pairs = pairs
|
142
|
+
pairs
|
143
|
+
end
|
144
|
+
|
145
|
+
def global_replace_pattern
|
146
|
+
@@global_replace_pattern_pairs ||= load_global_replace_pattern
|
147
|
+
end
|
148
|
+
|
149
|
+
def save_global_replace_pattern
|
150
|
+
path = File.join(get_root_dir, GLOBAL_REPLACE_NAME)
|
151
|
+
write_replace_txt(path, @@global_replace_pattern_pairs)
|
152
|
+
end
|
153
|
+
|
109
154
|
#
|
110
155
|
# AozoraEpub3 の実行ファイル(.jar)のフルパス取得
|
111
156
|
# 検索順序
|
@@ -152,7 +197,6 @@ module Narou
|
|
152
197
|
def get_misc_dir
|
153
198
|
File.join(get_root_dir, MISC_DIR)
|
154
199
|
end
|
155
|
-
memoize :get_misc_dir
|
156
200
|
|
157
201
|
require_relative "device"
|
158
202
|
|
@@ -171,4 +215,11 @@ module Narou
|
|
171
215
|
def web?
|
172
216
|
@@is_web
|
173
217
|
end
|
218
|
+
|
219
|
+
def update_sort_key_summaries(width = 27)
|
220
|
+
({"KEY" => "対象"}.merge(UPDATE_SORT_KEYS)).map { |(key, summary)|
|
221
|
+
"#{key.rjust(width)} : #{summary}"
|
222
|
+
}.join("\n")
|
223
|
+
end
|
224
|
+
end
|
174
225
|
end
|
data/lib/novelconverter.rb
CHANGED
@@ -32,9 +32,10 @@ class NovelConverter
|
|
32
32
|
def self.convert(target, options = {})
|
33
33
|
options = {
|
34
34
|
# default paraeters
|
35
|
-
output_filename: nil, display_inspector: false,
|
35
|
+
output_filename: nil, display_inspector: false,
|
36
|
+
ignore_force: false, ignore_default: false
|
36
37
|
}.merge(options)
|
37
|
-
setting = NovelSetting.load(target, options[:ignore_force])
|
38
|
+
setting = NovelSetting.load(target, options[:ignore_force], options[:ignore_default])
|
38
39
|
if setting
|
39
40
|
novel_converter = new(setting, options[:output_filename], options[:display_inspector])
|
40
41
|
return {
|
@@ -51,7 +52,8 @@ class NovelConverter
|
|
51
52
|
def self.convert_file(filename, options = {})
|
52
53
|
options = {
|
53
54
|
# default parameters
|
54
|
-
encoding: nil, output_filename: nil, display_inspector: false,
|
55
|
+
encoding: nil, output_filename: nil, display_inspector: false,
|
56
|
+
ignore_force: false, ignore_default: false
|
55
57
|
}.merge(options)
|
56
58
|
output_filename = options[:output_filename]
|
57
59
|
if output_filename
|
@@ -59,7 +61,7 @@ class NovelConverter
|
|
59
61
|
else
|
60
62
|
archive_path = File.dirname(filename) + "/"
|
61
63
|
end
|
62
|
-
setting = NovelSetting.
|
64
|
+
setting = NovelSetting.create(archive_path, options[:ignore_force], options[:ignore_default])
|
63
65
|
setting.author = ""
|
64
66
|
setting.title = File.basename(filename)
|
65
67
|
novel_converter = new(setting, output_filename, options[:display_inspector])
|
@@ -298,7 +300,7 @@ class NovelConverter
|
|
298
300
|
processed_title = processed_title.gsub("《", "※[#始め二重山括弧]")
|
299
301
|
.gsub("》", "※[#終わり二重山括弧]")
|
300
302
|
tempalte_name = (device && device.ibunko? ? NOVEL_TEXT_TEMPLATE_NAME_FOR_IBUNKO : NOVEL_TEXT_TEMPLATE_NAME)
|
301
|
-
Template.get(tempalte_name, binding)
|
303
|
+
Template.get(tempalte_name, binding, 1.0)
|
302
304
|
end
|
303
305
|
|
304
306
|
#
|
data/lib/novelsetting.rb
CHANGED
@@ -10,29 +10,53 @@ class NovelSetting
|
|
10
10
|
INI_NAME = "setting.ini"
|
11
11
|
REPLACE_NAME = "replace.txt"
|
12
12
|
|
13
|
-
attr_accessor :id, :author, :title, :archive_path, :replace_pattern
|
13
|
+
attr_accessor :id, :author, :title, :archive_path, :replace_pattern, :settings
|
14
14
|
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
15
|
+
#
|
16
|
+
# データベースに登録されている小説の設定を取得する
|
17
|
+
#
|
18
|
+
def self.load(target, ignore_force, ignore_default)
|
19
|
+
setting = create(target, ignore_force, ignore_default)
|
20
|
+
data = Downloader.get_data_by_target(target)
|
21
|
+
setting.id = data["id"]
|
22
|
+
setting.author = data["author"]
|
23
|
+
setting.title = data["title"]
|
24
|
+
setting
|
25
|
+
end
|
26
|
+
|
27
|
+
#
|
28
|
+
# 小説設定オブジェクトを作成する
|
29
|
+
#
|
30
|
+
# target には小説ID等の他、小説保存フォルダを指定できる。
|
31
|
+
# テキストファイル変換時はデータベースに登録されていないので load ではなくこちらを使用する
|
32
|
+
#
|
33
|
+
def self.create(target, ignore_force, ignore_default)
|
34
|
+
setting = new(target, ignore_force, ignore_default)
|
35
|
+
setting.load_settings
|
36
|
+
setting.set_attribute
|
37
|
+
setting.load_replace_pattern
|
38
|
+
setting
|
27
39
|
end
|
28
40
|
|
29
|
-
def initialize(
|
41
|
+
def initialize(target, ignore_force, ignore_default)
|
42
|
+
if File.directory?(target.to_s)
|
43
|
+
archive_path = target
|
44
|
+
else
|
45
|
+
archive_path = Downloader.get_novel_data_dir_by_target(target).to_s
|
46
|
+
end
|
30
47
|
@archive_path = File.expand_path(archive_path)
|
31
48
|
@ignore_force = ignore_force
|
49
|
+
@ignore_default = ignore_default
|
32
50
|
@replace_pattern = []
|
33
|
-
|
34
|
-
|
35
|
-
|
51
|
+
@settings = {}
|
52
|
+
end
|
53
|
+
|
54
|
+
def ini_path
|
55
|
+
File.join(@archive_path, INI_NAME)
|
56
|
+
end
|
57
|
+
|
58
|
+
def load_setting_ini
|
59
|
+
Ini.load_file(ini_path) rescue Ini.load("")
|
36
60
|
end
|
37
61
|
|
38
62
|
#
|
@@ -41,58 +65,93 @@ class NovelSetting
|
|
41
65
|
# 設定値の優先順位は
|
42
66
|
# 1. narou setting コマンドで設定した force.*
|
43
67
|
# 2. setting.ini
|
44
|
-
# 3.
|
68
|
+
# 3. narou setting コマンドで設定した default.*
|
69
|
+
# 4. ORIGINAL_SETTINGS
|
45
70
|
#
|
46
71
|
def load_settings
|
47
|
-
@
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
# 設定値を強制的に上書きするデータの読込
|
53
|
-
Inventory.load("local_setting", :local).each { |name, value|
|
54
|
-
if name =~ /^force\.(.+)$/
|
55
|
-
force_settings[$1] = value
|
56
|
-
end
|
57
|
-
}
|
58
|
-
end
|
59
|
-
DEFAULT_SETTINGS.each do |element|
|
72
|
+
@settings.clear
|
73
|
+
ini = load_setting_ini
|
74
|
+
force_settings = @ignore_force ? {} : NovelSetting.load_force_settings
|
75
|
+
default_settings = @ignore_default ? {} : NovelSetting.load_default_settings
|
76
|
+
ORIGINAL_SETTINGS.each do |element|
|
60
77
|
name, value, type = element[:name], element[:value], element[:type]
|
61
78
|
if force_settings.include?(name)
|
62
|
-
@
|
63
|
-
elsif ini["global"].include?(name) && type
|
64
|
-
@
|
79
|
+
@settings[name] = force_settings[name]
|
80
|
+
elsif ini["global"].include?(name) && type_eq_value(type, ini["global"][name])
|
81
|
+
@settings[name] = ini["global"][name]
|
82
|
+
elsif default_settings.include?(name)
|
83
|
+
@settings[name] = default_settings[name]
|
65
84
|
else
|
66
|
-
@
|
85
|
+
@settings[name] = value
|
67
86
|
end
|
68
87
|
end
|
69
88
|
# デフォルト設定以外を読み込む
|
70
89
|
ini["global"].each do |key, value|
|
71
|
-
unless @
|
72
|
-
@
|
90
|
+
unless @settings.include?(key)
|
91
|
+
@settings[key] = value
|
73
92
|
end
|
74
93
|
end
|
75
94
|
end
|
76
95
|
|
96
|
+
#
|
97
|
+
# local_settings から group.name 形式のデータを取得
|
98
|
+
#
|
99
|
+
# { name: value, ... } 形式のハッシュとして返す
|
100
|
+
#
|
101
|
+
def self.load_settings_by_pattern(pattern)
|
102
|
+
res = Inventory.load("local_setting", :local).map { |name, value|
|
103
|
+
if name =~ /^#{pattern}\.(.+)$/
|
104
|
+
[$1, value]
|
105
|
+
else
|
106
|
+
nil
|
107
|
+
end
|
108
|
+
}.compact.flatten
|
109
|
+
Hash[*res]
|
110
|
+
end
|
111
|
+
|
112
|
+
#
|
113
|
+
# force.* 設定を取得
|
114
|
+
#
|
115
|
+
def self.load_force_settings
|
116
|
+
load_settings_by_pattern("force")
|
117
|
+
end
|
118
|
+
|
119
|
+
#
|
120
|
+
# default.* 設定を取得
|
121
|
+
#
|
122
|
+
def self.load_default_settings
|
123
|
+
load_settings_by_pattern("default")
|
124
|
+
end
|
125
|
+
|
77
126
|
#
|
78
127
|
# 設定を保存
|
79
128
|
#
|
80
129
|
def save_settings
|
81
130
|
ini = Ini.new
|
82
131
|
ini.filename = File.join(@archive_path, INI_NAME)
|
83
|
-
ini.object["global"].merge!(@
|
132
|
+
ini.object["global"].merge!(@settings)
|
84
133
|
ini.save
|
85
134
|
end
|
86
135
|
|
136
|
+
def type_eq_value(type, value)
|
137
|
+
type_of_value = Helper.type_of_value(value)
|
138
|
+
case type
|
139
|
+
when :string, :select, :multiple
|
140
|
+
:string == type_of_value
|
141
|
+
else
|
142
|
+
type == type_of_value
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
87
146
|
#
|
88
147
|
# 指定された設定の型チェック
|
89
148
|
#
|
90
149
|
def check_value_of_type(name, value)
|
91
|
-
index =
|
150
|
+
index = ORIGINAL_SETTINGS.index { |v| v[:name] == name }
|
92
151
|
return unless index
|
93
|
-
|
94
|
-
if
|
95
|
-
raise Helper::InvalidVariableType,
|
152
|
+
original = ORIGINAL_SETTINGS[index]
|
153
|
+
if original && !type_eq_value(original[:type], value)
|
154
|
+
raise Helper::InvalidVariableType, original[:type]
|
96
155
|
end
|
97
156
|
end
|
98
157
|
|
@@ -100,15 +159,15 @@ class NovelSetting
|
|
100
159
|
# 設定データ用アクセサ定義
|
101
160
|
#
|
102
161
|
def set_attribute
|
103
|
-
@
|
162
|
+
@settings.each_key do |key|
|
104
163
|
instance_eval <<-EOS
|
105
164
|
def #{key}
|
106
|
-
@
|
165
|
+
@settings["#{key}"]
|
107
166
|
end
|
108
167
|
|
109
168
|
def #{key}=(value)
|
110
169
|
check_value_of_type("#{key}", value)
|
111
|
-
@
|
170
|
+
@settings["#{key}"] = value
|
112
171
|
end
|
113
172
|
EOS
|
114
173
|
end
|
@@ -118,12 +177,14 @@ class NovelSetting
|
|
118
177
|
# 配列風のアクセサ定義
|
119
178
|
#
|
120
179
|
def [](name)
|
121
|
-
@
|
180
|
+
@settings[name]
|
122
181
|
end
|
123
182
|
|
124
183
|
def []=(name, value)
|
125
|
-
|
126
|
-
|
184
|
+
unless value.nil?
|
185
|
+
check_value_of_type(name, value)
|
186
|
+
end
|
187
|
+
@settings[name] = value
|
127
188
|
end
|
128
189
|
|
129
190
|
#
|
@@ -133,16 +194,7 @@ class NovelSetting
|
|
133
194
|
@replace_pattern.clear
|
134
195
|
replace_txt_path = File.join(@archive_path, REPLACE_NAME)
|
135
196
|
if File.exist?(replace_txt_path)
|
136
|
-
|
137
|
-
fp.each do |line|
|
138
|
-
line.sub!(/[\r\n]+\z/, "")
|
139
|
-
next if line[0] == ";" # コメント記号
|
140
|
-
pattern = line.split("\t", 2)
|
141
|
-
if pattern.length == 2 && pattern[0]
|
142
|
-
@replace_pattern << pattern
|
143
|
-
end
|
144
|
-
end
|
145
|
-
end
|
197
|
+
@replace_pattern = Narou.parse_replace_txt(File.read(replace_txt_path, mode: "r:BOM|UTF-8"))
|
146
198
|
end
|
147
199
|
@replace_pattern
|
148
200
|
end
|
@@ -152,11 +204,10 @@ class NovelSetting
|
|
152
204
|
#
|
153
205
|
def save_replace_pattern
|
154
206
|
replace_txt_path = File.join(@archive_path, REPLACE_NAME)
|
155
|
-
|
156
|
-
File.write(replace_txt_path, buff)
|
207
|
+
Narou.write_replace_txt(replace_txt_path, @replace_pattern)
|
157
208
|
end
|
158
209
|
|
159
|
-
|
210
|
+
ORIGINAL_SETTINGS = [
|
160
211
|
# name: 変数名
|
161
212
|
# type: 変数の型
|
162
213
|
# value: 初期値
|
@@ -319,9 +370,11 @@ class NovelSetting
|
|
319
370
|
},
|
320
371
|
{
|
321
372
|
name: "title_date_align",
|
322
|
-
type: :
|
373
|
+
type: :select,
|
323
374
|
value: "right",
|
324
|
-
help: "enable_add_date_to_title で付与する日付の位置。left か right"
|
375
|
+
help: "enable_add_date_to_title で付与する日付の位置。left(タイトルの前) か right(タイトルの後)",
|
376
|
+
select_keys: %w(left right),
|
377
|
+
select_summaries: %w(タイトルの前 タイトルの後)
|
325
378
|
},
|
326
379
|
{
|
327
380
|
name: "enable_ruby_youon_to_big",
|