alfred-workflow 1.11.3 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -8,8 +8,8 @@ module Alfred
8
8
  def initialize(path, opts = {})
9
9
  if opts[:title]
10
10
  @title = opts[:title]
11
- elsif ['.ennote', '.webbookmark'].include? File.extname(path)
12
- @title = %x{mdls -name kMDItemDisplayName -raw '#{path}'}
11
+ elsif ['.ennote', '.webbookmark', '.vcf', '.abcdp', '.olk14Contact'].include? File.extname(path)
12
+ @title = %x{/usr/bin/mdls -name kMDItemDisplayName -raw '#{path}'}
13
13
  else
14
14
  @title = File.basename(path)
15
15
  end
@@ -24,10 +24,6 @@ module Alfred
24
24
  super @title, opts
25
25
  end
26
26
 
27
- def match?(query)
28
- all_title_match?(query)
29
- end
30
-
31
27
  end
32
28
  end
33
29
  end
@@ -63,6 +63,13 @@ module Alfred
63
63
  send(@matcher, query)
64
64
  end
65
65
 
66
+ #
67
+ # Matchers
68
+ #
69
+ def always_match?(query)
70
+ true
71
+ end
72
+
66
73
  def title_match?(query)
67
74
  return true if query.empty?
68
75
  if smartcase_query(query).match(@title)
@@ -125,6 +132,9 @@ module Alfred
125
132
 
126
133
  protected
127
134
 
135
+ #
136
+ # Regex helpers
137
+ #
128
138
  def build_regexp(query, option)
129
139
  begin
130
140
  Regexp.compile(".*#{query.gsub(/\s+/,'.*')}.*", option)
@@ -8,7 +8,7 @@ module Alfred
8
8
  def initialize(title, opts = {})
9
9
  unless File.exist? opts[:webloc]
10
10
  opts[:webloc] = ::Alfred::Util.make_webloc(
11
- opts[:title], opts[:url], File.dirname(opts[:webloc]))
11
+ opts[:title], opts[:url], opts[:folder])
12
12
  end
13
13
 
14
14
  @subtitle = opts[:url]
