2 filetools.C (former paths.C) - part of LyX project
3 General path-mangling functions
4 Copyright 1996 Ivan Schreter
5 Parts Copyright 1996 Dirk Niggemann
6 Parts Copyright 1985, 1990, 1993 Free Software Foundation, Inc.
7 Parts Copyright 1996 Asger Alstrup
11 lyx-filetool.C : tools functions for file/path handling
12 this file is part of LyX, the High Level Word Processor
13 Copyright 1995-1996, Matthias Ettrich and the LyX Team
22 #pragma implementation "filetools.h"
25 #include "filetools.h"
26 #include "lyx_gui_misc.h"
28 #include "support/path.h" // I know it's OS/2 specific (SMiyata)
33 // Which part of this is still necessary? (JMarc).
36 # define NAMLEN(dirent) strlen((dirent)->d_name)
38 # define dirent direct
39 # define NAMLEN(dirent) (dirent)->d_namlen
41 # include <sys/ndir.h>
51 extern string system_lyxdir;
52 extern string build_lyxdir;
53 extern string user_lyxdir;
54 extern string system_tempdir;
57 bool IsLyXFilename(string const & filename)
59 return contains(filename, ".lyx");
63 bool IsSGMLFilename(string const & filename)
65 return contains(filename, ".sgml");
69 // Substitutes spaces with underscores in filename (and path)
70 string SpaceLess(string const & file)
72 string name = OnlyFilename(file);
73 string path = OnlyPath(file);
75 for (string::size_type i = 0; i < name.length(); ++i) {
76 name[i] &= 0x7f; // set 8th bit to 0
79 // ok so we scan through the string twice, but who cares.
80 string keep("abcdefghijklmnopqrstuvwxyz"
81 "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
82 "@!\"'()*+,-./0123456789:;<=>?[]`|");
84 string::size_type pos = 0;
85 while ((pos = name.find_first_not_of(change, pos)) != string::npos) {
88 return AddName(path, name);
92 /// Returns an unique name to be used as a temporary file.
93 string TmpFileName(string const & dir, string const & mask)
94 {// With all these temporary variables, it should be safe enough :-) (JMarc)
97 tmpdir = system_tempdir;
100 string tmpfl = AddName(tmpdir, mask);
102 // find a uniq postfix for the filename...
103 // using the pid, and...
104 tmpfl += tostr(getpid());
108 for (int a='a'; a<= 'z'; ++a)
109 for (int b='a'; b<= 'z'; ++b)
110 for (int c='a'; c<= 'z'; ++c) {
111 // if this is not enough I have no idea what
113 ret = tmpfl + char(a) + char(b) + char(c);
114 // check if the file exist
115 if (!fnfo.newFile(ret).exist())
118 lyxerr << "Not able to find a uniq tmpfile name." << endl;
123 // Is a file readable ?
124 bool IsFileReadable (string const & path)
127 if (file.isOK() && file.isRegular() && file.readable())
134 // Is a file read_only?
135 // return 1 read-write
137 // -1 error (doesn't exist, no access, anything else)
138 int IsFileWriteable (string const & path)
140 FilePtr fp(path, FilePtr::update);
142 if ((errno == EACCES) || (errno == EROFS)) {
143 fp.reopen(path, FilePtr::read);
154 //returns 1: dir writeable
156 // -1: error- couldn't find out
157 int IsDirWriteable (string const & path)
159 string tmpfl = TmpFileName(path);
162 WriteFSAlert(_("LyX Internal Error!"),
163 _("Could not test if directory is writeable"));
166 FilePtr fp(tmpfl, FilePtr::truncate);
168 if (errno == EACCES) {
171 WriteFSAlert(_("LyX Internal Error!"),
172 _("Cannot open directory test file"));
177 if (remove (tmpfl.c_str())) {
178 WriteFSAlert(_("LyX Internal Error!"),
179 _("Created test file but cannot remove it?"));
186 // Uses a string of paths separated by ";"s to find a file to open.
187 // Can't cope with pathnames with a ';' in them. Returns full path to file.
188 // If path entry begins with $$LyX/, use system_lyxdir
189 // If path entry begins with $$User/, use user_lyxdir
190 // Example: "$$User/doc;$$LyX/doc"
191 string FileOpenSearch (string const & path, string const & name,
194 string real_file, path_element;
195 bool notfound = true;
196 string tmppath = split(path, path_element, ';');
198 while (notfound && !path_element.empty()) {
199 path_element = CleanupPath(path_element);
200 if (!suffixIs(path_element, '/'))
202 path_element = subst(path_element, "$$LyX", system_lyxdir);
203 path_element = subst(path_element, "$$User", user_lyxdir);
205 real_file = FileSearch(path_element, name, ext);
207 if (real_file.empty()) {
209 tmppath = split(tmppath, path_element, ';');
210 } while(!tmppath.empty() && path_element.empty());
216 if (ext.empty() && notfound) {
217 real_file = FileOpenSearch(path, name, "exe");
218 if (notfound) real_file = FileOpenSearch(path, name, "cmd");
225 // Returns the real name of file name in directory path, with optional
227 string FileSearch(string const & path, string const & name,
230 // if `name' is an absolute path, we ignore the setting of `path'
231 // Expand Environmentvariables in 'name'
232 string tmpname = ReplaceEnvironmentPath(name);
233 string fullname = MakeAbsPath(tmpname, path);
235 // search first without extension, then with it.
236 if (IsFileReadable(fullname))
238 else if (ext.empty())
240 else { // Is it not more reasonable to use ChangeExtension()? (SMiyata)
243 if (IsFileReadable(fullname))
251 // Search the file name.ext in the subdirectory dir of
253 // 2) build_lyxdir (if not empty)
255 string LibFileSearch(string const & dir, string const & name,
258 string fullname = FileSearch(AddPath(user_lyxdir,dir), name,
260 if (!fullname.empty())
263 if (!build_lyxdir.empty())
264 fullname = FileSearch(AddPath(build_lyxdir, dir),
266 if (!fullname.empty())
269 return FileSearch(AddPath(system_lyxdir,dir), name, ext);
273 string i18nLibFileSearch(string const & dir, string const & name,
276 string lang = token(string(GetEnv("LANG")), '_', 0);
278 if (lang.empty() || lang == "C")
279 return LibFileSearch(dir, name, ext);
281 string tmp = LibFileSearch(dir, lang + '_' + name,
286 return LibFileSearch(dir, name, ext);
291 string GetEnv(string const & envname)
293 // f.ex. what about error checking?
294 char const * const ch = getenv(envname.c_str());
295 string envstr = !ch ? "" : ch;
300 string GetEnvPath(string const & name)
303 string pathlist = subst(GetEnv(name), ':', ';');
305 string pathlist = subst(GetEnv(name), '\\', '/');
307 return strip(pathlist, ';');
311 bool PutEnv(string const & envstr)
314 #warning Look at and fix this.
316 // f.ex. what about error checking?
319 // this leaks, but what can we do about it?
320 // Is doing a getenv() and a free() of the older value
321 // a good idea? (JMarc)
322 retval = putenv((new string(envstr))->c_str());
326 string str = envstr.split(varname,'=');
327 retval = setenv(varname.c_str(), str.c_str(), true);
334 bool PutEnvPath(string const & envstr)
336 return PutEnv(envstr);
341 int DeleteAllFilesInDir (string const & path)
344 DIR * dir = opendir(path.c_str());
346 WriteFSAlert (_("Error! Cannot open directory:"), path);
349 while ((de = readdir(dir))) {
350 string temp = de->d_name;
351 if (temp=="." || temp=="..")
353 string unlinkpath = AddName (path, temp);
355 lyxerr.debug() << "Deleting file: " << unlinkpath << endl;
357 if (remove (unlinkpath.c_str()))
358 WriteFSAlert (_("Error! Could not remove file:"),
367 string CreateTmpDir (string const & tempdir, string const & mask)
369 string tmpfl = TmpFileName(tempdir, mask);
371 if ((tmpfl.empty()) || mkdir (tmpfl.c_str(), 0777)) {
372 WriteFSAlert(_("Error! Couldn't create temporary directory:"),
376 return MakeAbsPath(tmpfl);
381 int DestroyTmpDir (string const & tmpdir, bool Allfiles)
386 if (Allfiles && DeleteAllFilesInDir(tmpdir)) return -1;
387 if (rmdir(tmpdir.c_str())) {
388 WriteFSAlert(_("Error! Couldn't delete temporary directory:"),
396 string CreateBufferTmpDir (string const & pathfor)
398 return CreateTmpDir (pathfor, "lyx_bufrtmp");
402 int DestroyBufferTmpDir (string const & tmpdir)
404 return DestroyTmpDir (tmpdir, true);
408 string CreateLyXTmpDir (string const & deflt)
412 if ((!deflt.empty()) && (deflt != "/tmp")) {
413 if (mkdir (deflt.c_str(), 0777)) {
417 t = CreateTmpDir (deflt.c_str(), "lyx_tmp");
425 t = CreateTmpDir ("/tmp", "lyx_tmp");
431 int DestroyLyXTmpDir (string const & tmpdir)
433 return DestroyTmpDir (tmpdir, false); // Why false?
437 // Creates directory. Returns true if succesfull
438 bool createDirectory(string const & path, int permission)
440 string temp = strip(CleanupPath(path), '/');
443 WriteAlert(_("Internal error!"),
444 _("Call to createDirectory with invalid name"));
448 if (mkdir(temp.c_str(), permission)) {
449 WriteFSAlert (_("Error! Couldn't create directory:"), temp);
456 // Returns current working directory
459 int n = 256; // Assume path is less than 256 chars
461 char * tbuf = new char[n];
464 // Safe. Hopefully all getcwds behave this way!
465 while (((err = lyx::getcwd (tbuf, n)) == 0) && (errno == ERANGE)) {
466 // Buffer too small, double the buffersize and try again
472 if (err) result = tbuf;
478 // Strip filename from path name
479 string OnlyPath(string const & Filename)
481 // If empty filename, return empty
482 if (Filename.empty()) return Filename;
484 // Find last / or start of filename
485 string::size_type j = Filename.rfind('/');
486 if (j == string::npos)
488 return Filename.substr(0, j + 1);
492 // Convert relative path into absolute path based on a basepath.
493 // If relpath is absolute, just use that.
494 // If basepath is empty, use CWD as base.
495 string MakeAbsPath(string const & RelPath, string const & BasePath)
497 // checks for already absolute path
498 if (AbsolutePath(RelPath))
500 if(RelPath[0]!='/' && RelPath[0]!='\\')
504 // Copies given paths
505 string TempRel = CleanupPath(RelPath);
509 if (!BasePath.empty()) {
513 char * with_drive = new char[_MAX_PATH];
514 _abspath(with_drive, BasePath.c_str(), _MAX_PATH);
515 TempBase = with_drive;
521 if (AbsolutePath(TempRel))
522 return TempBase.substr(0, 2) + TempRel;
525 // Handle /./ at the end of the path
526 while(suffixIs(TempBase, "/./"))
527 TempBase.erase(TempBase.length() - 2);
529 // processes relative path
530 string RTemp = TempRel;
533 while (!RTemp.empty()) {
535 RTemp = split(RTemp, Temp, '/');
537 if (Temp==".") continue;
539 // Remove one level of TempBase
540 int i = TempBase.length()-2;
543 while (i > 0 && TempBase[i] != '/') --i;
547 while (i > 2 && TempBase[i] != '/') --i;
550 TempBase.erase(i, string::npos);
554 // Add this piece to TempBase
555 if (!suffixIs(TempBase, '/'))
561 // returns absolute path
566 // Correctly append filename to the pathname.
567 // If pathname is '.', then don't use pathname.
568 // Chops any path of filename.
569 string AddName(string const & path, string const & fname)
572 string basename = OnlyFilename(fname);
576 if (path != "." && path != "./" && !path.empty()) {
577 buf = CleanupPath(path);
578 if (!suffixIs(path, '/'))
582 return buf + basename;
586 // Strips path from filename
587 string OnlyFilename(string const & fname)
592 string::size_type j = fname.rfind('/');
593 if (j == string::npos) // no '/' in fname
597 return fname.substr(j + 1);
601 // Is a filename/path absolute?
602 bool AbsolutePath(string const & path)
605 return (!path.empty() && path[0] == '/');
607 return (!path.empty() && (path[0]=='/' || (isalpha((unsigned char) path[0]) && path.length()>1 && path[1]==':')));
612 // Create absolute path. If impossible, don't do anything
613 // Supports ./ and ~/. Later we can add support for ~logname/. (Asger)
614 string ExpandPath(string const & path)
616 Assert(!path.empty()); // We don't allow empty path. (Lgb)
617 // checks for already absolute path
618 string RTemp = ReplaceEnvironmentPath(path);
619 if (AbsolutePath(RTemp))
626 RTemp=split(RTemp, Temp, '/');
629 return GetCWD() + '/' + RTemp;
630 } else if (Temp=="~") {
631 return GetEnvPath("HOME") + '/' + RTemp;
632 } else if (Temp=="..") {
633 return MakeAbsPath(copy);
635 // Don't know how to handle this
641 // Constracts path/../path
642 // Can't handle "../../" or "/../" (Asger)
643 string NormalizePath(string const & path)
649 if (AbsolutePath(path))
652 // Make implicit current directory explicit
655 while (!RTemp.empty()) {
657 RTemp = split(RTemp, Temp, '/');
661 } else if (Temp=="..") {
662 // Remove one level of TempBase
663 int i = TempBase.length()-2;
664 while (i>0 && TempBase[i] != '/')
666 if (i>=0 && TempBase[i] == '/')
667 TempBase.erase(i+1, string::npos);
671 TempBase += Temp + '/';
675 // returns absolute path
679 string CleanupPath(string const & path)
681 #ifdef __EMX__ /* SMiyata: This should fix searchpath bug. */
682 string temppath = subst(path, '\\', '/');
683 temppath = subst(temppath, "//", "/");
684 return lowercase(temppath);
685 #else // On unix, nothing to do
692 // Search ${...} as Variable-Name inside the string and replace it with
693 // the denoted environmentvariable
694 // Allow Variables according to
695 // variable := '$' '{' [A-Za-z_]{[A-Za-z_0-9]*} '}'
698 string ReplaceEnvironmentPath(string const & path)
700 Assert(!path.empty()); // We don't allow empty path. (Lgb)
702 // CompareChar: Environmentvariables starts with this character
703 // PathChar: Next path component start with this character
704 // while CompareChar found do:
705 // Split String with PathChar
706 // Search Environmentvariable
707 // if found: Replace Strings
709 const char CompareChar = '$';
710 const char FirstChar = '{';
711 const char EndChar = '}';
712 const char UnderscoreChar = '_';
713 string EndString; EndString += EndChar;
714 string FirstString; FirstString += FirstChar;
715 string CompareString; CompareString += CompareChar;
716 const string RegExp("*}*"); // Exist EndChar inside a String?
718 // first: Search for a '$' - Sign.
720 string result1; //(copy); // for split-calls
721 string result0 = split(path, result1, CompareChar);
722 while (!result0.empty()) {
723 string copy1(result0); // contains String after $
725 // Check, if there is an EndChar inside original String.
727 if (!regexMatch(copy1, RegExp)) {
728 // No EndChar inside. So we are finished
729 result1 += CompareString + result0;
735 string res0 = split(copy1, res1, EndChar);
736 // Now res1 holds the environmentvariable
737 // First, check, if Contents is ok.
738 if (res1.empty()) { // No environmentvariable. Continue Loop.
739 result1 += CompareString + FirstString;
743 // check contents of res1
744 const char * res1_contents = res1.c_str();
745 if (*res1_contents != FirstChar) {
746 // Again No Environmentvariable
747 result1 += CompareString;
751 // Check for variable names
752 // Situation ${} is detected as "No Environmentvariable"
753 const char * cp1 = res1_contents+1;
754 bool result = isalpha((unsigned char) *cp1) || (*cp1 == UnderscoreChar);
756 while (*cp1 && result) {
757 result = isalnum((unsigned char) *cp1) ||
758 (*cp1 == UnderscoreChar);
763 // no correct variable name
764 result1 += CompareString + res1 + EndString;
765 result0 = split(res0, res1, CompareChar);
770 string env = GetEnv(res1_contents+1);
772 // Congratulations. Environmentvariable found
775 result1 += CompareString + res1 + EndString;
778 result0 = split(res0, res1, CompareChar);
782 } // ReplaceEnvironmentPath
785 // Make relative path out of two absolute paths
786 string MakeRelPath(string const & abspath0, string const & basepath0)
787 // Makes relative path out of absolute path. If it is deeper than basepath,
788 // it's easy. If basepath and abspath share something (they are all deeper
789 // than some directory), it'll be rendered using ..'s. If they are completely
790 // different, then the absolute path will be used as relative path.
792 // This is a hack. It should probaly be done in another way. Lgb.
793 string abspath = CleanupPath(abspath0);
794 string basepath = CleanupPath(basepath0);
796 return "<unknown_path>";
798 const int abslen = abspath.length();
799 const int baselen = basepath.length();
801 // Find first different character
803 while (i < abslen && i < baselen && abspath[i] == basepath[i]) ++i;
806 if (i < abslen && i < baselen
807 || (i<abslen && abspath[i] != '/' && i==baselen)
808 || (i<baselen && basepath[i] != '/' && i==abslen))
810 if (i) --i; // here was the last match
811 while (i && abspath[i] != '/') --i;
815 // actually no match - cannot make it relative
819 // Count how many dirs there are in basepath above match
820 // and append as many '..''s into relpath
823 while (j < baselen) {
824 if (basepath[j] == '/') {
825 if (j+1 == baselen) break;
831 // Append relative stuff from common directory to abspath
832 if (abspath[i] == '/') ++i;
833 for (; i<abslen; ++i)
836 if (suffixIs(buf, '/'))
837 buf.erase(buf.length() - 1);
838 // Substitute empty with .
845 // Append sub-directory(ies) to a path in an intelligent way
846 string AddPath(string const & path, string const & path_2)
849 string path2 = CleanupPath(path_2);
851 if (!path.empty() && path != "." && path != "./") {
852 buf = CleanupPath(path);
853 if (path[path.length() - 1] != '/')
859 int p2start = path2.find_first_not_of('/');
861 int p2end = path2.find_last_not_of('/');
863 string tmp = path2.substr(p2start, p2end - p2start + 1);
871 Change extension of oldname to extension.
872 Strips path off if no_path == true.
873 If no extension on oldname, just appends.
875 string ChangeExtension(string const & oldname, string const & extension,
878 string::size_type last_slash = oldname.rfind('/');
879 string::size_type last_dot;
880 if (last_slash != string::npos)
881 last_dot = oldname.find('.', last_slash);
883 last_dot = oldname.rfind('.');
886 // Make sure the extension starts with a dot
887 if (!extension.empty() && extension[0] != '.')
892 if (no_path && last_slash != string::npos) {
893 ++last_slash; // step it
894 ret_str = oldname.substr(last_slash,
895 last_dot - last_slash) + ext;
897 ret_str = oldname.substr(0, last_dot) + ext;
898 return CleanupPath(ret_str);
903 // Creates a nice compact path for displaying
904 string MakeDisplayPath (string const & path, unsigned int threshold)
906 const int l1 = path.length();
908 // First, we try a relative path compared to home
909 string home = GetEnvPath("HOME");
910 string relhome = MakeRelPath(path, home);
912 unsigned int l2 = relhome.length();
916 // If we backup from home or don't have a relative path,
917 // this try is no good
918 if (prefixIs(relhome, "../") || AbsolutePath(relhome)) {
919 // relative path was no good, just use the original path
926 // Is the path too long?
927 if (l2 > threshold) {
933 while (relhome.length() > threshold)
934 relhome = split(relhome, temp, '/');
936 // Did we shortend everything away?
937 if (relhome.empty()) {
938 // Yes, filename in itself is too long.
939 // Pick the start and the end of the filename.
940 relhome = OnlyFilename(path);
941 string head = relhome.substr(0, threshold/2 - 3);
943 l2 = relhome.length();
945 relhome.substr(l2 - threshold/2 - 2, l2 - 1);
946 relhome = head + "..." + tail;
949 return prefix + relhome;
953 bool LyXReadLink(string const & File, string & Link)
955 char LinkBuffer[512];
956 // Should be PATH_MAX but that needs autconf support
958 nRead = readlink(File.c_str(), LinkBuffer,sizeof(LinkBuffer)-1);
961 LinkBuffer[nRead] = 0;