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 "pathstack.h" // I know it's OS/2 specific (SMiyata)
31 // Which part of this is still necessary? (JMarc).
34 # define NAMLEN(dirent) strlen((dirent)->d_name)
36 # define dirent direct
37 # define NAMLEN(dirent) (dirent)->d_namlen
39 # include <sys/ndir.h>
49 extern string system_lyxdir;
50 extern string build_lyxdir;
51 extern string user_lyxdir;
52 extern string system_tempdir;
55 bool IsLyXFilename(string const & filename)
57 return contains(filename, ".lyx");
61 bool IsSGMLFilename(string const & filename)
63 return contains(filename, ".sgml");
67 // Substitutes spaces with underscores in filename (and path)
68 string SpaceLess(string const & file)
70 string name = OnlyFilename(file);
71 string path = OnlyPath(file);
73 for (string::size_type i = 0; i < name.length(); ++i) {
75 if (!isalnum(name[i]) && name[i] != '.')
78 string temp = AddName(path, name);
79 // Replace spaces with underscores, also in directory
80 // No!!! I checked it that it is not necessary.
81 // temp.subst(' ','_');
87 /// Returns an unique name to be used as a temporary file.
88 string TmpFileName(string const & dir, string const & mask)
89 {// With all these temporary variables, it should be safe enough :-) (JMarc)
92 tmpdir = system_tempdir;
95 string tmpfl = AddName(tmpdir, mask);
97 // find a uniq postfix for the filename...
98 // using the pid, and...
99 tmpfl += tostr(getpid());
103 for (int a='a'; a<= 'z'; ++a)
104 for (int b='a'; b<= 'z'; ++b)
105 for (int c='a'; c<= 'z'; ++c) {
106 // if this is not enough I have no idea what
108 ret = tmpfl + char(a) + char(b) + char(c);
109 // check if the file exist
110 if (!fnfo.newFile(ret).exist())
113 lyxerr.print("Not able to find a uniq tmpfile name.");
118 // Is a file readable ?
119 bool IsFileReadable (string const & path)
122 if (file.isOK() && file.isRegular() && file.readable())
129 // Is a file read_only?
130 // return 1 read-write
132 // -1 error (doesn't exist, no access, anything else)
133 int IsFileWriteable (string const & path)
135 FilePtr fp(path, FilePtr::update);
137 if ((errno == EACCES) || (errno == EROFS)) {
138 //fp = FilePtr(path, FilePtr::read);
139 fp.reopen(path, FilePtr::read);
150 //returns 1: dir writeable
152 // -1: error- couldn't find out
153 int IsDirWriteable (string const & path)
155 string tmpfl = TmpFileName(path);
158 WriteFSAlert(_("LyX Internal Error!"),
159 _("Could not test if directory is writeable"));
162 FilePtr fp(tmpfl, FilePtr::truncate);
164 if (errno == EACCES) {
167 WriteFSAlert(_("LyX Internal Error!"),
168 _("Cannot open directory test file"));
173 if (remove (tmpfl.c_str())) {
174 WriteFSAlert(_("LyX Internal Error!"),
175 _("Created test file but cannot remove it?"));
182 // Uses a string of paths separated by ";"s to find a file to open.
183 // Can't cope with pathnames with a ';' in them. Returns full path to file.
184 // If path entry begins with $$LyX/, use system_lyxdir
185 // If path entry begins with $$User/, use user_lyxdir
186 // Example: "$$User/doc;$$LyX/doc"
187 string FileOpenSearch (string const & path, string const & name,
190 string real_file, path_element;
191 bool notfound = true;
192 string tmppath=split(path, path_element, ';');
194 while (notfound && !path_element.empty()) {
195 path_element = CleanupPath(path_element);
196 if (!suffixIs(path_element, '/'))
198 path_element = subst(path_element, "$$LyX", system_lyxdir);
199 path_element = subst(path_element, "$$User", user_lyxdir);
201 real_file = FileSearch(path_element, name, ext);
203 if (real_file.empty()) {
204 tmppath = split(tmppath, path_element, ';');
210 if (ext.empty() && notfound) {
211 real_file = FileOpenSearch(path, name, "exe");
212 if (notfound) real_file = FileOpenSearch(path, name, "cmd");
219 // Returns the real name of file name in directory path, with optional
221 string FileSearch(string const & path, string const & name,
224 // if `name' is an absolute path, we ignore the setting of `path'
225 // Expand Environmentvariables in 'name'
226 string tmpname = ReplaceEnvironmentPath(name);
227 string fullname = MakeAbsPath(tmpname, path);
229 // search first without extension, then with it.
230 if (IsFileReadable(fullname))
232 else if (ext.empty())
234 else { // Is it not more reasonable to use ChangeExtension()? (SMiyata)
237 if (IsFileReadable(fullname))
245 // Search the file name.ext in the subdirectory dir of
247 // 2) build_lyxdir (if not empty)
249 string LibFileSearch(string const & dir, string const & name,
252 string fullname = FileSearch(AddPath(user_lyxdir,dir), name,
254 if (!fullname.empty())
257 if (!build_lyxdir.empty())
258 fullname = FileSearch(AddPath(build_lyxdir, dir),
260 if (!fullname.empty())
263 return FileSearch(AddPath(system_lyxdir,dir), name, ext);
267 string i18nLibFileSearch(string const & dir, string const & name,
270 string lang = token(string(GetEnv("LANG")), '_', 0);
272 if (lang.empty() || lang == "C")
273 return LibFileSearch(dir, name, ext);
275 string tmp = LibFileSearch(dir, lang + '_' + name,
280 return LibFileSearch(dir, name, ext);
285 string GetEnv(string const & envname)
287 // f.ex. what about error checking?
288 char const * const ch = getenv(envname.c_str());
289 string envstr = !ch ? "" : ch;
294 string GetEnvPath(string const & name)
297 string pathlist = subst(GetEnv(name), ':', ';');
299 string pathlist = subst(GetEnv(name), '\\', '/');
301 return strip(pathlist, ';');
305 bool PutEnv(string const & envstr)
307 #warning Look at and fix this.
308 // f.ex. what about error checking?
311 // this leaks, but what can we do about it?
312 // Is doing a getenv() and a free() of the older value
313 // a good idea? (JMarc)
314 retval = putenv((new string(envstr))->c_str());
318 string str = envstr.split(varname,'=');
319 retval = setenv(varname.c_str(), str.c_str(), true);
326 bool PutEnvPath(string const & envstr)
328 string pathlist = envstr;
329 #warning Verify that this is correct.
331 pathlist.subst(':', ';');
332 pathlist.subst('/', '\\');
334 return PutEnv(pathlist);
339 int DeleteAllFilesInDir (string const & path)
343 dir = opendir(path.c_str());
345 WriteFSAlert (_("Error! Cannot open directory:"), path);
348 while ((de = readdir(dir))) {
349 string temp = de->d_name;
350 if (temp=="." || temp=="..")
352 string unlinkpath = AddName (path, temp);
354 lyxerr.debug("Deleting file: " + unlinkpath);
356 if (remove (unlinkpath.c_str()))
357 WriteFSAlert (_("Error! Could not remove file:"),
366 string CreateTmpDir (string const & tempdir, string const & mask)
368 string tmpfl = TmpFileName(tempdir, mask);
370 if ((tmpfl.empty()) || mkdir (tmpfl.c_str(), 0777)) {
371 WriteFSAlert(_("Error! Couldn't create temporary directory:"),
375 return MakeAbsPath(tmpfl);
380 int DestroyTmpDir (string const & tmpdir, bool Allfiles)
382 if ((Allfiles) && (DeleteAllFilesInDir (tmpdir))) return -1;
383 if (rmdir(tmpdir.c_str())) {
385 if (errno == EBUSY) {
386 chdir(user_lyxdir.c_str()); // They are in the same drive.
387 if (!rmdir(tmpdir.c_str())) return 0;
390 WriteFSAlert(_("Error! Couldn't delete temporary directory:"),
398 string CreateBufferTmpDir (string const & pathfor)
400 return CreateTmpDir (pathfor, "lyx_bufrtmp");
404 int DestroyBufferTmpDir (string const & tmpdir)
406 return DestroyTmpDir (tmpdir, true);
410 string CreateLyXTmpDir (string const & deflt)
414 if ((!deflt.empty()) && (deflt != "/tmp")) {
415 if (mkdir (deflt.c_str(), 0777)) {
417 PathPush(user_lyxdir);
419 t = CreateTmpDir (deflt.c_str(), "lyx_tmp");
428 PathPush(user_lyxdir);
430 t = CreateTmpDir ("/tmp", "lyx_tmp");
439 int DestroyLyXTmpDir (string const & tmpdir)
441 return DestroyTmpDir (tmpdir, false); // Why false?
445 // Creates directory. Returns true if succesfull
446 bool createDirectory(string const & path, int permission)
448 string temp = strip(CleanupPath(path), '/');
451 WriteAlert(_("Internal error!"),
452 _("Call to createDirectory with invalid name"));
456 if (mkdir(temp.c_str(), permission)) {
457 WriteFSAlert (_("Error! Couldn't create directory:"), temp);
464 // Returns current working directory
467 int n = 256; // Assume path is less than 256 chars
469 char * tbuf = new char [n];
472 // Safe. Hopefully all getcwds behave this way!
473 while (((err = getcwd (tbuf, n)) == 0) && (errno == ERANGE)) {
474 // Buffer too small, double the buffersize and try again
480 if (err) result = tbuf;
486 // Strip filename from path name
487 string OnlyPath(string const & Filename)
489 // If empty filename, return empty
490 if (Filename.empty()) return Filename;
492 // Find last / or start of filename
493 string::size_type j = Filename.rfind('/');
496 return Filename.substr(0, j+1);
500 // Convert relative path into absolute path based on a basepath.
501 // If relpath is absolute, just use that.
502 // If basepath is empty, use CWD as base.
503 string MakeAbsPath(string const & RelPath, string const & BasePath)
505 // checks for already absolute path
506 if (AbsolutePath(RelPath))
508 if(RelPath[0]!='/' || RelPath[0]!='\\')
512 // Copies given paths
513 string TempRel = CleanupPath(RelPath);
517 if (!BasePath.empty()) {
521 char * with_drive = new char[_MAX_PATH];
522 _abspath(with_drive, BasePath.c_str(), _MAX_PATH);
523 TempBase = with_drive;
529 if (AbsolutePath(TempRel))
530 return TempBase[0] + TempRel;
533 // Handle /./ at the end of the path
534 while(suffixIs(TempBase, "/./"))
535 TempBase.erase(TempBase.length() - 2);
537 // processes relative path
538 string RTemp = TempRel;
541 while (!RTemp.empty()) {
543 RTemp = split(RTemp, Temp, '/');
545 if (Temp==".") continue;
547 // Remove one level of TempBase
548 int i = TempBase.length()-2;
551 while (i>0 && TempBase[i] != '/') --i;
555 while (i>2 && TempBase[i] != '/') --i;
558 TempBase.erase(i, string::npos);
562 // Add this piece to TempBase
563 if (!suffixIs(TempBase, '/'))
569 // returns absolute path
574 // Correctly append filename to the pathname.
575 // If pathname is '.', then don't use pathname.
576 // Chops any path of filename.
577 string AddName(string const & path, string const & fname)
580 string basename = OnlyFilename(fname);
584 if (path != "." && path != "./" && !path.empty()) {
585 buf = CleanupPath(path);
586 if (!suffixIs(path, '/'))
590 return buf + basename;
594 // Strips path from filename
595 string OnlyFilename(string const & fname)
597 Assert(!fname.empty()); // We don't allow empty filename. (Lgb)
599 string::size_type j = fname.rfind('/');
600 if (j == string::npos) // no '/' in fname
604 return fname.substr(j + 1);
608 // Is a filename/path absolute?
609 bool AbsolutePath(string const & path)
612 return (!path.empty() && path[0] == '/');
614 return (!path.empty() && path[0]=='/' || (isalpha((unsigned char) path[0]) && path[1]==':'));
619 // Create absolute path. If impossible, don't do anything
620 // Supports ./ and ~/. Later we can add support for ~logname/. (Asger)
621 string ExpandPath(string const & path)
623 Assert(!path.empty()); // We don't allow empty path. (Lgb)
624 // checks for already absolute path
625 string RTemp = ReplaceEnvironmentPath(path);
626 if (AbsolutePath(RTemp))
633 RTemp=split(RTemp, Temp, '/');
636 return GetCWD() + '/' + RTemp;
637 } else if (Temp=="~") {
638 return GetEnvPath("HOME") + '/' + RTemp;
639 } else if (Temp=="..") {
640 return MakeAbsPath(copy);
642 // Don't know how to handle this
648 // Constracts path/../path
649 // Can't handle "../../" or "/../" (Asger)
650 string NormalizePath(string const & path)
652 Assert(!path.empty()); // We don't allow empty path. (Lgb)
658 if (AbsolutePath(path))
661 // Make implicit current directory explicit
664 while (!RTemp.empty()) {
666 RTemp = split(RTemp, Temp, '/');
670 } else if (Temp=="..") {
671 // Remove one level of TempBase
672 int i = TempBase.length()-2;
673 while (i>0 && TempBase[i] != '/')
675 if (i>=0 && TempBase[i] == '/')
676 TempBase.erase(i+1, string::npos);
680 TempBase += Temp + '/';
684 // returns absolute path
688 string CleanupPath(string const & path)
690 #ifdef __EMX__ /* SMiyata: This should fix searchpath bug. */
691 string temppath(path);
692 subst(tmppath, '\\', '/');
693 subst(tmppath, "//", "/");
694 return lowercase(temppath);
695 #else // On unix, nothing to do
702 // Search ${...} as Variable-Name inside the string and replace it with
703 // the denoted environmentvariable
704 // Allow Variables according to
705 // variable := '$' '{' [A-Za-z_]{[A-Za-z_0-9]*} '}'
708 string ReplaceEnvironmentPath(string const & path)
710 Assert(!path.empty()); // We don't allow empty path. (Lgb)
712 // CompareChar: Environmentvariables starts with this character
713 // PathChar: Next path component start with this character
714 // while CompareChar found do:
715 // Split String with PathChar
716 // Search Environmentvariable
717 // if found: Replace Strings
719 const char CompareChar = '$';
720 const char FirstChar = '{';
721 const char EndChar = '}';
722 const char UnderscoreChar = '_';
723 string EndString; EndString += EndChar;
724 string FirstString; FirstString += FirstChar;
725 string CompareString; CompareString += CompareChar;
726 const string RegExp("*}*"); // Exist EndChar inside a String?
728 // first: Search for a '$' - Sign.
730 string result1; //(copy); // for split-calls
731 string result0 = split(path, result1, CompareChar);
732 while (!result0.empty()) {
733 string copy1(result0); // contains String after $
735 // Check, if there is an EndChar inside original String.
737 if (!regexMatch(copy1, RegExp)) {
738 // No EndChar inside. So we are finished
739 result1 += CompareString + result0;
745 string res0 = split(copy1, res1, EndChar);
746 // Now res1 holds the environmentvariable
747 // First, check, if Contents is ok.
748 if (res1.empty()) { // No environmentvariable. Continue Loop.
749 result1 += CompareString + FirstString;
753 // check contents of res1
754 const char * res1_contents = res1.c_str();
755 if (*res1_contents != FirstChar) {
756 // Again No Environmentvariable
757 result1 += CompareString;
761 // Check for variable names
762 // Situation ${} is detected as "No Environmentvariable"
763 const char * cp1 = res1_contents+1;
764 bool result = isalpha((unsigned char) *cp1) || (*cp1 == UnderscoreChar);
766 while (*cp1 && result) {
767 result = isalnum((unsigned char) *cp1) ||
768 (*cp1 == UnderscoreChar);
773 // no correct variable name
774 result1 += CompareString + res1 + EndString;
775 result0 = split(res0, res1, CompareChar);
780 string env = GetEnv(res1_contents+1);
782 // Congratulations. Environmentvariable found
785 result1 += CompareString + res1 + EndString;
788 result0 = split(res0, res1, CompareChar);
792 } // ReplaceEnvironmentPath
795 // Make relative path out of two absolute paths
796 string MakeRelPath(string const & abspath0, string const & basepath0)
797 // Makes relative path out of absolute path. If it is deeper than basepath,
798 // it's easy. If basepath and abspath share something (they are all deeper
799 // than some directory), it'll be rendered using ..'s. If they are completely
800 // different, then the absolute path will be used as relative path.
802 // This is a hack. It should probaly be done in another way. Lgb.
803 string abspath = CleanupPath(abspath0);
804 string basepath = CleanupPath(basepath0);
806 return "<unknown_path>";
808 const int abslen = abspath.length();
809 const int baselen = basepath.length();
811 // Find first different character
813 while (i < abslen && i < baselen && abspath[i] == basepath[i]) ++i;
816 if (i < abslen && i < baselen
817 || (i<abslen && abspath[i] != '/' && i==baselen)
818 || (i<baselen && basepath[i] != '/' && i==abslen))
820 if (i) --i; // here was the last match
821 while (i && abspath[i] != '/') --i;
825 // actually no match - cannot make it relative
829 // Count how many dirs there are in basepath above match
830 // and append as many '..''s into relpath
833 while (j < baselen) {
834 if (basepath[j] == '/') {
835 if (j+1 == baselen) break;
841 // Append relative stuff from common directory to abspath
842 if (abspath[i] == '/') ++i;
843 for (; i<abslen; ++i)
846 if (suffixIs(buf, '/'))
847 buf.erase(buf.length() - 1);
848 // Substitute empty with .
855 // Append sub-directory(ies) to a path in an intelligent way
856 string AddPath(string const & path, string const & path_2)
859 string path2 = CleanupPath(path_2);
861 if (!path.empty() && path != "." && path != "./") {
862 buf = CleanupPath(path);
863 if (path[path.length() - 1] != '/')
869 int p2start = path2.find_first_not_of('/');
870 //while (path2[p2start] == '/') ++p2start;
872 int p2end = path2.find_last_not_of('/');
873 //while (path2[p2end] == '/') --p2end;
875 string tmp = path2.substr(p2start, p2end - p2start + 1);
883 Change extension of oldname to extension.
884 Strips path off if no_path == true.
885 If no extension on oldname, just appends.
887 string ChangeExtension(string const & oldname, string const & extension,
890 string::size_type last_slash = oldname.rfind('/');
891 string::size_type last_dot;
892 if (last_slash != string::npos)
893 last_dot = oldname.find('.', last_slash);
895 last_dot = oldname.rfind('.');
898 // Make sure the extension starts with a dot
899 if (!extension.empty() && extension[0] != '.')
904 if (no_path && last_slash != string::npos) {
905 ++last_slash; // step it
906 ret_str = oldname.substr(last_slash,
907 last_dot - last_slash) + ext;
909 ret_str = oldname.substr(0, last_dot) + ext;
910 return CleanupPath(ret_str);
915 // Creates a nice compact path for displaying
916 string MakeDisplayPath (string const & path, unsigned int threshold)
918 const int l1 = path.length();
920 // First, we try a relative path compared to home
921 string home = GetEnvPath("HOME");
922 string relhome = MakeRelPath(path, home);
924 unsigned int l2 = relhome.length();
928 // If we backup from home or don't have a relative path,
929 // this try is no good
930 if (prefixIs(relhome, "../") || AbsolutePath(relhome)) {
931 // relative path was no good, just use the original path
938 // Is the path too long?
939 if (l2 > threshold) {
945 while (relhome.length() > threshold)
946 relhome = split(relhome, temp, '/');
948 // Did we shortend everything away?
949 if (relhome.empty()) {
950 // Yes, filename in itself is too long.
951 // Pick the start and the end of the filename.
952 relhome = OnlyFilename(path);
953 string head = relhome.substr(0, threshold/2 - 3);
955 l2 = relhome.length();
957 relhome.substr(l2 - threshold/2 - 2, l2 - 1);
958 relhome = head + "..." + tail;
961 return prefix + relhome;
964 bool LyXReadLink(string const & File, string & Link)
966 char LinkBuffer[512];
967 // Should be PATH_MAX but that needs autconf support
969 nRead = readlink(File.c_str(), LinkBuffer,sizeof(LinkBuffer)-1);
972 LinkBuffer[nRead] = 0;