bunchcli 1.0.0 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: '064668e5dae28ec08fe4a939dc270e9b1880116f68f8ad39fc3c2252cd7609fa'
4
- data.tar.gz: be7be6b0cf5404ae193aec36d50b4efa4b6672b03be4e875f60cd89fcf3a07b9
3
+ metadata.gz: 010c20799d4e9e35409737ee3dfd5e3a755096ff212eb9a02787dfcee16dc5e2
4
+ data.tar.gz: daccb516131e6816f4ea5cd153e14df691e3089600e77114e1755cf502efe131
5
5
  SHA512:
6
- metadata.gz: 59abf612ddfe2eff6f5b54bf7952e08f67cad5bc7c2ec06ca9eb6d15aa097798c6bfaf6686292041fba6dd4fd34e3dc013ef3ffdff60c271a85b0fa99b64b144
7
- data.tar.gz: e888ed65aef4791420808cc5220f275c9e3c9c618ba931d273f4b118f3ffdcecb978b641e298ba0d18024758977ba3e883095daca71859af25e381291f903ce9
6
+ metadata.gz: b9f94711eadea1d3cf6cd3ebef2244663cc793b1dc3bd121ac2bb1409431c24215d03b172db13d76966143529c99578bfce000bebbba89dbbf16410d1653ab93
7
+ data.tar.gz: 2923722c6988c146be4eaa19b20b732413eb95fad52c890a0bd47165e50a61e576042d4934d376a6f5ed992723f5d843ee645b3eaa7eb10cdb677628f996ce99
@@ -0,0 +1,19 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ bunchcli (1.0.0)
5
+
6
+ GEM
7
+ remote: https://rubygems.org/
8
+ specs:
9
+ rake (12.3.3)
10
+
11
+ PLATFORMS
12
+ x86_64-darwin-19
13
+
14
+ DEPENDENCIES
15
+ bunchcli!
16
+ rake (~> 12.0)
17
+
18
+ BUNDLED WITH
19
+ 2.2.4
data/bin/bunch CHANGED
@@ -13,18 +13,13 @@ bunch = Bunch.new
13
13
  optparse = OptionParser.new do |opts|
14
14
  opts.banner = 'CLI for Bunch.app'
15
15
 
16
- opts.on('-h', '--help', 'Display this screen') do |_opt|
17
- puts opts
18
- help
16
+ opts.on('-l', '--list', 'List available Bunches') do |_opt|
17
+ bunch.list_bunches
19
18
  Process.exit 0
20
19
  end
21
20
 
22
- opts.on('-f', '--force-refresh', 'Force refresh cached preferences') do |opt|
23
- bunch.update_cache
24
- end
25
-
26
- opts.on('-l', '--list', 'List available Bunches') do |_opt|
27
- bunch.list_bunches
21
+ opts.on('-s', '--show BUNCH', 'Show contents of Bunch') do |opt|
22
+ bunch.show(opt)
28
23
  Process.exit 0
29
24
  end
30
25
 
@@ -40,8 +35,24 @@ optparse = OptionParser.new do |opts|
40
35
  bunch.url_method = 'toggle'
41
36
  end
42
37
 
43
- opts.on('-s', '--show BUNCH', 'Show contents of Bunch') do |opt|
44
- bunch.show(opt)
38
+ opts.on('--snippet', 'Load as snippet') do |opt|
39
+ bunch.url_method = 'snippet'
40
+ end
41
+
42
+ opts.on('--fragment=FRAGMENT', 'Run a specific section') do |opt|
43
+ bunch.fragment = opt
44
+ end
45
+
46
+ opts.on('--vars=VARS', 'Variables to pass to a snippet, comma-separated') do |opt|
47
+ bunch.variables = opt
48
+ end
49
+
50
+ opts.on('-u', '--url', 'Output URL instead of opening') do |_opt|
51
+ bunch.show_url = true
52
+ end
53
+
54
+ opts.on('-i','--interactive', 'Interactively generate a Bunch url') do |opt|
55
+ BunchURLGenerator.new.generate
45
56
  Process.exit 0
