cmdline-sub 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/Gemfile +3 -0
- data/LICENSE +9 -0
- data/README +79 -0
- data/Rakefile +85 -0
- data/bin/sub +426 -0
- 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
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: []
|