1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122
|
# -*- coding: utf-8 -*-
#
# ruby config loader
#
require 'environment'
require 'serialthread'
require 'miku/miku'
require 'lib/timelimitedqueue'
require 'fileutils'
require 'set'
require 'yaml'
=begin rdoc
オブジェクトにデータ保存機能をつけるmix-in
includeすると、key-value形で恒久的にデータを保存するためのメソッドが提供される。
mikutter, CHIのプラグインでは通常はUserConfigをつかうこと。
=end
module ConfigLoader
extend Memoist
STORAGE_FILE = File.expand_path(File.join(Environment::SETTINGDIR, "setting.yml"))
TMP_FILE = File.expand_path(File.join(Environment::SETTINGDIR, "setting.writing.yml"))
PSTORE_FILE = File.expand_path(File.join(Environment::CONFROOT, "p_class_values.db"))
AVAILABLE_TYPES = [Hash, Array, Set, Numeric, String, Symbol, Time, NilClass, TrueClass, FalseClass].freeze
@@configloader_cache = nil
@@configloader_queue ||= TimeLimitedQueue.new(HYDE, 5, Set){ |keys|
File.open(TMP_FILE, 'w'.freeze){ |tmpfile|
YAML.dump(@@configloader_cache, tmpfile)
}
FileUtils.mv TMP_FILE, STORAGE_FILE
notice "configloader: wrote #{keys.size} keys (#{keys.to_a.join(', ')})" }
class << self
# 一度だけ自動的に呼ばれる(このソースファイルの一番下の方)
# メモリ上に設定データを読み込む。
# YAMLがなければ、旧データ形式(PStore)からデータを読み込む。
def boot
@@configloader_cache = if FileTest.exist?(STORAGE_FILE)
notice "load setting data from #{STORAGE_FILE}"
YAML.load_file(STORAGE_FILE)
elsif FileTest.exist?(PSTORE_FILE)
notice "load setting data from #{PSTORE_FILE}"
ConfigLoader.migration_from_pstore
else
notice "setting data not found"
Hash.new end end
# 旧データ形式(PStore)からデータを取得して返す
# ==== Return
# 設定データ(Hash)
def migration_from_pstore
require 'pstore'
PStore.new(PSTORE_FILE).transaction(true) { |db|
config = Hash.new
db.roots.each { |key|
config[key] = db[key] }
config } end
# _obj_ が保存可能な値なら _obj_ を返す。そうでなければ _ArgumentError_ 例外を投げる。
def validate(obj)
if AVAILABLE_TYPES.any?{|x| obj.is_a?(x)}
if obj.is_a? Hash
result = {}
obj.each{ |key, value|
result[self.validate(key)] = self.validate(value) }
result.freeze
elsif obj.is_a? Enumerable
obj.map(&method(:validate)).freeze
elsif not(obj.freezable?) or obj.frozen?
obj
else
obj.dup.freeze end
else
emes = "ConfigLoader recordable class of #{AVAILABLE_TYPES.join(',')} only. but #{obj.class} given."
error(emes)
raise ArgumentError.new(emes)
end
end
end
# キーに対応する値が存在するかを調べる。
# 値が設定されていれば、それが _nil_ や _false_ であっても _true_ を返す
# ==== Args
# [key] Symbol キー
# ==== Return
# [true] 存在する
# [false] 存在しない
def include?(key)
@@configloader_cache.has_key? configloader_key(key) end
# _key_ に対応するオブジェクトを取り出す。
# _key_ が存在しない場合は nil か _ifnone_ を返す
def at(key, ifnone=nil)
ckey = configloader_key(key)
if @@configloader_cache.has_key? ckey
@@configloader_cache[ckey]
else
ifnone end end
# _key_ にたいして _val_ を関連付ける。
def store(key, val)
ConfigLoader.validate(key)
val = ConfigLoader.validate(val)
ckey = configloader_key(key)
@@configloader_cache[ckey] = val
@@configloader_queue.push(ckey)
val end
private
def configloader_key(key)
"#{self.class.to_s}::#{key}".freeze end
memoize :configloader_key
boot
end
|