Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix get_canonical/absolute/normalize_path() functions #100

Merged
merged 1 commit into from
Jul 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
117 changes: 77 additions & 40 deletions base/fs.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// LAF Base Library
// Copyright (c) 2021-2022 Igara Studio S.A.
// Copyright (c) 2021-2024 Igara Studio S.A.
// Copyright (c) 2001-2018 David Capello
//
// This file is released under the terms of the MIT license.
Expand Down Expand Up @@ -27,16 +27,18 @@

namespace base {

// On Windows we can use \ or / as path separators, but on Unix-like
// platforms it's just /, as \ can be part of the file name.
#if LAF_WINDOWS
const std::string::value_type path_separator = '\\';
const std::string::value_type* path_separators = "\\/";
#else
const std::string::value_type path_separator = '/';
const std::string::value_type* path_separators = "/";
#endif

void make_all_directories(const std::string& path)
{
std::vector<std::string> parts;
split_string(path, parts, "/\\");
split_string(path, parts, path_separators);

std::string intermediate;
for (const std::string& component : parts) {
Expand All @@ -55,31 +57,6 @@ void make_all_directories(const std::string& path)
}
}

std::string get_absolute_path(const std::string& filename)
{
std::string fn = filename;
if (fn.size() > 2 &&
#if LAF_WINDOWS
fn[1] != ':'
#else
fn[0] != '/'
#endif
) {
fn = base::join_path(base::get_current_path(), fn);
}
fn = base::get_canonical_path(fn);
return fn;
}

bool is_path_separator(std::string::value_type chr)
{
return (
#if LAF_WINDOWS
chr == '\\' ||
#endif
chr == '/');
}

std::string get_file_path(const std::string& filename)
{
std::string::const_reverse_iterator rit;
Expand Down Expand Up @@ -213,10 +190,10 @@ std::string get_file_title_with_path(const std::string& filename)
std::string get_relative_path(const std::string& filename, const std::string& base_path)
{
std::vector<std::string> baseDirs;
split_string(base_path, baseDirs, "/\\");
split_string(base_path, baseDirs, path_separators);

std::vector<std::string> toParts;
split_string(filename, toParts, "/\\");
split_string(filename, toParts, path_separators);

// Find the common prefix
auto itFrom = baseDirs.begin();
Expand Down Expand Up @@ -270,19 +247,79 @@ std::string remove_path_separator(const std::string& path)

std::string fix_path_separators(const std::string& filename)
{
std::string result(filename);

// Replace any separator with the system path separator.
std::replace_if(result.begin(), result.end(),
is_path_separator, path_separator);

std::string result;
result.reserve(filename.size());
for (auto chr : filename) {
if (is_path_separator(chr)) {
if (result.empty() || !is_path_separator(result.back()))
result.push_back(path_separator);
}
else
result.push_back(chr);
}
return result;
}

std::string normalize_path(const std::string& filename)
// It tries to replicate the standard path::lexically_normal()
// algorithm from https://en.cppreference.com/w/cpp/filesystem/path
std::string normalize_path(const std::string& _path)
{
std::string fn = base::get_canonical_path(filename);
fn = base::fix_path_separators(fn);
// Normal form of an empty path is an empty path.
if (_path.empty())
return std::string();

// Replace multiple slashes with a single path_separator.
std::string path = fix_path_separators(_path);

std::string fn;
if (!path.empty() && path[0] == path_separator)
fn.push_back(path_separator);

std::vector<std::string> parts;
split_string(path, parts, path_separators);

// Last element generates a final dot or slash in normalized path.
bool last_dot = false;

auto n = int(parts.size());
for (int i=0; i<n; ++i) {
const auto& part = parts[i];

// Remove each dot part.
if (part == ".") {
last_dot = true;

if (i+1 == n)
break;

fn = join_path(fn, std::string());
continue;
}

if (!part.empty())
last_dot = false;

if (part != ".." && i+1 < n &&
parts[i+1] == "..") {
// Skip this "part/.."
++i;
last_dot = true;
}
else if (!part.empty()) {
fn = join_path(fn, part);
}
else
last_dot = true;
}
if (last_dot) {
if (fn.empty())
fn = ".";
else if (fn.back() != path_separator &&
// Don't include trailing slash for ".." filename
get_file_name(fn) != "..") {
fn.push_back(path_separator);
}
}
return fn;
}

Expand Down
36 changes: 26 additions & 10 deletions base/fs.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,14 @@ namespace base {

class Time;

// Default path separator (on Windows it is '\' and on Unix-like systems it is '/').
extern const std::string::value_type path_separator;
// Default path separator (on Windows it is '\' and on Unix-like
// systems it is '/').
#if LAF_WINDOWS
static constexpr const std::string::value_type path_separator = '\\';
#else
static constexpr const std::string::value_type path_separator = '/';
#endif
extern const std::string::value_type* path_separators;

bool is_file(const std::string& path);
bool is_directory(const std::string& path);
Expand Down Expand Up @@ -48,18 +54,28 @@ namespace base {
std::string get_lib_app_support_path();
#endif

// If the given filename is a relative path, it converts the
// filename to an absolute one.
// Converts an existing file path to an absolute one, or returns an
// empty string if the file doesn't exist. It uses realpath() on
// POSIX-like systems and GetFullPathName() on Windows.
std::string get_canonical_path(const std::string& path);

// TODO why get_canonical_path() is not enough?
std::string get_absolute_path(const std::string& filename);
// Returns the absolute path using lexical/string operations, and
// get_current_path() when needed. Doesn't require an existing file
// in "path". The returned path shouldn't contain "." or ".."
// elements (is a normalized path).
std::string get_absolute_path(const std::string& path);

paths list_files(const std::string& path);

// Returns true if the given character is a valud path separator
// (any of '\' or '/' characters).
bool is_path_separator(std::string::value_type chr);
inline constexpr bool is_path_separator(std::string::value_type chr) {
return (
#if LAF_WINDOWS
chr == '\\' ||
#endif
chr == '/');
}

// Returns only the path (without the last trailing slash).
std::string get_file_path(const std::string& filename);
Expand Down Expand Up @@ -89,9 +105,9 @@ namespace base {
// Replaces all separators with the system separator.
std::string fix_path_separators(const std::string& filename);

// Calls get_canonical_path() and fix_path_separators() for the
// given filename.
std::string normalize_path(const std::string& filename);
// Remove superfluous path elements ("/../" and "/./") and call
// fix_path_separators() for the given path.
std::string normalize_path(const std::string& path);

// Returns true if the filename contains one of the specified
// extensions. The "extensions" parameter must be a set of possible
Expand Down
Loading
Loading