# -*- coding: utf-8 -*- # # Copyright 2013 whiteleaf. All rights reserved. # require_relative "../inventory" require_relative "../novelsetting" require_relative "../eventable" require_relative "update" module Command class Setting < CommandBase include Narou::Eventable class InvalidSelectValue < StandardError; end def self.oneline_help "各コマンドの設定を変更します" end def initialize super "[= ...] [options]\n" \ "--burn [ ...]" @opt.separator <<-EOS ・各コマンドの設定の変更が出来ます。 ・Global な設定はユーザープロファイルに保存され、すべての narou コマンドで使われます ・下の一覧は一部です。すべてを確認するには -a オプションを付けて確認して下さい ・default. で始まる設定は、setting.ini で未設定時の項目の挙動を指定することが出来ます ・force. で始まる設定は、setting.ini や default.* 等の指定を全て無視して項目の挙動を強制出来ます Local Variable List: 説明 EOS @opt.separator(get_variable_list_strings(:local)) @opt.separator("\n Global Variable List:") @opt.separator(get_variable_list_strings(:global)) @opt.separator <<-EOS これ以外にも設定出来る項目があります。確認する場合は narou setting -a コマンドを参照して下さい Examples: narou setting --list # 現在の設置値一覧を表示 narou setting convert.no-open=true # 値を設定する narou setting convert.no-epub= # 右辺に何も書かないとその設定を削除出来る narou setting device # 変数名だけ書くと現在の値を確認出来る narou s convert.copy-to=C:/dropbox/mobi # パスにスペースが含まれる場合はダブルクウォーテーションで囲う narou s convert.copy-to="C:\\Documents and Settings\\user\\epub" Options: EOS @opt.on("-l", "--list", "現在の設定値を確認する") { output_setting_list exit 0 } @opt.on("-a", "--all", "設定できる全ての変数名を表示する") { @options["all"] = true display_variable_list exit 0 } @opt.on("--burn", "指定した小説の未設定項目に共通設定を焼き付ける") { @options["burn"] = true } end def get_scope_of_variable_name(name) [:local, :global].each do |scope| if SETTING_VARIABLES[scope].include?(name) return scope end end nil end # # 値の文字列を設定に基づいた型にキャストして、[scope, value] 形式で返す # 不正な設定名もしくは値の場合は例外を吐く # def casting_variable(name, value) scope = get_scope_of_variable_name(name) unless scope raise Helper::InvalidVariableName, "#{name} は不明な名前です" end variable_definition = SETTING_VARIABLES[scope][name] case variable_definition[:type] when :select select_values = variable_definition[:select_keys] unless select_values.include?(value) raise InvalidSelectValue, "不明な値です。#{select_values.join(", ")} の中から指定して下さい" end casted_value = value when :multiple select_values = variable_definition[:select_keys] value.split(",").each do |input_value| unless select_values.include?(input_value) raise InvalidSelectValue, "不明な値です。#{select_values.join(", ")} の中から指定して下さい" end end casted_value = value else casted_value = Helper.string_cast_to_type(value, variable_definition[:type]) end [scope, casted_value] end def output_setting_list settings = { local: Inventory.load("local_setting", :local), global: Inventory.load("global_setting", :global) } settings.each do |scope, scoped_settings| puts "[#{scope.capitalize} Variables]" scoped_settings.each do |name, value| if value =~ / / value = "'#{value}'" end puts "#{name}=#{value}".termcolor end end end def output_error(msg, name = nil) @error_count += 1 error msg trigger(:error, msg, name) end def sweep_dust_variable(target_name, settings) deleted = false settings.each_value do |scoped_settings| if scoped_settings.has_key?(target_name) scoped_settings.delete(target_name) deleted = true end end deleted end def output_value(name, settings) scope = get_scope_of_variable_name(name) if scope puts settings[scope][name] else output_error("#{name} という変数は存在しません", name) end end def execute(argv) super if @options["burn"] burn_default_settings(argv) return end if argv.empty? puts @opt.help return end settings = { local: Inventory.load("local_setting", :local), global: Inventory.load("global_setting", :global) } device = Narou.get_device @error_count = 0 self.extend(device.get_hook_module) if device argv.each do |arg| name, value = arg.split("=", 2).map(&:strip) if name == "" output_error("書式が間違っています。変数名=値 のように書いて下さい") next end if value.nil? output_value(name, settings) next end scope = get_scope_of_variable_name(name) unless scope if value == "" # 定義上ではすでに存在しないが、設定ファイルには残っている古い変数 # を削除できるようにする if sweep_dust_variable(name, settings) puts "#{name} の設定を削除しました" else output_error("#{name} という変数は存在しません", name) end else output_error("#{name} という変数は設定出来ません", name) end next end if value == "" hook_call(:modify_settings, settings[scope], name, nil) next end begin scope, casted_value = casting_variable(name, value) rescue Helper::InvalidVariableName, Helper::InvalidVariableType, InvalidSelectValue => e output_error(e.message, name) next end hook_call(:modify_settings, settings[scope], name, casted_value) end settings[:local].save settings[:global].save exit @error_count if @error_count > 0 end def modify_settings(scoped_settings, name, value) if value.nil? scoped_settings.delete(name) puts "#{name} の設定を削除しました" else scoped_settings[name] = value puts "#{name} を #{value} に設定しました" end if name == "device" && value modify_settings_when_device_changed(scoped_settings) end end def modify_settings_when_device_changed(settings) device = Device.create(settings["device"]) message = StringIO.new device.get_relative_variables.each do |name, value| if value.nil? settings.delete(name) message.puts " ← #{name} が削除されました".termcolor elsif settings[name].nil? || settings[name] != value settings[name] = value message.puts " → #{name} が #{value} に変更されました".termcolor end end if message.length > 0 puts "端末を#{device.display_name}に指定したことで、以下の関連設定が変更されました" puts message.string end rescue Device::UnknownDevice => e output_error("#{e.message}\n設定できるのは #{Device::DEVICES.keys.join(", ")} です", "device") end def get_variable_list_strings(scope) result = "" SETTING_VARIABLES[scope].each do |name, info| if @options["all"] || !info[:invisible] type_description = Helper.variable_type_to_description(info[:type]) result << " #{name.ljust(18)} #{type_description} #{info[:help]}\n".termcolor end end result end def display_variable_list puts "Local Variable List:" puts get_variable_list_strings(:local).gsub(/^ {4}/, "") puts puts "Global Variable List:" puts get_variable_list_strings(:global).gsub(/^ {4}/, "") end # # 小説の setting.ini の未設定項目に共通設定を焼き付ける # # default.* が設定されているならそれを、なければオリジナル設定を # def burn_default_settings(argv) if argv.empty? error "対象小説を指定して下さい" exit Narou::EXIT_ERROR_CODE end msg = "指定された小説のsetting.iniの未項目設定に共通設定を焼き付けます。\n" \ "(共通設定とはsetting.iniの項目が未設定時に使用される default.* 系設定およびNarou.rbオリジナル設定のこと)\n" \ "よろしいですか" return unless Narou::Input.confirm(msg) tagname_to_ids(argv) argv.each do |arg| data = Downloader.get_data_by_target(arg) unless data error "#{arg} は存在しません" next end novel_setting = NovelSetting.new(arg, true, true) # 空っぽの設定を作成 novel_setting.settings = novel_setting.load_setting_ini["global"] original_settings = NovelSetting.get_original_settings default_settings = NovelSetting.load_default_settings original_settings.each do |original| name = original[:name] unless novel_setting.settings.include?(name) if default_settings.include?(name) novel_setting[name] = default_settings[name] else novel_setting[name] = original[:value] end end end novel_setting.save_settings puts "#{data["title"]} の設定を保存しました" end end def self.get_setting_variables SETTING_VARIABLES end def self.get_setting_tab_names SETTING_TAB_NAMES end def self.get_setting_tab_info SETTING_TAB_INFO end SETTING_TAB_NAMES = { general: "一般", detail: "詳細", global: "Global", default: "default.*", force: "force.*", command: "コマンド", } SETTING_TAB_INFO = { global: "Global な設定はユーザープロファイルに保存され、すべての narou コマンドで使われます", default: "default.* 系の設定は個別の変換設定で未設定の項目の挙動を指定することが出来ます", force: "force.* 系の設定は個別設定、default.* 等の設定を無視して反映されるようになります", command: "default_args.* 系の設定は、各種コマンドのオプションを省略した場合に使用されるオプションを指定出来ます", } SETTING_VARIABLES = { local: { # 変数名 => { type: 受け付ける型, help: 説明[, invisible: 不可視化フラグ, # select_keys: 選択肢型の時のキー(配列), # select_summaries: 選択肢型の時のキーの概要(配列)] } "device" => { type: :select, help: "変換、送信対象の端末(sendの--help参照)", select_keys: Device::DEVICES.keys, select_summaries: Device::DEVICES.values.map { |d| d::DISPLAY_NAME }, tab: :general }, "hotentry" => { type: :boolean, help: "新着投稿だけをまとめたデータを作る", tab: :general }, "hotentry.auto-mail" => { type: :boolean, help: "hotentryをメールで送る(mail設定済みの場合)", tab: :detail }, "update.interval" => { type: :float, help: "更新時に各作品間で指定した秒数待機する(処理時間を含む)。最低#{Update::Interval::MIN}秒以上", tab: :general }, "update.strong" => { type: :boolean, help: "改稿日当日の連続更新でも更新漏れが起きないように、中身もチェックして更新を検知する(やや処理が重くなる)", tab: :general }, "update.logging" => { type: :boolean, help: "更新時のログを保存する", tab: :detail }, "update.convert-only-new-arrival" => { type: :boolean, help: "更新時に新着のみ変換を実行する", tab: :general }, "update.sort-by" => { type: :select, help: "アップデートを指定した項目順で行う\n#{Narou.update_sort_key_summaries(40)}", select_keys: Narou::UPDATE_SORT_KEYS.keys, select_summaries: Narou::UPDATE_SORT_KEYS.values, tab: :general }, "convert.copy-to" => { type: :directory, help: "変換したらこのフォルダにコピーする\n" \ " ※注意:存在しないフォルダだとエラーになる", tab: :general }, "convert.copy_to" => { type: :directory, help: "copy-toの昔の書き方(非推奨)", invisible: true }, "convert.no-epub" => { type: :boolean, help: "EPUB変換を無効にする", invisible: true, tab: :detail }, "convert.no-mobi" => { type: :boolean, help: "MOBI変換を無効にする", invisible: true, tab: :detail }, "convert.no-strip" => { type: :boolean, help: "MOBIのstripを無効にする\n" \ " ※注意:KDP用のMOBIはstripしないでください", invisible: true, tab: :detail }, "convert.no-zip" => { type: :boolean, help: "i文庫用のzipファイル作成を無効にする", invisible: true, tab: :detail }, "convert.no-open" => { type: :boolean, help: "変換時に保存フォルダを開かないようにする", tab: :general }, "convert.inspect" => { type: :boolean, help: "常に変換時に調査結果を表示する", tab: :detail }, "convert.multi-device" => { type: :multiple, help: "複数の端末用に同時に変換する。deviceよりも優先される。" \ "端末名をカンマ区切りで入力。ただのEPUBを出力したい場合はepubを指定", select_keys: Device::DEVICES.keys, select_summaries: Device::DEVICES.values.map { |d| d::DISPLAY_NAME }, tab: :general }, "convert.copy-to-grouping" => { type: :boolean, help: "copy-toで指定したフォルダの中で更に端末毎にフォルダを振り分ける", tab: :general }, "convert.filename-to-ncode" => { type: :boolean, help: "書籍ファイル名をNコードで出力する(ドメイン_Nコードの形式)", tab: :general }, "download.interval" => { type: :float, help: "各話DL時に指定秒数待機する。デフォルト#{Downloader::DEFAULT_INTERVAL_WAIT}秒", tab: :detail }, "download.wait-steps" => { type: :integer, help: "指定した話数ごとに長めのウェイトが入る\n" \ " ※注意:11以上を設定してもなろうの場合は10話ごとにウェイトが入ります", tab: :detail }, "download.use-subdirectory" => { type: :boolean, help: "小説を一定数ごとにサブフォルダへ分けて保存する\n" \ " ※注意:小説を大量に同一フォルダに保存するとパフォーマンスが劣化する回避策", tab: :detail }, "download.choices-of-digest-options" => { type: :string, help: "ダイジェスト化選択肢が出た場合に、自動で項目を選択する。" \ "カンマ区切りで選択したい順番に数字で記入する。" \ "最終的に更新かキャンセルが選択されなかった場合はキャンセル扱いになる\n" \ "#{Downloader.choices_to_string(width: 27)}", tab: :detail }, "send.without-freeze" => { type: :boolean, help: "送信時に凍結された小説は対象外にする", tab: :general }, "send.backup-bookmark" => { type: :boolean, help: "一括送信時に栞データを自動でバックアップする(KindlePW系用)", tab: :detail }, "multiple-delimiter" => { type: :string, help: "--multiple指定時の区切り文字", tab: :detail }, "economy" => { type: :multiple, help: "容量節約に関する設定。カンマ区切りで設定\n" \ "(cleanup_temp:変換後に作業ファイルを削除 send_delete:送信後に書籍ファイルを削除 " \ "nosave_diff:差分ファイルを保存しない nosave_raw:rawデータを保存しない)", select_keys: %w(cleanup_temp send_delete nosave_diff nosave_raw), select_summaries: %w(変換後に作業ファイルを削除 送信後に書籍ファイルを削除 差分ファイルを保存しない rawデータを保存しない), tab: :detail }, "guard-spoiler" => { type: :boolean, help: "ネタバレ防止機能。ダウンロード時の各話タイトルを伏せ字で表示する", tab: :detail }, "theme" => { type: :select, help: "WEB UI 用テーマ選択", invisible: true, select_keys: Narou.get_theme_names, select_summaries: Narou.get_theme_names, tab: :general }, "normalize-filename" => { type: :boolean, help: "ファイル名の文字列をNFCで正規化する。※既存データとの互換性が無くなる可能性があるので、バックアップを取った上で機能を理解の上有効にして下さい", tab: :detail, }, }, global: { "aozoraepub3dir" => { type: :directory, help: "AozoraEpub3のあるフォルダを指定", invisible: true }, "difftool" => { type: :string, help: "Diffで使うツールのパスを指定する", tab: :global }, "difftool.arg" => { type: :string, help: "difftoolで使う引数を設定(オプション)", tab: :global }, "no-color" => { type: :boolean, help: "カラー表示を無効にする", tab: :global }, "server-port" => { type: :integer, help: "WEBサーバ起動時のポート", tab: :global }, "server-bind" => { type: :string, help: "WEBサーバのホスト制限(未設定時:起動PCのIP)。頻繁にローカルIPが変わってしまう場合は127.0.0.1の指定を推奨", invisible: true, tab: :global }, "server-ws-add-accepted-domains" => { type: :string, help: "PushServer の accepted_domains に追加するホストのリスト(カンマ区切り)", invisible: true, tab: :global }, "over18" => { type: :boolean, help: "18歳以上かどうか", invisible: true, tab: :global }, "dismiss-notice" => { type: :boolean, help: "お知らせを消す", invisible: true, tab: :global }, } } %w(default force).each do |kind| NovelSetting::ORIGINAL_SETTINGS.each do |default| value = { type: default[:type], help: "\n " + default[:help], invisible: true, select_keys: default[:select_keys], select_summaries: default[:select_summaries], tab: kind.intern } SETTING_VARIABLES[:local]["#{kind}." + default[:name]] = value end end Dir.glob(File.expand_path(File.join(File.dirname(__FILE__), "*.rb"))) do |path| cmd_name = File.basename(path, ".rb") SETTING_VARIABLES[:local]["default_args." + cmd_name] = { type: :string, help: "#{cmd_name} コマンドのデフォルトオプション", invisible: true, tab: :command } end SETTING_VARIABLES.freeze end end