bunchcli 1.1.12 → 1.1.14

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1eae5ef196f082cf2f09b09b42f91f5bfa2c87f023337483064549907aeb4c47
4
- data.tar.gz: 76a96e232d5d94b12af6073a6c71d5dcd6f652cdeda6a5dad309b903f687deb1
3
+ metadata.gz: 3e569677c996de604f94399e8006421686b41ccbbaac882a1546d4d14a9d071c
4
+ data.tar.gz: eabee141d2bbc1dafc8014d6b6e30ef1bada5fc9ec15846509e2cca4c4a788e5
5
5
  SHA512:
6
- metadata.gz: dce1d77092144c64ff53ad79dbfbf41ce4f669428b9ab9fbf4f9f751e9eb3e538037bf66ad97e50cce88ff95125d5768707f3617a28c4acdb577f9c9987063b4
7
- data.tar.gz: 6a0f47f065fc2d3f1bdf8a562957e9c1bc31b035698abc263892a02877974b773e16694dfb2320a815170fa6aed267bfde581ed64da84653a2d78ff2d8da7dac
6
+ metadata.gz: 5404b8ca066959471a44057905bd1c3d379a6c23b464745b2a061ec93587a8ea6e92a006a7ce8d10bf6704b52bf8e8c9a2f970c8f0b6a9eb6fcc1c7ee22abbea
7
+ data.tar.gz: 89995c81e7256502f74aef2652076ffb6a29266353bdb89788ff568cd27b72410e2aff485a4bb174b8b4d57a2687d3627caa884ebe83fb26ca7677afd9cf725d
data/AUTHORS ADDED
@@ -0,0 +1 @@
1
+ Brett Terpstra <me@brettterpstra.com>
data/CHANGELOG.md CHANGED
@@ -1,3 +1,8 @@
1
+ ### 1.1.13
2
+
3
+ - Sort Bunches when listing and searching
4
+ - Execute shortest match rather than first match
5
+
1
6
  ### 1.1.11pre
2
7
 
3
8
  - Fix for Terminal when checking for bundle id
data/bin/bunch CHANGED
@@ -1,11 +1,19 @@
1
- #!/usr/bin/env ruby
1
+ #!/usr/bin/env ruby -W1
2
+ # frozen_string_literal: true
2
3
 
3
4
  require 'optparse'
4
5
  require 'bunch'
5
6
 
7
+ def app_running?(app)
8
+ !`ps ax|grep -i "#{app}.app"|grep -v grep`.empty?
9
+ end
10
+
11
+ TARGET_APP = app_running?('Bunch Beta') ? 'Bunch Beta' : 'Bunch'
12
+ TARGET_URL = TARGET_APP == 'Bunch Beta' ? 'x-bunch-beta' : 'x-bunch'
13
+
6
14
  def help
7
15
  puts "\nUsage: #{File.basename(__FILE__)} [options] BUNCH_NAME|PATH_TO_FILE"
8
- puts "\nBunch names are case insensitive and will execute first match"
16
+ puts "\nBunch names are case insensitive and will execute shortest match"
9
17
  puts "Use 'bunch -h' to display options"
10
18
  end
11
19
 
@@ -52,20 +60,20 @@ optparse = OptionParser.new do |opts|
52
60
  bunch.variables = opt
53
61
  end
54
62
 
55
- opts.on('--pref', 'Set a preference. Run without argument to list available preferences.') do |opt|
63
+ opts.on('--pref', 'Set a preference. Run without argument to list available preferences.') do
56
64
  bunch.url_method = 'setPref'
57
65
  end
58
66
 
59
- opts.on('-u', '--url', 'Output URL instead of opening') do |_opt|
67
+ opts.on('-u', '--url', 'Output URL instead of opening') do
60
68
  bunch.show_url = true
61
69
  end
62
70
 
63
- opts.on('-i','--interactive', 'Interactively generate a Bunch url') do |opt|
71
+ opts.on('-i', '--interactive', 'Interactively generate a Bunch url') do
64
72
  BunchURLGenerator.new.generate
