Skip to content

Commit 728eefb

Browse files
committed
Add Exec2, the "better version of Exec"
Add a new function `Exec2` which is as easy to use as `Exec` but much more powerful and less easy to misuse: - don't pass everything to a shell, thus avoiding compatibility issues due to different shells on different systems (this also has one downside: you can't use redirects like ">/dev/null") - pass arguments as separate strings, thus avoiding any issues with quoting quotes, quoting spaces, etc. - return the exit code of the executed command so that one can determine its success or failure; the return value is actually a record to allow returning other data, such as the programs output - make it very easy to override the input and output streams - by default, pass no input to the program (instead of passing anything the user might type, as `Exec` does); this can be changed by adding an input stream to the argument list - by default, capture any output of the program into a string and return it, instead of just printing the output to the console; this can be changed by adding an output stream to the argument list
1 parent 0009f2b commit 728eefb

4 files changed

Lines changed: 111 additions & 28 deletions

File tree

lib/helpview.gi

Lines changed: 29 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ if ARCH_IS_WINDOWS() then
7979
winfilename:=MakeExternalFilename( SplitString( filename, "#" )[1] );
8080
fi;
8181
Print( "Opening help page ", winfilename, " in default windows browser ... \c" );
82-
Exec( Concatenation("start ", winfilename ) );
82+
Exec2( "start", winfilename );
8383
Print( "done! \n" );
8484
end
8585
);
@@ -137,7 +137,7 @@ elif ARCH_IS_MAC_OS_X() then
137137
fi;
138138
file := file.file;
139139
fi;
140-
Exec(Concatenation("open -a Preview ", file));
140+
Exec2("open", "-a", "Preview", file);
141141
Print("# see page ", page, " in the Preview window.\n");
142142
end
143143
);
@@ -154,7 +154,7 @@ elif ARCH_IS_MAC_OS_X() then
154154
fi;
155155
file := file.file;
156156
fi;
157-
Exec(Concatenation("open -a \"Adobe Reader\" ", file));
157+
Exec2("open", "-a", "Adobe Reader", file);
158158
Print("# see page ", page, " in the Adobe Reader window.\n");
159159
end
160160
);
@@ -171,7 +171,7 @@ elif ARCH_IS_MAC_OS_X() then
171171
fi;
172172
file := file.file;
173173
fi;
174-
Exec(Concatenation("open ", file));
174+
Exec2("open ", file);
175175
Print("# see page ", page, " in the pdf viewer window.\n");
176176
end
177177
);
@@ -186,15 +186,15 @@ elif ARCH_IS_MAC_OS_X() then
186186
fi;
187187
file := file.file;
188188
fi;
189-
Exec( Concatenation(
190-
"osascript <<ENDSCRIPT\n",
191-
"tell application \"Skim\"\n",
192-
"activate\n",
193-
"open \"", file, "\"\n",
194-
"set theDoc to document of front window\n",
195-
"go theDoc to page ",String(page)," of theDoc\n",
196-
"end tell\n",
197-
"ENDSCRIPT\n" ) );
189+
Exec2("osascript",
190+
InputTextString(Concatenation("""
191+
tell application "Skim"
192+
activate
193+
open """, ViewString(file), """
194+
set theDoc to document of front window
195+
go theDoc to page """,String(page),""" of theDoc
196+
end tell
197+
""")));
198198
return;
199199
end
200200
);
@@ -206,17 +206,20 @@ else # UNIX but not macOS
206206
HELP_VIEWER_INFO.browser := rec(
207207
type := "url",
208208
show := function( url )
209+
local str;
210+
str := "";
211+
Exec2("wslpath", "-a", "-w", url, OutputTextString(str, false));
209212
# Ignoring part of the URL after '#' since we are unable
210213
# to navigate to the precise location on Windows
211214
url := SplitString( url, "#" )[1];
212-
Exec(Concatenation("explorer.exe \"$(wslpath -a -w \"",url, "\")\""));
215+
Exec2("explorer.exe", str);
213216
end
214217
);
215218

216219
HELP_VIEWER_INFO.("pdf viewer") := rec(
217220
type := "pdf",
218221
show := function(file)
219-
local page;
222+
local page, str;
220223
# unfortunately one cannot (yet?) give a start page to windows
221224
page := 1;
222225
if IsRecord(file) then
@@ -225,7 +228,10 @@ else # UNIX but not macOS
225228
fi;
226229
file := file.file;
227230
fi;
228-
Exec(Concatenation("explorer.exe \"$(wslpath -a -w \"",file, "\")\""));
231+
232+
str := "";
233+
Exec2("wslpath", "-a", "-w", url, OutputTextString(str, false));
234+
Exec2("explorer.exe", str);
229235
Print("# see page ", page, " in PDF.\n");
230236
end
231237
);
@@ -234,15 +240,15 @@ else # UNIX but not macOS
234240
HELP_VIEWER_INFO.netscape := rec(
235241
type := "url",
236242
show := function(url)
237-
Exec(Concatenation("netscape -remote \"openURL(file:", url, ")\""));
243+
Exec2("netscape", "-remote", Concatenation("openURL(file:", url, ")"));
238244
end
239245
);
240246

241247
# html version with mozilla
242248
HELP_VIEWER_INFO.mozilla := rec(
243249
type := "url",
244250
show := function(url)
245-
Exec(Concatenation("mozilla -remote \"openURL(file:", url, ")\""));
251+
Exec2("mozilla", "-remote", Concatenation("openURL(file:", url, ")"));
246252
end
247253
);
248254

