hiiro 0.1.24 → 0.1.25

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.
data/bin/h-link CHANGED
@@ -7,136 +7,234 @@ require 'pry'
7
7
  require 'tempfile'
8
8
  require "hiiro"
9
9
 
10
- LINKS_FILE = File.join(Dir.home, '.config/hiiro/links.yml')
11
- LINK_TEMPLATE = {
12
- 'url' => 'http://EXAMPLE',
13
- 'description' => '',
14
- 'shorthand' => nil,
15
- 'created_at' => Time.now.iso8601,
16
- }
10
+ class LinkManager
11
+ LINKS_FILE = File.join(Dir.home, '.config/hiiro/links.yml')
12
+ LINK_TEMPLATE = {
13
+ 'url' => 'https://',
14
+ 'description' => '',
15
+ 'shorthand' => nil,
16
+ 'created_at' => Time.now.iso8601,
17
+ }
17
18
 
19
+ class Link
20
+ attr_accessor :url, :description, :shorthand, :created_at
18
21
 
22
+ def initialize(url:, description: '', shorthand: nil, created_at: Time.now.iso8601)
23
+ @url = url
24
+ @description = description
25
+ @shorthand = shorthand
26
+ @created_at = created_at
27
+ end
19
28
 
20
- o = Hiiro.init(*ARGV, plugins: [Tmux, Pins], links_file: LINKS_FILE)
29
+ def self.from_hash(hash)
30
+ new(
31
+ url: hash['url'],
32
+ description: hash['description'] || '',
33
+ shorthand: hash['shorthand'],
34
+ created_at: hash['created_at'] || Time.now.iso8601
35
+ )
36
+ end
21
37
 
22
- def ensure_links_file
23
- dir = File.dirname(LINKS_FILE)
24
- FileUtils.mkdir_p(dir) unless Dir.exist?(dir)
25
- File.write(LINKS_FILE, [].to_yaml) unless File.exist?(LINKS_FILE)
26
- end
38
+ def to_h
39
+ {
40
+ 'url' => @url,
41
+ 'description' => @description,
42
+ 'shorthand' => @shorthand,
43
+ 'created_at' => @created_at
44
+ }
45
+ end
46
+
47
+ def to_yaml
48
+ to_h.to_yaml
49
+ end
27
50
 
28
- def hash_matches?(links, *args)
29
- terms = args.map(&:downcase)
51
+ def matches?(*terms)
52
+ searchable = [
53
+ @url,
54
+ @description,
55
+ @shorthand
56
+ ].compact.join(' ').downcase
30
57
 
31
- links.select do |line, link|
32
- searchable = [
33
- link['url'],
34
- link['description'],
35
- link['shorthand']
36
- ].compact.join(' ').downcase
58
+ terms.all? { |term| searchable.downcase.include?(term.downcase) }
59
+ end
37
60
 
38
- terms.all? { |term| searchable.include?(term) }
61
+ def display_string(index = nil)
62
+ num = index ? "#{(index + 1).to_s.rjust(3)}." : ""
63
+ shorthand_str = @shorthand ? " [#{@shorthand}]" : ""
64
+ desc_str = @description.to_s.empty? ? "" : " - #{@description}"
65
+ "#{num}#{shorthand_str} #{@url}#{desc_str}".strip
66
+ end
39
67
  end
40
- end
41
68
 
42
- def search_links(links, *args)
43
- terms = args.map(&:downcase)
69
+ attr_reader :links_file
44
70
 
45
- links.select do |link|
46
- link_matches?(link, *terms)
71
+ def initialize(links_file = LINKS_FILE)
72
+ @links_file = links_file
47
73
  end
48
- end
49
74
 
50
- def link_matches?(link, *terms)
51
- searchable = [
52
- link['url'],
53
- link['description'],
54
- link['shorthand']
55
- ].compact.join(' ').downcase
75
+ def ensure_links_file
76
+ dir = File.dirname(@links_file)
77
+ FileUtils.mkdir_p(dir) unless Dir.exist?(dir)
78
+ File.write(@links_file, [].to_yaml) unless File.exist?(@links_file)
79
+ end
56
80
 
57
- terms.all? { |term| searchable.include?(term) }
58
- end
81
+ def hash_matches?(links_hash, *args)
82
+ links_hash.select { |line, link| link.matches?(*args) }
83
+ end
84
+
85
+ def search_links(links, *args)
86
+ links.select { |link| link.matches?(*args) }
87
+ end
59
88
 