65
73
  Process.exit 0
66
74
  end
67
75
 
68
- opts.on('--show-config', 'Display all configuration values') do |opt|
76
+ opts.on('--show-config', 'Display all configuration values') do
69
77
  bunch.show_config
70
78
  Process.exit 0
71
79
  end
@@ -75,8 +83,10 @@ optparse = OptionParser.new do |opts|
75
83
  Process.exit 0
76
84
  end
77
85
 
78
- opts.on('-f', '--force-refresh', 'Force refresh cached preferences') do |opt|
86
+ opts.on('-f', '--force-refresh', 'Force refresh cached preferences') do
79
87
  bunch.update_cache
88
+ warn 'Cache refreshed'
89
+ Process.exit 0
80
90
  end
81
91
 
82
92
  opts.on('-h', '--help', 'Display this screen') do |_opt|
@@ -93,14 +103,14 @@ end
93
103
 
94
104
  optparse.parse!
95
105
 
96
- unless ARGV.length > 0
97
- if STDIN.stat.size > 0
106
+ if ARGV.empty?
107
+ if $stdin.stat.size.positive?
98
108
  bunch.url_method = 'raw'
99
- bunch.open(CGI.escape(STDIN.read))
109
+ bunch.open(CGI.escape($stdin.read))
100
110
  elsif bunch.url_method == 'setPref'
101
111
  bunch.list_preferences
102
112
  else
103
- puts "CLI for Bunches.app"
113
+ puts 'CLI for Bunches.app'
104
114
  help
105
115
  end
106
116
  else
@@ -1,3 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Main Bunch CLI Class
1
4
  class Bunch
2
5
  include Util
3
6
  attr_writer :url_method, :fragment, :variables, :show_url
@@ -15,10 +18,10 @@ class Bunch
15
18
 
16
19
  def launch_if_needed
17
20
  pid = `ps ax | grep 'MacOS/Bunch'|grep -v grep`.strip
18
- if pid == ""
19
- `open -a Bunch`
20
- sleep 2
21
- end
21
+ return unless pid == ''
22
+
23
+ `open -a Bunch`
24
+ sleep 2
22
25
  end
23
26
 
24
27
  def update_cache
@@ -32,20 +35,19 @@ class Bunch
32
35
  'bunches' => bunches,
33
36
  'updated' => Time.now.strftime('%s').to_i
34
37
  }
35
- File.open(target,'w') do |f|
38
+ File.open(target, 'w') do |f|
36
39
  f.puts YAML.dump(settings)
37
40
  end
38
- return settings
41
+
42
+ settings
39
43
  end
40
44
 
41
45
  def get_cache
42
46
  target = File.expand_path(CACHE_FILE)
43
- if File.exists?(target)
47
+ if File.exist?(target)
44
48
  settings = YAML.load(IO.read(target))
45
49
  now = Time.now.strftime('%s').to_i
46
- if now - settings['updated'].to_i > CACHE_TIME
47
- settings = update_cache
48
- end
50
+ settings = update_cache if now - settings['updated'].to_i > CACHE_TIME
49
51
  else
50
52
  settings = update_cache
51
53
  end
@@ -55,14 +57,14 @@ class Bunch
55
57
  end
56
58
 
57
59
  def variable_query
58
- vars = @variables.split(/,/).map { |v| v.strip }
60
+ vars = @variables.split(/,/).map(&:strip)
59
61
  query = []
60
- vars.each { |v|
61
- parts = v.split(/=/).map { |v| v.strip }
62
+ vars.each do |v|
63
+ parts = v.split(/=/).map(&:strip)
62
64
  k = parts[0]
63
65
  v = parts[1]
64
66
  query << "#{k}=#{CGI.escape(v)}"
65
- }
67
+ end
66
68
  query
67
69
  end
68
70
 
@@ -72,11 +74,11 @@ class Bunch
72
74
  `osascript -e 'tell app "#{TARGET_APP}" to list bunches'`.strip.split(/,/).each do |b|