@@ -275,36 +281,36 @@ else # UNIX but not macOS
275281
HELP_VIEWER_INFO.lynx := rec(
276282
type := "url",
277283
show := function(url)
278-
Exec(Concatenation("lynx \"", url, "\""));
284+
Exec2("lynx", url);
279285
end
280286
);
281287

282288
# html version with w3m
283289
HELP_VIEWER_INFO.w3m := rec(
284290
type := "url",
285291
show := function(url)
286-
Exec(Concatenation("w3m \"", url, "\""));
292+
Exec2("w3m", url);
287293
end
288294
);
289295

290296
HELP_VIEWER_INFO.elinks := rec(
291297
type := "url",
292298
show := function(url)
293-
Exec(Concatenation("elinks \"", url, "\""));
299+
Exec2("elinks", url);
294300
end
295301
);
296302

297303
HELP_VIEWER_INFO.links2ng := rec(
298304
type := "url",
299305
show := function(url)
300-
Exec(Concatenation("links2 \"", url, "\""));
306+
Exec2("links2", url);
301307
end
302308
);
303309

304310
HELP_VIEWER_INFO.links2 := rec(
305311
type := "url",
306312
show := function(url)
307-
Exec(Concatenation("links2 -g \"", url, "\""));
313+
Exec2("links2", "-g", url);
308314
end
309315
);
310316
fi;

lib/process.gd

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,3 +186,5 @@ DeclareOperation( "Process",
186186
## <#/GAPDoc>
187187
##
188188
DeclareGlobalFunction( "Exec" );
189+
190+
DeclareGlobalName( "Exec2" );

lib/process.gi

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -261,3 +261,77 @@ InstallGlobalFunction( Exec, function( arg )
261261
Process( dir, shell, InputTextUser(), OutputTextUser(), [ cs, cmd ] );
262262

263263
end );
264+
265+
# TODO: document this, come up with a better name, write some tests...
266+
BindGlobal( "Exec2", function( arg )
267+
local args, result, a, input, output, dir, cmd;
268+
269+
args := [];
270+
result := rec();
271+
272+
# parse the inputs
273+
for a in arg do
274+
if IsDirectory(a) then
275+
if IsBound(dir) then
276+
Error("must specify at most one working directory");
277+
fi;
278+
dir := a;
279+
elif IsInputStream(a) then
280+
if IsBound(input) then
281+
Error("must specify at most one input stream");
282+
fi;
283+
input := a;
284+
elif IsOutputStream(a) then
285+
if IsBound(output) then
286+
Error("must specify at most one output stream");
287+
fi;
288+
output := a;
289+
elif IsString(a) then
290+
ConvertToStringRep(a);
291+
if not IsBound(cmd) then
292+
cmd := a;
293+
else
294+
Add(args, a);
295+
fi;
296+
else
297+
Error("unsupported argument type");
298+
fi;
299+
od;
300+
301+
if not IsBound(cmd) then
302+
Error("must specify a command to execute");
303+
fi;
304+
305+
# determine full executable path if it is not already a path
306+
if not '/' in cmd then
307+
a := Filename( DirectoriesSystemPrograms(), cmd );
308+
if a = fail and ARCH_IS_WINDOWS() then
309+
a := Filename( DirectoriesSystemPrograms(), Concatenation( cmd, ".exe" ) );
310+
fi;
311+
if a = fail then
312+
Error("could not locate executable for '", cmd, "'");
313+
fi;
314+
cmd := a;
315+
fi;
316+
317+
# set default working directory if necessary
318+
if not IsBound(dir) then
319+
dir := DirectoryCurrent();
320+
fi;
321+
322+
# if no input stream was specified, pass no input to the command
323+
if not IsBound(input) then
324+
input := InputTextNone();
325+
fi;
326+
327+
# if no output stream was specified, put output into the returned record
328+
if not IsBound(output) then
329+
result.output := "";
330+
output := OutputTextString(result.output, false);
331+
fi;
332+
333+
# execute the command
334+
result.status := Process( dir, cmd, input, output, args );
335+
336+
return result;
337+
end );

lib/streams.gi

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1311,19 +1311,20 @@ InstallGlobalFunction( InputFromUser,
13111311
InstallGlobalFunction( OpenExternal, function(filename)
13121312
local file;
13131313
if ARCH_IS_MAC_OS_X() then
1314-
Exec(Concatenation("open \"",filename,"\""));
1314+
Exec2("open", filename);
13151315
elif ARCH_IS_WINDOWS() then
1316-
Exec(Concatenation("cmd /c start \"",filename,"\""));
1316+
Exec2("cmd", "/c", "start", filename);
13171317
elif ARCH_IS_WSL() then
13181318
# If users pass a URL, make sure if does not get mangled.
13191319
if ForAny(["https://", "http://"], {pre} -> StartsWith(filename, pre)) then
13201320
file := filename;
13211321
else
1322-
file := Concatenation("$(wslpath -a -w \"",filename,"\")");
1322+
file := "";
1323+
Exec2("wslpath", "-a", "-w", filename, OutputTextString(file, false));
13231324
fi;
1324-
Exec(Concatenation("explorer.exe \"", file, "\""));
1325+
Exec2("explorer.exe", file);
13251326
else
1326-
Exec(Concatenation("xdg-open \"",filename,"\""));
1327+
Exec2("xdg-open", filename);
13271328
fi;
13281329
end );
13291330

0 commit comments

Comments
 (0)