alfred-workflow 1.11.3 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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