Skip to content

Commit 62272fc

Browse files
fix: honor PATHEXT on Windows in search_in_path
- Read PATHEXT environment variable or use default extensions - Try original name first, then each extension if name has no extension - Update Windows is_executable to use filesystem::status like POSIX
1 parent 027bb55 commit 62272fc

File tree

1 file changed

+57
-8
lines changed

1 file changed

+57
-8
lines changed

src/clice.cc

Lines changed: 57 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -95,19 +95,46 @@ auto search_in_path(std::string_view name) -> std::string {
9595

9696
#ifdef _WIN32
9797
constexpr char path_sep = ';';
98-
constexpr auto is_executable = [](const std::filesystem::path& p) {
99-
return std::filesystem::exists(p) && !std::filesystem::is_directory(p);
98+
99+
// Get PATHEXT or use default extensions
100+
std::vector<std::string> extensions;
101+
const char* pathext_env = std::getenv("PATHEXT");
102+
if(pathext_env) {
103+
std::string_view pathext_view(pathext_env);
104+
size_t ext_start = 0;
105+
while(ext_start < pathext_view.size()) {
106+
size_t ext_end = pathext_view.find(';', ext_start);
107+
if(ext_end == std::string_view::npos) {
108+
ext_end = pathext_view.size();
109+
}
110+
std::string_view ext = pathext_view.substr(ext_start, ext_end - ext_start);
111+
if(!ext.empty()) {
112+
extensions.emplace_back(ext);
113+
}
114+
ext_start = ext_end + 1;
115+
}
116+
} else {
117+
extensions = {".exe", ".cmd", ".bat", ".com"};
118+
}
119+
120+
// Check if name already has an extension
121+
bool has_extension = name.find('.') != std::string_view::npos;
122+
123+
auto is_executable = [](const std::filesystem::path& p) {
124+
std::error_code ec;
125+
auto status = std::filesystem::status(p, ec);
126+
return !ec && std::filesystem::exists(status) && !std::filesystem::is_directory(status);
100127
};
101128
#else
102129
constexpr char path_sep = ':';
103-
constexpr auto is_executable = [](const std::filesystem::path& p) {
130+
auto is_executable = [](const std::filesystem::path& p) {
104131
std::error_code ec;
105132
auto status = std::filesystem::status(p, ec);
106133
if(ec || !std::filesystem::exists(status) || std::filesystem::is_directory(status)) {
107134
return false;
108135
}
109-
return (status.permissions() & (std::filesystem::perms::owner_exec |
110-
std::filesystem::perms::group_exec |
136+
return (status.permissions() & (std::filesystem::perms::owner_exec |
137+
std::filesystem::perms::group_exec |
111138
std::filesystem::perms::others_exec)) != std::filesystem::perms::none;
112139
};
113140
#endif
@@ -119,20 +146,42 @@ auto search_in_path(std::string_view name) -> std::string {
119146
if(end == std::string_view::npos) {
120147
end = path_view.size();
121148
}
122-
149+
123150
std::string_view dir = path_view.substr(start, end - start);
124151
if(!dir.empty()) {
152+
#ifdef _WIN32
153+
// Try the name as-is first
125154
std::filesystem::path full_path = std::filesystem::path(dir) / name;
126155
std::error_code ec;
127156
auto canonical = std::filesystem::canonical(full_path, ec);
128157
if(!ec && is_executable(canonical)) {
129158
return canonical.string();
130159
}
160+
161+
// If name doesn't have an extension, try each PATHEXT extension
162+
if(!has_extension) {
163+
for(const auto& ext : extensions) {
164+
std::string name_with_ext = std::string(name) + ext;
165+
full_path = std::filesystem::path(dir) / name_with_ext;
166+
canonical = std::filesystem::canonical(full_path, ec);
167+
if(!ec && is_executable(canonical)) {
168+
return canonical.string();
169+
}
170+
}
171+
}
172+
#else
173+
std::filesystem::path full_path = std::filesystem::path(dir) / name;
174+
std::error_code ec;
175+
auto canonical = std::filesystem::canonical(full_path, ec);
176+
if(!ec && is_executable(canonical)) {
177+
return canonical.string();
178+
}
179+
#endif
131180
}
132-
181+
133182
start = end + 1;
134183
}
135-
184+
136185
return std::string(name);
137186
}
138187

0 commit comments

Comments
 (0)