-
Notifications
You must be signed in to change notification settings - Fork 7
/
command_substitution.lua
179 lines (160 loc) · 5.86 KB
/
command_substitution.lua
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
--------------------------------------------------------------------------------
-- PROTOTYPE SCRIPT -- Disabled by default; see below for how to enable it.
--------------------------------------------------------------------------------
-- Usage:
--
-- This simulates very simplistic command substitutions similar to bash. Any
-- "$(command)" in a command line is replaced by the output from running the
-- specified command.
--
-- For example:
--
-- echo $(date /t & time /t)
--
-- First runs "date /t & time /t" and replaces the "$(...)" with the output from
-- the command. Since the output is the current date and the current time,
-- after command substitution the command line becomes something like:
--
-- echo Sat 07/09/2022 11:08 PM
--
-- And then finally the resulting command is executed.
--
-- The following global configuration variables in Lua control how this script
-- functions:
--
-- clink_gizmos_command_substitution
-- [true|false] Set this global variable to true to enable this
-- script. This script is disabled by default.
--
--
-- IMPORTANT WARNING: This is a very simple and stupid implementation, and it
-- does not (and cannot) work the same as bash. It will not work quite as
-- expected in many cases. But if the limitations are understood and respected,
-- then it can still be useful and powerful.
--
--
-- Here are some of the limitations:
--
-- - WHETHER a command substitution runs is different than in bash!
-- - The ORDER in which command substitutions run is different than in bash!
-- - Only a small subset of the bash syntax is supported.
-- - Nested substitutions are not supported; neither nested via typing nor
-- nested via substitution.
-- - This spawns new cmd shells to invoke commands. This means commands cannot
-- affect the current shell's state: changing env vars or cwd or etc do not
-- affect the current shell.
-- - Newlines and tab characters in the output are replaced with spaces before
-- substitution into the command line.
-- - CMD does not support command lines longer than a total length of about
-- 8,000 characters.
--
-- Bash intelligently skips command substitutions that don't need to be
-- performed, for example in an `else` clause that is not reached. But this
-- script stupidly ALWAYS performs ALL command substitutions no matter whether
-- CMD will actually reach processing that part of the command line.
--
-- Bash intelligently performs command substitutions in the correct order with
-- respect to other parts of the command line that precede or follow the command
-- substitutions. But this script stupidly performs ALL command substitutions
-- BEFORE any other processing happens. That means command substitutions can't
-- successfully refer to or use outputs from earlier parts of the command line;
-- because this script does not understand the rest of the command line and
-- doesn't evaluate things in the right order.
if not clink_gizmos_command_substitution then -- luacheck: no global
return
end
if not clink.onfilterinput then
print('command_substitution.lua requires a newer version of Clink; please upgrade.')
return
end
settings.add('color.command_substitution', 'sgr 7', 'Color for command substitutions')
local function find_command_end(line, s)
local quote
local level = 1
local i = s + 2
while i <= #line do
local c = line:sub(i, i)
if c == '"' then
quote = not quote
elseif quote then -- luacheck: ignore 542
-- Accept characters between quotes verbatim.
elseif c == '^' then
i = i + 1
elseif c == '(' then
level = level + 1
elseif c == ')' then
level = level - 1
if level == 0 then
return i
end
end
i = i + 1
end
end
local function substitution(line)
local i = 1
local result = ''
local continue
while true do
-- Find a $(command).
local s = line:find('%$%(', i)
local e = s and find_command_end(line, s)
if not s or not e then
-- Concat the rest of the input line.
result = result..line:sub(i)
break
end
-- Concat what precedes the $(command).
result = result..line:sub(i, s - 1)
i = e + 1
-- Substitution was found, so halt further onfilterinput processing.
continue = false
-- Spawn a new shell to invoke the $(command).
local c = line:sub(s + 2, e - 1)
local f = io.popen(c)
if f then
-- Read the command's output.
local o = f:read('*a') or ''
-- Trim trailing line endings.
while true do
local t = o:sub(#o)
if t ~= '\r' or t ~= '\n' then
break
end
o = o:sub(1, #o - 1)
end
f:close()
-- Replace problem characters with spaces.
o = o:gsub('[\r\n\t]', ' ')
-- Append the output to the input line.
result = result..o
end
end
return result, continue
end
clink.onfilterinput(substitution)
local cl = clink.classifier(1)
function cl:classify(commands) -- luacheck: no unused
if not commands[1] then
return
end
local color = settings.get('color.command_substitution')
if not color or color == '' then
return
end
local line_state = commands[1].line_state
local classifications = commands[1].classifications
local line = line_state:getline()
local i = 1
while true do
-- Find a $(command).
local s = line:find('%$%(', i)
local e = s and find_command_end(line, s)
if not s or not e then
break
end
-- Apply color.
classifications:applycolor(s, e + 1 - s, color, true--[[overwrite]])
i = e + 1
end
end