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
|
# -*- coding: utf-8 -*-
require_relative 'model/intent'
require_relative 'model/intent_token'
Plugin.create(:intent) do
# 全てのIntentを列挙するためのフィルタ
defevent :intent_catalog,
prototype: [:<<]
# _uri_ を開くことができる Model を列挙するためのフィルタ
defevent :model_of_uri,
prototype: [URI, :<<]
# _model_slug_ を開くことができる Intent を列挙するためのフィルタ
defevent :intent_select_by_model_slug,
prototype: [Symbol, :<<]
# 第二引数のリソースを、第一引数のIntentのうちどれで開くかを決められなかった時に発生する。
# intent_selectorプラグインがこれを受け取ってダイアログとか出す
defevent :intent_select,
prototype: [Enumerable, tcor(URI, String, Retriever::Model)]
# IntentTokenの次にあたるintentを発生させる。
defevent :intent_forward,
priority: :ui_response,
prototype: [Plugin::Intent::IntentToken]
# _model_ を開く方法を新しく登録する。
# ==== Args
# [model] サポートするModelのClass
# [label:]
# 開き方を説明する文字列。
# あるModelを開く手段が複数ある場合、ユーザは _label_ の内容とともに、どうやって開くか選択することになる。
# 省略した場合はpluginの名前になる
# [slug:]
# このintentのslug。他のintentと重複してはならない。
# 通常は指定しなくてもユニークなslugが割り当てられるが、同じ _model_ に二つ以上のintentを登録する場合は、同じslugが自動生成されてしまうので、ユニークな値を設定しなければならない。
# 省略した場合はPluginとModelから自動生成される
# [&proc]
# パーマリンクを開く時に、 Plugin::Intent::IntentToken を引数に呼ばれる。
# ==== Return
# self
defdsl :intent do |model, label: nil, slug: :"#{self.spec[:slug]}_#{model.slug}", &proc|
label ||= (self.spec[:name] || self.spec[:slug])
my_intent = Plugin::Intent::Intent.new(slug: slug, label: label, model_slug: model.slug)
filter_intent_select_by_model_slug do |target_model_slug, intents|
if model.slug == target_model_slug
intents << my_intent
end
[target_model_slug, intents]
end
filter_intent_all do |intents|
intents << my_intent
[intents]
end
add_event(:"intent_open_#{slug}", &proc)
self
end
on_open do |object|
case object
when Plugin::Intent::IntentToken
Plugin.call("intent_open_#{object.intent.slug}", object)
when Retriever::Model
open_model(object)
when String, URI
open_uri(object.is_a?(URI) ? object : URI.parse(object))
end
end
on_intent_forward do |intent_token|
case intent_token[:source]
when Retriever::Model
open_model(intent_token[:source], token: intent_token)
else
open_uri(intent_token.uri, token: intent_token)
end
end
# _uri_ をUI上で開く。
# このメソッドが呼ばれたらIntentTokenを生成して、開くことを試みる。
# open_modelのほうが高速なので、modelオブジェクトが存在するならばopen_modelを呼ぶこと。
# ==== Args
# [uri] 対象となるURI
# [token:] 親となるIntentToken
def open_uri(uri, token: nil)
model_slugs = Plugin.filtering(:model_of_uri, uri.freeze, Set.new).last
if model_slugs.empty?
error "model not found to open for #{uri}"
return
end
intents = model_slugs.lazy.flat_map{|model_slug|
Plugin.filtering(:intent_select_by_model_slug, model_slug, []).last
}
if token
intents = intents.reject{|intent| token.intent_ancestors.include?(intent) }
end
head = intents.first(2)
case head.size
when 0
error "intent not found to open for #{model_slugs.to_a}"
return
when 1
Plugin::Intent::IntentToken.open(
uri: uri,
intent: head.first,
parent: token)
else
Plugin.call(:intent_select, intents, uri)
end
end
# _model_ をUI上で開く。
# このメソッドが呼ばれたらIntentTokenを生成して、開くことを試みる。
# open_uriは、Modelが必要になった時にURIからModelの取得生成を試みるが、
# このメソッドはヒントとして _model_ を与えるため、探索が発生せず高速に処理できる。
# ==== Args
# [model] 対象となるRetriever::Model
def open_model(model, token: nil)
intents = Plugin.filtering(:intent_select_by_model_slug, model.class.slug, Set.new).last
if token
intents = intents.reject{|intent| token.intent_ancestors.include?(intent) }
end
head = intents.first(2)
case head.size
when 0
type_strict model.uri => URI
open_uri(model.uri, token: token)
when 1
intent = head.first
Plugin::Intent::IntentToken.open(
uri: model.uri,
model: model,
intent: intent,
parent: token)
else
Plugin.call(:intent_select, intents, model)
end
end
end
|