Fixes fl_filename_relative on Linux, Mac, and Windows (#787)

* fixed filename_relative for Linux
* Fixing fl_filename_relative for MSWindows.
* Update documentation
* Fixed docs.
* Fixes Linux and macOS builds
This commit is contained in:
Matthias Melcher 2023-10-11 22:26:36 +01:00 committed by GitHub
parent 33b601e574
commit c41b3a1a64
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 196 additions and 144 deletions

View File

@ -62,8 +62,10 @@ FL_EXPORT Fl_String fl_filename_path(const Fl_String &filename);
FL_EXPORT Fl_String fl_filename_ext(const Fl_String &filename);
FL_EXPORT Fl_String fl_filename_setext(const Fl_String &filename, const Fl_String &new_extension);
FL_EXPORT Fl_String fl_filename_expand(const Fl_String &from);
FL_EXPORT int fl_filename_absolute(char *to, int tolen, const char *from, const char *cwd);
FL_EXPORT Fl_String fl_filename_absolute(const Fl_String &from);
FL_EXPORT Fl_String fl_filename_absolute(const Fl_String &from, const Fl_String &base);
FL_EXPORT int fl_filename_relative(char *to, int tolen, const char *from, const char *cwd);
FL_EXPORT Fl_String fl_filename_relative(const Fl_String &from);
FL_EXPORT Fl_String fl_filename_relative(const Fl_String &from, const Fl_String &base);
FL_EXPORT Fl_String fl_getcwd();
@ -77,9 +79,7 @@ FL_EXPORT Fl_String fl_getcwd();
inline char *fl_filename_setext(char *to, const char *ext) { return fl_filename_setext(to, FL_PATH_MAX, ext); }
inline int fl_filename_expand(char *to, const char *from) { return fl_filename_expand(to, FL_PATH_MAX, from); }
FL_EXPORT int fl_filename_absolute(char *to, int tolen, const char *from, const char *cwd);
inline int fl_filename_absolute(char *to, const char *from) { return fl_filename_absolute(to, FL_PATH_MAX, from); }
FL_EXPORT int fl_filename_relative(char *to, int tolen, const char *from, const char *cwd);
inline int fl_filename_relative(char *to, const char *from) { return fl_filename_relative(to, FL_PATH_MAX, from); }
# endif /* __cplusplus */

View File

@ -549,90 +549,101 @@ int Fl_WinAPI_System_Driver::filename_expand(char *to, int tolen, const char *fr
int // O - 0 if no change, 1 if changed
Fl_WinAPI_System_Driver::filename_relative(char *to, // O - Relative filename
int tolen, // I - Size of "to" buffer
const char *from, // I - Absolute filename
const char *base) // I - Find path relative to this path
int tolen, // I - Size of "to" buffer
const char *dest_dir, // I - Absolute filename
const char *base_dir) // I - Find path relative to this path
{
char *newslash; // Directory separator
const char *slash; // Directory separator
char *cwd = 0L, *cwd_buf = 0L;
if (base) cwd = cwd_buf = fl_strdup(base);
// Find the relative path from base_dir to dest_dir.
// Both paths must be absolute and well formed (contain no /../ and /./ segments).
// return if "from" is not an absolute path
if (from[0] == '\0' ||
(!isdirsep(*from) && !isalpha(*from) && from[1] != ':' &&
!isdirsep(from[2]))) {
strlcpy(to, from, tolen);
if (cwd_buf) free(cwd_buf);
return 0;
}
// return if "cwd" is not an absolute path
if (!cwd || cwd[0] == '\0' ||
(!isdirsep(*cwd) && !isalpha(*cwd) && cwd[1] != ':' &&
!isdirsep(cwd[2]))) {
strlcpy(to, from, tolen);
if (cwd_buf) free(cwd_buf);
return 0;
}
// convert all backslashes into forward slashes
for (newslash = strchr(cwd, '\\'); newslash; newslash = strchr(newslash + 1, '\\'))
*newslash = '/';
// test for the exact same string and return "." if so
if (!strcasecmp(from, cwd)) {
strlcpy(to, ".", tolen);
free(cwd_buf);
return (1);
}
// test for the same drive. Return the absolute path if not
if (tolower(*from & 255) != tolower(*cwd & 255)) {
// Not the same drive...
strlcpy(to, from, tolen);
free(cwd_buf);
// return if any of the pointers is NULL
if (!to || !dest_dir || !base_dir) {
return 0;
}
// compare the path name without the drive prefix
from += 2; cwd += 2;
// compare both path names until we find a difference
for (slash = from, newslash = cwd;
*slash != '\0' && *newslash != '\0';
slash ++, newslash ++)
if (isdirsep(*slash) && isdirsep(*newslash)) continue;
else if (tolower(*slash & 255) != tolower(*newslash & 255)) break;
// skip over trailing slashes
if ( *newslash == '\0' && *slash != '\0' && !isdirsep(*slash)
&&(newslash==cwd || !isdirsep(newslash[-1])) )
newslash--;
// now go back to the first character of the first differing paths segment
while (!isdirsep(*slash) && slash > from) slash --;
if (isdirsep(*slash)) slash ++;
// do the same for the current dir
if (isdirsep(*newslash)) newslash --;
if (*newslash != '\0')
while (!isdirsep(*newslash) && newslash > cwd) newslash --;
// prepare the destination buffer
to[0] = '\0';
to[tolen - 1] = '\0';
// now add a "previous dir" sequence for every following slash in the cwd
while (*newslash != '\0') {
if (isdirsep(*newslash)) strlcat(to, "../", tolen);
newslash ++;
// if there is a drive letter, make sure both paths use the same drive
if ( base_dir[0] < 128 && isalpha(base_dir[0]) && base_dir[1] == ':'
&& dest_dir[0] < 128 && isalpha(dest_dir[0]) && dest_dir[1] == ':') {
if (tolower(base_dir[0]) != tolower(dest_dir[0])) {
strlcpy(to, dest_dir, tolen);
return 0;
}
// same drive, so skip to the start of the path
base_dir += 2;
dest_dir += 2;
}
// finally add the differing path from "from"
strlcat(to, slash, tolen);
// return if `base_dir` or `dest_dir` is not an absolute path
if (!isdirsep(*base_dir) || !isdirsep(*dest_dir)) {
strlcpy(to, dest_dir, tolen);
return 0;
}
const char *base_i = base_dir; // iterator through the base directory string
const char *base_s = base_dir; // pointer to the last dir separator found
const char *dest_i = dest_dir; // iterator through the destination directory
const char *dest_s = dest_dir; // pointer to the last dir separator found
// compare both path names until we find a difference
for (;;) {
#if 0 // case sensitive
base_i++;
dest_i++;
char b = *base_i, d = *dest_i;
#else // case insensitive
base_i += fl_utf8len1(*base_i);
int b = fl_tolower(fl_utf8decode(base_i, NULL, NULL));
dest_i += fl_utf8len1(*dest_i);
int d = fl_tolower(fl_utf8decode(dest_i, NULL, NULL));
#endif
int b0 = (b == 0) || (isdirsep(b));
int d0 = (d == 0) || (isdirsep(d));
if (b0 && d0) {
base_s = base_i;
dest_s = dest_i;
}
if (b == 0 || d == 0)
break;
if (b != d)
break;
}
// base_s and dest_s point at the last separator we found
// base_i and dest_i point at the first character that differs
// test for the exact same string and return "." if so
if ( (base_i[0] == 0 || (isdirsep(base_i[0]) && base_i[1] == 0))
&& (dest_i[0] == 0 || (isdirsep(dest_i[0]) && dest_i[1] == 0))) {
strlcpy(to, ".", tolen);
return 0;
}
// prepare the destination buffer
to[0] = '\0';
to[tolen - 1] = '\0';
// count the directory segments remaining in `base_dir`
int n_up = 0;
for (;;) {
char b = *base_s++;
if (b == 0)
break;
if (isdirsep(b) && *base_s)
n_up++;
}
// now add a "previous dir" sequence for every following slash in the cwd
if (n_up > 0)
strlcat(to, "..", tolen);
for (; n_up > 1; --n_up)
strlcat(to, "/..", tolen);
// finally add the differing path from "from"
if (*dest_s) {
if (n_up)
strlcat(to, "/", tolen);
strlcat(to, dest_s + 1, tolen);
}
free(cwd_buf);
return 1;
}

View File

@ -28,7 +28,7 @@
#include <stdlib.h>
#include "flstring.h"
static inline int isdirsep(char c) {return c == '/';}
static inline int isdirsep(int c) {return c == '/';}
/** Makes a filename absolute from a relative filename to the current working directory.
\code
@ -124,22 +124,49 @@ int Fl_System_Driver::filename_absolute(char *to, int tolen, const char *from, c
/** Makes a filename relative to the current working directory.
\code
#include <FL/filename.H>
[..]
fl_chdir("/var/tmp/somedir"); // set cwd to /var/tmp/somedir
[..]
char out[FL_PATH_MAX];
fl_filename_relative(out, sizeof(out), "/var/tmp/somedir/foo.txt"); // out="foo.txt", return=1
fl_filename_relative(out, sizeof(out), "/var/tmp/foo.txt"); // out="../foo.txt", return=1
fl_filename_relative(out, sizeof(out), "foo.txt"); // out="foo.txt", return=0 (no change)
fl_filename_relative(out, sizeof(out), "./foo.txt"); // out="./foo.txt", return=0 (no change)
fl_filename_relative(out, sizeof(out), "../foo.txt"); // out="../foo.txt", return=0 (no change)
\endcode
\param[out] to resulting relative filename
\param[in] tolen size of the relative filename buffer
\param[in] from absolute filename
\return 0 if no change, non zero otherwise
Return the \a from path made relative to the working directory, similar to
C++17 `std::filesystem::path::lexically_relative`. This function can also be
called with a fourth argument for a user supplied \a base directory path
These conversions are purely lexical. They do not check that the paths exist,
do not follow symlinks, and do not access the filesystem at all.
Path arguments must be absolute (start at the root directory) and must not
contain `.` or `..` segments, or double separators. A single trailing
separator is ok.
On Windows, path arguments must start with a drive name, e.g. `c:\`.
Windows network paths and other special paths starting
with a double separator are not supported (`\\cloud\drive\path`,
`\\?\`, etc.) . Separators can be `\` and `/` and will be preserved.
Newly created separators are alway the forward slash `/`.
On Windows and macOS, the path segment tests are case insensitive.
If the path can not be generated, \a from path is copied into the \a to
buffer and 0 is returned.
\code
#include <FL/filename.H>
[..]
fl_chdir("/var/tmp/somedir"); // set cwd to /var/tmp/somedir
[..]
char out[FL_PATH_MAX];
fl_filename_relative(out, sizeof(out), "/var/tmp/somedir/foo.txt"); // out="foo.txt", return=1
fl_filename_relative(out, sizeof(out), "/var/tmp/foo.txt"); // out="../foo.txt", return=1
fl_filename_relative(out, sizeof(out), "foo.txt"); // out="foo.txt", return=0 (no change)
fl_filename_relative(out, sizeof(out), "./foo.txt"); // out="./foo.txt", return=0 (no change)
fl_filename_relative(out, sizeof(out), "../foo.txt"); // out="../foo.txt", return=0 (no change)
\endcode
\param[out] to resulting relative filename
\param[in] tolen size of the relative filename buffer
\param[in] from absolute filename
\return 0 if no change, non zero otherwise
\see fl_filename_relative(char *to, int tolen, const char *from, const char *base)
\see fl_filename_relative(const Fl_String &from, const Fl_String &base)
\see fl_filename_relative(const Fl_String &from)
*/
int fl_filename_relative(char *to, int tolen, const char *from)
{
@ -154,11 +181,13 @@ int fl_filename_relative(char *to, int tolen, const char *from)
/** Makes a filename relative to any other directory.
\param[out] to resulting relative filename
\param[out] to resulting relative filepath
\param[in] tolen size of the relative filename buffer
\param[in] from absolute filename
\param[in] base generate filename relative to this absolute filename
\param[in] from absolute filepath
\param[in] base generate filepath relative to this absolute filepath
\return 0 if no change, non zero otherwise
\see fl_filename_relative(char *to, int tolen, const char *from)
*/
int fl_filename_relative(char *to, int tolen, const char *from, const char *base) {
return Fl::system_driver()->filename_relative(to, tolen, from, base);
@ -171,70 +200,82 @@ int fl_filename_relative(char *to, int tolen, const char *from, const char *base
\{
*/
int Fl_System_Driver::filename_relative(char *to, int tolen, const char *from, const char *base)
int Fl_System_Driver::filename_relative(char *to, int tolen, const char *dest_dir, const char *base_dir)
{
char *newslash; // Directory separator
const char *slash; // Directory separator
char *cwd = 0L, *cwd_buf = 0L;
if (base) cwd = cwd_buf = fl_strdup(base);
// Find the relative path from base_dir to dest_dir.
// Both paths must be absolute and well formed (contain no /../ and /./ segments).
const char *base_i = base_dir; // iterator through the base directory string
const char *base_s = base_dir; // pointer to the last dir separator found
const char *dest_i = dest_dir; // iterator through the destination directory
const char *dest_s = dest_dir; // pointer to the last dir separator found
// return if "from" is not an absolute path
if (from[0] == '\0' || !isdirsep(*from)) {
strlcpy(to, from, tolen);
if (cwd_buf) free(cwd_buf);
// return if any of the pointers is NULL
if (!to || !dest_dir || !base_dir) {
return 0;
}
// return if "cwd" is not an absolute path
if (!cwd || cwd[0] == '\0' || !isdirsep(*cwd)) {
strlcpy(to, from, tolen);
if (cwd_buf) free(cwd_buf);
// return if `base_dir` or `dest_dir` is not an absolute path
if (!isdirsep(*base_dir) || !isdirsep(*dest_dir)) {
strlcpy(to, dest_dir, tolen);
return 0;
}
// test for the exact same string and return "." if so
if (!strcmp(from, cwd)) {
strlcpy(to, ".", tolen);
free(cwd_buf);
return (1);
}
// compare both path names until we find a difference
for (slash = from, newslash = cwd;
*slash != '\0' && *newslash != '\0';
slash ++, newslash ++)
if (isdirsep(*slash) && isdirsep(*newslash)) continue;
else if (*slash != *newslash) break;
for (;;) {
#ifndef __APPLE__ // case sensitive
base_i++;
dest_i++;
char b = *base_i, d = *dest_i;
#else // case insensitive
base_i += fl_utf8len1(*base_i);
int b = fl_tolower(fl_utf8decode(base_i, NULL, NULL));
dest_i += fl_utf8len1(*dest_i);
int d = fl_tolower(fl_utf8decode(dest_i, NULL, NULL));
#endif
int b0 = (b==0) || (isdirsep(b));
int d0 = (d==0) || (isdirsep(d));
if (b0 && d0) {
base_s = base_i;
dest_s = dest_i;
}
if (b==0 || d==0) break;
if (b!=d) break;
}
// base_s and dest_s point at the last separator we found
// base_i and dest_i point at the first character that differs
// skip over trailing slashes
if ( *newslash == '\0' && *slash != '\0' && !isdirsep(*slash)
&&(newslash==cwd || !isdirsep(newslash[-1])) )
newslash--;
// now go back to the first character of the first differing paths segment
while (!isdirsep(*slash) && slash > from) slash --;
if (isdirsep(*slash)) slash ++;
// do the same for the current dir
if (isdirsep(*newslash)) newslash --;
if (*newslash != '\0')
while (!isdirsep(*newslash) && newslash > cwd) newslash --;
// test for the exact same string and return "." if so
if ( (base_i[0] == 0 || (isdirsep(base_i[0]) && base_i[1] == 0))
&& (dest_i[0] == 0 || (isdirsep(dest_i[0]) && dest_i[1] == 0))) {
strlcpy(to, ".", tolen);
return 0;
}
// prepare the destination buffer
to[0] = '\0';
to[tolen - 1] = '\0';
// now add a "previous dir" sequence for every following slash in the cwd
while (*newslash != '\0') {
if (isdirsep(*newslash)) strlcat(to, "../", tolen);
newslash ++;
// count the directory segments remaining in `base_dir`
int n_up = 0;
for (;;) {
char b = *base_s++;
if (b==0) break;
if (isdirsep(b) && *base_s) n_up++;
}
// finally add the differing path from "from"
strlcat(to, slash, tolen);
// now add a "previous dir" sequence for every following slash in the cwd
if (n_up>0)
strlcat(to, "..", tolen);
for (; n_up>1; --n_up)
strlcat(to, "/..", tolen);
// finally add the differing path from "from"
if (*dest_s) {
if (n_up)
strlcat(to, "/", tolen);
strlcat(to, dest_s+1, tolen);
}
free(cwd_buf);
return 1;
}