46
57
  end
47
58
 
@@ -49,6 +60,16 @@ optparse = OptionParser.new do |opts|
49
60
  bunch.show_config
50
61
  Process.exit 0
51
62
  end
63
+
64
+ opts.on('-f', '--force-refresh', 'Force refresh cached preferences') do |opt|
65
+ bunch.update_cache
66
+ end
67
+
68
+ opts.on('-h', '--help', 'Display this screen') do |_opt|
69
+ puts opts
70
+ help
71
+ Process.exit 0
72
+ end
52
73
  end
53
74
 
54
75
  optparse.parse!
@@ -4,4 +4,5 @@ CACHE_FILE = "~/.bunch_cli_cache"
4
4
  require "bunch/version"
5
5
  require 'yaml'
6
6
  require 'cgi'
7
+ require 'bunch/url_generator'
7
8
  require 'bunch/bunchCLI'
@@ -1,10 +1,15 @@
1
1
  class Bunch
2
- attr_writer :url_method
2
+ include Util
3
+ attr_writer :url_method, :fragment, :variables, :show_url
3
4
 
4
5
  def initialize
5
6
  @bunch_dir = nil
6
7
  @url_method = nil
7
8
  @bunches = nil
9
+ @fragment = nil
10
+ @variables = nil
11
+ @success = nil
12
+ @show_url = false
8
13
  get_cache
9
14
  end
10
15
 
@@ -41,6 +46,18 @@ class Bunch
41
46
  @bunches = settings['bunches'] || generate_bunch_list
42
47
  end
43
48
 
49
+ def variable_query
50
+ vars = @variables.split(/,/).map { |v| v.strip }
51
+ query = []
52
+ vars.each { |v|
53
+ parts = v.split(/=/).map { |v| v.strip }
54
+ k = parts[0]
55
+ v = parts[1]
56
+ query << "#{k}=#{CGI.escape(v)}"
57
+ }
58
+ query
59
+ end
60
+
44
61
  # items.push({title: 0})
45
62
  def generate_bunch_list
46
63
  items = []
@@ -69,12 +86,15 @@ class Bunch
69
86
  end
70
87
 
71
88
  def url(bunch)
89
+ params = "&x-success=#{@success}" if @success
72
90
  if url_method == 'file'
