aboutsummaryrefslogtreecommitdiffstats
path: root/core/plugin.rb
blob: f8828c1c2212bb3e81c7e3967f15963d6a6f0e70 (plain)
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