cmdline-sub 0.1.1 → 0.1.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README +13 -5
- data/Rakefile +1 -0
- data/bin/sub +90 -81
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c389766a40ec1f0894ff39ae9156d8ab1f737e32da5e6a83ac82e758fd420e06
|
4
|
+
data.tar.gz: cbe56894e3d43b63ec14da75db54b820c9347a197e32c9ae6a5cc649ddabdb1c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 71806a610d4b0a4e3e14e6ed75ab31f0dc4d1c34ac72767b3b8e063ed139322685f1c0585929f58f447a25e07f717171892d7ed35ace82f4a8d67ad9dd00b394
|
7
|
+
data.tar.gz: d08f7dd174f7546d4b451a5ee6a0639eea308ca894db10c861dbd3556efbe6965770c2529a822da2a64821de52a68444d3771fa253a9e6f46ba17923e65b77fb
|
data/README
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
Sub
|
2
2
|
===
|
3
3
|
|
4
|
-
sub v0.1.
|
5
|
-
Usage: sub COMMANDLINE -- (PATTERN/SUBSTITUTION[/
|
4
|
+
sub v0.1.3
|
5
|
+
Usage: sub COMMANDLINE -- (PATTERN/SUBSTITUTION[/SUB_FLAGS])+ (/GLOBAL_FLAGS)?
|
6
6
|
|
7
7
|
sub substitutes the matching pattern in every word in the command line with the
|
8
8
|
substitution. Only the first matched pattern in the word is substituted unless
|
@@ -53,16 +53,22 @@ Substitution flags:
|
|
53
53
|
ex: sub cp here.txt there.txt -- CP/mv/i #=> mv here.txt there.txt
|
54
54
|
-g: General substitution: substitute all matches in the word, not just the first
|
55
55
|
ex: sub cp here.txt there.txt -- ./_/g #=> __ ________ _________
|
56
|
-
-
|
57
|
-
ex: sub ls mydir -- mydir/*/
|
56
|
+
-r: Don't escape substituted value, allow shell to expand and split it
|
57
|
+
ex: sub ls mydir -- 'mydir/*/r' #=> ls bin README
|
58
58
|
Global flags:
|
59
|
-
-p: Print the command instead of executing it
|
59
|
+
-p: Print the command instead of executing it (adds newline)
|
60
|
+
-P: Print the command instead of executing it (doesn't add newline)
|
60
61
|
-c: Copy the command to clipboard instead of executing it
|
61
62
|
-I: Set interactive mode: shows the command and asks if you want to execute it
|
62
63
|
-v: Set verbose mode
|
63
64
|
-D: Set debug mode
|
65
|
+
Other flags:
|
66
|
+
-h,--help: Show help (-h shows short help, --help shows full)
|
67
|
+
-v,--version: Show version followed by a newline
|
64
68
|
|
65
69
|
Install:
|
70
|
+
gem install cmdline-sub
|
71
|
+
or:
|
66
72
|
cd $HOME
|
67
73
|
git clone git@github.com:luke-gru/sub.git
|
68
74
|
cd sub
|
@@ -71,6 +77,8 @@ Install:
|
|
71
77
|
# INSTALL_PREFIX="/bin" sudo rake install
|
72
78
|
|
73
79
|
Uninstall:
|
80
|
+
gem uninstall cmdline-sub
|
81
|
+
or:
|
74
82
|
cd $HOME/sub
|
75
83
|
sudo rake uninstall # removes /usr/local/bin/sub
|
76
84
|
# or
|
data/Rakefile
CHANGED
@@ -57,6 +57,7 @@ end
|
|
57
57
|
|
58
58
|
desc "Uninstall (removes /usr/local/bin/sub or $(INSTALL_PREFIX)/sub). Use sudo if necessary."
|
59
59
|
task :uninstall do
|
60
|
+
require "fileutils"
|
60
61
|
install_dir = ENV["INSTALL_PREFIX"] || "/usr/local/bin"
|
61
62
|
unless File.directory?(install_dir)
|
62
63
|
$stderr.puts "Directory #{install_dir} does not exist. Uninstall failed."
|
data/bin/sub
CHANGED
@@ -2,10 +2,15 @@
|
|
2
2
|
# vim: set ft=ruby
|
3
3
|
# frozen_string_literal: true
|
4
4
|
|
5
|
-
|
5
|
+
if ENV["TESTING_SUB_CMD"] == "1" && ENV["DEBUGGING_SUB_CMD"] == "1"
|
6
|
+
require "debug"
|
7
|
+
end
|
8
|
+
require "shellwords"
|
9
|
+
|
10
|
+
VERSION = "0.1.3"
|
6
11
|
|
7
12
|
USAGE = <<EOF
|
8
|
-
Usage: sub COMMANDLINE -- (PATTERN/SUBSTITUTION[/
|
13
|
+
Usage: sub COMMANDLINE -- (PATTERN/SUBSTITUTION[/SUB_FLAGS])+ (/GLOBAL_FLAGS)?
|
9
14
|
|
10
15
|
sub substitutes the matching pattern in every word in the command line with the
|
11
16
|
substitution. Only the first matched pattern in the word is substituted unless
|
@@ -46,6 +51,8 @@ EOF
|
|
46
51
|
|
47
52
|
INSTALLATION = <<EOF
|
48
53
|
Install:
|
54
|
+
\t gem install cmdline-sub
|
55
|
+
or:
|
49
56
|
\tcd $HOME
|
50
57
|
\tgit clone git@github.com:luke-gru/sub.git
|
51
58
|
\tcd sub
|
@@ -56,6 +63,8 @@ EOF
|
|
56
63
|
|
57
64
|
UNINSTALLATION = <<EOF
|
58
65
|
Uninstall:
|
66
|
+
\t gem uninstall cmdline-sub
|
67
|
+
or:
|
59
68
|
\tcd $HOME/sub
|
60
69
|
\tsudo rake uninstall # removes /usr/local/bin/sub
|
61
70
|
\t# or
|
@@ -64,7 +73,7 @@ EOF
|
|
64
73
|
|
65
74
|
LICENSE = "License: MIT"
|
66
75
|
|
67
|
-
|
76
|
+
SUB_FLAGS = [
|
68
77
|
# per substitution flags
|
69
78
|
{ flag: 'f', name: :first_match, desc: "Substitute first matching word only.",
|
70
79
|
example: "sub wget https://wget.com -- wget/curl/f #=> curl https://wget.com",
|
@@ -81,10 +90,11 @@ FLAGS = [
|
|
81
90
|
{ flag: 'g', name: :general, desc: "General substitution: substitute all matches in the word, not just the first",
|
82
91
|
example: "sub cp here.txt there.txt -- ./_/g #=> __ ________ _________",
|
83
92
|
},
|
84
|
-
{ flag: '
|
85
|
-
example: [ "sub ls mydir -- mydir/*/
|
93
|
+
{ flag: 'r', name: :raw_mode, desc: "Don't escape substituted value, allow shell to expand and split it",
|
94
|
+
example: [ "sub ls mydir -- 'mydir/*/r' #=> ls bin README" ]
|
86
95
|
},
|
87
|
-
|
96
|
+
]
|
97
|
+
GLOBAL_FLAGS = [
|
88
98
|
# global flags
|
89
99
|
{ flag: 'p', name: :print_only, desc: "Print the command instead of executing it (adds newline)" },
|
90
100
|
{ flag: 'P', name: :print_only_no_newline, desc: "Print the command instead of executing it (doesn't add newline)" },
|
@@ -93,28 +103,26 @@ FLAGS = [
|
|
93
103
|
{ flag: 'v', name: :verbose, desc: "Set verbose mode" },
|
94
104
|
{ flag: 'D', name: :debug, desc: "Set debug mode" },
|
95
105
|
]
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
}
|
104
|
-
|
106
|
+
OTHER_FLAGS = [
|
107
|
+
{ flag: ['h', '--help'], name: :help, desc: "Show help (-h shows short help, --help shows full)", },
|
108
|
+
{ flag: ['v', '--version'], name: :version, desc: "Show version followed by a newline" },
|
109
|
+
]
|
110
|
+
FLAGS = SUB_FLAGS + GLOBAL_FLAGS + OTHER_FLAGS
|
111
|
+
FLAGS_BY_NAME = FLAGS.map do |f|
|
112
|
+
{ f[:name] => f[:flag] }
|
113
|
+
end.inject(Hash.new) { |memo, h| memo.merge(h) }
|
114
|
+
GLOBAL_FLAGS_BY_NAME = GLOBAL_FLAGS.map do |f|
|
115
|
+
{ f[:name] => f[:flag] }
|
116
|
+
end.inject(Hash.new) { |memo, h| memo.merge(h) }
|
117
|
+
RAW_GLOBAL_FLAGS = GLOBAL_FLAGS_BY_NAME.values
|
105
118
|
|
106
119
|
def print_help(full: true)
|
107
120
|
output = String.new
|
108
121
|
output << "sub v#{VERSION}" << "\n"
|
109
122
|
output << USAGE << "\n"
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
if fhash.nil?
|
114
|
-
output << "Global flags:\n"
|
115
|
-
next
|
116
|
-
end
|
117
|
-
output << "-#{fhash[:flag]}:\t#{fhash[:desc]}\n"
|
123
|
+
output_flag_info = lambda do |fhash|
|
124
|
+
flags = Array(fhash[:flag])
|
125
|
+
output << "-#{flags.join(',')}:\t#{fhash[:desc]}\n"
|
118
126
|
if ex = fhash[:example]
|
119
127
|
case ex
|
120
128
|
when String
|
@@ -126,6 +134,13 @@ def print_help(full: true)
|
|
126
134
|
end
|
127
135
|
end
|
128
136
|
end
|
137
|
+
output << "Flags:\n\n"
|
138
|
+
output << "Substitution flags:\n"
|
139
|
+
SUB_FLAGS.each(&output_flag_info)
|
140
|
+
output << "Global flags:\n"
|
141
|
+
GLOBAL_FLAGS.each(&output_flag_info)
|
142
|
+
output << "Other flags:\n"
|
143
|
+
OTHER_FLAGS.each(&output_flag_info)
|
129
144
|
if full
|
130
145
|
output << "\n" << INSTALLATION
|
131
146
|
output << "\n" << UNINSTALLATION
|
@@ -142,11 +157,8 @@ def parse_argv_and_sub_strs(argv)
|
|
142
157
|
ret_argv = []
|
143
158
|
sub_strs = []
|
144
159
|
if argv.size == 0
|
145
|
-
|
146
|
-
|
147
|
-
ret_argv.replace line.strip.split(/\s+/)
|
148
|
-
line = Readline.readline "pat/sub[/options]: ", true
|
149
|
-
sub_strs << line.strip
|
160
|
+
print_help(full: true)
|
161
|
+
exit 1
|
150
162
|
else
|
151
163
|
last_dashdash_idx = argv.rindex('--')
|
152
164
|
argv.each_with_index do |arg, i|
|
@@ -178,26 +190,28 @@ end
|
|
178
190
|
|
179
191
|
argv, sub_strs = parse_argv_and_sub_strs(ARGV)
|
180
192
|
|
181
|
-
def
|
193
|
+
def parse_simple_flag!(flags_str, char)
|
182
194
|
flag = flags_str.include?(char)
|
183
195
|
flags_str.delete!(char)
|
184
196
|
flag
|
185
197
|
end
|
186
198
|
|
187
199
|
def flag_by_name(name)
|
188
|
-
|
189
|
-
next if fhash.nil?
|
190
|
-
if fhash[:name] == name
|
191
|
-
return fhash[:flag]
|
192
|
-
end
|
193
|
-
end
|
194
|
-
raise ArgumentError, "bad flag: #{name}"
|
200
|
+
FLAGS_BY_NAME.fetch(name)
|
195
201
|
end
|
196
202
|
|
197
|
-
def
|
203
|
+
def raw_global_flags?(raw_flags)
|
198
204
|
raw_flags = raw_flags.strip
|
199
205
|
return false if raw_flags.empty?
|
200
|
-
raw_flags.gsub(Regexp.new(
|
206
|
+
raw_flags.gsub(Regexp.new(RAW_GLOBAL_FLAGS.join('|')), '').empty?
|
207
|
+
end
|
208
|
+
|
209
|
+
def parse_global_flag!(name, flags, global_flags)
|
210
|
+
if global_flags[name]
|
211
|
+
flags.delete!(GLOBAL_FLAGS_BY_NAME[name])
|
212
|
+
else
|
213
|
+
global_flags[name] = parse_simple_flag!(flags, flag_by_name(name))
|
214
|
+
end
|
201
215
|
end
|
202
216
|
|
203
217
|
def parse_sub_strs(sub_strs)
|
@@ -205,11 +219,11 @@ def parse_sub_strs(sub_strs)
|
|
205
219
|
pats = []
|
206
220
|
subs = []
|
207
221
|
match_flags = []
|
208
|
-
global_flags =
|
222
|
+
global_flags = GLOBAL_FLAGS_BY_NAME.transform_values { nil }
|
209
223
|
raw_flags = []
|
210
224
|
sub_strs.each_with_index do |sub_str, sub_i|
|
211
225
|
# global flags at end of substitutions list, starts with /, ex: /o
|
212
|
-
if sub_strs[sub_i+1].nil? && sub_str[0] == '/' &&
|
226
|
+
if sub_strs[sub_i+1].nil? && sub_str[0] == '/' && raw_global_flags?(sub_str[1..-1])
|
213
227
|
flags = sub_str[1..-1]
|
214
228
|
else
|
215
229
|
pat, sub, flags = sub_str.split(/(?<!\\)\//)
|
@@ -219,48 +233,27 @@ def parse_sub_strs(sub_strs)
|
|
219
233
|
flags.strip!
|
220
234
|
raw_flags << flags.dup
|
221
235
|
flags_hash = {
|
222
|
-
first_match:
|
223
|
-
last_match:
|
224
|
-
|
225
|
-
literal:
|
226
|
-
ignorecase:
|
227
|
-
general:
|
236
|
+
first_match: parse_simple_flag!(flags, flag_by_name(:first_match)),
|
237
|
+
last_match: parse_simple_flag!(flags, flag_by_name(:last_match)),
|
238
|
+
raw_mode: parse_simple_flag!(flags, flag_by_name(:raw_mode)),
|
239
|
+
literal: parse_simple_flag!(flags, flag_by_name(:literal)),
|
240
|
+
ignorecase: parse_simple_flag!(flags, flag_by_name(:ignorecase)),
|
241
|
+
general: parse_simple_flag!(flags, flag_by_name(:general)),
|
228
242
|
}
|
243
|
+
if flags_hash[:raw_mode]
|
244
|
+
flags_hash[:raw_mode] = 0 # bitfield
|
245
|
+
end
|
229
246
|
pats << pat
|
230
247
|
subs << sub
|
231
248
|
match_flags << flags_hash
|
232
249
|
end
|
233
250
|
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
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
|
251
|
+
parse_global_flag!(:interactive, flags, global_flags)
|
252
|
+
parse_global_flag!(:print_only, flags, global_flags)
|
253
|
+
parse_global_flag!(:print_only_no_newline, flags, global_flags)
|
254
|
+
parse_global_flag!(:copy_to_clipboard, flags, global_flags)
|
255
|
+
parse_global_flag!(:verbose, flags, global_flags)
|
256
|
+
parse_global_flag!(:debug, flags, global_flags)
|
264
257
|
unless flags.empty?
|
265
258
|
$stderr.puts "Warning: unknown flag#{'s' if flags.size != 1}: #{flags}"
|
266
259
|
end
|
@@ -272,7 +265,7 @@ end
|
|
272
265
|
pats, subs, flags_hashes, raw_flags, global_flags = parse_sub_strs(sub_strs)
|
273
266
|
|
274
267
|
if pats.empty? && flags_hashes.empty? && global_flags.empty?
|
275
|
-
$stderr.puts "Incorrect substitution, format is: pattern/substitution[/
|
268
|
+
$stderr.puts "Incorrect substitution, format is: pattern/substitution[/flags]"
|
276
269
|
exit 1
|
277
270
|
end
|
278
271
|
|
@@ -314,6 +307,9 @@ def argv_replace!(argv, regexps, subs, flags)
|
|
314
307
|
scan_size = arg.scan(regexp).size
|
315
308
|
scan_size = 1 if scan_size > 1
|
316
309
|
end
|
310
|
+
if flags[i].fetch(:raw_mode)
|
311
|
+
flags[i][:raw_mode] |= i+1
|
312
|
+
end
|
317
313
|
if new_arg != arg || new_arg == subs[i]
|
318
314
|
num_replacements += scan_size
|
319
315
|
end
|
@@ -370,7 +366,7 @@ def copy!(cmd_line, global_flags)
|
|
370
366
|
end
|
371
367
|
end
|
372
368
|
|
373
|
-
def exec_cmd(argv, regexps, subs, raw_flags,
|
369
|
+
def exec_cmd(argv, regexps, subs, flags_hashes, raw_flags, global_flags, num_replacements)
|
374
370
|
if global_flags.fetch(:debug)
|
375
371
|
puts "Patterns: #{regexps.inspect}"
|
376
372
|
puts "Substitutions: #{subs.inspect}"
|
@@ -393,11 +389,24 @@ def exec_cmd(argv, regexps, subs, raw_flags, num_replacements, global_flags)
|
|
393
389
|
end
|
394
390
|
|
395
391
|
cmd = argv.shift
|
396
|
-
|
392
|
+
argv_string = String.new
|
393
|
+
argv.each_with_index do |arg, i|
|
394
|
+
raw_mode = flags_hashes.any? { |flags_hash| flags_hash[:raw_mode] && (flags_hash[:raw_mode] & i+1) != 0 }
|
395
|
+
if raw_mode
|
396
|
+
argv_string << arg
|
397
|
+
else
|
398
|
+
argv_string << Shellwords.escape(arg)
|
399
|
+
end
|
400
|
+
unless argv[i+1].nil?
|
401
|
+
argv_string << " "
|
402
|
+
end
|
403
|
+
end
|
404
|
+
cmd_line = "#{cmd} #{argv_string}"
|
397
405
|
action = global_flags.fetch(:copy_to_clipboard) ? "copy" : "execute"
|
398
406
|
if global_flags.fetch(:interactive) && !global_flags.fetch(:print_only)
|
399
407
|
$stdout.puts "Would you like to #{action} the following command? [y(es),n(o)]"
|
400
408
|
$stdout.puts cmd_line
|
409
|
+
$stdout.flush
|
401
410
|
ans = $stdin.gets().strip
|
402
411
|
if ans !~ /y(es)?/i # treat as no
|
403
412
|
exit 1
|
@@ -411,7 +420,7 @@ def exec_cmd(argv, regexps, subs, raw_flags, num_replacements, global_flags)
|
|
411
420
|
copy!(cmd_line, global_flags) if global_flags.fetch(:copy_to_clipboard)
|
412
421
|
exit 0 if global_flags.fetch(:print_only) || global_flags.fetch(:print_only_no_newline) || global_flags.fetch(:copy_to_clipboard)
|
413
422
|
begin
|
414
|
-
exec
|
423
|
+
exec cmd_line
|
415
424
|
rescue SystemCallError => e
|
416
425
|
if e.class == Errno::ENOENT
|
417
426
|
# act like a shell
|
@@ -423,4 +432,4 @@ def exec_cmd(argv, regexps, subs, raw_flags, num_replacements, global_flags)
|
|
423
432
|
end
|
424
433
|
end
|
425
434
|
|
426
|
-
exec_cmd(argv, regexps, subs, raw_flags,
|
435
|
+
exec_cmd(argv, regexps, subs, flags_hashes, raw_flags, global_flags, num_replacements)
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: cmdline-sub
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Luke Gruber
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-
|
11
|
+
date: 2024-12-19 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: debug
|