73
- %(x-bunch://raw?file=#{bunch})
91
+ %(x-bunch://raw?file=#{bunch}#{params})
74
92
  elsif url_method == 'raw'
75
- %(x-bunch://raw?txt=#{bunch})
93
+ %(x-bunch://raw?txt=#{bunch}#{params})
94
+ elsif url_method == 'snippet'
95
+ %(x-bunch://snippet?file=#{bunch}#{params})
76
96
  else
77
- %(x-bunch://#{url_method}?bunch=#{bunch[:title]})
97
+ %(x-bunch://#{url_method}?bunch=#{bunch[:title]}#{params})
78
98
  end
79
99
  end
80
100
 
@@ -107,28 +127,54 @@ class Bunch
107
127
  def open(str)
108
128
  # get front app
109
129
  front_app = %x{osascript -e 'tell application "System Events" to return name of first application process whose frontmost is true'}.strip
130
+ bid = bundle_id(front_app)
131
+ @success = bid if (bid)
132
+
110
133
  if @url_method == 'raw'
111
134
  warn 'Running raw string'
112
- `open '#{url(str)}'`
135
+ if @show_url
136
+ $stdout.puts url(str)
137
+ else
138
+ `open '#{url(str)}'`
139
+ end
140
+ elsif @url_method == 'snippet'
141
+ _url = url(str)
142
+ params = []
143
+ params << "fragment=#{CGI.escape(@fragment)}" if @fragment
144
+ params.concat(variable_query) if @variables
145
+ _url += '&' + params.join('&')
146
+ if @show_url
147
+ $stdout.puts _url
148
+ else
149
+ warn "Opening snippet"
150
+ `open '#{_url}'`
151
+ end
113
152
  else
114
153
  bunch = find_bunch(str)
115
154
  unless bunch
116
155
  if File.exists?(str)
117
156
  @url_method = 'file'
118
- warn "Opening file"
119
- `open '#{url(str)}'`
157
+ if @show_url
158
+ $stdout.puts url(str)
159
+ else
160
+ warn "Opening file"
161
+ `open '#{url(str)}'`
162
+ end
120
163
  else
121
164
  warn 'No matching Bunch found'
122
165
  Process.exit 1
123
166
  end
124
167
  else
125
- warn "#{human_action} #{bunch[:title]}"
126
-
127
- `open "#{url(bunch)}"`
168
+ if @show_url
169
+ $stdout.puts url(str)
170
+ else
171
+ warn "#{human_action} #{bunch[:title]}"
172
+ `open '#{url(bunch)}'`
173
+ end
128
174
  end
129
175
  end
130
176
  # attempt to restore front app
131
- %x{osascript -e 'delay 2' -e 'tell application "#{front_app}" to activate'}
177
+ # %x{osascript -e 'delay 2' -e 'tell application "#{front_app}" to activate'}
132
178
  end
133
179
 
134
180
  def show(str)
@@ -0,0 +1,296 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'readline'
4
+ require 'cgi'
5
+
6
+ # String additions
7
+ class String
8
+ def text?
9
+ res = `file "#{self}"`
10
+ res =~ /text/
11
+ end
12
+ end
13
+
14
+ # misc utils
15
+ module Util
16
+ def bundle_id(app)
17
+ shortname = app.sub(/\.app$/, '')
18
+ apps = `mdfind -onlyin /Applications -onlyin /Applications/Setapp -onlyin /Applications/Utilities -onlyin ~/Applications -onlyin /Developer/Applications 'kMDItemKind==Application'`
19
+
20
+ app = apps.split(/\n/).select! { |line| line.chomp =~ /#{shortname}\.app$/ }[0]
21
+
22
+ if app
23
+ bid = `mdls -name kMDItemCFBundleIdentifier -r "#{app}"`.chomp
24
+ else
25
+ warn "Could not locate bundle id for #{shortname}"
26
+ end
27
+ bid
28
+ end
29
+ end
30
+
31
+ # CLI Prompt utilities
32
+ module Prompt
33
+ def choose_number(query = '->', max)
34
+ stty_save = `stty -g`.chomp
35
+ sel = nil
36
+ begin
37
+ while !sel =~ /^\d+$/ || sel.to_i <= 0 || sel.to_i > max
38
+ sel = Readline.readline("#{query}", true)
39
+ return nil if sel =~ /^\s*$/
40
+ end
41
+ rescue Interrupt
42
+ system('stty', stty_save) # Restore
43
+ exit
44
+ end
45
+ sel ? sel.to_i : nil
46
+ end
47
+
48
+ def get_line(query = '->')
49
+ stty_save = `stty -g`.chomp
50
+ begin
51
+ line = Readline.readline("#{query}: ", true)
52
+ rescue Interrupt
53
+ system('stty', stty_save) # Restore
54
+ exit
55
+ end
56
+ line.chomp
57
+ end
58
+
59
+ def get_text(query = 'Enter text, ^d to end')
60
+ stty_save = `stty -g`.chomp
61
+ lines = []
62
+ puts query
63
+ begin
64
+ while (line = Readline.readline)
65
+ lines << line
66
+ end
67
+ rescue Interrupt
68
+ system('stty', stty_save) # Restore
69
+ exit
70
+ end
71
+ lines.join("\n").chomp
72
+ end
73
+
74
+ def url_encode_text
75
+ text = get_text
76
+ puts
77
+ CGI.escape(text)
78
+ end
79
+ end
80
+
81
+ # Single menu item
82
+ class MenuItem
83
+ attr_accessor :id, :title, :value
84
+
85
+ def initialize(id, title, value)
86
+ @id = id
87
+ @title = title
88
+ @value = value
89
+ end
90
+ end
91
+
92
+ # Collection of menu items
93
+ class Menu
94
+ include Prompt
95
+ attr_accessor :items
96
+
97
+ def initialize(items)
98
+ @items = items
99
+ end
100
+
101
+ def choose(query = 'Select an item')
102
+ throw 'No items initialized' if @items.nil?
103
+ STDERR.puts
104
+ STDERR.puts "┌#{("─" * 74)}┐"
105
+ intpad = Math::log10(@items.length).to_i + 1
106
+ @items.each_with_index do |item, idx|
107
+ idxstr = "%#{intpad}d" % (idx + 1)
108
+ line = "#{idxstr}: #{item.title}"
109
+ pad = 74 - line.length
110
+ STDERR.puts "│#{line}#{" " * pad}│"
111
+ end
112
+ STDERR.puts "└┤ #{query} ├#{"─" * (70 - query.length)}┘"
113
+ sel = choose_number("> ", @items.length)
114
+ sel ? @items[sel.to_i - 1] : nil
115
+ end
116
+ end
117
+
118
+ class Snippet
119
+ attr_accessor :fragments, :contents
120
+
121
+ def initialize(file)
122
+ if File.exist?(File.expand_path(file))
123
+ @contents = IO.read(File.expand_path(file))
124
+ @fragments = fragments
125
+ else
126
+ throw ('Tried to initialize snippet with invalid file')
127
+ end
128
+ end
129
+
130
+ def fragments
131
+ rx = /(?i-m)(?:[-#]+)\[([\s\S]*?)\][-# ]*\n([\s\S]*?)(?=\n(?:-+\[|#+\[|$))/
132
+ matches = @contents.scan(rx)
133
+ fragments = {}
134
+ matches.each do |m|
135
+ key = m[0]
136
+ value = m[1]
137
+
138
+ fragments[key] = value
139
+ end
140
+ fragments
141
+ end
142
+
143
+ def choose_fragment
144
+ unless @fragments.empty?
145
+ items = []
146
+ @fragments.each { |k, v| items << MenuItem.new(k, k, v) }
147
+ menu = Menu.new(items)
148
+ return menu.choose('Select fragment')
149
+ end
150
+ nil
151
+ end
152
+ end
153
+
154
+ # File search functions
155
+ class BunchFinder
156
+ include Prompt
157
+ attr_accessor :config_dir
158
+
159
+ def initialize
160
+ config_dir = `defaults read com.brettterpstra.bunch configDir`.strip
161
+ config_dir = File.expand_path(config_dir)
162
+ if File.directory?(config_dir)
163
+ @config_dir = config_dir
164
+ else
165
+ throw 'Unable to retrieve Bunches Folder'
166
+ end
167
+ end
168
+
169
+ def files_to_items(dir, pattern)
170
+ Dir.chdir(dir)
171
+ items = []
172
+ Dir.glob(pattern) do |f|
173
+ if f.text?
174
+ filename = File.basename(f)
175
+ items << MenuItem.new(filename, filename, filename)
176
+ end
177
+ end
178
+ items
179
+ end
180
+
181
+ def choose_bunch
182
+ items = files_to_items(@config_dir, '*.bunch')
183
+ items.map! do |item|
184
+ item.title = File.basename(item.title, '.bunch')
185
+ item.value = File.basename(item.title, '.bunch')
186
+ item
187
+ end
188
+ menu = Menu.new(items)
189
+ menu.choose('Select a Bunch')
190
+ end
191
+
192
+ def choose_snippet
193
+ items = files_to_items(@config_dir, '*')
194
+ menu = Menu.new(items)
195
+ menu.choose('Select a Snippet')
196
+ end
197
+
198
+ def expand_path(file)
199
+ File.join(@config_dir, file)
200
+ end
201
+
202
+ def contents(snippet)
203
+ IO.read(File.join(@config_dir, snippet))
204
+ end
205
+
206
+ def variables(content)
207
+ matches = content.scan(/\$\{(\S+)(:.*?)?\}/)
208
+ variables = []
209
+ matches.each { |m| variables << m[0].sub(/:\S+$/, '') }
210
+ variables.uniq
211
+ end
212
+
213
+ def fill_variables(text)
214
+ vars = variables(text)
215
+ output = []
216
+ unless vars.empty?
217
+ puts 'Enter values for variables'
218
+ vars.each do |var|
219
+ res = get_line(var)
220
+ output << [var, CGI.escape(res)] unless res.empty?
221
+ end
222
+ end
223
+ output
224
+ end
225
+ end
226
+
227
+ class BunchURLGenerator
228
+ include Prompt
229
+ include Util
230
+
231
+ def generate
232
+ menu_items = [
233
+ MenuItem.new('open', 'Open a Bunch', 'open'),
234
+ MenuItem.new('close', 'Close a Bunch', 'close'),
235
+ MenuItem.new('toggle', 'Toggle a Bunch', 'toggle'),
236
+ MenuItem.new('snippet', 'Load a Snippet', 'snippet'),
237
+ MenuItem.new('raw', 'Load raw text', 'raw')
238
+ ]
239
+
240
+ menu = Menu.new(menu_items)
241
+ finder = BunchFinder.new
242
+
243
+ selection = menu.choose
244
+ Process.exit 0 unless selection
245
+ url = "x-bunch://#{selection.value}"
246
+ parameters = []
247
+ case selection.id
248
+ when /(open|close|toggle)/
249
+ parameters << ['bunch', CGI.escape(finder.choose_bunch.value)]
250
+ when /snippet/
251
+ filename = finder.choose_snippet.value
252
+ parameters << ['file', filename]
253
+ filename = finder.expand_path(filename)
254
+ snippet = Snippet.new(filename)
255
+ fragment = snippet.choose_fragment
256
+ if fragment
257
+ parameters << ['fragment', CGI.escape(fragment.title)]
258
+ contents = fragment.value
259
+ else
260
+ contents = snippet.contents
261
+ end
262
+ variables = finder.fill_variables(contents)
263
+ parameters.concat(variables) if variables.length
264
+ when /raw/
265
+ parameters << ['text', menu.url_encode_text]
266
+ else
267
+ Process.exit 0
268
+ end
269
+
270
+ menu.items = [
271
+ MenuItem.new('app', 'Application', 'find_bid'),
272
+ MenuItem.new('url', 'URL', 'get_line(')
273
+ ]
274
+
275
+ selection = menu.choose('Add success action? (Enter to skip)')
276
+
277
+ if selection
278
+ case selection.id
279
+ when /app/
280
+ app = get_line('Application name')
281
+ value = bundle_id(app)
282
+ when /url/
283
+ value = get_line('URL')
284
+ end
285
+
286
+ parameters << ['x-success', value] if value
287
+
288
+ delay = get_line('Delay for success action')
289
+ parameters << ['x-delay', delay.to_s] if delay =~ /^\d+$/
290
+ end
291
+
292
+ query_string = parameters.map { |param| "#{param[0]}=#{param[1]}" }.join('&')
293
+
294
+ puts url + '?' + query_string
295
+ end
296
+ end
@@ -1,3 +1,3 @@
1
1
  module BunchCLI
2
- VERSION = "1.0.0"
2
+ VERSION = "1.1.0"
3
3
  end
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.0.0
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Brett Terpstra
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-12-19 00:00:00.000000000 Z
11
+ date: 2021-01-24 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description:
14
14
  email:
@@ -21,6 +21,7 @@ files:
21
21
  - ".gitignore"
22
22
  - CHANGELOG.md
23
23
  - Gemfile
24
+ - Gemfile.lock
24
25
  - LICENSE.txt
25
26
  - README.md
26
27
  - Rakefile
@@ -28,6 +29,7 @@ files:
28
29
  - bunchcli.gemspec
29
30
  - lib/bunch.rb
30
31
  - lib/bunch/bunchCLI.rb
32
+ - lib/bunch/url_generator.rb
31
33
  - lib/bunch/version.rb
32
34
  homepage: https://brettterpstra.com/projects/bunch
33
35
  licenses: