File: plugin.rb

package info (click to toggle)
ruby-pluggaloid 1.7.0-3
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 260 kB
  • sloc: ruby: 1,752; sh: 4; makefile: 2
file content (331 lines) | stat: -rw-r--r-- 10,696 bytes parent folder | download | duplicates (2)
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
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
# -*- coding: utf-8 -*-

require 'instance_storage'
require 'delayer'
require 'securerandom'
require 'set'

# プラグインの本体。
# DSLを提供し、イベントやフィルタの管理をする
module Pluggaloid
  class Plugin
    include InstanceStorage

    class << self
      attr_writer :vm

      def vm
        @vm ||= begin
                  raise Pluggaloid::NoDefaultDelayerError, "Default Delayer was not set." unless Delayer.default
                  vm = Pluggaloid::VM.new(
                    Delayer: Delayer.default,
                    Plugin: self,
                    Event: Pluggaloid::Event,
                    Listener: Pluggaloid::Listener,
                    Filter: Pluggaloid::Filter,
                    HandlerTag: Pluggaloid::HandlerTag,
                    Subscriber: Pluggaloid::Subscriber,
                    StreamGenerator: Pluggaloid::StreamGenerator
                  )
                  vm.Event.vm = vm end end

      # プラグインのインスタンスを返す。
      # ブロックが渡された場合、そのブロックをプラグインのインスタンスのスコープで実行する
      # ==== Args
      # [plugin_name] プラグイン名
      # ==== Return
      # Plugin
      def create(plugin_name, &body)
        self[plugin_name].instance_eval(&body) if body
        self[plugin_name] end

      # イベントを宣言する。
      # ==== Args
      # [event_name] イベント名
      # [options] 以下のキーを持つHash
      # :prototype :: 引数の数と型。Arrayで、type_strictが解釈できる条件を設定する
      # :priority :: Delayerの優先順位
      def defevent(event_name, options = {})
        vm.Event[event_name].options = options end

      # イベント _event_name_ を発生させる
      # ==== Args
      # [event_name] イベント名
      # [*args] イベントの引数
      # ==== Return
      # Delayer
      def call(event_name, *args)
        vm.Event[event_name].call(*args) end

      # 引数 _args_ をフィルタリングした結果を返す
      # ==== Args
      # [*args] 引数
      # ==== Return
      # フィルタされた引数の配列
      def filtering(event_name, *args)
        vm.Event[event_name].filtering(*args) end

      # フィルタ _event_name_ を実行し、defeventでPluggaloid::COLLECTと
      # 宣言されている引数の結果を列挙するEnumeratorを返す
      # ==== Args
      # [event_name] イベント名(String | Symbol)
      # [*specs] Pluggaloid::COLLECT以外の引数
      # ==== Return
      # [Enumerator]
      def collect(event_name, *specs)
        vm.Event[event_name].collect(*specs)
      end

      # 互換性のため
      def uninstall(plugin_name)
        self[plugin_name].uninstall end

      # 互換性のため
      def filter_cancel!
        vm.Filter.cancel! end

      alias plugin_list instances_name

      alias __clear_aF4e__ clear!
      def clear!
        if defined?(@vm) and @vm
          @vm.Event.clear!
          @vm = nil end
        __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] 監視するEventのインスタンス
    # [name:] 名前(String | nil)
    # [slug:] イベントリスナスラッグ(Symbol | nil)
    # [tags:] Pluggaloid::HandlerTag|Array リスナのタグ
    # [&callback] コールバック
    # ==== Return
    # Pluggaloid::Listener
    def add_event(event_name, **kwrest, &callback)
      result = vm.Listener.new(vm.Event[event_name], **kwrest, &callback)
      @events << result
      result end

    # イベントフィルタを新しく登録する
    # ==== Args
    # [event_name] イベント名(String | Symbol)
    # [name:] 名前(String | nil)
    # [slug:] フィルタスラッグ(Symbol | nil)
    # [tags:] Pluggaloid::HandlerTag|Array フィルタのタグ
    # [&callback] コールバック
    # ==== Return
    # Pluggaloid::Filter
    def add_event_filter(event_name, **kwrest, &callback)
      result = vm.Filter.new(vm.Event[event_name], **kwrest, &callback)
      @filters << result
      result end

    def generate(event_name, *specs, **kwrest, &block)
      vm.StreamGenerator.new(vm.Event[event_name], *specs, plugin: self, **kwrest, &block)
    end

    def subscribe(event_name, *specs, **kwrest, &block)
      if block
        result = vm.Subscriber.new(vm.Event[event_name], *specs, **kwrest, &block)
        @events << result
        result
      else
        Stream.new(
          Enumerator.new do |yielder|
            @events << vm.Subscriber.new(vm.Event[event_name], *specs, **kwrest) do |stream|
              stream.each(&yielder.method(:<<))
            end
          end.lazy
        )
      end
    end

    def subscribe?(event_name, *specs)
      vm.Event[event_name].subscribe?(*specs)
    end

    def collect(event_name, *specs)
      self.class.collect(event_name, *specs)
    end

    # 追加・削除がフィルタに反映されるコレクションオブジェクトを作成する。
    # 同時に _event_name_ にフィルタが定義され、フィルタが呼ばれると
    # その時点のコレクションオブジェクトの内容を全て列挙する。
    # フィルタと対になるコレクションオブジェクトは、 _&block_ の引数として渡される。
    # ==== Args
    # [event_name] イベント名(String | Symbol)
    # [*specs] Pluggaloid::COLLECT以外の引数
    # [&block] コレクションオブジェクトを受け取って一度だけ実行されるblock
    # ==== Return
    # _&block_ の戻り値
    def collection(event_name, *specs, &block)
      event = vm.Event[event_name]
      mutation = Pluggaloid::Collection.new(event, *specs)
      add_event_filter(event_name, name: 'collection(%s line %i)' % block.source_location) do |*args|
        if mutation.argument_hash_same?(args)
          mutation.values.each(&args[event.collect_index].method(:<<))
        end
        args
      end
      block.call(mutation)
    end

    # このプラグインのHandlerTagを作る。
    # ブロックが渡された場合は、ブロックの中を実行し、ブロックの中で定義された
    # Handler全てにTagを付与する。
    # ==== Args
    # [slug] スラッグ
    # [name] タグ名
    # ==== Return
    # Pluggaloid::HandlerTag
    def handler_tag(slug=SecureRandom.uuid, name=slug, &block)
      tag = case slug
            when String, Symbol
              vm.HandlerTag.new(slug: slug.to_sym, name: name.to_s, plugin: self)
            when vm.HandlerTag
              slug
            else
              raise Pluggaloid::TypeError, "Argument `slug' must be instance of Symbol, String or Pluggaloid::HandlerTag, but given #{slug.class}."
            end
      if block
        handlers = @events + @filters
        block.(tag)
        (@events + @filters - handlers).each do |handler|
          handler.add_tag(tag)
        end
      else
        tag
      end
    end

    # イベントリスナを列挙する
    # ==== Return
    # Set of Pluggaloid::Listener
    def listeners(&block)
      if block
        @events.each(&block)
      else
        @events.dup
      end
    end

    # フィルタを列挙する
    # ==== Return
    # Set of Pluggaloid::Filter
    def filters(&block)
      if block
        @filters.each(&block)
      else
        @filters.dup
      end
    end

    # イベントを削除する。
    # 引数は、Pluggaloid::ListenerかPluggaloid::Filterのみ(on_*やfilter_*の戻り値)。
    # 互換性のため、二つ引数がある場合は第一引数は無視され、第二引数が使われる。
    # ==== Args
    # [*args] 引数
    # ==== Return
    # self
    def detach(*args)
      listener = args.last
      case listener
      when vm.Listener, vm.Subscriber
        @events.delete(listener)
        listener.detach
      when vm.Filter
        @filters.delete(listener)
        listener.detach
      when Enumerable
        listener.each(&method(:detach))
      else
        raise ArgumentError, "Argument must be Pluggaloid::Listener, Pluggaloid::Filter, Pluggaloid::HandlerTag or Enumerable. But given #{listener.class}."
      end
      self end

    # このプラグインを破棄する
    # ==== Return
    # self
    def uninstall
      vm.Event[:unload].call(self.name)
      vm.Delayer.new do
        @events.map(&:detach)
        @filters.map(&:detach)
        self.class.destroy name
      end
      self end

    # イベント _event_name_ を宣言する
    # ==== Args
    # [event_name] イベント名
    # [options] イベントの定義
    def defevent(event_name, options={})
      vm.Event[event_name].defevent({ plugin: self, **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(&callback)
      add_event(:unload) do |plugin_slug|
        if plugin_slug == self.name
          callback.call
        end
      end
    end
    alias :on_unload :onunload

    # マジックメソッドを追加する。
    # on_?name :: add_event(name)
    # filter_?name :: add_event_filter(name)
    def method_missing(method, *args, **kwrest, &proc)
      method_name = method.to_s
      case
      when method_name.start_with?('on')
        event_name = method_name[(method_name[2] == '_' ? 3 : 2)..method_name.size]
        add_event(event_name.to_sym, *args, **kwrest, &proc)
      when method_name.start_with?('filter')
        event_name = method_name[(method_name[6] == '_' ? 7 : 6)..method_name.size]
        add_event_filter(event_name.to_sym, **kwrest, &proc)
      else
        super
      end
    end

    private

    def vm
      self.class.vm end

  end
end