pluggaloid 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +15 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +63 -0
- data/Rakefile +17 -0
- data/lib/pluggaloid.rb +21 -0
- data/lib/pluggaloid/error.rb +12 -0
- data/lib/pluggaloid/event.rb +108 -0
- data/lib/pluggaloid/filter.rb +38 -0
- data/lib/pluggaloid/listener.rb +29 -0
- data/lib/pluggaloid/plugin.rb +194 -0
- data/lib/pluggaloid/version.rb +3 -0
- data/pluggaloid.gemspec +27 -0
- data/test/multi_vm_test.rb +56 -0
- data/test/plugin_test.rb +148 -0
- metadata +137 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 2549e40abed501bd7546a76f8368daed95e8436a
|
4
|
+
data.tar.gz: e28a4c206fe73a1d6baac70902ac64c29be1f9ff
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 831b352d0c7aad2f39b44de0da81ccf984e9568834f731fbf160cf929181f998c001102bfabc1eabe86158e4aaacf3775251584a5826fdf1113590d04f99ee7a
|
7
|
+
data.tar.gz: 09ffac441afd19d38dd4e318cd95cedd3d202f8eb35eb3e57e8a3314658c593219021ed61f11adeb36541a47d732e0938e3a4b418e6f6f5c504f99782557cfe1
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2015 Toshiaki Asai
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,63 @@
|
|
1
|
+
# Pluggaloid
|
2
|
+
|
3
|
+
mikutterのプラグイン機構です。
|
4
|
+
登録したプラグイン同士がイベントを使って通信できるようになります。
|
5
|
+
|
6
|
+
## Installation
|
7
|
+
|
8
|
+
Add this line to your application's Gemfile:
|
9
|
+
|
10
|
+
```ruby
|
11
|
+
gem 'pluggaloid'
|
12
|
+
```
|
13
|
+
|
14
|
+
And then execute:
|
15
|
+
|
16
|
+
$ bundle
|
17
|
+
|
18
|
+
Or install it yourself as:
|
19
|
+
|
20
|
+
$ gem install pluggaloid
|
21
|
+
|
22
|
+
## Usage
|
23
|
+
|
24
|
+
```ruby
|
25
|
+
require 'pluggaloid'
|
26
|
+
|
27
|
+
main = Pluggaloid.new(Delayer.generate_class(priority: %i<high normal low>, default: :normal))
|
28
|
+
|
29
|
+
main.Plugin.create(:write_to_stdout) do
|
30
|
+
on_logging do |message|
|
31
|
+
puts "logging: #{message}"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
main.Plugin.call(:logging, "boot.")
|
36
|
+
main.Plugin.call(:logging, "event test.")
|
37
|
+
main.Plugin.call(:logging, "exit.")
|
38
|
+
|
39
|
+
main.Delayer.run while not main.Delayer.empty?
|
40
|
+
```
|
41
|
+
|
42
|
+
### Pluggaloid::new
|
43
|
+
|
44
|
+
Pluggaloid::newは、プラグイン機構を制御するためのDelayer, Plugin, Event, Listener, Filterのサブクラスを新しく作って返すメソッドです。
|
45
|
+
戻り値はStructで、それぞれのクラス名がメンバの名前になっています。
|
46
|
+
|
47
|
+
| Member | Description |
|
48
|
+
|----------|-------------------------------------|
|
49
|
+
| Delayer | Pluggaloid::new に渡したDelayer |
|
50
|
+
| Plugin | Pluggaloid::Plugin のサブクラス |
|
51
|
+
| Event | Pluggaloid::Event のサブクラス |
|
52
|
+
| Listener | Pluggaloid::Listener のサブクラス |
|
53
|
+
| Filter | Pluggaloid::Filter のサブクラス |
|
54
|
+
|
55
|
+
コンストラクタの唯一の引数には `Delayer.generate_class(priority: %i<high normal low>, default: :normal)`のように、優先順位付きでデフォルト優先度が設定されたDelayerを渡します。
|
56
|
+
|
57
|
+
## Contributing
|
58
|
+
|
59
|
+
1. Fork it ( https://github.com/toshia/pluggaloid/fork )
|
60
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
61
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
62
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
63
|
+
5. Create a new Pull Request
|
data/Rakefile
ADDED
data/lib/pluggaloid.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
require "pluggaloid/version"
|
2
|
+
require "pluggaloid/plugin"
|
3
|
+
require 'pluggaloid/event'
|
4
|
+
require 'pluggaloid/listener'
|
5
|
+
require 'pluggaloid/filter'
|
6
|
+
require 'pluggaloid/error'
|
7
|
+
|
8
|
+
require 'delayer'
|
9
|
+
|
10
|
+
module Pluggaloid
|
11
|
+
VM = Struct.new(*%i<Delayer Plugin Event Listener Filter>)
|
12
|
+
|
13
|
+
def self.new(delayer)
|
14
|
+
vm = VM.new(delayer,
|
15
|
+
Class.new(Plugin),
|
16
|
+
Class.new(Event),
|
17
|
+
Class.new(Listener),
|
18
|
+
Class.new(Filter))
|
19
|
+
vm.Plugin.vm = vm.Event.vm = vm
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,108 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
|
3
|
+
class Pluggaloid::Event
|
4
|
+
include InstanceStorage
|
5
|
+
|
6
|
+
# オプション。以下のキーを持つHash
|
7
|
+
# :prototype :: 引数の数と型。Arrayで、type_strictが解釈できる条件を設定する
|
8
|
+
# :priority :: Delayerの優先順位
|
9
|
+
attr_accessor :options
|
10
|
+
|
11
|
+
# フィルタを別のスレッドで実行する。偽ならメインスレッドでフィルタを実行する
|
12
|
+
@filter_another_thread = false
|
13
|
+
|
14
|
+
def initialize(*args)
|
15
|
+
super
|
16
|
+
@options = {}
|
17
|
+
@listeners = []
|
18
|
+
@filters = [] end
|
19
|
+
|
20
|
+
def vm
|
21
|
+
self.class.vm end
|
22
|
+
|
23
|
+
# イベントの優先順位を取得する
|
24
|
+
# ==== Return
|
25
|
+
# プラグインの優先順位
|
26
|
+
def priority
|
27
|
+
if @options.has_key? :priority
|
28
|
+
@options[:priority] end end
|
29
|
+
|
30
|
+
# イベントを引数 _args_ で発生させる
|
31
|
+
# ==== Args
|
32
|
+
# [*args] イベントの引数
|
33
|
+
# ==== Return
|
34
|
+
# Delayerか、イベントを待ち受けているリスナがない場合はnil
|
35
|
+
def call(*args)
|
36
|
+
if self.class.filter_another_thread
|
37
|
+
if @filters.empty?
|
38
|
+
vm.delayer.new(*Array(priority)) do
|
39
|
+
call_all_listeners(args) end
|
40
|
+
else
|
41
|
+
Thread.new do
|
42
|
+
filtered_args = filtering(*args)
|
43
|
+
if filtered_args.is_a? Array
|
44
|
+
vm.Delayer.new(*Array(priority)) do
|
45
|
+
call_all_listeners(filtered_args) end end end end
|
46
|
+
else
|
47
|
+
vm.Delayer.new(*Array(priority)) do
|
48
|
+
args = filtering(*args) if not @filters.empty?
|
49
|
+
call_all_listeners(args) if args.is_a? Array end end end
|
50
|
+
|
51
|
+
# 引数 _args_ をフィルタリングした結果を返す
|
52
|
+
# ==== Args
|
53
|
+
# [*args] 引数
|
54
|
+
# ==== Return
|
55
|
+
# フィルタされた引数の配列
|
56
|
+
def filtering(*args)
|
57
|
+
catch(:filter_exit) {
|
58
|
+
@filters.reduce(args){ |acm, event_filter|
|
59
|
+
event_filter.filtering(*acm) } } end
|
60
|
+
|
61
|
+
def add_listener(listener)
|
62
|
+
unless listener.is_a? Pluggaloid::Listener
|
63
|
+
raise Pluggaloid::ArgumentError, "First argument must be Pluggaloid::Listener, but given #{listener.class}." end
|
64
|
+
@listeners << listener
|
65
|
+
self end
|
66
|
+
|
67
|
+
def delete_listener(event_filter)
|
68
|
+
@listeners.delete(event_filter)
|
69
|
+
self end
|
70
|
+
|
71
|
+
# イベントフィルタを追加する
|
72
|
+
# ==== Args
|
73
|
+
# [event_filter] イベントフィルタ(Filter)
|
74
|
+
# ==== Return
|
75
|
+
# self
|
76
|
+
def add_filter(event_filter)
|
77
|
+
unless event_filter.is_a? Pluggaloid::Filter
|
78
|
+
raise Pluggaloid::ArgumentError, "First argument must be Pluggaloid::Filter, but given #{event_filter.class}." end
|
79
|
+
@filters << event_filter
|
80
|
+
self end
|
81
|
+
|
82
|
+
# イベントフィルタを削除する
|
83
|
+
# ==== Args
|
84
|
+
# [event_filter] イベントフィルタ(EventFilter)
|
85
|
+
# ==== Return
|
86
|
+
# self
|
87
|
+
def delete_filter(event_filter)
|
88
|
+
@filters.delete(event_filter)
|
89
|
+
self end
|
90
|
+
|
91
|
+
private
|
92
|
+
def call_all_listeners(args)
|
93
|
+
catch(:plugin_exit) do
|
94
|
+
@listeners.each do |listener|
|
95
|
+
listener.call(*args) end end end
|
96
|
+
|
97
|
+
class << self
|
98
|
+
attr_accessor :filter_another_thread, :vm
|
99
|
+
|
100
|
+
alias __clear_aF4e__ clear!
|
101
|
+
def clear!
|
102
|
+
@filter_another_thread = false
|
103
|
+
__clear_aF4e__()
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
clear!
|
108
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
|
3
|
+
class Pluggaloid::Filter
|
4
|
+
# フィルタ内部で使う。フィルタの実行をキャンセルする。Plugin#filtering はfalseを返し、
|
5
|
+
# イベントのフィルタの場合は、そのイベントの実行自体をキャンセルする。
|
6
|
+
# また、 _result_ が渡された場合、Event#filtering の戻り値は _result_ になる。
|
7
|
+
def self.cancel!(result=false)
|
8
|
+
throw :filter_exit, result end
|
9
|
+
|
10
|
+
# ==== Args
|
11
|
+
# [event] 監視するEventのインスタンス
|
12
|
+
# [&callback] コールバック
|
13
|
+
def initialize(event, &callback)
|
14
|
+
raise Pluggaloid::TypeError, "Argument `event' must be instance of Pluggaloid::Event, but given #{event.class}." unless event.is_a? Pluggaloid::Event
|
15
|
+
@event = event
|
16
|
+
@callback = Proc.new
|
17
|
+
event.add_filter self end
|
18
|
+
|
19
|
+
# イベントを実行する
|
20
|
+
# ==== Args
|
21
|
+
# [*args] イベントの引数
|
22
|
+
# ==== Return
|
23
|
+
# 加工後の引数の配列
|
24
|
+
def filtering(*args)
|
25
|
+
length = args.size
|
26
|
+
result = @callback.call(*args, &self.class.method(:cancel!))
|
27
|
+
if length != result.size
|
28
|
+
raise Pluggaloid::FilterError, "filter changes arguments length (#{length} to #{result.size})" end
|
29
|
+
result end
|
30
|
+
|
31
|
+
# このリスナを削除する
|
32
|
+
# ==== Return
|
33
|
+
# self
|
34
|
+
def detach
|
35
|
+
@event.delete_filter(self)
|
36
|
+
self end
|
37
|
+
|
38
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
|
3
|
+
class Pluggaloid::Listener
|
4
|
+
# プラグインコールバックをこれ以上実行しない。
|
5
|
+
def self.cancel!
|
6
|
+
throw :plugin_exit, false end
|
7
|
+
|
8
|
+
# ==== Args
|
9
|
+
# [event] 監視するEventのインスタンス
|
10
|
+
# [&callback] コールバック
|
11
|
+
def initialize(event, &callback)
|
12
|
+
raise Pluggaloid::TypeError, "Argument `event' must be instance of Pluggaloid::Event, but given #{event.class}." unless event.is_a? Pluggaloid::Event
|
13
|
+
@event = event
|
14
|
+
@callback = Proc.new
|
15
|
+
event.add_listener(self) end
|
16
|
+
|
17
|
+
# イベントを実行する
|
18
|
+
# ==== Args
|
19
|
+
# [*args] イベントの引数
|
20
|
+
def call(*args)
|
21
|
+
@callback.call(*args, &self.class.method(:cancel!)) end
|
22
|
+
|
23
|
+
# このリスナを削除する
|
24
|
+
# ==== Return
|
25
|
+
# self
|
26
|
+
def detach
|
27
|
+
@event.delete_listener(self)
|
28
|
+
self end
|
29
|
+
end
|
@@ -0,0 +1,194 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
|
3
|
+
require 'instance_storage'
|
4
|
+
require 'delayer'
|
5
|
+
|
6
|
+
# プラグインの本体。
|
7
|
+
# DSLを提供し、イベントやフィルタの管理をする
|
8
|
+
module Pluggaloid
|
9
|
+
class Plugin
|
10
|
+
include InstanceStorage
|
11
|
+
|
12
|
+
class << self
|
13
|
+
attr_writer :vm
|
14
|
+
|
15
|
+
def vm
|
16
|
+
@vm ||= begin
|
17
|
+
raise Pluggaloid::NoDefaultDelayerError, "Default Delayer was not set." unless Delayer.default
|
18
|
+
vm = Pluggaloid::VM.new(
|
19
|
+
Delayer.default,
|
20
|
+
self,
|
21
|
+
Pluggaloid::Event,
|
22
|
+
Pluggaloid::Listener,
|
23
|
+
Pluggaloid::Filter)
|
24
|
+
vm.Event.vm = vm end end
|
25
|
+
|
26
|
+
# プラグインのインスタンスを返す。
|
27
|
+
# ブロックが渡された場合、そのブロックをプラグインのインスタンスのスコープで実行する
|
28
|
+
# ==== Args
|
29
|
+
# [plugin_name] プラグイン名
|
30
|
+
# ==== Return
|
31
|
+
# Plugin
|
32
|
+
def create(plugin_name, &body)
|
33
|
+
self[plugin_name].instance_eval(&body) if body
|
34
|
+
self[plugin_name] end
|
35
|
+
|
36
|
+
# イベントを宣言する。
|
37
|
+
# ==== Args
|
38
|
+
# [event_name] イベント名
|
39
|
+
# [options] 以下のキーを持つHash
|
40
|
+
# :prototype :: 引数の数と型。Arrayで、type_strictが解釈できる条件を設定する
|
41
|
+
# :priority :: Delayerの優先順位
|
42
|
+
def defevent(event_name, options = {})
|
43
|
+
vm.Event[event_name].options = options end
|
44
|
+
|
45
|
+
# イベント _event_name_ を発生させる
|
46
|
+
# ==== Args
|
47
|
+
# [event_name] イベント名
|
48
|
+
# [*args] イベントの引数
|
49
|
+
# ==== Return
|
50
|
+
# Delayer
|
51
|
+
def call(event_name, *args)
|
52
|
+
vm.Event[event_name].call(*args) end
|
53
|
+
|
54
|
+
# 引数 _args_ をフィルタリングした結果を返す
|
55
|
+
# ==== Args
|
56
|
+
# [*args] 引数
|
57
|
+
# ==== Return
|
58
|
+
# フィルタされた引数の配列
|
59
|
+
def filtering(event_name, *args)
|
60
|
+
vm.Event[event_name].filtering(*args) end
|
61
|
+
|
62
|
+
# 互換性のため
|
63
|
+
def uninstall(plugin_name)
|
64
|
+
self[plugin_name].uninstall end
|
65
|
+
|
66
|
+
# 互換性のため
|
67
|
+
def filter_cancel!
|
68
|
+
vm.Filter.cancel! end
|
69
|
+
|
70
|
+
alias plugin_list instances_name
|
71
|
+
|
72
|
+
alias __clear_aF4e__ clear!
|
73
|
+
def clear!
|
74
|
+
if defined?(@vm) and @vm
|
75
|
+
@vm.Event.clear!
|
76
|
+
@vm = nil end
|
77
|
+
__clear_aF4e__() end
|
78
|
+
end
|
79
|
+
|
80
|
+
# プラグインの名前
|
81
|
+
attr_reader :name
|
82
|
+
|
83
|
+
# spec
|
84
|
+
attr_accessor :spec
|
85
|
+
|
86
|
+
# 最初にプラグインがロードされた時刻(uninstallされるとリセットする)
|
87
|
+
attr_reader :defined_time
|
88
|
+
|
89
|
+
# ==== Args
|
90
|
+
# [plugin_name] プラグイン名
|
91
|
+
def initialize(*args)
|
92
|
+
super
|
93
|
+
@defined_time = Time.new.freeze
|
94
|
+
@events = Set.new
|
95
|
+
@filters = Set.new end
|
96
|
+
|
97
|
+
# イベントリスナを新しく登録する
|
98
|
+
# ==== Args
|
99
|
+
# [event_name] イベント名
|
100
|
+
# [&callback] イベントのコールバック
|
101
|
+
# ==== Return
|
102
|
+
# Pluggaloid::Listener
|
103
|
+
def add_event(event_name, &callback)
|
104
|
+
result = vm.Listener.new(vm.Event[event_name], &callback)
|
105
|
+
@events << result
|
106
|
+
result end
|
107
|
+
|
108
|
+
# イベントフィルタを新しく登録する
|
109
|
+
# ==== Args
|
110
|
+
# [event_name] イベント名
|
111
|
+
# [&callback] イベントのコールバック
|
112
|
+
# ==== Return
|
113
|
+
# EventFilter
|
114
|
+
def add_event_filter(event_name, &callback)
|
115
|
+
result = vm.Filter.new(vm.Event[event_name], &callback)
|
116
|
+
@filters << result
|
117
|
+
result end
|
118
|
+
|
119
|
+
# イベントを削除する。
|
120
|
+
# 引数は、Pluggaloid::ListenerかPluggaloid::Filterのみ(on_*やfilter_*の戻り値)。
|
121
|
+
# 互換性のため、二つ引数がある場合は第一引数は無視され、第二引数が使われる。
|
122
|
+
# ==== Args
|
123
|
+
# [*args] 引数
|
124
|
+
# ==== Return
|
125
|
+
# self
|
126
|
+
def detach(*args)
|
127
|
+
listener = args.last
|
128
|
+
if listener.is_a? vm.Listener
|
129
|
+
@events.delete(listener)
|
130
|
+
listener.detach
|
131
|
+
elsif listener.is_a? vm.Filter
|
132
|
+
@filters.delete(listener)
|
133
|
+
listener.detach end
|
134
|
+
self end
|
135
|
+
|
136
|
+
# このプラグインを破棄する
|
137
|
+
# ==== Return
|
138
|
+
# self
|
139
|
+
def uninstall
|
140
|
+
@events.map(&:detach)
|
141
|
+
@filters.map(&:detach)
|
142
|
+
self.class.destroy name
|
143
|
+
execute_unload_hook
|
144
|
+
self end
|
145
|
+
|
146
|
+
# イベント _event_name_ を宣言する
|
147
|
+
# ==== Args
|
148
|
+
# [event_name] イベント名
|
149
|
+
# [options] イベントの定義
|
150
|
+
def defevent(event_name, options={})
|
151
|
+
vm.Event[event_name].options.merge!({plugin: self}.merge(options)) end
|
152
|
+
|
153
|
+
# DSLメソッドを新しく追加する。
|
154
|
+
# 追加されたメソッドは呼ぶと &callback が呼ばれ、その戻り値が返される。引数も順番通り全て &callbackに渡される
|
155
|
+
# ==== Args
|
156
|
+
# [dsl_name] 新しく追加するメソッド名
|
157
|
+
# [&callback] 実行されるメソッド
|
158
|
+
# ==== Return
|
159
|
+
# self
|
160
|
+
def defdsl(dsl_name, &callback)
|
161
|
+
self.class.instance_eval {
|
162
|
+
define_method(dsl_name, &callback) }
|
163
|
+
self end
|
164
|
+
|
165
|
+
# プラグインが Plugin.uninstall される時に呼ばれるブロックを登録する。
|
166
|
+
def onunload
|
167
|
+
@unload_hook ||= []
|
168
|
+
@unload_hook.push(Proc.new) end
|
169
|
+
alias :on_unload :onunload
|
170
|
+
|
171
|
+
# マジックメソッドを追加する。
|
172
|
+
# on_?name :: add_event(name)
|
173
|
+
# filter_?name :: add_event_filter(name)
|
174
|
+
def method_missing(method, *args, &proc)
|
175
|
+
case method.to_s
|
176
|
+
when /\Aon_?(.+)\Z/
|
177
|
+
add_event($1.to_sym, &proc)
|
178
|
+
when /\Afilter_?(.+)\Z/
|
179
|
+
add_event_filter($1.to_sym, &proc)
|
180
|
+
when /\Ahook_?(.+)\Z/
|
181
|
+
add_event_hook($1.to_sym, &proc)
|
182
|
+
else
|
183
|
+
super end end
|
184
|
+
|
185
|
+
private
|
186
|
+
|
187
|
+
def execute_unload_hook
|
188
|
+
@unload_hook.each{ |unload| unload.call } if(defined?(@unload_hook)) end
|
189
|
+
|
190
|
+
def vm
|
191
|
+
self.class.vm end
|
192
|
+
|
193
|
+
end
|
194
|
+
end
|
data/pluggaloid.gemspec
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'pluggaloid/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "pluggaloid"
|
8
|
+
spec.version = Pluggaloid::VERSION
|
9
|
+
spec.authors = ["Toshiaki Asai"]
|
10
|
+
spec.email = ["toshi.alternative@gmail.com"]
|
11
|
+
spec.summary = %q{Extensible plugin system}
|
12
|
+
spec.description = %q{Pluggaloid is extensible plugin system for mikutter.}
|
13
|
+
spec.homepage = ""
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0")
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_dependency 'delayer'
|
22
|
+
spec.add_dependency 'instance_storage', ">= 1.0.0", "< 2.0.0"
|
23
|
+
|
24
|
+
spec.add_development_dependency "bundler", "~> 1.7"
|
25
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
26
|
+
spec.add_development_dependency "minitest"
|
27
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
|
3
|
+
require 'bundler/setup'
|
4
|
+
require 'minitest/autorun'
|
5
|
+
|
6
|
+
require 'pluggaloid'
|
7
|
+
|
8
|
+
describe(Pluggaloid) do
|
9
|
+
before do
|
10
|
+
Delayer.default = Delayer.generate_class(priority: %i<high normal low>, default: :normal)
|
11
|
+
Pluggaloid::Plugin.clear!
|
12
|
+
end
|
13
|
+
|
14
|
+
def eval_all_events(delayer=Delayer)
|
15
|
+
native = Thread.list
|
16
|
+
yield if block_given?
|
17
|
+
delayer.run while not(delayer.empty? and (Thread.list - native).empty?)
|
18
|
+
end
|
19
|
+
|
20
|
+
it "should take new plugin classes" do
|
21
|
+
pluggaloid = Pluggaloid.new(Delayer.default)
|
22
|
+
assert_equal Delayer.default, pluggaloid.Delayer, ""
|
23
|
+
assert_includes(pluggaloid.Plugin.ancestors, Pluggaloid::Plugin, "Plugin should subbclass of Pluggaloid::Plugin")
|
24
|
+
assert_includes(pluggaloid.Event.ancestors, Pluggaloid::Event, "Event should subbclass of Pluggaloid::Event")
|
25
|
+
assert_includes(pluggaloid.Listener.ancestors, Pluggaloid::Listener, "Listener should subbclass of Pluggaloid::Listener")
|
26
|
+
assert_includes(pluggaloid.Filter.ancestors, Pluggaloid::Filter, "Filter should subbclass of Pluggaloid::Filter")
|
27
|
+
end
|
28
|
+
|
29
|
+
it "call event in new vm" do
|
30
|
+
vm_a = Pluggaloid.new(Delayer.generate_class(priority: %i<high normal low>, default: :normal))
|
31
|
+
vm_b = Pluggaloid.new(Delayer.generate_class(priority: %i<high normal low>, default: :normal))
|
32
|
+
foo = bar = false
|
33
|
+
vm_a.Plugin.create(:foo_plugin) do
|
34
|
+
on_foo do
|
35
|
+
foo = true end end
|
36
|
+
vm_b.Plugin.create(:bar_plugin) do
|
37
|
+
on_bar do
|
38
|
+
bar = true end end
|
39
|
+
|
40
|
+
assert_equal(%i<foo_plugin>, vm_a.Plugin.instances_name)
|
41
|
+
assert_equal(%i<bar_plugin>, vm_b.Plugin.instances_name)
|
42
|
+
|
43
|
+
eval_all_events(vm_a.Delayer) do
|
44
|
+
vm_a.Plugin.call(:foo)
|
45
|
+
vm_a.Plugin.call(:bar) end
|
46
|
+
assert foo, "Event foo should be called"
|
47
|
+
refute bar, "Event bar should not be called"
|
48
|
+
|
49
|
+
foo = bar = false
|
50
|
+
eval_all_events(vm_b.Delayer) do
|
51
|
+
vm_b.Plugin.call(:foo)
|
52
|
+
vm_b.Plugin.call(:bar) end
|
53
|
+
refute foo, "Event foo should not be called"
|
54
|
+
assert bar, "Event bar should be called"
|
55
|
+
end
|
56
|
+
end
|
data/test/plugin_test.rb
ADDED
@@ -0,0 +1,148 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
|
3
|
+
require 'bundler/setup'
|
4
|
+
require 'minitest/autorun'
|
5
|
+
|
6
|
+
require 'pluggaloid'
|
7
|
+
|
8
|
+
describe(Pluggaloid::Plugin) do
|
9
|
+
before do
|
10
|
+
Delayer.default = Delayer.generate_class(priority: %i<high normal low>, default: :normal)
|
11
|
+
Pluggaloid::Plugin.clear!
|
12
|
+
end
|
13
|
+
|
14
|
+
def eval_all_events
|
15
|
+
native = Thread.list
|
16
|
+
yield if block_given?
|
17
|
+
Delayer.run while not(Delayer.empty? and (Thread.list - native).empty?)
|
18
|
+
end
|
19
|
+
|
20
|
+
it "fire and filtering event, receive a plugin" do
|
21
|
+
sum = 0
|
22
|
+
Pluggaloid::Plugin.create(:event) do
|
23
|
+
on_increase do |v|
|
24
|
+
sum += v end
|
25
|
+
|
26
|
+
filter_increase do |v|
|
27
|
+
[v * 2] end end
|
28
|
+
eval_all_events do
|
29
|
+
Pluggaloid::Event[:increase].call(1) end
|
30
|
+
assert_equal(2, sum)
|
31
|
+
end
|
32
|
+
|
33
|
+
it "filter in another thread" do
|
34
|
+
filter_thread = nil
|
35
|
+
Pluggaloid::Plugin.create(:event) do
|
36
|
+
on_thread do
|
37
|
+
end
|
38
|
+
|
39
|
+
filter_thread do
|
40
|
+
filter_thread = Thread.current
|
41
|
+
[] end end
|
42
|
+
eval_all_events do
|
43
|
+
Pluggaloid::Event[:thread].call end
|
44
|
+
assert filter_thread
|
45
|
+
assert_equal Thread.current, filter_thread
|
46
|
+
|
47
|
+
Pluggaloid::Event.filter_another_thread = true
|
48
|
+
filter_thread = nil
|
49
|
+
eval_all_events do
|
50
|
+
Pluggaloid::Event[:thread].call end
|
51
|
+
assert filter_thread, "The filter doesn't run."
|
52
|
+
refute_equal Thread.current, filter_thread, 'The filter should execute in not a main thread'
|
53
|
+
end
|
54
|
+
|
55
|
+
it "uninstall" do
|
56
|
+
sum = 0
|
57
|
+
Pluggaloid::Plugin.create(:event) do
|
58
|
+
on_increase do |v|
|
59
|
+
sum += v end
|
60
|
+
filter_increase do |v|
|
61
|
+
[v * 2] end end
|
62
|
+
eval_all_events do
|
63
|
+
Pluggaloid::Plugin.create(:event).uninstall
|
64
|
+
Pluggaloid::Event[:increase].call(1) end
|
65
|
+
assert_equal(0, sum)
|
66
|
+
end
|
67
|
+
|
68
|
+
it "detach" do
|
69
|
+
sum = 0
|
70
|
+
event = filter = nil
|
71
|
+
Pluggaloid::Plugin.create(:event) do
|
72
|
+
event = on_increase do |v|
|
73
|
+
sum += v end
|
74
|
+
filter = filter_increase do |v|
|
75
|
+
[v * 2] end end
|
76
|
+
eval_all_events do
|
77
|
+
Pluggaloid::Event[:increase].call(1) end
|
78
|
+
assert_equal(2, sum, "It should execute filter when event called")
|
79
|
+
|
80
|
+
eval_all_events do
|
81
|
+
Pluggaloid::Plugin[:event].detach filter
|
82
|
+
Pluggaloid::Event[:increase].call(1) end
|
83
|
+
assert_equal(3, sum, "It should not execute detached filter when event called")
|
84
|
+
|
85
|
+
eval_all_events do
|
86
|
+
Pluggaloid::Plugin.create(:event).detach event
|
87
|
+
Pluggaloid::Event[:increase].call(1) end
|
88
|
+
assert_equal(3, sum, "It should not executed detached event")
|
89
|
+
end
|
90
|
+
|
91
|
+
it "get plugin list" do
|
92
|
+
assert_empty(Pluggaloid::Plugin.plugin_list, "Plugin list must empty in first time")
|
93
|
+
Pluggaloid::Plugin.create(:plugin_0)
|
94
|
+
assert_equal(%i<plugin_0>, Pluggaloid::Plugin.plugin_list, "The new plugin should appear plugin list")
|
95
|
+
Pluggaloid::Plugin.create(:plugin_1)
|
96
|
+
assert_equal(%i<plugin_0 plugin_1>, Pluggaloid::Plugin.plugin_list, "The new plugin should appear plugin list")
|
97
|
+
end
|
98
|
+
|
99
|
+
it "dsl method defevent" do
|
100
|
+
Pluggaloid::Plugin.create :defevent do
|
101
|
+
defevent :increase, prototype: [Integer] end
|
102
|
+
assert_equal([Integer], Pluggaloid::Event[:increase].options[:prototype])
|
103
|
+
assert_equal(Pluggaloid::Plugin[:defevent], Pluggaloid::Event[:increase].options[:plugin])
|
104
|
+
end
|
105
|
+
|
106
|
+
it "unload hook" do
|
107
|
+
value = 0
|
108
|
+
Pluggaloid::Plugin.create(:unload) {
|
109
|
+
on_unload {
|
110
|
+
value += 2 }
|
111
|
+
on_unload {
|
112
|
+
value += 1 } }
|
113
|
+
assert_equal(value, 0)
|
114
|
+
Pluggaloid::Plugin.create(:unload).uninstall
|
115
|
+
assert_equal(value, 3)
|
116
|
+
end
|
117
|
+
|
118
|
+
describe "defdsl" do
|
119
|
+
it "simple dsl" do
|
120
|
+
Pluggaloid::Plugin.create :dsl_def do
|
121
|
+
defdsl :twice do |number|
|
122
|
+
number * 2 end end
|
123
|
+
|
124
|
+
dsl_use = Pluggaloid::Plugin[:dsl_use]
|
125
|
+
assert_equal(4, dsl_use.twice(2))
|
126
|
+
assert_equal(0, dsl_use.twice(0))
|
127
|
+
assert_equal(-26, dsl_use.twice(-13))
|
128
|
+
end
|
129
|
+
|
130
|
+
it "callback dsl" do
|
131
|
+
Pluggaloid::Plugin.create :dsl_def do
|
132
|
+
defdsl :rejector do |value, &condition|
|
133
|
+
value.reject(&condition) end end
|
134
|
+
|
135
|
+
dsl_use = Pluggaloid::Plugin.create(:dsl_use)
|
136
|
+
assert_equal([2, 4, 6], dsl_use.rejector(1..6){ |d| 0 != (d & 1) })
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
it 'raises NoDefaultDelayerError if Delayer do not have default delayer' do
|
141
|
+
Delayer.default = nil
|
142
|
+
Pluggaloid::Plugin.clear!
|
143
|
+
assert_raises(Pluggaloid::NoDefaultDelayerError) do
|
144
|
+
Pluggaloid::Plugin.create(:raises) do
|
145
|
+
on_no_default_delayer_error do; end end end
|
146
|
+
end
|
147
|
+
|
148
|
+
end
|
metadata
ADDED
@@ -0,0 +1,137 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: pluggaloid
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Toshiaki Asai
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-08-03 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: delayer
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: instance_storage
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 1.0.0
|
34
|
+
- - "<"
|
35
|
+
- !ruby/object:Gem::Version
|
36
|
+
version: 2.0.0
|
37
|
+
type: :runtime
|
38
|
+
prerelease: false
|
39
|
+
version_requirements: !ruby/object:Gem::Requirement
|
40
|
+
requirements:
|
41
|
+
- - ">="
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: 1.0.0
|
44
|
+
- - "<"
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: 2.0.0
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: bundler
|
49
|
+
requirement: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - "~>"
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '1.7'
|
54
|
+
type: :development
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
requirements:
|
58
|
+
- - "~>"
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: '1.7'
|
61
|
+
- !ruby/object:Gem::Dependency
|
62
|
+
name: rake
|
63
|
+
requirement: !ruby/object:Gem::Requirement
|
64
|
+
requirements:
|
65
|
+
- - "~>"
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
version: '10.0'
|
68
|
+
type: :development
|
69
|
+
prerelease: false
|
70
|
+
version_requirements: !ruby/object:Gem::Requirement
|
71
|
+
requirements:
|
72
|
+
- - "~>"
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
version: '10.0'
|
75
|
+
- !ruby/object:Gem::Dependency
|
76
|
+
name: minitest
|
77
|
+
requirement: !ruby/object:Gem::Requirement
|
78
|
+
requirements:
|
79
|
+
- - ">="
|
80
|
+
- !ruby/object:Gem::Version
|
81
|
+
version: '0'
|
82
|
+
type: :development
|
83
|
+
prerelease: false
|
84
|
+
version_requirements: !ruby/object:Gem::Requirement
|
85
|
+
requirements:
|
86
|
+
- - ">="
|
87
|
+
- !ruby/object:Gem::Version
|
88
|
+
version: '0'
|
89
|
+
description: Pluggaloid is extensible plugin system for mikutter.
|
90
|
+
email:
|
91
|
+
- toshi.alternative@gmail.com
|
92
|
+
executables: []
|
93
|
+
extensions: []
|
94
|
+
extra_rdoc_files: []
|
95
|
+
files:
|
96
|
+
- ".gitignore"
|
97
|
+
- Gemfile
|
98
|
+
- LICENSE.txt
|
99
|
+
- README.md
|
100
|
+
- Rakefile
|
101
|
+
- lib/pluggaloid.rb
|
102
|
+
- lib/pluggaloid/error.rb
|
103
|
+
- lib/pluggaloid/event.rb
|
104
|
+
- lib/pluggaloid/filter.rb
|
105
|
+
- lib/pluggaloid/listener.rb
|
106
|
+
- lib/pluggaloid/plugin.rb
|
107
|
+
- lib/pluggaloid/version.rb
|
108
|
+
- pluggaloid.gemspec
|
109
|
+
- test/multi_vm_test.rb
|
110
|
+
- test/plugin_test.rb
|
111
|
+
homepage: ''
|
112
|
+
licenses:
|
113
|
+
- MIT
|
114
|
+
metadata: {}
|
115
|
+
post_install_message:
|
116
|
+
rdoc_options: []
|
117
|
+
require_paths:
|
118
|
+
- lib
|
119
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
120
|
+
requirements:
|
121
|
+
- - ">="
|
122
|
+
- !ruby/object:Gem::Version
|
123
|
+
version: '0'
|
124
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
125
|
+
requirements:
|
126
|
+
- - ">="
|
127
|
+
- !ruby/object:Gem::Version
|
128
|
+
version: '0'
|
129
|
+
requirements: []
|
130
|
+
rubyforge_project:
|
131
|
+
rubygems_version: 2.4.5
|
132
|
+
signing_key:
|
133
|
+
specification_version: 4
|
134
|
+
summary: Extensible plugin system
|
135
|
+
test_files:
|
136
|
+
- test/multi_vm_test.rb
|
137
|
+
- test/plugin_test.rb
|