Loading...
Searching...
No Matches
ExtPotCommand.h
Go to the documentation of this file.
1/*
2** This file is part of eOn.
3**
4** SPDX-License-Identifier: BSD-3-Clause
5**
6** Copyright (c) 2010--present, eOn Development Team
7** All rights reserved.
8**
9** Repo:
10** https://github.com/TheochemUI/eOn
11*/
12#pragma once
13
14#include <cctype>
15#include <filesystem>
16#include <fstream>
17#include <string>
18#include <string_view>
19#include <system_error>
20
21namespace eonc::pot {
22
25inline std::string shellQuote(const std::string &text) {
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}
43
48inline std::string shebangInterpreter(const std::filesystem::path &path) {
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}
94
96inline bool isPythonInterpreterName(std::string name) {
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}
107
115inline std::string wrapResolvedProgram(const std::string &command,
116 const std::string &python) {
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}
144
145} // namespace eonc::pot
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.
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.