60
- def load_link_hash(links=nil)
61
- links ||= load_links
89
+ def load_link_hash(links=nil)
90
+ links ||= load_links
62
91
 
63
- links.each_with_index.each_with_object({}) do |(link, idx), h|
64
- num = (idx + 1).to_s.rjust(3)
65
- desc = link['description'].to_s.empty? ? "" : " - #{link['description']}"
66
- h["#{num}. #{link['url']}#{desc}"] = link
92
+ links.each_with_index.each_with_object({}) do |(link, idx), h|
93
+ h[link.display_string(idx)] = link
94
+ end
67
95
  end
68
- end
69
96
 
70
- def load_links
71
- ensure_links_file
72
- YAML.load_file(LINKS_FILE) || []
73
- end
97
+ def load_links
98
+ ensure_links_file
99
+ links_data = YAML.load_file(@links_file) || []
100
+ links_data.map { |hash| Link.from_hash(hash) }
101
+ end
74
102
 
75
- def edit_link(link=LINK_TEMPLATE)
76
- tmpfile = Tempfile.new(['link-edit-', '.yml'])
77
- tmpfile.write(link.to_yaml)
78
- tmpfile.close
103
+ def edit_links(links=nil)
104
+ links_array = if links.nil?
105
+ [LINK_TEMPLATE]
106
+ elsif links.is_a?(Array)
107
+ links.map { |link| link.is_a?(Link) ? link.to_h : link }
108
+ else
109
+ [links.is_a?(Link) ? links.to_h : links]
110
+ end
79
111
 
80
- system(ENV['EDITOR'] || 'vim', tmpfile.path)
112
+ tmpfile = Tempfile.new(['link-edit-', '.yml'])
113
+ tmpfile.write(links_array.to_yaml)
114
+ tmpfile.close
81
115
 
82
- updated = YAML.load_file(tmpfile.path)
83
- tmpfile.unlink
116
+ system(ENV['EDITOR'] || 'vim', tmpfile.path)
84
117
 
85
- updated
86
- end
118
+ updated_data = YAML.load_file(tmpfile.path)
119
+ tmpfile.unlink
87
120
 
88
- def save_links(links)
89
- ensure_links_file
90
- File.write(LINKS_FILE, links.to_yaml)
91
- end
121
+ # Handle both array and single hash results
122
+ updated_array = updated_data.is_a?(Array) ? updated_data : [updated_data]
123
+ updated_array.map { |hash| Link.from_hash(hash) }
124
+ end
92
125
 
93
- def find_link_by_ref(ref, links)
94
- if ref =~ /^\d+$/
95
- idx = ref.to_i - 1
96
- return [idx, links[idx]] if idx >= 0 && idx < links.length
97
- else
98
- links.each_with_index do |link, idx|
99
- return [idx, link] if link['shorthand'] == ref
126
+ def save_links(links)
127
+ ensure_links_file
128
+ links_data = links.map { |link| link.to_h }
129
+ File.write(@links_file, links_data.to_yaml)
130
+ end
131
+
132
+ def extract_placeholders(url)
133
+ url.scan(/\{(\w+)\}/).flatten
134
+ end
135
+
136
+ def has_placeholders?(url)
137
+ !extract_placeholders(url).empty?
138
+ end
139
+
140
+ def prompt_for_placeholder_values(url, link=nil)
141
+ placeholders = extract_placeholders(url)
142
+ return {} if placeholders.empty?
143
+
144
+ # Create a YAML template with placeholder names as keys
145
+ template = placeholders.each_with_object({}) { |name, hash| hash[name] = '' }
146
+
147
+ tmpfile = Tempfile.new(['link-params-', '.yml'])
148
+
149
+ # Add the link as YAML comments if provided
150
+ if link
151
+ link_yaml = link.to_yaml
152
+ link_yaml.each_line do |line|
153
+ tmpfile.puts("# #{line.chomp}")
154
+ end
155
+ tmpfile.puts("#")
156
+ tmpfile.puts("# Fill in placeholder values below:")
157
+ tmpfile.puts("#")
158
+ end
159
+
160
+ tmpfile.write(template.to_yaml)
161
+ tmpfile.close
162
+
163
+ editor = ENV['EDITOR'] || 'safe_nvim' || 'nvim'
164
+ system(editor, tmpfile.path)
165
+
166
+ values = YAML.load_file(tmpfile.path) || {}
167
+ tmpfile.unlink
168
+
169
+ values
170
+ end
171
+
172
+ def substitute_placeholders(url, values)
173
+ result = url.dup
174
+ values.each do |key, value|
175
+ # Replace spaces with + for URL encoding
176
+ encoded_value = value.to_s.gsub(' ', '+')
177
+ result.gsub!("{#{key}}", encoded_value)
100
178
  end
