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
|
# -*- coding: utf-8 -*-
#
# ruby config loader
#
miquire :core, 'environment'
miquire :core, 'serialthread'
miquire :miku, 'miku'
miquire :lib, 'timelimitedqueue'
require 'fileutils'
require 'set'
require 'yaml'
=begin rdoc
オブジェクトにデータ保存機能をつけるmix-in
includeすると、key-value形で恒久的にデータを保存するためのメソッドが提供される。
mikutter, CHIのプラグインでは通常はUserConfigをつかうこと。
=end
module ConfigLoader
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
|