Filenames with spaces; output beside the input file; warning fixes (from dev c08eb1bbfa4c)
- Filenames may contain spaces: quote on the command line; filename lists use a standalone "/" separator; unseparated names that do not exist are rejoined into names that do (announced); leading ~ expands. - Without -o, output is written to the input file's directory; -o fully specifies the output directory and basename. - "Word not parsed" warnings now appear without -v, only for text that survives removal, with correct line numbers. - New tst/filename_test.sh regression suite. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
114
mac/file.cpp
114
mac/file.cpp
@@ -115,6 +115,120 @@ bool file_exists(const std::string& pathname, bool error_if_not, bool is_directo
|
||||
}
|
||||
|
||||
|
||||
// Filename lists (see file.h): standalone-"/" separation, existence-rescue
|
||||
// grouping, and tilde expansion.
|
||||
|
||||
std::string expand_tilde(const std::string& path)
|
||||
{
|
||||
if (path == "~" || (path.size() > 1 && path[0] == '~' && path[1] == '/')) {
|
||||
std::string home = get_env_var("HOME");
|
||||
if (!home.empty()) {
|
||||
return home + path.substr(1);
|
||||
}
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
static bool filename_exists(const std::string& name, const std::string& base_dir)
|
||||
{
|
||||
std::string resolved = expand_tilde(name);
|
||||
if (!base_dir.empty() && !fs::path(resolved).is_absolute()) {
|
||||
resolved = base_dir + "/" + resolved;
|
||||
}
|
||||
return fs::is_regular_file(resolved);
|
||||
}
|
||||
|
||||
strings_t group_filename_tokens(const strings_t& tokens, const std::string& base_dir)
|
||||
{
|
||||
strings_t result {};
|
||||
if (std::find(tokens.begin(), tokens.end(), "/") != tokens.end()) {
|
||||
// Deterministic form: standalone "/" separates the filenames; the
|
||||
// tokens between separators form one name. No existence checks.
|
||||
strings_t group {};
|
||||
for (const std::string& t : tokens) {
|
||||
if (t == "/") {
|
||||
if (!group.empty()) result.push_back(join(group));
|
||||
group.clear();
|
||||
} else {
|
||||
group.push_back(t);
|
||||
}
|
||||
}
|
||||
if (!group.empty()) result.push_back(join(group));
|
||||
} else {
|
||||
// Rescue: a token naming an existing file stands alone; one that
|
||||
// does not is joined with following tokens until the accumulated
|
||||
// name exists. A name that never resolves is kept as given, so the
|
||||
// missing-file error downstream reports what the user wrote.
|
||||
size_t i = 0;
|
||||
while (i < tokens.size()) {
|
||||
if (filename_exists(tokens[i], base_dir)) {
|
||||
result.push_back(tokens[i]);
|
||||
++i;
|
||||
continue;
|
||||
}
|
||||
std::string acc = tokens[i];
|
||||
size_t j = i + 1;
|
||||
bool found = false;
|
||||
while (j < tokens.size()) {
|
||||
acc += " " + tokens[j];
|
||||
++j;
|
||||
if (filename_exists(acc, base_dir)) {
|
||||
std::cerr << command_name << ": interpreting \""
|
||||
<< acc << "\" as one filename\n";
|
||||
result.push_back(acc);
|
||||
i = j;
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
result.push_back(tokens[i]);
|
||||
++i;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (std::string& name : result) {
|
||||
name = expand_tilde(name);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
strings_t resolve_filename_list(const std::string& text, const std::string& base_dir)
|
||||
{
|
||||
// Split on standalone "/" at the string level first, so a name's inner
|
||||
// spacing survives exactly; without a separator, fall back to
|
||||
// whitespace tokens and the rescue in group_filename_tokens().
|
||||
auto standalone_slash = [&](size_t i) {
|
||||
return text[i] == '/'
|
||||
&& (i == 0 || std::isspace(static_cast<unsigned char>(text[i-1])))
|
||||
&& (i + 1 == text.size() || std::isspace(static_cast<unsigned char>(text[i+1])));
|
||||
};
|
||||
bool has_separator = false;
|
||||
for (size_t i = 0; i < text.size(); ++i) {
|
||||
if (standalone_slash(i)) {
|
||||
has_separator = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (has_separator) {
|
||||
strings_t result {};
|
||||
std::string part {};
|
||||
for (size_t i = 0; i < text.size(); ++i) {
|
||||
if (standalone_slash(i)) {
|
||||
part = trim(part);
|
||||
if (!part.empty()) result.push_back(expand_tilde(part));
|
||||
part.clear();
|
||||
} else {
|
||||
part += text[i];
|
||||
}
|
||||
}
|
||||
part = trim(part);
|
||||
if (!part.empty()) result.push_back(expand_tilde(part));
|
||||
return result;
|
||||
}
|
||||
return group_filename_tokens(word_split(text), base_dir);
|
||||
}
|
||||
|
||||
std::string string_from_file(const std::string& pathname, bool strip_surrounding_whitespace)
|
||||
{
|
||||
std::regex klammertext_filename_re { R"(.*\.kt?)" };
|
||||
|
||||
Reference in New Issue
Block a user