179
+ result
180
+ end
181
+
182
+ def find_link_by_ref(ref, links)
183
+ if ref =~ /^\d+$/
184
+ idx = ref.to_i - 1
185
+ return [idx, links[idx]] if idx >= 0 && idx < links.length
186
+ else
187
+ links.each_with_index do |link, idx|
188
+ return [idx, link] if link.shorthand == ref
189
+ end
190
+ end
191
+ [nil, nil]
101
192
  end
102
- [nil, nil]
103
193
  end
104
194
 
195
+ lm = LinkManager.new
196
+ o = Hiiro.init(*ARGV, plugins: [Tmux, Pins], links_file: lm.links_file)
197
+
105
198
  o.add_subcmd(:add) do |*args|
106
- links = load_links
199
+ links = lm.load_links
107
200
 
108
201
  if args.empty?
109
- updated = edit_link
202
+ new_links = lm.edit_links
110
203
 
111
- links << updated
112
- save_links(links)
204
+ links.concat(new_links)
205
+ lm.save_links(links)
206
+
207
+ if new_links.length == 1
208
+ puts "Saved link ##{links.length}: #{new_links.first.url}"
209
+ else
210
+ puts "Saved #{new_links.length} links (#{links.length - new_links.length + 1}-#{links.length})"
211
+ end
113
212
  exit 0
114
213
  end
115
214
 
116
215
  url = args.shift
117
216
  description = args.join(' ')
118
217
 
119
- links << {
120
- 'url' => url,
121
- 'description' => description,
122
- 'shorthand' => nil,
123
- 'created_at' => Time.now.iso8601
124
- }
125
- save_links(links)
218
+ new_link = LinkManager::Link.new(
219
+ url: url,
220
+ description: description,
221
+ shorthand: nil,
222
+ created_at: Time.now.iso8601
223
+ )
224
+
225
+ links << new_link
226
+ lm.save_links(links)
126
227
 
127
228
  puts "Saved link ##{links.length}: #{url}"
128
229
  end
129
230
 
130
231
  o.add_subcmd(:ls) do |*args|
131
- links = load_links
232
+ links = lm.load_links
132
233
  if links.empty?
133
234
  puts "No links saved."
134
235
  else
135
236
  links.each_with_index do |link, idx|
136
- num = (idx + 1).to_s.rjust(3)
137
- shorthand = link['shorthand'] ? " [#{link['shorthand']}]" : ""
138
- desc = link['description'].to_s.empty? ? "" : " - #{link['description']}"
139
- puts "#{num}.#{shorthand} #{link['url']}#{desc}"
237
+ puts link.display_string(idx)
140
238
  end
141
239
  end
142
240
  end
@@ -151,64 +249,59 @@ o.add_subcmd(:search) do |*args|
151
249
  exit 1
152
250
  end
153
251
 
154
- links = load_links
155
- terms = args.map(&:downcase)
156
-
157
- matches = links.each_with_index.select do |link, idx|
158
- searchable = [
159
- link['url'],
160
- link['description'],
161
- link['shorthand']
162
- ].compact.join(' ').downcase
163
-
164
- terms.all? { |term| searchable.include?(term) }
165
- end
252
+ links = lm.load_links
253
+ matches = links.each_with_index.select { |link, idx| link.matches?(*args) }
166
254
 
167
255
  if matches.empty?
168
256
  puts "No links found matching: #{args.join(' ')}"
169
257
  else
170
258
  matches.each do |link, idx|
171
- num = (idx + 1).to_s.rjust(3)
172
- shorthand = link['shorthand'] ? " [#{link['shorthand']}]" : ""
173
- desc = link['description'].to_s.empty? ? "" : " - #{link['description']}"
174
- puts "#{num}.#{shorthand} #{link['url']}#{desc}"
259
+ puts link.display_string(idx)
175
260
  end
176
261
  end
