aboutsummaryrefslogtreecommitdiffstats
path: root/core/plugin/intent/intent.rb
blob: 4d3d9bf6945d94c953f5a6ae56b33d4daddc2488 (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
# -*- 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