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.

Files changed (53) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/ChangeLog.md +56 -0
  4. data/README.md +56 -29
  5. data/lib/command/convert.rb +39 -7
  6. data/lib/command/diff.rb +42 -11
  7. data/lib/command/inspect.rb +1 -1
  8. data/lib/command/setting.rb +150 -45
  9. data/lib/command/tag.rb +8 -8
  10. data/lib/command/update.rb +57 -1
  11. data/lib/commandbase.rb +3 -0
  12. data/lib/converterbase.rb +17 -9
  13. data/lib/database.rb +4 -0
  14. data/lib/device/epub.rb +1 -1
  15. data/lib/device/ibooks.rb +1 -1
  16. data/lib/device/ibunko.rb +13 -6
  17. data/lib/device/kindle.rb +1 -1
  18. data/lib/device/kobo.rb +1 -1
  19. data/lib/device/reader.rb +1 -1
  20. data/lib/downloader.rb +10 -5
  21. data/lib/helper.rb +114 -3
  22. data/lib/ini.rb +3 -1
  23. data/lib/inventory.rb +3 -1
  24. data/lib/loadconverter.rb +1 -11
  25. data/lib/mailer.rb +1 -0
  26. data/lib/narou.rb +56 -5
  27. data/lib/novelconverter.rb +7 -5
  28. data/lib/novelsetting.rb +116 -63
  29. data/lib/template.rb +4 -4
  30. data/lib/version.rb +1 -1
  31. data/lib/web/appserver.rb +40 -9
  32. data/lib/web/public/resources/narou.library.js +35 -3
  33. data/lib/web/public/resources/narou.ui.js +16 -1
  34. data/lib/web/pushserver.rb +1 -0
  35. data/lib/web/settingmessages.rb +6 -3
  36. data/lib/web/views/diff_list.haml +11 -0
  37. data/lib/web/views/edit_replace_txt.haml +59 -0
  38. data/lib/web/views/help.haml +2 -2
  39. data/lib/web/views/index.haml +6 -4
  40. data/lib/web/views/layout.haml +11 -2
  41. data/lib/web/views/novels/setting.haml +51 -66
  42. data/lib/web/views/settings.haml +52 -15
  43. data/lib/web/views/style.scss +44 -2
  44. data/lib/web/views/widget.haml +6 -8
  45. data/narou.gemspec +45 -6
  46. data/spec/convert_spec.rb +1 -1
  47. data/spec/converterbase_spec.rb +25 -1
  48. data/spec/generator/convert_spec_gen.rb +1 -1
  49. data/spec/helper_spec.rb +8 -0
  50. data/spec/novelsetting_spec.rb +1 -1
  51. data/template/novel.txt.erb +1 -1
  52. data/template/setting.ini.erb +15 -3
  53. 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 = open(file, "r:BOM|UTF-8") { |fp| fp.read }
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
@@ -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!(YAML.load_file(@inventory_file_path))
41
+ self.merge!(Helper::CacheLoader.memo(@inventory_file_path) { |yaml|
42
+ YAML.load(yaml)
43
+ })
42
44
  end
43
45
  end
44
46
 
@@ -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
- if Helper.os_windows?
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
@@ -4,6 +4,7 @@
4
4
  #
5
5
 
6
6
  require "yaml"
7
+ require "singleton"
7
8
  require_relative "narou"
8
9
 
9
10
  class Mailer
@@ -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
@@ -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, ignore_force: 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, ignore_force: 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.new(archive_path, options[:ignore_force])
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
  #
@@ -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
- def self.load(target, ignore_force)
16
- archive_path = Downloader.get_novel_data_dir_by_target(target)
17
- if archive_path
18
- setting = new(archive_path, ignore_force)
19
- data = Downloader.get_data_by_target(target)
20
- setting.id = data["id"]
21
- setting.author = data["author"]
22
- setting.title = data["title"]
23
- setting
24
- else
25
- nil
26
- end
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(archive_path, ignore_force)
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
- load_settings
34
- set_attribute
35
- load_replace_pattern
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. DEFAULT_SETTINGS
68
+ # 3. narou setting コマンドで設定した default.*
69
+ # 4. ORIGINAL_SETTINGS
45
70
  #
46
71
  def load_settings
47
- @setting = {}
48
- ini_path = File.join(@archive_path, INI_NAME)
49
- ini = Ini.load_file(ini_path) rescue Ini.load("")
50
- force_settings = {}
51
- unless @ignore_force
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
- @setting[name] = force_settings[name]
63
- elsif ini["global"].include?(name) && type == Helper.type_of_value(ini["global"][name])
64
- @setting[name] = ini["global"][name]
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
- @setting[name] = value
85
+ @settings[name] = value
67
86
  end
68
87
  end
69
88
  # デフォルト設定以外を読み込む
70
89
  ini["global"].each do |key, value|
71
- unless @setting.include?(key)
72
- @setting[key] = value
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!(@setting)
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 = DEFAULT_SETTINGS.index { |v| v[:name] == name }
150
+ index = ORIGINAL_SETTINGS.index { |v| v[:name] == name }
92
151
  return unless index
93
- default = DEFAULT_SETTINGS[index]
94
- if default && default[:type] != Helper.type_of_value(value)
95
- raise Helper::InvalidVariableType, default[:type]
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
- @setting.each_key do |key|
162
+ @settings.each_key do |key|
104
163
  instance_eval <<-EOS
105
164
  def #{key}
106
- @setting["#{key}"]
165
+ @settings["#{key}"]
107
166
  end
108
167
 
109
168
  def #{key}=(value)
110
169
  check_value_of_type("#{key}", value)
111
- @setting["#{key}"] = value
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
- @setting[name]
180
+ @settings[name]
122
181
  end
123
182
 
124
183
  def []=(name, value)
125
- check_value_of_type(name, value)
126
- @setting[name] = value
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
- open(replace_txt_path, "r:BOM|UTF-8") do |fp|
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
- buff = @replace_pattern.each_with_object("\t").map(&:join).join("\n")
156
- File.write(replace_txt_path, buff)
207
+ Narou.write_replace_txt(replace_txt_path, @replace_pattern)
157
208
  end
158
209
 
159
- DEFAULT_SETTINGS = [
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: :string,
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",