177
262
  end
178
263
 
179
264
  o.add_subcmd(:select) do |*args|
180
- links = load_links
265
+ links = lm.load_links
181
266
 
182
267
  if links.empty?
183
268
  STDERR.puts "No links saved."
184
269
  exit 1
185
270
  end
186
271
 
187
- lines = load_link_hash(links)
272
+ lines = lm.load_link_hash(links)
188
273
 
189
274
  if args.any?
190
- lines = hash_matches?(lines, *args)
275
+ lines = lm.hash_matches?(lines, *args)
191
276
  end
192
277
 
193
278
  require 'open3'
194
279
  selected, status = Open3.capture2('sk', stdin_data: lines.keys.join("\n"))
195
280
 
196
281
  if status.success? && !selected.strip.empty?
197
- if selected =~ /^\s*(\d+)\.\s+(\S+)/
198
- puts $2
282
+ link = lines[selected.strip]
283
+ if link
284
+ url = link.url
285
+
286
+ if lm.has_placeholders?(url)
287
+ values = lm.prompt_for_placeholder_values(url, link)
288
+ url = lm.substitute_placeholders(url, values)
289
+ end
290
+
291
+ puts url
199
292
  end
200
293
  end
201
294
  end
202
295
 
203
296
  o.add_subcmd(:editall) do |*args|
204
- links_before = load_links
205
- system(ENV['EDITOR'] || 'vim', LINKS_FILE)
297
+ links_before = lm.load_links
298
+ system(ENV['EDITOR'] || 'vim', lm.links_file)
206
299
 
207
300
  begin
208
- links_after = load_links
301
+ links_after = lm.load_links
209
302
  rescue => e
210
303
  puts "ERROR: Unable to read updated file...reverting."
211
- save_links(links_before)
304
+ lm.save_links(links_before)
212
305
  end
213
306
  end
214
307
 
@@ -218,40 +311,58 @@ o.add_subcmd(:edit) do |*args|
218
311
  exit 1
219
312
  end
220
313
 
221
- links = load_links
222
- idx, link = find_link_by_ref(args.first, links)
314
+ links = lm.load_links
315
+ idx, link = lm.find_link_by_ref(args.first, links)
223
316
 
224
317
  if link.nil?
225
318
  puts "Link not found: #{args.first}"
226
319
  exit 1
227
320
  end
228
321
 
229
- updated = edit_link(link)
322
+ updated_links = lm.edit_links(link)
323
+
324
+ # Replace the original link with the first updated link
325
+ # If multiple links were added, insert them all
326
+ links[idx] = updated_links.first
327
+ if updated_links.length > 1
328
+ links.insert(idx + 1, *updated_links[1..-1])
329
+ end
230
330
 
231
- links[idx] = updated
232
- save_links(links)
331
+ lm.save_links(links)
233
332
 
234
- puts "Updated link ##{idx + 1}"
333
+ if updated_links.length == 1
334
+ puts "Updated link ##{idx + 1}"
335
+ else
336
+ puts "Updated link ##{idx + 1} and added #{updated_links.length - 1} more"
337
+ end
235
338
  end
236
339
 
237
340
  o.add_subcmd(:open) do |*args|
238
- links = load_links
341
+ links = lm.load_links
239
342
 
240
343
  if args.empty?
241
- lines = load_link_hash(links)
344
+ lines = lm.load_link_hash(links)
242
345
 
243
346
  exit_code = 1
244
347
 
245
348
  if args.any?
246
- lines = hash_matches?(lines, *args)
349
+ lines = lm.hash_matches?(lines, *args)
247
350
  end
248
351
 
249
352
  require 'open3'
250
353
  selected, status = Open3.capture2('sk', stdin_data: lines.keys.join("\n"))
251
354
 
252
355
  if status.success? && !selected.strip.empty?
253
- if selected =~ /^\s*(\d+)\.\s+(\S+)/
254
- system('open', $2)
356
+ link = lines[selected.strip]
357
+ if link
358
+ url = link.url
359
+
360
+ if lm.has_placeholders?(url)
361
+ values = lm.prompt_for_placeholder_values(url, link)
362
+ url = lm.substitute_placeholders(url, values)
363
+ end
364
+
365
+ system('open', url)
255
366
  exit_code = 0
256
367
  end
257
368
  end
@@ -259,10 +370,10 @@ o.add_subcmd(:open) do |*args|
259
370
  exit exit_code