73
75
  b.strip!
74
76
  items.push(
75
- path: File.join(bunch_dir, b + '.bunch'),
77
+ path: File.join(bunch_dir, "#{b}.bunch"),
76
78
  title: b
77
79
  )
78
80
  end
79
- items
81
+ items.sort_by { |b| b[:title].downcase }
80
82
  end
81
83
 
82
84
  def bunch_dir
@@ -97,14 +99,16 @@ class Bunch
97
99
  end
98
100
 
99
101
  def url(bunch)
102
+ bunch = CGI.escape(bunch).gsub(/\+/, '%20')
100
103
  params = "&x-success=#{@success}" if @success
101
- if url_method == 'file'
104
+ case url_method
105
+ when /file/
102
106
  %(#{TARGET_URL}://raw?file=#{bunch}#{params})
103
- elsif url_method == 'raw'
107
+ when /raw/
104
108
  %(#{TARGET_URL}://raw?txt=#{bunch}#{params})
105
- elsif url_method == 'snippet'
109
+ when /snippet/
106
110
  %(#{TARGET_URL}://snippet?file=#{bunch}#{params})
107
- elsif url_method == 'setPref'
111
+ when /setPref/
108
112
  %(#{TARGET_URL}://setPref?#{bunch})
109
113
  else
110
114
  %(#{TARGET_URL}://#{url_method}?bunch=#{bunch}#{params})
@@ -122,30 +126,24 @@ class Bunch
122
126
  end
123
127
 
124
128
  def find_bunch(str)
125
- found_bunch = false
129
+ matches = []
126
130
 
127
- bunches.each do |bunch|
128
- if bunch[:title].downcase =~ /.*?#{str}.*?/i
129
- found_bunch = bunch
130
- break
131
- end
132
- end
133
- found_bunch
131
+ bunches.each { |bunch| matches.push(bunch) if bunch[:title].downcase =~ /.*?#{str}.*?/i }
132
+ matches.min_by(&:length)
134
133
  end
135
134
 
136
135
  def human_action
137
- (url_method.gsub(/e$/, '') + 'ing').capitalize
136
+ "#{url_method.gsub(/e$/, '')}ing".capitalize
138
137
  end
139
138
 
140
139
  def list_preferences
141
- prefs =<<EOF
142
- toggleBunches=[0,1] Allow Bunches to be both opened and closed
143
- configDir=[path] Absolute path to Bunches folder
144
- singleBunchMode=[0,1] Close open Bunch when opening new one
145
- preserveOpenBunches=[0,1] Restore Open Bunches on Launch
146
- debugLevel=[0-4] Set the logging level for the Bunch Log
147
- EOF
148
- puts prefs
140
+ puts <<~EOHELP
141
+ toggleBunches=[0,1] Allow Bunches to be both opened and closed
142
+ configDir=[path] Absolute path to Bunches folder
143
+ singleBunchMode=[0,1] Close open Bunch when opening new one
144
+ preserveOpenBunches=[0,1] Restore Open Bunches on Launch
145
+ debugLevel=[0-4] Set the logging level for the Bunch Log
146
+ EOHELP
149
147
  end
150
148
 
151
149
 
@@ -153,39 +151,40 @@ EOF
153
151
  launch_if_needed
154
152
  # get front app
155
153
  front_app = %x{osascript -e 'tell application "System Events" to return name of first application process whose frontmost is true'}.strip
156
- bid = bundle_id(front_app) rescue false
157
- @success = bid if (bid)
154
+ bid = bundle_id(front_app) || false
155
+ @success = bid if bid
158
156
 
159
- if @url_method == 'raw'
157
+ case @url_method
158
+ when /raw/
160
159
  warn 'Running raw string'
161
160
  if @show_url
162
161
  $stdout.puts url(str)
163
162
  else
164
163
  `open '#{url(str)}'`
165
164
  end
166
- elsif @url_method == 'snippet'
167
- _url = url(str)
165
+ when /snippet/
166
+ this_url = url(str)
168
167
  params = []
169
168
  params << "fragment=#{CGI.escape(@fragment)}" if @fragment
170
169
  params.concat(variable_query) if @variables
171
- _url += '&' + params.join('&')
170
+ this_url += "&#{params.join('&')}" if params.length.positive?
172
171
  if @show_url
173
- $stdout.puts _url
172
+ $stdout.puts this_url
174
173
  else
175
- warn "Opening snippet"
176
- `open '#{_url}'`
174
+ warn 'Opening snippet'
175
+ `open '#{this_url}'`
177
176
  end
178
- elsif @url_method == 'setPref'
177
+ when /setPref/
179
178
  if str =~ /^(\w+)=([^= ]+)$/
180
- _url = url(str)
179
+ this_url = url(str)
181
180
  if @show_url
182
- $stdout.puts _url
181
+ $stdout.puts this_url
183
182
  else
184
183
  warn "Setting preference #{str}"
185
- `open '#{_url}'`
184
+ `open '#{this_url}'`
186
185
  end
187
186
  else
188
- warn "Invalid key=value pair"
187
+ warn 'Invalid key=value pair'
189
188
  Process.exit 1
190
189
  end
191
190
  else
@@ -193,30 +192,28 @@ EOF
193
192
  params = []
194
193
  params << "fragment=#{CGI.escape(@fragment)}" if @fragment
195
194
  params.concat(variable_query) if @variables
196
- unless bunch
197
- if File.exists?(str)
198
- @url_method = 'file'
199
- _url = url(str)
200
- _url += '&' + params.join('&') if params.length
201
- if @show_url
202
- $stdout.puts _url
203
- else
204
- warn "Opening file"
205
- `open '#{_url}'`
206
- end
195
+ if bunch
196
+ this_url = url(bunch[:title])
197
+ this_url += "&#{params.join('&')}" if params.length
198
+ if @show_url
199
+ $stdout.puts this_url
207
200
  else
208
- warn 'No matching Bunch found'
209
- Process.exit 1
201
+ warn "#{human_action} #{bunch[:title]}"
202
+ `open '#{this_url}'`
210
203
  end
211
- else
212
- _url = url(bunch[:title])
213
- _url += '&' + params.join('&') if params.length
204
+ elsif File.exist?(str)
205
+ @url_method = 'file'
206
+ this_url = url(str)
207
+ this_url += "&#{params.join('&')}" if params.length
214
208
  if @show_url
215
- $stdout.puts _url
209
+ $stdout.puts this_url
216
210
  else
217
- warn "#{human_action} #{bunch[:title]}"
218
- `open '#{_url}'`
211
+ warn 'Opening file'
212
+ `open '#{this_url}'`
219
213
  end
214
+ else
215
+ warn 'No matching Bunch found'
216
+ Process.exit 1
220
217
  end
221
218
  end
222
219
  # attempt to restore front app
@@ -229,7 +226,7 @@ EOF
229
226
  puts output
230
227
  end
231
228
 
232
- def show_config(key=nil)
229
+ def show_config(key = nil)
233
230
  case key
234
231
  when /(folder|dir)/
235
232
  puts bunch_dir
@@ -240,10 +237,8 @@ EOF
240
237
  else
241
238
  puts "Bunches Folder: #{bunch_dir}"
242
239
  puts "Default URL Method: #{url_method}"
243
- puts "Cached Bunches"
244
- bunches.each {|b|
245
- puts " - #{b[:title]}"
246
- }
240
+ puts 'Cached Bunches'
241
+ bunches.each { |b| puts " - #{b[:title]}" }
247
242
  end
248
243
  end
249
244
  end
@@ -15,25 +15,31 @@ end
15
15
  module Util
16
16
  def bundle_id(app)
17
17
  shortname = app.sub(/\.app$/, '')
18
- apps = `mdfind -onlyin /Applications -onlyin /Applications/Setapp -onlyin /Applications/Utilities -onlyin ~/Applications -onlyin /Developer/Applications -onlyin /System/Applications 'kMDItemKind==Application'`
18
+ app_dirs = [
19
+ '/Applications',
20
+ '/Applications/Setapp',
21
+ '/Applications/Utilities',
22
+ '~/Applications',
23
+ '/Developer/Applications',
24
+ '/System/Applications'
25
+ ]
26
+ only_in = app_dirs.map { |dir| "-onlyin #{dir}" }.join(' ')
27
+ apps = `mdfind #{only_in} 'kMDItemKind==Application'`
19
28
 
20
- return false if !apps || apps.strip.length == 0
29
+ return false if !apps || apps.strip.empty?
21
30
 
22
31
  foundapps = apps.split(/\n/).select! { |line| line.chomp =~ /#{shortname}\.app$/i }
23
32
 
24
- if foundapps.length > 0
25
- foundapp = foundapps[0]
26
- else
27
- return false
28
- end
33
+ return false if foundapps.empty?
34
+
35
+ foundapp = foundapps[0]
29
36
 
30
37
  if foundapp
31
- bid = `mdls -name kMDItemCFBundleIdentifier -r "#{foundapp}"`.chomp
38
+ `mdls -name kMDItemCFBundleIdentifier -r "#{foundapp}"`.chomp
32
39
  else
33
40
  # warn "Could not locate bundle id for #{shortname}, using provided app name"
34
- bid = app
41
+ app
35
42
  end
36
- bid
37
43
  end
38
44
  end
39
45
 
@@ -80,10 +86,43 @@ module Prompt
80
86
  lines.join("\n").chomp
81
87
  end
82
88
 
89
+ def yn(question, default_response: false)
90
+ default = default_response || 'n'
91
+
92
+ # if this isn't an interactive shell, answer default
93
+ return default.downcase == 'y' unless $stdout.isatty
94
+
95
+ # clear the buffer
96
+ if ARGV&.length
97
+ ARGV.length.times do
98
+ ARGV.shift
99
+ end
100
+ end
101
+ system 'stty cbreak'
102
+
103
+ options = if default
104
+ default =~ /y/i ? '[Y/n]' : '[y/N]'
105
+ else
106
+ '[y/n]'
107
+ end
108
+
109
+ $stdout.syswrite "#{question.sub(/\?$/, '')} #{options}? "
110
+ res = $stdin.sysread 1
111
+ puts
112
+ system 'stty cooked'
113
+
114
+ res.chomp!
115
+ res.downcase!
116
+
117
+ res = default.downcase if res == ''
118
+
119
+ res =~ /y/i
120
+ end
121
+
83
122
  def url_encode_text
84
123
  text = get_text
85
124
  puts
86
- CGI.escape(text)
125
+ CGI.escape(text).gsub(/\+/, '%20')
87
126
  end
88
127
  end
89
128
 
@@ -109,17 +148,17 @@ class Menu
109
148
 
110
149
  def choose(query = 'Select an item')
111
150
  throw 'No items initialized' if @items.nil?
112
- STDERR.puts
113
- STDERR.puts "┌#{("" * 74)}┐"
114
- intpad = Math::log10(@items.length).to_i + 1
151
+ $stderr.puts
152
+ warn "┌#{'' * 74}┐"
153
+ intpad = Math.log10(@items.length).to_i + 1
115
154
  @items.each_with_index do |item, idx|
116
- idxstr = "%#{intpad}d" % (idx + 1)
155
+ idxstr = format("%#{intpad}d", idx + 1)
117
156
  line = "#{idxstr}: #{item.title}"
118
157
  pad = 74 - line.length
119
- STDERR.puts "│#{line}#{" " * pad}│"
158
+ warn "│#{line}#{' ' * pad}│"
120
159
  end
121
- STDERR.puts "└┤ #{query} ├#{"" * (70 - query.length)}┘"
122
- sel = choose_number("> ", @items.length)
160
+ warn "└┤ #{query} ├#{'' * (70 - query.length)}┘"
161
+ sel = choose_number('> ', @items.length)
123
162
  sel ? @items[sel.to_i - 1] : nil
124
163
  end
125
164
  end
@@ -130,13 +169,13 @@ class Snippet
130
169
  def initialize(file)
131
170
  if File.exist?(File.expand_path(file))
132
171
  @contents = IO.read(File.expand_path(file))
133
- @fragments = fragments
172
+ @fragments = find_fragments
134
173
  else
135
- throw ('Tried to initialize snippet with invalid file')
174
+ throw 'Tried to initialize snippet with invalid file'
136
175
  end
137
176
  end
138
177
 
139
- def fragments
178
+ def find_fragments
140
179
  rx = /(?i-m)(?:[-#]+)\[([\s\S]*?)\][-# ]*\n([\s\S]*?)(?=\n(?:-+\[|#+\[|$))/
141
180
  matches = @contents.scan(rx)
142
181
  fragments = {}
@@ -167,6 +206,7 @@ class BunchFinder
167
206
 
168
207
  def initialize
169
208
  config_dir = `osascript -e 'tell app "#{TARGET_APP}" to get preference "Folder"'`.strip
209
+ config_dir.sub!(%r{^file://}, '')
170
210
  config_dir = File.expand_path(config_dir)
171
211
  if File.directory?(config_dir)
172
212
  @config_dir = config_dir
@@ -177,10 +217,8 @@ class BunchFinder
177
217
 
178
218
  def bunches_to_items
179
219
  items = []
180
- `osascript -e 'tell app "#{TARGET_APP}" to list bunches'`.strip.split(/,/).each do |b|
181
- filename = b.strip
182
- items << MenuItem.new(filename, filename, filename)
183
- end
220
+ bunches = `osascript -e 'tell app "#{TARGET_APP}" to list bunches'`.strip.split(/,/).map(&:strip)
221
+ bunches.sort_by(&:downcase).each { |b| items << MenuItem.new(b, b, b) }
184
222
  items
185
223
  end
186
224
 
@@ -307,8 +345,15 @@ class BunchURLGenerator
307
345
  parameters << ['x-delay', delay.to_s] if delay =~ /^\d+$/
308
346
  end
309
347
 
310
- query_string = parameters.map { |param| "#{param[0]}=#{param[1]}" }.join('&')
348
+ query_string = parameters.map { |param| "#{param[0]}=#{param[1].gsub(/\+/, '%20')}" }.join('&')
349
+ full_url = "#{url}?#{query_string}".strip
311
350
 
312
- puts url + '?' + query_string
351
+ res = yn('Copy URL to clipboard')
352
+ if res
353
+ `echo '#{full_url}'|tr -d '\n'|pbcopy`
354
+ warn 'Copied to clipboard'
355
+ else
356
+ puts full_url
357
+ end
313
358
  end
314
359
  end
data/lib/bunch/version.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module BunchCLI
2
- VERSION = "1.1.12"
4
+ VERSION = '1.1.14'
3
5
  end
data/lib/bunch.rb CHANGED
@@ -1,10 +1,7 @@
1
1
  CACHE_TIME = 86400 #seconds, 1 day = 86400
2
2
  CACHE_FILE = "~/.bunch_cli_cache"
3
- TARGET_APP = "Bunch"
4
3
 
5
- TARGET_URL = TARGET_APP == 'Bunch Beta' ? 'x-bunch-beta' : 'x-bunch'
6
-
7
- require "bunch/version"
4
+ require 'bunch/version'
8
5
  require 'yaml'
9
6
  require 'cgi'
10
7
  require 'bunch/url_generator'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bunchcli
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.12
4
+ version: 1.1.14
5
5
  platform: ruby
6
6
  authors:
7
7
  - Brett Terpstra
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-06-22 00:00:00.000000000 Z
11
+ date: 2022-07-30 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description:
14
14
  email:
@@ -19,6 +19,7 @@ extensions: []
19
19
  extra_rdoc_files: []
20
20
  files:
21
21
  - ".gitignore"
22
+ - AUTHORS
22
23
  - CHANGELOG.md
23
24
  - Gemfile
24
25
  - LICENSE.txt