@@ -0,0 +1,133 @@
1
+ require 'alfred/util'
2
+
3
+ require 'set'
4
+ require "rexml/document"
5
+
6
+ module Alfred
7
+
8
+ module Handler
9
+ class Base
10
+ Base_Invoke_Order = 100
11
+
12
+ attr_reader :status, :order
13
+
14
+ def initialize(alfred, opts = {})
15
+ @core = alfred
16
+ @order = Base_Invoke_Order
17
+ @status = :initialize
18
+ end
19
+
20
+
21
+ def on_parser
22
+ ;
23
+ end
24
+
25
+ def on_help
26
+ []
27
+ end
28
+
29
+ def feedback?
30
+ true
31
+ end
32
+ def on_feedback
33
+ raise NotImplementedError
34
+ end
35
+
36
+ def action?(arg)
37
+ arg.is_a?(Hash) && arg[:handler].eql?(@settings[:handler])
38
+ end
39
+
40
+ def on_action(arg)
41
+ ;
42
+ end
43
+
44
+ def on_close
45
+ ;
46
+ end
47
+
48
+ def register
49
+ @core.handler_controller.register(self)
50
+ end
51
+
52
+ def <=>(other)
53
+ order <=> other.order
54
+ end
55
+
56
+
57
+ def status_message(text, exitstatus)
58
+ if exitstatus == 0
59
+ return "⭕ #{text}"
60
+ else
61
+ return "❌ #{text}"
62
+ end
63
+ end
64
+
65
+
66
+ # from alfred core
67
+ def xml_builder(arg)
68
+ @core.xml_builder(arg)
69
+ end
70
+
71
+ def options
72
+ @core.options
73
+ end
74
+
75
+ def parser
76
+ @core.query_parser
77
+ end
78
+
79
+ def query
80
+ @core.query
81
+ end
82
+
83
+ def ui
84
+ @core.ui
85
+ end
86
+
87
+ def feedback
88
+ @core.feedback
89
+ end
90
+ end
91
+
92
+
93
+ class Controller
94
+ ## handlers are called based on handler.order
95
+ # 1-10 : critical handler
96
+ # 100 : base order
97
+
98
+ include Enumerable
99
+
100
+ def initialize
101
+ @handlers = SortedSet.new
102
+ @status = {:break => [:break, :exclusive]}
103
+ end
104
+
105
+ def register(handler)
106
+ raise InvalidArgument unless handler.is_a? ::Alfred::Handler::Base
107
+ @handlers.add(handler)
108
+ end
109
+
110
+ def empty?
111
+ @handlers.empty?
112
+ end
113
+
114
+ def each
115
+ return enum_for(__method__) unless block_given?
116
+
117
+ @handlers.each do |h|
118
+ yield(h)
119
+ end
120
+ end
121
+
122
+ def each_handler
123
+ return enum_for(__method__) unless block_given?
124
+
125
+ @handlers.each do |h|
126
+ yield(h)
127
+ break if @status[:break].include?(h.status)
128
+ end
129
+ end
130
+
131
+ end
132
+ end
133
+ end
@@ -0,0 +1,86 @@
1
+ require 'alfred/handler'
2
+ require 'fuzzy_match'
3
+ require 'amatch'
4
+
5
+ module Alfred
6
+ module Handler
7
+
8
+ class Autocomplete < Base
9
+ def initialize(alfred, opts = {})
10
+ super
11
+ @settings = {
12
+ :handler => 'Autocomplete' ,
13
+ :items => {} ,
14
+ :fuzzy_score => 0.5 ,
15
+ }.update(opts)
16
+
17
+ if @settings[:items].empty?
18
+ @load_from_workflow_setting = true
19
+ else
20
+ @load_from_workflow_setting = false
21
+ end
22
+ FuzzyMatch.engine = :amatch
23
+ end
24
+
25
+
26
+
27
+ def on_feedback
28
+ if @load_from_workflow_setting
29
+ @settings[:items].merge! @core.workflow_setting[:autocomplete]
30
+ end
31
+
32
+ before, option, tail = @core.last_option
33
+
34
+ base_item ={
35
+ :match? => :always_match? ,
36
+ :subtitle => "↩ to autocomplete" ,
37
+ :valid => 'no' ,
38
+ :icon => ::Alfred::Feedback.CoreServicesIcon('ForwardArrowIcon') ,
39
+ }
40
+
41
+ if @settings[:items].has_key? tail
42
+ unify_items(@settings[:items][tail]).each do |item|
43
+ base_item[:autocomplete] = "#{(before + [tail, item[:complete]]).join(' ')} "
44
+ feedback.add_item(base_item.update(item))
45
+ end
46
+ else
47
+ add_fuzzy_match_feedback(unify_items(@settings[:items][option]),
48
+ before, tail, base_item, feedback)
49
+ end
50
+ end
51
+
52
+
53
+ def add_fuzzy_match_feedback(items, before, query, base_item, to_feedback)
54
+ matcher = FuzzyMatch.new(items, :read => :complete)
55
+ matcher.find_all_with_score(query).each do |item, dice_similar, leven_similar|
56
+ next if item[:complete].size < query.size
57
+
58
+ if (item[:complete].start_with?(query) or
59
+ dice_similar > @settings[:fuzzy_score] or
60
+ leven_similar > @settings[:fuzzy_score])
61
+
62
+ base_item[:autocomplete] = "#{(before + [item[:complete]]).join(' ')} "
63
+ to_feedback.add_item(base_item.update(item))
64
+ end
65
+ end
66
+ end
67
+
68
+ def unify_items(items)
69
+ return [] unless items
70
+ items.map do |item|
71
+ if item.is_a? String
72
+ {:title => item, :complete => item}
73
+ elsif item.is_a? Hash
74
+ unless item.has_key? :complete
75
+ item[:complete] = item[:title]
76
+ end
77
+ item
78
+ else
79
+ raise InvalidArgument, "autocomplete handler can only accept string or hash"
80
+ end
81
+ end
82
+ end
83
+
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,130 @@
1
+ require 'moneta'
2
+ require 'alfred/util'
3
+
4
+ #
5
+ # = Alfred Callback Hander
6
+ #
7
+ # Each callback is stored using Moneta via YAML backend.!
8
+ #
9
+ # == Example:
10
+ # Suppose we have a callback with key "demo"
11
+ #
12
+ # - @backend[ENTRIES_KEY] => {
13
+ # 'demo' => {:key => 'demo', :title => 'title', :subtitle => ...}
14
+ # }
15
+ #
16
+ # - @backend['demo'] => the feedback items
17
+ #
18
+ module Alfred::Handler
19
+
20
+ class Callback < Base
21
+ ENTRIES_KEY = 'feedback_entries'
22
+
23
+ def initialize(alfred, opts = {})
24
+ super
25
+ @settings = {
26
+ :handler => 'Callback' ,
27
+ :exclusive? => true ,
28
+ :backend_dir => @core.volatile_storage_path ,
29
+ :backend_file => 'callback.yaml' ,
30
+ :handler_order => ( Base_Invoke_Order / 12 )
31
+ }.update(opts)
32
+
33
+ @order = @settings[:handler_order]
34
+ end
35
+
36
+
37
+ def on_parser
38
+ parser.on("--callback [CALLBACK]", "Alfred callback feedback") do |v|
39
+ options.callback = v || ''
40
+ end
41
+ end
42
+
43
+ def feedback?
44
+ options.callback
45
+ end
46
+
47
+ def on_feedback
48
+ return unless feedback?
49
+ if entries[options.callback]
50
+ feedback.merge! backend[options.callback]
51
+ @status = :exclusive if @settings[:exclusive?]
52
+
53
+ elsif entries.empty?
54
+ # show a warn feedback item
55
+ feedback.add_item(
56
+ {
57
+ :title => 'No available callback!' ,
58
+ :valid => 'no' ,
59
+ :autocomplete => '' ,
60
+ :subtitle => 'Please check it later. Background task may still be running.',
61
+ :icon => ::Alfred::Feedback.CoreServicesIcon('Unsupported') ,
62
+ }
63
+ )
64
+ else
65
+ # list available callbacks
66
+ entries.each do |key, entry|
67
+ feedback.add_item(
68
+ {
69
+ :title => "Feedback Callback: #{key}" ,
70
+ :subtitle => "#{entry[:timestamp]}",
71
+ :valid => 'no' ,
72
+ :autocomplete => "--callback '#{key}'" ,
73
+ :icon => ::Alfred::Feedback.CoreServicesIcon('AliasBadgeIcon') ,
74
+ }.merge(entry)
75
+ )
76
+ end
77
+ @status = :exclusive if @settings[:exclusive?]
78
+ end
79
+ end
80
+
81
+
82
+ def on_close
83
+ backend.close
84
+ end
85
+
86
+
87
+ def on_callback(keyword, entry, feedback_items)
88
+ add_entry(entry, feedback_items)
89
+ Alfred::Util.notify("#{keyword} --callback '#{entry[:key]}'",
90
+ entry[:title] || entry[:key],
91
+ entry)
92
+ end
93
+
94
+
95
+ def add_entry(entry, feedback_items)
96
+ entry.merge!(:timestamp => Time.now)
97
+ key = entry[:key]
98
+ new_entries = entries.merge(key => entry)
99
+ backend[ENTRIES_KEY] = new_entries
100
+ backend[key] = feedback_items
101
+ end
102
+
103
+ def remove_entry(key)
104
+ new_entries = entries.delete(key)
105
+ backend[ENTRIES_KEY] = new_entries
106
+ backend.delete(key)
107
+ end
108
+
109
+
110
+
111
+ def entries
112
+ backend[ENTRIES_KEY]
113
+ end
114
+
115
+
116
+ def backend
117
+ @backend ||= Moneta.new(:YAML,
118
+ :file => File.join(@settings[:backend_dir],
119
+ @settings[:backend_file]))
120
+
121
+ unless @backend.key?(ENTRIES_KEY)
122
+ @backend[ENTRIES_KEY] = {}
123
+ end
124
+ @backend
125
+ end
126
+
127
+ private
128
+
129
+ end
130
+ end
@@ -0,0 +1,33 @@
1
+ module Alfred::Handler
2
+
3
+ class Config < Base
4
+ def initialize(alfred, opts = {})
5
+ super
6
+ @order = 20
7
+ @settings = {
8
+ :setting => alfred.workflow_setting ,
9
+ :break? => true ,
10
+ :handler => 'Config'
11
+ }.update(opts)
12
+
13
+ end
14
+
15
+ def on_parser
16
+ opts.on("-c", "--config CONFIG", "Config Workflow Settings") do |v|
17
+ options.config = v
18
+ end
19
+ end
20
+
21
+ def on_help
22
+ {
23
+ :kind => 'text' ,
24
+ :title => '-c, --config [query]' ,
25
+ :subtitle => 'Config Workflow Settings' ,
26
+ }
27
+ end
28
+
29
+ def on_feedback
30
+ end
31
+
32
+ end
33
+ end
@@ -0,0 +1,162 @@
1
+ require 'alfred/handler'
2
+
3
+
4
+ module Alfred
5
+ module Handler
6
+
7
+ class HelpItem < ::Hash
8
+ Base_Order = 10
9
+ def initialize(attributes = {}, &block)
10
+ super(&block)
11
+ initialize_attributes(attributes)
12
+ end
13
+
14
+ def <=>(other)
15
+ self[:order] <=> other[:order]
16
+ end
17
+
18
+ private
19
+
20
+ def initialize_attributes(attributes)
21
+ attributes.each_pair do |att, value|
22
+ self[att] = value
23
+ end if attributes
24
+ self[:order] = Base_Order unless self[:order]
25
+ end
26
+ end
27
+
28
+
29
+ class Help < Base
30
+ def initialize(alfred, opts = {})
31
+ super
32
+ @settings = {
33
+ :handler => 'Help' ,
34
+ :exclusive? => true ,
35
+ :with_handler_help => true ,
36
+ :items => [] ,
37
+ :handler_order => ( Base_Invoke_Order / 10 )
38
+ }.update(opts)
39
+
40
+ @order = @settings[:handler_order]
41
+
42
+ if @settings[:items].empty?
43
+ @load_from_workflow_setting = true
44
+ else
45
+ @load_from_workflow_setting = false
46
+ end
47
+
48
+ end
49
+
50
+ def on_parser
51
+ parser.on_tail('-?', '-h', '--help', 'Workflow Helper') do
52
+ options.help = true
53
+ end
54
+ end
55
+
56
+ def on_help
57
+ {
58
+ :kind => 'text' ,
59
+ :valid => 'no' ,
60
+ :autocomplete => '-h' ,
61
+ :match? => :always_match? ,
62
+ :order => (HelpItem::Base_Order * 12) ,
63
+ :title => '-?, -h, --help [Show Workflow Usage Help]' ,
64
+ :subtitle => 'Other feedbacks are blocked.' ,
65
+ }
66
+ end
67
+
68
+ def feedback?
69
+ options.help
70
+ end
71
+
72
+ def on_feedback
73
+ return unless feedback?
74
+
75
+ if @settings[:with_handler_help]
76
+ @settings[:items].push @core.on_help
77
+ @core.handler_controller.each do |h|
78
+ @settings[:items].push h.on_help
79
+ end
80
+ end
81
+
82
+ if @load_from_workflow_setting
83
+ if @core.workflow_setting.has_key?(:help)
84
+ @settings[:items].push @core.workflow_setting[:help]
85
+ end
86
+ end
87
+
88
+ @settings[:items].flatten!.compact!
89
+ @settings[:items].map! { |i| HelpItem.new(i) }.sort!
90
+
91
+ @settings[:items].each do |item|
92
+
93
+ case item[:kind]
94
+ when 'file'
95
+ item[:path] = File.expand_path(item[:path])
96
+ # action is handled by fallback action in the main loop
97
+ feedback.add_file_item(item[:path], item)
98
+ when 'url'
99
+ item[:arg] = xml_builder(
100
+ :handler => @settings[:handler] ,
101
+ :kind => item[:kind] ,
102
+ :url => item[:url]
103
+ )
104
+
105
+ feedback.add_item(
106
+ {
107
+ :icon => ::Alfred::Feedback.CoreServicesIcon('BookmarkIcon')
108
+ }.merge(item)
109
+ )
110
+
111
+ when 'text', 'message'
112
+ item[:arg] = xml_builder(
113
+ {
114
+ :handler => @settings[:handler] ,
115
+ :kind => item[:kind] ,
116
+ }
117
+ )
118
+
119
+ feedback.add_item(
120
+ {
121
+ :valid => 'no' ,
122
+ :autocomplete => '' ,
123
+ :icon => ::Alfred::Feedback.CoreServicesIcon('ClippingText') ,
124
+ }.merge(item)
125
+ )
126
+
127
+ else
128
+ if item.has_key? :title
129
+ item[:arg] = xml_builder(
130
+ {
131
+ :handler => @settings[:handler] ,
132
+ :kind => item[:kind] ,
133
+ }.merge(item)
134
+ )
135
+
136
+ feedback.add_item(
137
+ {
138
+ :icon => ::Alfred::Feedback.CoreServicesIcon('HelpIcon'),
139
+ }.merge(item)
140
+ )
141
+ end
142
+ end
143
+ end
144
+
145
+ @status = :exclusive if @settings[:exclusive?]
146
+ end
147
+
148
+
149
+ def on_action(arg)
150
+ return unless action?(arg)
151
+
152
+ case arg[:kind]
153
+ when 'url'
154
+ ::Alfred::Util.open_url(arg[:url])
155
+ when 'file'
156
+ %x{open "#{arg[:path]}"}
157
+ end
158
+ end
159
+ end
160
+
161
+ end
162
+ end