Loading...
Searching...
No Matches
eonc::pot Namespace Reference

Functions

std::string commandFailure (int status)
 Describe how a command run through system() ended.
void runOrThrow (const std::string &command)
 Run a shell command, throwing when it does not succeed.
std::string shellQuote (const std::string &text)
 Wrap a path for the shell system() hands the command to, so spaces in it survive.
std::string shebangInterpreter (const std::filesystem::path &path)
 Return the interpreter name from a #! line, or empty.
bool isPythonInterpreterName (std::string name)
 True when name is python, python3, python3.12, python.exe, ...
std::string wrapResolvedProgram (const std::string &command, const std::string &python)
 Quote a resolved program path, and on Windows prefix a script so cmd.exe can start it.

Function Documentation

◆ commandFailure()

std::string eonc::pot::commandFailure ( int status)
inlinenodiscard

Describe how a command run through system() ended.

Parameters
statusThe value system() returned.
Returns
An empty string when the command ran to completion with exit status 0, otherwise a human readable reason for the failure.

Definition at line 29 of file ExternalCommand.h.

29 {
30 if (status == -1) {
31 return "could not be started";
32 }
33#ifndef _WIN32
34 if (WIFSIGNALED(status)) {
35 return "died on signal " + std::to_string(WTERMSIG(status));
36 }
37 if (!WIFEXITED(status)) {
38 return "terminated abnormally";
39 }
40 if (WEXITSTATUS(status) != 0) {
41 return "exited with status " + std::to_string(WEXITSTATUS(status));
42 }
43#else
44 if (status != 0) {
45 return "exited with status " + std::to_string(status);
46 }
47#endif
48 return {};
49}

◆ isPythonInterpreterName()

bool eonc::pot::isPythonInterpreterName ( std::string name)
inline

True when name is python, python3, python3.12, python.exe, ...

Definition at line 96 of file ExtPotCommand.h.

96 {
97 for (char &c : name) {
98 c = static_cast<char>(std::tolower(static_cast<unsigned char>(c)));
99 }
100 constexpr std::string_view exe{".exe"};
101 if (name.size() > exe.size() &&
102 name.compare(name.size() - exe.size(), exe.size(), exe.data()) == 0) {
103 name.resize(name.size() - exe.size());
104 }
105 return name.rfind("python", 0) == 0;
106}

◆ runOrThrow()

void eonc::pot::runOrThrow ( const std::string & command)
inline

Run a shell command, throwing when it does not succeed.

External potentials feed whatever the command leaves behind straight into the optimizer, so a failed command has to stop the run rather than let the caller parse a stale or partial result file.

Definition at line 56 of file ExternalCommand.h.

56 {
57 const std::string reason = commandFailure(std::system(command.c_str()));
58 if (!reason.empty()) {
59 throw std::runtime_error("Command \"" + command + "\" " + reason);
60 }
61}
std::string commandFailure(int status)
Describe how a command run through system() ended.

◆ shebangInterpreter()

std::string eonc::pot::shebangInterpreter ( const std::filesystem::path & path)
inline

Return the interpreter name from a #! line, or empty.

#!/usr/bin/env python3 and #!/usr/bin/python3 both yield python3. env flags (-S) are skipped. A file with no shebang yields empty.

Definition at line 48 of file ExtPotCommand.h.

48 {
49 std::ifstream in(path);
50 std::string line;
51 if (!in || !std::getline(in, line)) {
52 return {};
53 }
54 if (!line.empty() && line.back() == '\r') {
55 line.pop_back();
56 }
57 if (line.size() < 2 || line[0] != '#' || line[1] != '!') {
58 return {};
59 }
60 std::string rest = line.substr(2);
61 auto take = [](std::string &s) -> std::string {
62 const auto begin = s.find_first_not_of(" \t");
63 if (begin == std::string::npos) {
64 s.clear();
65 return {};
66 }
67 const auto end = s.find_first_of(" \t", begin);
68 std::string tok = end == std::string::npos ? s.substr(begin)
69 : s.substr(begin, end - begin);
70 if (end == std::string::npos) {
71 s.clear();
72 } else {
73 s.erase(0, end);
74 }
75 return tok;
76 };
77 const std::string first = take(rest);
78 if (first.empty()) {
79 return {};
80 }
81 std::string name = std::filesystem::path(first).filename().string();
82 if (name == "env") {
83 std::string tok = take(rest);
84 while (!tok.empty() && tok.front() == '-') {
85 tok = take(rest);
86 }
87 if (tok.empty()) {
88 return {};
89 }
90 name = std::filesystem::path(tok).filename().string();
91 }
92 return name;
93}

◆ shellQuote()

std::string eonc::pot::shellQuote ( const std::string & text)
inline

Wrap a path for the shell system() hands the command to, so spaces in it survive.

Definition at line 25 of file ExtPotCommand.h.

25 {
26#ifdef _WIN32
27 // cmd.exe takes double quotes and offers no escape for an embedded one,
28 // which a Windows path cannot hold anyway.
29 return "\"" + text + "\"";
30#else
31 std::string quoted{"'"};
32 for (const char c : text) {
33 if (c == '\'') {
34 quoted += "'\\''";
35 } else {
36 quoted += c;
37 }
38 }
39 quoted += "'";
40 return quoted;
41#endif
42}

◆ wrapResolvedProgram()

std::string eonc::pot::wrapResolvedProgram ( const std::string & command,
const std::string & python )
inline

Quote a resolved program path, and on Windows prefix a script so cmd.exe can start it.

cmd.exe will not execute an extensionless file, quoted or not. A Python shebang or a .py suffix becomes python "path". A native .exe / .bat / .cmd is quoted only. A command line that is not a single existing file is returned unchanged.

Definition at line 115 of file ExtPotCommand.h.

116 {
117 std::error_code ec;
118 if (!std::filesystem::is_regular_file(command, ec) || ec) {
119 return command;
120 }
121#ifdef _WIN32
122 const std::filesystem::path named{command};
123 std::string ext = named.extension().string();
124 for (char &c : ext) {
125 c = static_cast<char>(std::tolower(static_cast<unsigned char>(c)));
126 }
127 if (ext == ".exe" || ext == ".com" || ext == ".bat" || ext == ".cmd") {
128 return shellQuote(command);
129 }
130 const std::string interp = shebangInterpreter(named);
131 if (isPythonInterpreterName(interp) || ext == ".py" || ext == ".pyw") {
132 const std::string runner = python.empty() ? std::string{"python"} : python;
133 return runner + " " + shellQuote(command);
134 }
135 if (!interp.empty()) {
136 return interp + " " + shellQuote(command);
137 }
138 return shellQuote(command);
139#else
140 (void)python;
141 return shellQuote(command);
142#endif
143}
bool isPythonInterpreterName(std::string name)
True when name is python, python3, python3.12, python.exe, ...
std::string shellQuote(const std::string &text)
Wrap a path for the shell system() hands the command to, so spaces in it survive.
std::string shebangInterpreter(const std::filesystem::path &path)
Return the interpreter name from a #! line, or empty.