cmdline-sub 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (7) hide show
  1. checksums.yaml +7 -0
  2. data/Gemfile +3 -0
  3. data/LICENSE +9 -0
  4. data/README +79 -0
  5. data/Rakefile +85 -0
  6. data/bin/sub +426 -0
  7. metadata +96 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 2fd227736fa0bc8d59c84b1909f3c7abfd63ec86325a855ae5e4e2302693f35b
4
+ data.tar.gz: 84c336782e6d58eddba82d06874809d22074c968fd94a82c61a88043d922bb52
5
+ SHA512:
6
+ metadata.gz: e1955cee9482f5b650924d6302185519553bc82dc9959b28449e57c5bb2f5527e2553eb18be60fe5a7f8cb9d752d5baf68709bab3448b66dfda3d196b2ad51e5
7
+ data.tar.gz: 9435447e39679f02f4e2365faa2c01cd2b7e9fb3ca88388e5d894b913c6edf5885fde6a32504e85cd4da29d75a8c0395ae265dbdd3d5917265ca4fc3dd4116e8
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source "https://rubygems.org"
2
+
3
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,9 @@
1
+ MIT
2
+
3
+ Copyright (c) 2024 Luke Gruber
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6
+
7
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8
+
9
+ THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README ADDED
@@ -0,0 +1,79 @@
1
+ Sub
2
+ ===
3
+
4
+ sub v0.1.0
5
+ Usage: sub COMMANDLINE -- (PATTERN/SUBSTITUTION[/SUB_OPTIONS])+ (/GLOBAL_OPTIONS)?
6
+
7
+ sub substitutes the matching pattern in every word in the command line with the
8
+ substitution. Only the first matched pattern in the word is substituted unless
9
+ the 'g' flag is given. There are various options that change the way substitutions
10
+ are performed or the way matches are made. Patterns can have regular expression
11
+ wildcards (special characters) in them, and the regular expression engine used is
12
+ Ruby's. Special characters are interpreted as special unless the 'L' flag is given.
13
+
14
+ See [https://docs.ruby-lang.org/en/master/Regexp.html#class-Regexp-label-Special+Characters]
15
+ for more information on the special characters that can be used inside patterns.
16
+
17
+ The sub utility is useful mainly when editing long commandlines, so you don't have to
18
+ <ctrl-b> and <ctrl-f> (or <left> and <right>) through the commandline to edit it. The
19
+ examples given here don't feature long commandlines for the sake of being brief and
20
+ explanatory.
21
+
22
+ Ex: sub ld -m elf_i386 ex4.o -o ex4 -- ex4/ex5
23
+ This will output the following, and then execute it:
24
+ ld -m elf_i386 ex5.o -o ex5
25
+
26
+ Multiple substitutions:
27
+
28
+ Multiple substitutions can be given as well, such as:
29
+
30
+ Ex: sub ld -m elf_i386 ex4.o -o ex4 -- ex4/ex5 i386/arm64
31
+ ld -m elf_arm64 ex5.o -o ex5
32
+
33
+ If multiple substitutions are given, each subsitution pattern is matched with
34
+ each word, then the next substitution pattern does the same.
35
+
36
+ Global flags:
37
+ Global flags can be given as the last argument:
38
+
39
+ ex: sub find . | grep '.txt' -- txt/md /p
40
+ In this example, the print flag is given as a final argument.
41
+ See flags information below.
42
+
43
+ Flags:
44
+
45
+ Substitution flags:
46
+ -f: Substitute first matching word only.
47
+ ex: sub wget https://wget.com -- wget/curl/f #=> curl https://wget.com
48
+ -l: Substitute last matching word only
49
+ ex: sub ls -al -- l//l #=> ls -a
50
+ -L: Interpret wildcards in pattern as literals
51
+ ex: sub cp here.txt there.txt -- ./_/L #=> cp here_txt there_txt
52
+ -i: Set pattern to ignore the case of the match
53
+ ex: sub cp here.txt there.txt -- CP/mv/i #=> mv here.txt there.txt
54
+ -g: General substitution: substitute all matches in the word, not just the first
55
+ ex: sub cp here.txt there.txt -- ./_/g #=> __ ________ _________
56
+ -e: Expand * in commandline after replacements are made
57
+ ex: sub ls mydir -- mydir/*/e #=> ls bin README
58
+ Global flags:
59
+ -p: Print the command instead of executing it
60
+ -c: Copy the command to clipboard instead of executing it
61
+ -I: Set interactive mode: shows the command and asks if you want to execute it
62
+ -v: Set verbose mode
63
+ -D: Set debug mode
64
+
65
+ Install:
66
+ cd $HOME
67
+ git clone git@github.com:luke-gru/sub.git
68
+ cd sub
69
+ sudo rake install # creates /usr/local/bin/sub
70
+ # or
71
+ # INSTALL_PREFIX="/bin" sudo rake install
72
+
73
+ Uninstall:
74
+ cd $HOME/sub
75
+ sudo rake uninstall # removes /usr/local/bin/sub
76
+ # or
77
+ # INSTALL_PREFIX="/bin" sudo rake uninstall # removes /bin/sub
78
+
79
+ License: MIT
data/Rakefile ADDED
@@ -0,0 +1,85 @@
1
+ require "rake/testtask"
2
+
3
+ Rake::TestTask.new do |t|
4
+ t.test_files = FileList['test/*_test.rb']
5
+ t.verbose = true
6
+ end
7
+
8
+ desc "auto-generate README"
9
+ task :readme do
10
+ header = "Sub\n===\n\n"
11
+ help = `./bin/sub --help`
12
+ if $?.exitstatus != 0
13
+ $stderr.puts "./bin/sub --help not found!"
14
+ exit 1
15
+ end
16
+ before = File.read("README")
17
+ File.open("README", 'w+') do |f|
18
+ f.write(header + help)
19
+ end
20
+ after = File.read("README")
21
+ puts "generated README"
22
+ puts "changed: #{before != after}"
23
+ end
24
+
25
+ desc "Install (copy files to /usr/local/bin or INSTALL_PREFIX). Use sudo if necessary."
26
+ task :install do
27
+ require "fileutils"
28
+ install_dir = ENV["INSTALL_PREFIX"] || "/usr/local/bin"
29
+ unless File.directory?(install_dir)
30
+ $stderr.puts "Directory #{install_dir} does not exist. Install failed."
31
+ exit 1
32
+ end
33
+ paths = ENV["PATH"].split(":")
34
+ unless paths.include?(install_dir)
35
+ $stderr.puts "#{install_dir} is not in your PATH. Install failed."
36
+ end
37
+ bin_file = File.join(File.dirname(__FILE__), "bin", "sub")
38
+ unless File.exist?(bin_file)
39
+ $stderr.puts "File #{bin_file} doesn't exist. Error: install failed."
40
+ exit 1
41
+ end
42
+ begin
43
+ FileUtils.cp(bin_file, install_dir)
44
+ rescue Errno::EACCES => e
45
+ $stderr.puts "Failed to install"
46
+ $stderr.puts e.message
47
+ if `logname`.strip != 'root'
48
+ $stderr.puts "Hint: Try using sudo before the command: sudo rake install"
49
+ end
50
+ exit 1
51
+ end
52
+ if (which = `which sub`.strip) != File.join(install_dir, 'sub')
53
+ $stderr.puts "Warning: `which sub` returned wrong value: '#{which}'"
54
+ end
55
+ exit 0
56
+ end
57
+
58
+ desc "Uninstall (removes /usr/local/bin/sub or $(INSTALL_PREFIX)/sub). Use sudo if necessary."
59
+ task :uninstall do
60
+ install_dir = ENV["INSTALL_PREFIX"] || "/usr/local/bin"
61
+ unless File.directory?(install_dir)
62
+ $stderr.puts "Directory #{install_dir} does not exist. Uninstall failed."
63
+ exit 1
64
+ end
65
+ bin_file = File.join(install_dir, "sub")
66
+ unless File.exist?(bin_file)
67
+ $stderr.puts "File #{bin_file} doesn't exist."
68
+ exit 0
69
+ end
70
+ begin
71
+ FileUtils.rm(bin_file)
72
+ rescue Errno::EACCES => e
73
+ $stderr.puts "Failed to uninstall"
74
+ $stderr.puts e.message
75
+ if `logname`.strip != 'root'
76
+ $stderr.puts "Hint: Try using sudo before the command: sudo rake uninstall"
77
+ end
78
+ exit 1
79
+ end
80
+ system("which sub")
81
+ if $?.exitstatus == 0
82
+ $stderr.puts "Warning: sub is still in your PATH"
83
+ end
84
+ exit 0
85
+ end
data/bin/sub ADDED
@@ -0,0 +1,426 @@
1
+ #!/usr/bin/env ruby
2
+ # vim: set ft=ruby
3
+ # frozen_string_literal: true
4
+
5
+ VERSION = "0.1.1"
6
+
7
+ USAGE = <<EOF
8
+ Usage: sub COMMANDLINE -- (PATTERN/SUBSTITUTION[/SUB_OPTIONS])+ (/GLOBAL_OPTIONS)?
9
+
10
+ sub substitutes the matching pattern in every word in the command line with the
11
+ substitution. Only the first matched pattern in the word is substituted unless
12
+ the 'g' flag is given. There are various options that change the way substitutions
13
+ are performed or the way matches are made. Patterns can have regular expression
14
+ wildcards (special characters) in them, and the regular expression engine used is
15
+ Ruby's. Special characters are interpreted as special unless the 'L' flag is given.
16
+
17
+ See [https://docs.ruby-lang.org/en/master/Regexp.html#class-Regexp-label-Special+Characters]
18
+ for more information on the special characters that can be used inside patterns.
19
+
20
+ The sub utility is useful mainly when editing long commandlines, so you don't have to
21
+ <ctrl-b> and <ctrl-f> (or <left> and <right>) through the commandline to edit it. The
22
+ examples given here don't feature long commandlines for the sake of being brief and
23
+ explanatory.
24
+
25
+ Ex: sub ld -m elf_i386 ex4.o -o ex4 -- ex4/ex5
26
+ This will output the following, and then execute it:
27
+ ld -m elf_i386 ex5.o -o ex5
28
+
29
+ Multiple substitutions:
30
+
31
+ Multiple substitutions can be given as well, such as:
32
+
33
+ Ex: sub ld -m elf_i386 ex4.o -o ex4 -- ex4/ex5 i386/arm64
34
+ ld -m elf_arm64 ex5.o -o ex5
35
+
36
+ If multiple substitutions are given, each subsitution pattern is matched with
37
+ each word, then the next substitution pattern does the same.
38
+
39
+ Global flags:
40
+ Global flags can be given as the last argument:
41
+
42
+ ex: sub find . | grep '.txt' -- txt/md /p
43
+ In this example, the print flag is given as a final argument.
44
+ See flags information below.
45
+ EOF
46
+
47
+ INSTALLATION = <<EOF
48
+ Install:
49
+ \tcd $HOME
50
+ \tgit clone git@github.com:luke-gru/sub.git
51
+ \tcd sub
52
+ \tsudo rake install # creates /usr/local/bin/sub
53
+ \t# or
54
+ \t# INSTALL_PREFIX="/bin" sudo rake install
55
+ EOF
56
+
57
+ UNINSTALLATION = <<EOF
58
+ Uninstall:
59
+ \tcd $HOME/sub
60
+ \tsudo rake uninstall # removes /usr/local/bin/sub
61
+ \t# or
62
+ \t# INSTALL_PREFIX="/bin" sudo rake uninstall # removes /bin/sub
63
+ EOF
64
+
65
+ LICENSE = "License: MIT"
66
+
67
+ FLAGS = [
68
+ # per substitution flags
69
+ { flag: 'f', name: :first_match, desc: "Substitute first matching word only.",
70
+ example: "sub wget https://wget.com -- wget/curl/f #=> curl https://wget.com",
71
+ },
72
+ { flag: 'l', name: :last_match, desc: "Substitute last matching word only",
73
+ example: "sub ls -al -- l//l #=> ls -a",
74
+ },
75
+ { flag: 'L', name: :literal, desc: "Interpret wildcards in pattern as literals",
76
+ example: "sub cp here.txt there.txt -- ./_/L #=> cp here_txt there_txt",
77
+ },
78
+ { flag: 'i', name: :ignorecase, desc: "Set pattern to ignore the case of the match",
79
+ example: "sub cp here.txt there.txt -- CP/mv/i #=> mv here.txt there.txt",
80
+ },
81
+ { flag: 'g', name: :general, desc: "General substitution: substitute all matches in the word, not just the first",
82
+ example: "sub cp here.txt there.txt -- ./_/g #=> __ ________ _________",
83
+ },
84
+ { flag: 'e', name: :expand_star, desc: "Expand * in commandline after replacements are made",
85
+ example: [ "sub ls mydir -- mydir/*/e #=> ls bin README" ]
86
+ },
87
+ nil, # separator
88
+ # global flags
89
+ { flag: 'p', name: :print_only, desc: "Print the command instead of executing it (adds newline)" },
90
+ { flag: 'P', name: :print_only_no_newline, desc: "Print the command instead of executing it (doesn't add newline)" },
91
+ { flag: 'c', name: :copy_to_clipboard, desc: "Copy the command to clipboard instead of executing it" },
92
+ { flag: 'I', name: :interactive, desc: "Set interactive mode: shows the command and asks if you want to execute it" },
93
+ { flag: 'v', name: :verbose, desc: "Set verbose mode" },
94
+ { flag: 'D', name: :debug, desc: "Set debug mode" },
95
+ ]
96
+ GLOBAL_FLAGS_MAP = {
97
+ print_only: 'p',
98
+ print_only_no_newline: 'P',
99
+ copy_to_clipboard: 'c',
100
+ interactive: 'I',
101
+ verbose: 'v',
102
+ debug: 'D',
103
+ }
104
+ GLOBAL_FLAGS = GLOBAL_FLAGS_MAP.values
105
+
106
+ def print_help(full: true)
107
+ output = String.new
108
+ output << "sub v#{VERSION}" << "\n"
109
+ output << USAGE << "\n"
110
+ output << "Flags:\n\n"
111
+ output << "Substitution flags:\n"
112
+ FLAGS.each do |fhash|
113
+ if fhash.nil?
114
+ output << "Global flags:\n"
115
+ next
116
+ end
117
+ output << "-#{fhash[:flag]}:\t#{fhash[:desc]}\n"
118
+ if ex = fhash[:example]
119
+ case ex
120
+ when String
121
+ output << " \t ex: #{ex}\n"
122
+ when Array
123
+ ex.each do |x|
124
+ output << " \t ex: #{x}\n"
125
+ end
126
+ end
127
+ end
128
+ end
129
+ if full
130
+ output << "\n" << INSTALLATION
131
+ output << "\n" << UNINSTALLATION
132
+ output << "\n" << LICENSE
133
+ end
134
+ $stdout.puts output
135
+ end
136
+
137
+ def print_version
138
+ $stdout.puts VERSION
139
+ end
140
+
141
+ def parse_argv_and_sub_strs(argv)
142
+ ret_argv = []
143
+ sub_strs = []
144
+ if argv.size == 0
145
+ require "readline"
146
+ line = Readline.readline "cmd: ", true
147
+ ret_argv.replace line.strip.split(/\s+/)
148
+ line = Readline.readline "pat/sub[/options]: ", true
149
+ sub_strs << line.strip
150
+ else
151
+ last_dashdash_idx = argv.rindex('--')
152
+ argv.each_with_index do |arg, i|
153
+ if arg == '--' && i == last_dashdash_idx
154
+ j = i+1
155
+ while sub_str = argv[j]
156
+ sub_strs << sub_str
157
+ j += 1
158
+ end
159
+ break
160
+ else
161
+ ret_argv << arg
162
+ end
163
+ end
164
+ end
165
+ if sub_strs.empty?
166
+ cmd = ret_argv.shift
167
+ if cmd == "--help" || cmd == "-h"
168
+ print_help(full: cmd == "--help")
169
+ exit 0
170
+ elsif cmd == "--version" || cmd == "-v"
171
+ print_version
172
+ exit 0
173
+ end
174
+ exec cmd, *ret_argv
175
+ end
176
+ [ret_argv, sub_strs]
177
+ end
178
+
179
+ argv, sub_strs = parse_argv_and_sub_strs(ARGV)
180
+
181
+ def flag(flags_str, char)
182
+ flag = flags_str.include?(char)
183
+ flags_str.delete!(char)
184
+ flag
185
+ end
186
+
187
+ def flag_by_name(name)
188
+ FLAGS.each do |fhash|
189
+ next if fhash.nil?
190
+ if fhash[:name] == name
191
+ return fhash[:flag]
192
+ end
193
+ end
194
+ raise ArgumentError, "bad flag: #{name}"
195
+ end
196
+
197
+ def raw_global_options?(raw_flags)
198
+ raw_flags = raw_flags.strip
199
+ return false if raw_flags.empty?
200
+ raw_flags.gsub(Regexp.new(GLOBAL_FLAGS.join('|')), '').empty?
201
+ end
202
+
203
+ def parse_sub_strs(sub_strs)
204
+ ret = []
205
+ pats = []
206
+ subs = []
207
+ match_flags = []
208
+ global_flags = GLOBAL_FLAGS_MAP.transform_values { nil }
209
+ raw_flags = []
210
+ sub_strs.each_with_index do |sub_str, sub_i|
211
+ # global flags at end of substitutions list, starts with /, ex: /o
212
+ if sub_strs[sub_i+1].nil? && sub_str[0] == '/' && raw_global_options?(sub_str[1..-1])
213
+ flags = sub_str[1..-1]
214
+ else
215
+ pat, sub, flags = sub_str.split(/(?<!\\)\//)
216
+ pat = pat.to_s.dup
217
+ sub = sub.to_s.dup
218
+ flags = flags.to_s.dup
219
+ flags.strip!
220
+ raw_flags << flags.dup
221
+ flags_hash = {
222
+ first_match: flag(flags, flag_by_name(:first_match)),
223
+ last_match: flag(flags, flag_by_name(:last_match)),
224
+ expand_star: flag(flags, flag_by_name(:expand_star)),
225
+ literal: flag(flags, flag_by_name(:literal)),
226
+ ignorecase: flag(flags, flag_by_name(:ignorecase)),
227
+ general: flag(flags, flag_by_name(:general)),
228
+ }
229
+ pats << pat
230
+ subs << sub
231
+ match_flags << flags_hash
232
+ end
233
+
234
+ if global_flags[:interactive]
235
+ flags.delete!(GLOBAL_FLAGS_MAP[:interactive])
236
+ else
237
+ global_flags[:interactive] = flag(flags, flag_by_name(:interactive)) || ARGV.size == 0
238
+ end
239
+ if global_flags[:print_only]
240
+ flags.delete!(GLOBAL_FLAGS_MAP[:print_only])
241
+ else
242
+ global_flags[:print_only] = flag(flags, flag_by_name(:print_only))
243
+ end
244
+ if global_flags[:print_only_no_newline]
245
+ flags.delete!(GLOBAL_FLAGS_MAP[:print_only_no_newline])
246
+ else
247
+ global_flags[:print_only_no_newline] = flag(flags, flag_by_name(:print_only_no_newline))
248
+ end
249
+ if global_flags[:copy_to_clipboard]
250
+ flags.delete!(GLOBAL_FLAGS_MAP[:copy_to_clipboard])
251
+ else
252
+ global_flags[:copy_to_clipboard] = flag(flags, flag_by_name(:copy_to_clipboard))
253
+ end
254
+ if global_flags[:verbose]
255
+ flags.delete!(GLOBAL_FLAGS_MAP[:verbose])
256
+ else
257
+ global_flags[:verbose] = flag(flags, flag_by_name(:verbose))
258
+ end
259
+ if global_flags[:debug]
260
+ flags.delete!(GLOBAL_FLAGS_MAP[:debug])
261
+ else
262
+ global_flags[:debug] = flag(flags, flag_by_name(:debug))
263
+ end
264
+ unless flags.empty?
265
+ $stderr.puts "Warning: unknown flag#{'s' if flags.size != 1}: #{flags}"
266
+ end
267
+ end
268
+
269
+ ret << pats << subs << match_flags << raw_flags << global_flags
270
+ end
271
+
272
+ pats, subs, flags_hashes, raw_flags, global_flags = parse_sub_strs(sub_strs)
273
+
274
+ if pats.empty? && flags_hashes.empty? && global_flags.empty?
275
+ $stderr.puts "Incorrect substitution, format is: pattern/substitution[/options]"
276
+ exit 1
277
+ end
278
+
279
+ def build_regexps(pats, flags_hashes)
280
+ ret = []
281
+ pats.each_with_index do |pat, idx|
282
+ flags = flags_hashes[idx]
283
+ regexp_opts = String.new
284
+ regexp_opts << 'i' if flags.fetch(:ignorecase)
285
+ ret << if flags.fetch(:literal)
286
+ Regexp.new Regexp.escape(pat), regexp_opts
287
+ else
288
+ Regexp.new(pat, regexp_opts)
289
+ end
290
+ end
291
+ ret
292
+ end
293
+
294
+ regexps = build_regexps(pats, flags_hashes)
295
+
296
+ NextRegexpIteration = Class.new(StopIteration)
297
+
298
+ def argv_replace!(argv, regexps, subs, flags)
299
+ raise ArgumentError unless [regexps.size, subs.size, flags.size].uniq.size == 1
300
+ num_replacements = 0
301
+
302
+ argv.map! { |arg| arg.frozen? ? arg.dup : arg } # make sure they're mutable
303
+
304
+ regexps.each_with_index do |regexp, i|
305
+ begin
306
+ iter_method = flags[i].fetch(:last_match) ? :reverse_each : :each
307
+ argv.send(iter_method) do |arg|
308
+ if arg =~ regexp
309
+ if flags[i].fetch(:general)
310
+ new_arg = arg.gsub(regexp, subs[i])
311
+ scan_size = arg.scan(regexp).size
312
+ else
313
+ new_arg = arg.sub(regexp, subs[i])
314
+ scan_size = arg.scan(regexp).size
315
+ scan_size = 1 if scan_size > 1
316
+ end
317
+ if new_arg != arg || new_arg == subs[i]
318
+ num_replacements += scan_size
319
+ end
320
+ arg.replace(new_arg)
321
+ if flags[i].fetch(:first_match) || flags[i].fetch(:last_match)
322
+ raise NextRegexpIteration
323
+ end
324
+ end
325
+ end
326
+ rescue NextRegexpIteration
327
+ # continue
328
+ end
329
+ end
330
+
331
+ argv.delete_if { |arg| arg.strip.empty? }
332
+
333
+ num_replacements
334
+ end
335
+
336
+ num_replacements = argv_replace!(argv, regexps, subs, flags_hashes)
337
+
338
+ def copy!(cmd_line, global_flags)
339
+ cmd_line = cmd_line.dup
340
+ cmd_line << "\n" if cmd_line.empty?
341
+ if /linux/i =~ RUBY_PLATFORM
342
+ copy_bin = "xclip"
343
+ copy_cmd = "xclip -selection clipboard"
344
+ copy_proc = lambda do |f|
345
+ system("#{copy_cmd} < #{f.path}", out: "/dev/null")
346
+ end
347
+ elsif /darwin/i =~ RUBY_PLATFORM
348
+ copy_bin = "pbcopy"
349
+ copy_cmd = copy_bin
350
+ copy_proc = lambda do |f|
351
+ system("cat #{f.path} | #{copy_cmd}", out: "/dev/null")
352
+ end
353
+ else
354
+ $stderr.puts "Warning: don't know how to get copy command for #{RUBY_PLATFORM}"
355
+ global_flags[:print_only] = true
356
+ return
357
+ end
358
+ system("which #{copy_bin}", out: "/dev/null")
359
+ unless $?.success?
360
+ $stderr.puts "Warning: can't find #{copy_bin}, please install it and put it in your PATH"
361
+ global_flags[:print_only] = true
362
+ return
363
+ end
364
+ require "tempfile"
365
+ Tempfile.create("sub_cmd") do |f|
366
+ f.write cmd_line
367
+ f.flush
368
+ copy_proc.call(f)
369
+ puts "Copied" if $?.success?
370
+ end
371
+ end
372
+
373
+ def exec_cmd(argv, regexps, subs, raw_flags, num_replacements, global_flags)
374
+ if global_flags.fetch(:debug)
375
+ puts "Patterns: #{regexps.inspect}"
376
+ puts "Substitutions: #{subs.inspect}"
377
+ puts "Flags: #{raw_flags.inspect}"
378
+ end
379
+ if global_flags.fetch(:verbose)
380
+ puts "#{num_replacements} replacement#{'s' if num_replacements != 1}"
381
+ end
382
+
383
+ if argv.empty?
384
+ copy!("", global_flags) if global_flags.fetch(:copy_to_clipboard)
385
+ if global_flags.fetch(:print_only)
386
+ puts ""
387
+ elsif global_flags.fetch(:print_only_no_newline)
388
+ print "" # does nothing
389
+ else
390
+ puts "Nothing to execute"
391
+ end
392
+ exit 0
393
+ end
394
+
395
+ cmd = argv.shift
396
+ cmd_line = "#{cmd} #{argv.join(' ')}"
397
+ action = global_flags.fetch(:copy_to_clipboard) ? "copy" : "execute"
398
+ if global_flags.fetch(:interactive) && !global_flags.fetch(:print_only)
399
+ $stdout.puts "Would you like to #{action} the following command? [y(es),n(o)]"
400
+ $stdout.puts cmd_line
401
+ ans = $stdin.gets().strip
402
+ if ans !~ /y(es)?/i # treat as no
403
+ exit 1
404
+ end
405
+ end
406
+ if global_flags.fetch(:print_only) || global_flags.fetch(:print_only_no_newline) || !global_flags.fetch(:interactive)
407
+ print_method = global_flags.fetch(:print_only_no_newline) ? :print : :puts
408
+ $stdout.send(print_method, cmd_line)
409
+ $stdout.flush
410
+ end
411
+ copy!(cmd_line, global_flags) if global_flags.fetch(:copy_to_clipboard)
412
+ exit 0 if global_flags.fetch(:print_only) || global_flags.fetch(:print_only_no_newline) || global_flags.fetch(:copy_to_clipboard)
413
+ begin
414
+ exec cmd, *argv
415
+ rescue SystemCallError => e
416
+ if e.class == Errno::ENOENT
417
+ # act like a shell
418
+ $stderr.puts "#{cmd}: command not found"
419
+ else
420
+ $stderr.puts "#{e.class}: #{e.message}"
421
+ end
422
+ exit e.errno
423
+ end
424
+ end
425
+
426
+ exec_cmd(argv, regexps, subs, raw_flags, num_replacements, global_flags)
metadata ADDED
@@ -0,0 +1,96 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: cmdline-sub
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.1
5
+ platform: ruby
6
+ authors:
7
+ - Luke Gruber
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2024-11-20 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: debug
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 1.9.2
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 1.9.2
27
+ - !ruby/object:Gem::Dependency
28
+ name: minitest
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: 5.25.1
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: 5.25.1
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: 13.2.1
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: 13.2.1
55
+ description: |
56
+ sub substitutes the matching pattern in every word in the command line with the
57
+ substitution. Only the first matched pattern in the word is substituted unless
58
+ the 'g' flag is given. There are various options that change the way substitutions
59
+ are performed or the way matches are made. Patterns can have regular expression
60
+ wildcards (special characters) in them, and the regular expression engine used is
61
+ Ruby's. Special characters are interpreted as special unless the 'L' flag is given.
62
+ email: luke.gru@gmail.com
63
+ executables:
64
+ - sub
65
+ extensions: []
66
+ extra_rdoc_files: []
67
+ files:
68
+ - Gemfile
69
+ - LICENSE
70
+ - README
71
+ - Rakefile
72
+ - bin/sub
73
+ homepage: https://github.com/luke-gru/sub
74
+ licenses:
75
+ - MIT
76
+ metadata: {}
77
+ post_install_message:
78
+ rdoc_options: []
79
+ require_paths:
80
+ - lib
81
+ required_ruby_version: !ruby/object:Gem::Requirement
82
+ requirements:
83
+ - - ">="
84
+ - !ruby/object:Gem::Version
85
+ version: 2.0.0
86
+ required_rubygems_version: !ruby/object:Gem::Requirement
87
+ requirements:
88
+ - - ">="
89
+ - !ruby/object:Gem::Version
90
+ version: '0'
91
+ requirements: []
92
+ rubygems_version: 3.5.16
93
+ signing_key:
94
+ specification_version: 4
95
+ summary: Sub is a commandline substitution editor
96
+ test_files: []