260
371
  end
261
372
 
262
- idx, link = find_link_by_ref(args.first, links)
373
+ idx, link = lm.find_link_by_ref(args.first, links)
263
374
 
264
- if link.nil?
265
- matches = search_links(links, *args)
375
+ if link.nil?
376
+ matches = lm.search_links(links, *args)
266
377
 
267
378
  if matches.count == 1
268
379
  link = matches.first
@@ -274,11 +385,18 @@ o.add_subcmd(:open) do |*args|
274
385
  exit 1
275
386
  end
276
387
 
277
- system('open', link['url'])
388
+ url = link.url
389
+
390
+ if lm.has_placeholders?(url)
391
+ values = lm.prompt_for_placeholder_values(url, link)
392
+ url = lm.substitute_placeholders(url, values)
393
+ end
394
+
395
+ system('open', url)
278
396
  end
279
397
 
280
398
  o.add_subcmd(:path) do |*args|
281
- print LINKS_FILE
399
+ print lm.links_file
282
400
  end
283
401
 
284
402
  begin
data/bin/h-mic ADDED
@@ -0,0 +1,93 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'hiiro'
4
+
5
+ hiiro = Hiiro.init(*ARGV)
6
+
7
+ SAVED_VOLUME_FILE = File.join(Dir.home, '.no-mic-saved-volume')
8
+ DEFAULT_VOLUME = 75
9
+
10
+ def get_input_volume
11
+ `osascript -e 'input volume of (get volume settings)'`.strip.to_i
12
+ end
13
+
14
+ def set_input_volume(vol)
15
+ system('osascript', '-e', "set volume input volume #{vol}")
16
+ end
17
+
18
+ def save_volume(vol)
19
+ File.write(SAVED_VOLUME_FILE, vol.to_s) if vol > 0
20
+ end
21
+
22
+ def get_saved_volume
23
+ if File.exist?(SAVED_VOLUME_FILE)
24
+ File.read(SAVED_VOLUME_FILE).strip.to_i
25
+ else
26
+ DEFAULT_VOLUME
27
+ end
28
+ end
29
+
30
+ hiiro.add_subcmd(:set) do |new_volume|
31
+ old_volume = get_input_volume
32
+ puts "Microphone muted (was at #{old_volume}%)"
33
+
34
+ set_input_volume(new_volume)
35
+
36
+ current_volume = get_input_volume
37
+ puts "Microphone muted (set to #{current_volume}%)"
38
+ end
39
+
40
+ hiiro.add_subcmd(:mute) do
41
+ current_volume = get_input_volume
42
+
43
+ if current_volume == 0
44
+ puts "Microphone is already muted"
45
+ else
46
+ save_volume(current_volume)
47
+ set_input_volume(0)
48
+ puts "Microphone muted (was at #{current_volume}%)"
49
+ end
50
+ end
51
+
52
+ hiiro.add_subcmd(:unmute) do
53
+ current_volume = get_input_volume
54
+
55
+ if current_volume > 0
56
+ puts "Microphone is already unmuted (currently at #{current_volume}%)"
57
+ else
58
+ restore_volume = get_saved_volume
59
+ set_input_volume(restore_volume)
60
+ puts "Microphone unmuted (restored to #{restore_volume}%)"
61
+ end
62
+ end
63
+
64
+ hiiro.add_subcmd(:toggle) do
65
+ current_volume = get_input_volume
66
+
67
+ if current_volume == 0
68
+ restore_volume = get_saved_volume
69
+ set_input_volume(restore_volume)
70
+ puts "Microphone unmuted (restored to #{restore_volume}%)"
71
+ else
72
+ save_volume(current_volume)
73
+ set_input_volume(0)
74
+ puts "Microphone muted (was at #{current_volume}%)"
75
+ end
76
+ end
77
+
78
+ hiiro.add_subcmd(:status) do
79
+ current_volume = get_input_volume
80
+
81
+ if current_volume == 0
82
+ puts "Microphone: MUTED"
83
+ else
84
+ puts "Microphone: ON (volume: #{current_volume}%)"
85
+ end
86
+ end
87
+
88
+ hiiro.add_subcmd(:edit) do
89
+ editor = ENV.fetch('EDITOR', 'nvim')
90
+ system(editor, __FILE__)
91
+ end
92
+
93
+ hiiro.run