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
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
|
# -*- coding: utf-8 -*-
miquire :core, 'configloader', 'environment', 'event', 'event_listener', 'event_filter'
miquire :lib, "instance_storage", 'delayer'
# プラグインの本体。
# DSLを提供し、イベントやフィルタの管理をする
class Plugin
include ConfigLoader
include InstanceStorage
class << self
# プラグインのインスタンスを返す。
# ブロックが渡された場合、そのブロックをプラグインのインスタンスのスコープで実行する
# ==== Args
# [plugin_name] プラグイン名
# ==== Return
# Plugin
def create(plugin_name, &body)
type_strict plugin_name => Symbol
Plugin[plugin_name].instance_eval(&body) if body
Plugin[plugin_name] end
# イベントを宣言する。
# ==== Args
# [event_name] イベント名
# [options] 以下のキーを持つHash
# :prototype :: 引数の数と型。Arrayで、type_strictが解釈できる条件を設定する
# :priority :: Delayerの優先順位
def defevent(event_name, options = {})
type_strict event_name => Symbol, options => Hash
Event[event_name].options = options end
# イベント _event_name_ を発生させる
# ==== Args
# [event_name] イベント名
# [*args] イベントの引数
# ==== Return
# Delayer
def call(event_name, *args)
type_strict event_name => Symbol
Event[event_name].call(*args) end
# 引数 _args_ をフィルタリングした結果を返す
# ==== Args
# [*args] 引数
# ==== Return
# フィルタされた引数の配列
def filtering(event_name, *args)
type_strict event_name => Symbol
Event[event_name].filtering(*args)
end
# 互換性のため
def uninstall(plugin_name)
self[plugin_name].uninstall
end
# 互換性のため
def filter_cancel!
EventFilter.cancel! end
alias plugin_list instances_name
def activity(kind, title, args = {})
Plugin.call(:modify_activity,
{ plugin: nil,
kind: kind,
title: title,
date: Time.new,
description: title }.merge(args)) end
alias __clear_aF4e__ clear!
def clear!
Event.clear!
__clear_aF4e__()
end
end
# プラグインの名前
attr_reader :name
# spec
attr_accessor :spec
# 最初にプラグインがロードされた時刻(uninstallされるとリセットする)
attr_reader :defined_time
# ==== Args
# [plugin_name] プラグイン名
def initialize(*args)
super
@defined_time = Time.new.freeze
@events = Set.new
@filters = Set.new end
# イベントリスナを新しく登録する
# ==== Args
# [event_name] イベント名
# [&callback] イベントのコールバック
# ==== Return
# EventListener
def add_event(event_name, &callback)
type_strict event_name => :to_sym, callback => :call
result = EventListener.new(Event[event_name.to_sym], &callback)
@events << result
result end
# イベントフィルタを新しく登録する
# ==== Args
# [event_name] イベント名
# [&callback] イベントのコールバック
# ==== Return
# EventFilter
def add_event_filter(event_name, &callback)
type_strict event_name => :to_sym, callback => :call
result = EventFilter.new(Event[event_name.to_sym], &callback)
@filters << result
result end
# イベントを削除する。
# 引数は、EventListenerかEventFilterのみ(on_*やfilter_*の戻り値)。
# 互換性のため、二つ引数がある場合は第一引数は無視され、第二引数が使われる。
# ==== Args
# [*args] 引数
# ==== Return
# self
def detach(*args)
listener = args.last
if listener.is_a? EventListener
@events.delete(listener)
listener.detach
elsif listener.is_a? EventFilter
@filters.delete(listener)
listener.detach end
self end
# このプラグインを破棄する
# ==== Return
# self
def uninstall
@events.map &:detach
@filters.map &:detach
self.class.destroy name
execute_unload_hook
self end
# プラグインストレージの _key_ の値を取り出す
# ==== Args
# [key] 取得するキー
# [ifnone] キーに対応する値が存在しない場合
# ==== Return
# プラグインストレージ内のキーに対応する値
def at(key, ifnone=nil)
super("#{@name}_#{key}".to_sym, ifnone) end
# プラグインストレージに _key_ とその値 _vel_ の対応を保存する
# ==== Args
# [key] 取得するキー
# [val] 値
def store(key, val)
super("#{@name}_#{key}".to_sym, val) end
# イベント _event_name_ を宣言する
# ==== Args
# [event_name] イベント名
# [options] イベントの定義
def defevent(event_name, options={})
Event[event_name].options.merge!({plugin: self}.merge(options)) end
# DSLメソッドを新しく追加する。
# 追加されたメソッドは呼ぶと &callback が呼ばれ、その戻り値が返される。引数も順番通り全て &callbackに渡される
# ==== Args
# [dsl_name] 新しく追加するメソッド名
# [&callback] 実行されるメソッド
# ==== Return
# self
def defdsl(dsl_name, &callback)
self.class.instance_eval {
define_method(dsl_name, &callback) }
self
end
# プラグインが Plugin.uninstall される時に呼ばれるブロックを登録する。
def onunload
@unload_hook ||= []
@unload_hook.push(Proc.new) end
alias :on_unload :onunload
# mikutterコマンドを定義
# ==== Args
# [slug] コマンドスラッグ
# [options] コマンドオプション
# [&exec] コマンドの実行内容
def command(slug, options, &exec)
command = options.merge(slug: slug, exec: exec, plugin: @name).freeze
add_event_filter(:command){ |menu|
menu[slug] = command
[menu] } end
# 設定画面を作る
# ==== Args
# - String name タイトル
# - Proc &place 設定画面を作る無名関数
def settings(name, &place)
add_event_filter(:defined_settings) do |tabs|
[tabs.melt << [name, place, @name]] end end
# 画像ファイルのパスを得る
# ==== Args
# - String filename ファイル名
def get_skin(filename)
plugin_skin_dir = File.join(spec[:path], "skin")
if File.exist?(plugin_skin_dir)
Skin.get(filename, [plugin_skin_dir])
else
Skin.get(filename)
end
end
# マジックメソッドを追加する。
# on_?name :: add_event(name)
# filter_?name :: add_event_filter(name)
def method_missing(method, *args, &proc)
case method.to_s
when /\Aon_?(.+)\Z/
add_event($1.to_sym, &proc)
when /\Afilter_?(.+)\Z/
add_event_filter($1.to_sym, &proc)
when /\Ahook_?(.+)\Z/
add_event_hook($1.to_sym, &proc)
else
super end end
private
def execute_unload_hook
@unload_hook.each{ |unload| unload.call } if(defined?(@unload_hook)) end
end
|