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:
parent
33b601e574
commit
c41b3a1a64
@ -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 */
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user