File: programs.m2

package info (click to toggle)
macaulay2 1.21%2Bds-3
  • links: PTS, VCS
  • area: main
  • in suites: bookworm
  • size: 133,096 kB
  • sloc: cpp: 110,377; ansic: 16,306; javascript: 4,193; makefile: 3,821; sh: 3,580; lisp: 764; yacc: 590; xml: 177; python: 140; perl: 114; lex: 65; awk: 3
file content (172 lines) | stat: -rw-r--r-- 6,707 bytes parent folder | download
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
needs "methods.m2"

Program = new Type of HashTable
ProgramRun = new Type of HashTable
programPaths = new MutableHashTable

fixPath = programPath -> (
    -- escape any unescaped spaces or parentheses
    programPath = replace(///(?<!\\)([ ()])///, ///\\\1///, programPath);
    -- we expect a trailing slash in the path, but the paths given in the
    -- PATH environment variable likely will not have one, so we add one
    -- if needed
    if last programPath != "/" then programPath | "/"
    else programPath
)

-- returns (found, thisVersion)
-- found is an integer:
foundProgram              := 0
didNotFindProgram         := 1
foundProgramButOldVersion := 2
foundProgramButBadVersion := 3
-- thisVersion is a string containing the version number found, or null
--   if MinimumVersion option is not given or the version number can't be
--   be determined.
checkProgramPath = (cmds, pathToTry, prefix, opts) -> (
    verboseLog := if opts.Verbose then printerr else identity;
    -- unescape spaces/parentheses and resolve HOME for fileExists and fileMode
    unescapedPathToTry := replace(///^\$\{?HOME\}?///, getenv "HOME",
	replace(///\\([ ()])///, ///\1///, pathToTry));
    found := if all(apply(cmds, cmd -> addPrefix(cmd, prefix)), cmd -> (
	exe := unescapedPathToTry | first separate(" ", cmd);
	if not fileExists exe then (
	    verboseLog(exe, " does not exist"); false) else
	-- check executable bit
	if fileMode exe & 0o111 == 0 then (
	    verboseLog(exe, " exists but is not executable"); false) else (
	    verboseLog(exe, " exists and is executable");
	    verboseLog("running ", format(pathToTry | cmd), ":");
	    ret := run(pathToTry | cmd |
		if opts.Verbose then "" else " > /dev/null 2>&1" );
	    verboseLog("return value: " | ret);
	    ret == 0))) then foundProgram else didNotFindProgram;
    thisVersion := null;
    if found == foundProgram and opts.MinimumVersion =!= null then (
	thisVersion = replace("(^\\s+)|(\\s+$)", "", get("!" | pathToTry |
		addPrefix(opts.MinimumVersion_1, prefix)));
	if not match(///^\d[\-+\.:\~\da-zA-Z]*$///, thisVersion) then (
	    verboseLog("found version ", format thisVersion,
		" but this does not appear to be a valid version number");
	    found = foundProgramButBadVersion;
	    thisVersion = null;
	) else if thisVersion >= opts.MinimumVersion_0 then
	    verboseLog("found version ", thisVersion, " >= ",
		opts.MinimumVersion_0)
	else (
	    verboseLog("found, but version ", thisVersion, " < ",
		opts.MinimumVersion_0);
	    found = foundProgramButOldVersion;
	)
    );
    (found, thisVersion)
)

addPrefix = (cmd, prefix) ->
    if match(prefix_0, first separate(" ", cmd)) then prefix_1 | cmd else cmd

findProgram = method(TypicalValue => Program,
    Options => {
	RaiseError => true,
	Verbose => false,
	Prefix => {},
	AdditionalPaths => {},
	MinimumVersion => null
    })
findProgram(String, String) := opts -> (name, cmd) ->
    findProgram(name, {cmd}, opts)
findProgram(String, List) := opts -> (name, cmds) -> (
    if not (instance(opts.Prefix, List) and
	all(opts.Prefix, x -> instance(x, Sequence)) and
	all(opts.Prefix, x -> class \ x === (String, String))) then
	error "expected Prefix to be a list of sequences of two strings";
    if not (instance(opts.AdditionalPaths, List) and
	all(opts.AdditionalPaths, x -> instance(x, String))) then
	error "expected AdditionalPaths to be a list of strings";
    if opts.MinimumVersion =!= null and not(
	instance(opts.MinimumVersion, Sequence) and
	class \ opts.MinimumVersion === (String, String)) then
	error "expected MinimumVersion to be a sequence of two strings";
    pathsToTry := fixPath \ join(
	-- try user-configured path first
	if programPaths#?name then {programPaths#name} else {},
	-- now try M2-installed path
	{prefixDirectory | currentLayout#"programs"},
	-- any additional paths specified by the caller
	opts.AdditionalPaths,
	-- try PATH
	if getenv "PATH" == "" then {} else apply(separate(":", getenv "PATH"),
	    dir -> if dir == "" then "." else dir),
	-- try directory containing M2-binary
	{bindir});
    prefixes := {(".*", "")} | opts.Prefix;
    errorCode := didNotFindProgram;
    versionFound := "0.0";
    for pathToTry in pathsToTry do for prefix in prefixes do (
	(found, thisVersion) := checkProgramPath(cmds, pathToTry, prefix, opts);
	if found == foundProgram then return new Program from {
	    "name" => name,
	    "path" => pathToTry,
	    "prefix" => prefix } | if opts.MinimumVersion =!= null then {
	    "version" => thisVersion} else {} else
	if found != didNotFindProgram then (
	    errorCode = found;
	    if found == foundProgramButOldVersion and
		thisVersion > versionFound then versionFound = thisVersion
	    )
	);
    if opts.RaiseError then error(
	if errorCode == didNotFindProgram then "could not find " | name else
	if errorCode == foundProgramButOldVersion then "found " | name |
	    ", but version (" | versionFound | ") is too low" else
	if errorCode == foundProgramButBadVersion then "found " | name |
	    ", but could not determine version" else "unknown error");
    )

runProgram = method(TypicalValue => ProgramRun,
    Options => {
	RaiseError => true,
	KeepFiles => false,
	Verbose => false,
	RunDirectory => null
	})
runProgram(Program, String) := opts -> (program, args) ->
    runProgram(program, program#"name", args, opts)
runProgram(Program, String, String) := opts -> (program, name, args) -> (
    tmpFile := temporaryFileName();
    outFile := tmpFile | ".out";
    errFile := tmpFile | ".err";
    cmd := if opts.RunDirectory =!= null then (
	if not isDirectory opts.RunDirectory then
	    makeDirectory opts.RunDirectory;
	"cd " | opts.RunDirectory | " && " ) else "";
    cmd = cmd | program#"path" | addPrefix(name, program#"prefix") | " " | args;
    if match("\\|", cmd) then cmd = "{ " | cmd | ";}";
    returnValue := run (cmd | " > " | outFile | " 2> " | errFile);
    message := "running: " | cmd | "\n";
    output := get outFile;
    if output != "" then message = message | output;
    err := get errFile;
    if err != "" then message = message | err;
    if opts.Verbose then print(message);
    result := {
	"command" => cmd,
	"output" => output,
	"error" => err,
	"return value" => returnValue};
    if opts.KeepFiles then result = result | {
	"output file" => outFile,
	"error file" => errFile}
    else (
	removeFile outFile;
	removeFile errFile;
    );
    if opts.RaiseError and returnValue != 0 then error(
	program#"name" | " returned an error" |
	if opts.Verbose then "" else "\n" | message);
    new ProgramRun from result
)

net Program := toString Program := program -> program#"name"
html Program := html @@ toString
net ProgramRun := pr -> net pr#"return value"