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
31 #pragma implementation "filetools.h"
34 #include "filetools.h"
35 #include "LSubstring.h"
36 #include "lyx_gui_misc.h"
38 #include "support/path.h" // I know it's OS/2 specific (SMiyata)
42 // Which part of this is still necessary? (JMarc).
45 # define NAMLEN(dirent) strlen((dirent)->d_name)
47 # define dirent direct
48 # define NAMLEN(dirent) (dirent)->d_namlen
50 # include <sys/ndir.h>
65 extern string system_lyxdir;
66 extern string build_lyxdir;
67 extern string user_lyxdir;
68 extern string system_tempdir;
71 bool IsLyXFilename(string const & filename)
73 return contains(filename, ".lyx");
77 // Substitutes spaces with underscores in filename (and path)
78 string MakeLatexName(string const & file)
80 string name = OnlyFilename(file);
81 string path = OnlyPath(file);
83 for (string::size_type i = 0; i < name.length(); ++i) {
84 name[i] &= 0x7f; // set 8th bit to 0
87 // ok so we scan through the string twice, but who cares.
88 string keep("abcdefghijklmnopqrstuvwxyz"
89 "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
90 "@!\"'()*+,-./0123456789:;<=>?[]`|");
92 string::size_type pos = 0;
93 while ((pos = name.find_first_not_of(keep, pos)) != string::npos) {
96 return AddName(path, name);
99 // Substitutes spaces with underscores in filename (and path)
100 string QuoteName(string const & name)
102 // CHECK Add proper emx support here!
104 return '\'' + name + '\'';
111 /// Returns an unique name to be used as a temporary file.
112 string TmpFileName(string const & dir, string const & mask)
113 {// With all these temporary variables, it should be safe enough :-) (JMarc)
116 tmpdir = system_tempdir;
119 string tmpfl = AddName(tmpdir, mask);
121 // find a uniq postfix for the filename...
122 // using the pid, and...
123 tmpfl += tostr(getpid());
127 for (int a = 'a'; a <= 'z'; ++a)
128 for (int b = 'a'; b <= 'z'; ++b)
129 for (int c = 'a'; c <= 'z'; ++c) {
130 // if this is not enough I have no idea what
132 ret = tmpfl + char(a) + char(b) + char(c);
133 // check if the file exist
134 if (!fnfo.newFile(ret).exist())
137 lyxerr << "Not able to find a uniq tmpfile name." << endl;
142 // Is a file readable ?
143 bool IsFileReadable (string const & path)
146 if (file.isOK() && file.isRegular() && file.readable())
153 // Is a file read_only?
154 // return 1 read-write
156 // -1 error (doesn't exist, no access, anything else)
157 int IsFileWriteable (string const & path)
160 if (fi.access(FileInfo::wperm|FileInfo::rperm)) // read-write
162 if (fi.readable()) // read-only
164 return -1; // everything else.
168 //returns 1: dir writeable
170 // -1: error- couldn't find out
171 int IsDirWriteable (string const & path)
173 string tmpfl = TmpFileName(path);
176 WriteFSAlert(_("LyX Internal Error!"),
177 _("Could not test if directory is writeable"));
181 if (fi.writable()) return 1;
187 // Uses a string of paths separated by ";"s to find a file to open.
188 // Can't cope with pathnames with a ';' in them. Returns full path to file.
189 // If path entry begins with $$LyX/, use system_lyxdir
190 // If path entry begins with $$User/, use user_lyxdir
191 // Example: "$$User/doc;$$LyX/doc"
192 string FileOpenSearch (string const & path, string const & name,
195 string real_file, path_element;
196 bool notfound = true;
197 string tmppath = split(path, path_element, ';');
199 while (notfound && !path_element.empty()) {
200 path_element = CleanupPath(path_element);
201 if (!suffixIs(path_element, '/'))
203 path_element = subst(path_element, "$$LyX", system_lyxdir);
204 path_element = subst(path_element, "$$User", user_lyxdir);
206 real_file = FileSearch(path_element, name, ext);
208 if (real_file.empty()) {
210 tmppath = split(tmppath, path_element, ';');
211 } while(!tmppath.empty() && path_element.empty());
217 if (ext.empty() && notfound) {
218 real_file = FileOpenSearch(path, name, "exe");
219 if (notfound) real_file = FileOpenSearch(path, name, "cmd");
226 // Returns the real name of file name in directory path, with optional
228 string FileSearch(string const & path, string const & name,
231 // if `name' is an absolute path, we ignore the setting of `path'
232 // Expand Environmentvariables in 'name'
233 string tmpname = ReplaceEnvironmentPath(name);
234 string fullname = MakeAbsPath(tmpname, path);
236 // search first without extension, then with it.
237 if (IsFileReadable(fullname))
239 else if (ext.empty())
241 else { // Is it not more reasonable to use ChangeExtension()? (SMiyata)
244 if (IsFileReadable(fullname))
252 // Search the file name.ext in the subdirectory dir of
254 // 2) build_lyxdir (if not empty)
256 string LibFileSearch(string const & dir, string const & name,
259 string fullname = FileSearch(AddPath(user_lyxdir, dir),
261 if (!fullname.empty())
264 if (!build_lyxdir.empty())
265 fullname = FileSearch(AddPath(build_lyxdir, dir),
267 if (!fullname.empty())
270 return FileSearch(AddPath(system_lyxdir, dir), name, ext);
274 string i18nLibFileSearch(string const & dir, string const & name,
277 string lang = token(string(GetEnv("LANG")), '_', 0);
279 if (lang.empty() || lang == "C")
280 return LibFileSearch(dir, name, ext);
282 string tmp = LibFileSearch(dir, lang + '_' + name,
287 return LibFileSearch(dir, name, ext);
292 string GetEnv(string const & envname)
294 // f.ex. what about error checking?
295 char const * const ch = getenv(envname.c_str());
296 string envstr = !ch ? "" : ch;
301 string GetEnvPath(string const & name)
304 string pathlist = subst(GetEnv(name), ':', ';');
306 string pathlist = subst(GetEnv(name), '\\', '/');
308 return strip(pathlist, ';');
312 bool PutEnv(string const & envstr)
314 // CHECK Look at and fix this.
315 // f.ex. what about error checking?
318 // this leaks, but what can we do about it?
319 // Is doing a getenv() and a free() of the older value
320 // a good idea? (JMarc)
321 // Actually we don't have to leak...calling putenv like this
322 // should be enough: ... and this is obviously not enough if putenv
323 // does not make a copy of the string. It is also not very wise to
324 // put a string on the free store. If we have to leak we should do it
326 char * leaker = new char[envstr.length() + 1];
327 envstr.copy(leaker, envstr.length());
328 leaker[envstr.length()] = '\0';
329 int retval = lyx::putenv(leaker);
331 // If putenv does not make a copy of the char const * this
332 // is very dangerous. OTOH if it does take a copy this is the
334 // The only implementation of putenv that I have seen does not
335 // allocate memory. _And_ after testing the putenv in glibc it
336 // seems that we need to make a copy of the string contents.
337 // I will enable the above.
338 //int retval = lyx::putenv(envstr.c_str());
342 string str = envstr.split(varname,'=');
343 int retval = setenv(varname.c_str(), str.c_str(), true);
345 // No environment setting function. Can this happen?
346 int retval = 1; //return an error condition.
353 bool PutEnvPath(string const & envstr)
355 return PutEnv(envstr);
360 int DeleteAllFilesInDir (string const & path)
362 // I have decided that we will be using parts from the boost
363 // library. Check out http://www.boost.org/
364 // For directory access we will then use the directory_iterator.
365 // Then the code will be something like:
366 // directory_iterator dit(path.c_str());
367 // if (<some way to detect failure>) {
368 // WriteFSAlert(_("Error! Cannot open directory:"), path);
371 // for (; dit != <someend>; ++dit) {
372 // if ((*dit) == 2." || (*dit) == "..")
374 // string unlinkpath = AddName(path, temp);
375 // if (remove(unlinkpath.c_str()))
376 // WriteFSAlert(_("Error! Could not remove file:"),
380 DIR * dir = opendir(path.c_str());
382 WriteFSAlert (_("Error! Cannot open directory:"), path);
386 while ((de = readdir(dir))) {
387 string temp = de->d_name;
388 if (temp == "." || temp == "..")
390 string unlinkpath = AddName (path, temp);
392 lyxerr.debug() << "Deleting file: " << unlinkpath << endl;
394 if (remove(unlinkpath.c_str()))
395 WriteFSAlert (_("Error! Could not remove file:"),
404 string CreateTmpDir (string const & tempdir, string const & mask)
406 string tmpfl = TmpFileName(tempdir, mask);
408 if ((tmpfl.empty()) || lyx::mkdir (tmpfl.c_str(), 0777)) {
409 WriteFSAlert(_("Error! Couldn't create temporary directory:"),
413 return MakeAbsPath(tmpfl);
418 int DestroyTmpDir (string const & tmpdir, bool Allfiles)
423 if (Allfiles && DeleteAllFilesInDir(tmpdir)) return -1;
424 if (rmdir(tmpdir.c_str())) {
425 WriteFSAlert(_("Error! Couldn't delete temporary directory:"),
433 string CreateBufferTmpDir (string const & pathfor)
435 return CreateTmpDir(pathfor, "lyx_bufrtmp");
439 int DestroyBufferTmpDir (string const & tmpdir)
441 return DestroyTmpDir(tmpdir, true);
445 string CreateLyXTmpDir (string const & deflt)
447 if ((!deflt.empty()) && (deflt != "/tmp")) {
448 if (lyx::mkdir(deflt.c_str(), 0777)) {
452 string t = CreateTmpDir (deflt.c_str(), "lyx_tmp");
460 string t = CreateTmpDir ("/tmp", "lyx_tmp");
466 int DestroyLyXTmpDir (string const & tmpdir)
468 return DestroyTmpDir (tmpdir, false); // Why false?
472 // Creates directory. Returns true if succesfull
473 bool createDirectory(string const & path, int permission)
475 string temp = strip(CleanupPath(path), '/');
478 WriteAlert(_("Internal error!"),
479 _("Call to createDirectory with invalid name"));
483 if (lyx::mkdir(temp.c_str(), permission)) {
484 WriteFSAlert (_("Error! Couldn't create directory:"), temp);
491 // Returns current working directory
494 int n = 256; // Assume path is less than 256 chars
496 char * tbuf = new char[n];
498 // Safe. Hopefully all getcwds behave this way!
499 while (((err = lyx::getcwd (tbuf, n)) == 0) && (errno == ERANGE)) {
500 // Buffer too small, double the buffersize and try again
507 if (err) result = tbuf;
513 // Strip filename from path name
514 string OnlyPath(string const & Filename)
516 // If empty filename, return empty
517 if (Filename.empty()) return Filename;
519 // Find last / or start of filename
520 string::size_type j = Filename.rfind('/');
521 if (j == string::npos)
523 return Filename.substr(0, j + 1);
527 // Convert relative path into absolute path based on a basepath.
528 // If relpath is absolute, just use that.
529 // If basepath is empty, use CWD as base.
530 string MakeAbsPath(string const & RelPath, string const & BasePath)
532 // checks for already absolute path
533 if (AbsolutePath(RelPath))
535 if(RelPath[0]!= '/' && RelPath[0]!= '\\')
539 // Copies given paths
540 string TempRel = CleanupPath(RelPath);
544 if (!BasePath.empty()) {
548 char * with_drive = new char[_MAX_PATH];
549 _abspath(with_drive, BasePath.c_str(), _MAX_PATH);
550 TempBase = with_drive;
556 if (AbsolutePath(TempRel))
557 return TempBase.substr(0, 2) + TempRel;
560 // Handle /./ at the end of the path
561 while(suffixIs(TempBase, "/./"))
562 TempBase.erase(TempBase.length() - 2);
564 // processes relative path
565 string RTemp = TempRel;
568 while (!RTemp.empty()) {
570 RTemp = split(RTemp, Temp, '/');
572 if (Temp == ".") continue;
574 // Remove one level of TempBase
575 int i = TempBase.length() - 2;
578 while (i > 0 && TempBase[i] != '/') --i;
582 while (i > 2 && TempBase[i] != '/') --i;
585 TempBase.erase(i, string::npos);
589 // Add this piece to TempBase
590 if (!suffixIs(TempBase, '/'))
596 // returns absolute path
601 // Correctly append filename to the pathname.
602 // If pathname is '.', then don't use pathname.
603 // Chops any path of filename.
604 string AddName(string const & path, string const & fname)
607 string basename = OnlyFilename(fname);
611 if (path != "." && path != "./" && !path.empty()) {
612 buf = CleanupPath(path);
613 if (!suffixIs(path, '/'))
617 return buf + basename;
621 // Strips path from filename
622 string OnlyFilename(string const & fname)
627 string::size_type j = fname.rfind('/');
628 if (j == string::npos) // no '/' in fname
632 return fname.substr(j + 1);
636 // Is a filename/path absolute?
637 bool AbsolutePath(string const & path)
640 return (!path.empty() && path[0] == '/');
642 return (!path.empty() && (path[0] == '/' || (isalpha(static_cast<unsigned char>(path[0])) && path.length()>1 && path[1] == ':')));
647 // Create absolute path. If impossible, don't do anything
648 // Supports ./ and ~/. Later we can add support for ~logname/. (Asger)
649 string ExpandPath(string const & path)
651 // checks for already absolute path
652 string RTemp = ReplaceEnvironmentPath(path);
653 if (AbsolutePath(RTemp))
660 RTemp= split(RTemp, Temp, '/');
663 return GetCWD() + '/' + RTemp;
664 } else if (Temp == "~") {
665 return GetEnvPath("HOME") + '/' + RTemp;
666 } else if (Temp == "..") {
667 return MakeAbsPath(copy);
669 // Don't know how to handle this
675 // Constracts path/../path
676 // Can't handle "../../" or "/../" (Asger)
677 string NormalizePath(string const & path)
683 if (AbsolutePath(path))
686 // Make implicit current directory explicit
689 while (!RTemp.empty()) {
691 RTemp = split(RTemp, Temp, '/');
695 } else if (Temp == "..") {
696 // Remove one level of TempBase
697 int i = TempBase.length() - 2;
698 while (i > 0 && TempBase[i] != '/')
700 if (i >= 0 && TempBase[i] == '/')
701 TempBase.erase(i + 1, string::npos);
705 TempBase += Temp + '/';
709 // returns absolute path
713 string CleanupPath(string const & path)
715 #ifdef __EMX__ /* SMiyata: This should fix searchpath bug. */
716 string temppath = subst(path, '\\', '/');
717 temppath = subst(temppath, "//", "/");
718 return lowercase(temppath);
719 #else // On unix, nothing to do
724 string GetFileContents(string const & fname) {
725 FileInfo finfo(fname);
727 ifstream ifs(fname.c_str());
729 std::ostringstream ofs;
731 #warning The rumour goes that this might leak, but who really cares?
738 return ofs.str().c_str();
741 char const * tmp = ofs.str();
748 lyxerr << "LyX was not able to read file '" << fname << "'" << endl;
754 // Search ${...} as Variable-Name inside the string and replace it with
755 // the denoted environmentvariable
756 // Allow Variables according to
757 // variable := '$' '{' [A-Za-z_]{[A-Za-z_0-9]*} '}'
760 string ReplaceEnvironmentPath(string const & path)
763 // CompareChar: Environmentvariables starts with this character
764 // PathChar: Next path component start with this character
765 // while CompareChar found do:
766 // Split String with PathChar
767 // Search Environmentvariable
768 // if found: Replace Strings
770 char const CompareChar = '$';
771 char const FirstChar = '{';
772 char const EndChar = '}';
773 char const UnderscoreChar = '_';
774 string EndString; EndString += EndChar;
775 string FirstString; FirstString += FirstChar;
776 string CompareString; CompareString += CompareChar;
777 string const RegExp("*}*"); // Exist EndChar inside a String?
779 // first: Search for a '$' - Sign.
781 string result1; //(copy); // for split-calls
782 string result0 = split(path, result1, CompareChar);
783 while (!result0.empty()) {
784 string copy1(result0); // contains String after $
786 // Check, if there is an EndChar inside original String.
788 if (!regexMatch(copy1, RegExp)) {
789 // No EndChar inside. So we are finished
790 result1 += CompareString + result0;
796 string res0 = split(copy1, res1, EndChar);
797 // Now res1 holds the environmentvariable
798 // First, check, if Contents is ok.
799 if (res1.empty()) { // No environmentvariable. Continue Loop.
800 result1 += CompareString + FirstString;
804 // check contents of res1
805 char const * res1_contents = res1.c_str();
806 if (*res1_contents != FirstChar) {
807 // Again No Environmentvariable
808 result1 += CompareString;
812 // Check for variable names
813 // Situation ${} is detected as "No Environmentvariable"
814 char const * cp1 = res1_contents + 1;
815 bool result = isalpha(*cp1) || (*cp1 == UnderscoreChar);
817 while (*cp1 && result) {
818 result = isalnum(*cp1) ||
819 (*cp1 == UnderscoreChar);
824 // no correct variable name
825 result1 += CompareString + res1 + EndString;
826 result0 = split(res0, res1, CompareChar);
831 string env = GetEnv(res1_contents+1);
833 // Congratulations. Environmentvariable found
836 result1 += CompareString + res1 + EndString;
839 result0 = split(res0, res1, CompareChar);
843 } // ReplaceEnvironmentPath
846 // Make relative path out of two absolute paths
847 string MakeRelPath(string const & abspath0, string const & basepath0)
848 // Makes relative path out of absolute path. If it is deeper than basepath,
849 // it's easy. If basepath and abspath share something (they are all deeper
850 // than some directory), it'll be rendered using ..'s. If they are completely
851 // different, then the absolute path will be used as relative path.
853 // This is a hack. It should probaly be done in another way. Lgb.
854 string abspath = CleanupPath(abspath0);
855 string basepath = CleanupPath(basepath0);
857 return "<unknown_path>";
859 const int abslen = abspath.length();
860 const int baselen = basepath.length();
862 // Find first different character
864 while (i < abslen && i < baselen && abspath[i] == basepath[i]) ++i;
867 if (i < abslen && i < baselen
868 || (i<abslen && abspath[i] != '/' && i == baselen)
869 || (i<baselen && basepath[i] != '/' && i == abslen))
871 if (i) --i; // here was the last match
872 while (i && abspath[i] != '/') --i;
876 // actually no match - cannot make it relative
880 // Count how many dirs there are in basepath above match
881 // and append as many '..''s into relpath
884 while (j < baselen) {
885 if (basepath[j] == '/') {
886 if (j + 1 == baselen) break;
892 // Append relative stuff from common directory to abspath
893 if (abspath[i] == '/') ++i;
894 for (; i < abslen; ++i)
897 if (suffixIs(buf, '/'))
898 buf.erase(buf.length() - 1);
899 // Substitute empty with .
906 // Append sub-directory(ies) to a path in an intelligent way
907 string AddPath(string const & path, string const & path_2)
910 string path2 = CleanupPath(path_2);
912 if (!path.empty() && path != "." && path != "./") {
913 buf = CleanupPath(path);
914 if (path[path.length() - 1] != '/')
919 int p2start = path2.find_first_not_of('/');
921 int p2end = path2.find_last_not_of('/');
923 string tmp = path2.substr(p2start, p2end - p2start + 1);
931 Change extension of oldname to extension.
932 Strips path off if no_path == true.
933 If no extension on oldname, just appends.
935 string ChangeExtension(string const & oldname, string const & extension)
937 string::size_type last_slash = oldname.rfind('/');
938 string::size_type last_dot = oldname.rfind('.');
939 if (last_dot < last_slash && last_slash != string::npos)
940 last_dot = string::npos;
943 // Make sure the extension starts with a dot
944 if (!extension.empty() && extension[0] != '.')
945 ext= '.' + extension;
949 return CleanupPath(oldname.substr(0, last_dot) + ext);
953 // Creates a nice compact path for displaying
954 string MakeDisplayPath (string const & path, unsigned int threshold)
956 const int l1 = path.length();
958 // First, we try a relative path compared to home
959 string home = GetEnvPath("HOME");
960 string relhome = MakeRelPath(path, home);
962 unsigned int l2 = relhome.length();
966 // If we backup from home or don't have a relative path,
967 // this try is no good
968 if (prefixIs(relhome, "../") || AbsolutePath(relhome)) {
969 // relative path was no good, just use the original path
976 // Is the path too long?
977 if (l2 > threshold) {
983 while (relhome.length() > threshold)
984 relhome = split(relhome, temp, '/');
986 // Did we shortend everything away?
987 if (relhome.empty()) {
988 // Yes, filename in itself is too long.
989 // Pick the start and the end of the filename.
990 relhome = OnlyFilename(path);
991 string head = relhome.substr(0, threshold/2 - 3);
993 l2 = relhome.length();
995 relhome.substr(l2 - threshold/2 - 2, l2 - 1);
996 relhome = head + "..." + tail;
999 return prefix + relhome;
1003 bool LyXReadLink(string const & File, string & Link)
1005 char LinkBuffer[512];
1006 // Should be PATH_MAX but that needs autconf support
1007 int nRead = readlink(File.c_str(), LinkBuffer, sizeof(LinkBuffer)-1);
1010 LinkBuffer[nRead] = 0;
1016 typedef pair<int, string> cmdret;
1018 cmdret do_popen(string const & cmd)
1020 // One question is if we should use popen or
1021 // create our own popen based on fork, exec, pipe
1022 // of course the best would be to have a
1023 // pstream (process stream), with the
1024 // variants ipstream, opstream
1025 FILE * inf = popen(cmd.c_str(), "r");
1029 ret += static_cast<char>(c);
1032 int pret = pclose(inf);
1033 return make_pair(pret, ret);
1037 string findtexfile(string const & fil, string const & /*format*/)
1039 /* There is no problem to extend this function too use other
1040 methods to look for files. It could be setup to look
1041 in environment paths and also if wanted as a last resort
1042 to a recursive find. One of the easier extensions would
1043 perhaps be to use the LyX file lookup methods. But! I am
1044 going to implement this until I see some demand for it.
1048 // If the file can be found directly, we just return a
1049 // absolute path version of it.
1050 if (FileInfo(fil).exist())
1051 return MakeAbsPath(fil);
1053 // No we try to find it using kpsewhich.
1054 // It seems from the kpsewhich manual page that it is safe to use
1055 // kpsewhich without --format: "When the --format option is not
1056 // given, the search path used when looking for a file is inferred
1057 // from the name given, by looking for a known extension. If no
1058 // known extension is found, the search path for TeX source files
1060 // However, we want to take advantage of the format sine almost all
1061 // the different formats has environment variables that can be used
1062 // to controll which paths to search. f.ex. bib looks in
1063 // BIBINPUTS and TEXBIB. Small list follows:
1064 // bib - BIBINPUTS, TEXBIB
1066 // graphic/figure - TEXPICTS, TEXINPUTS
1067 // ist - TEXINDEXSTYLE, INDEXSTYLE
1068 // pk - PROGRAMFONTS, PKFONTS, TEXPKS, GLYPHFONTS, TEXFONTS
1070 // tfm - TFMFONTS, TEXFONTS
1071 // This means that to use kpsewhich in the best possible way we
1072 // should help it by setting additional path in the approp. envir.var.
1073 string kpsecmd = "kpsewhich " + fil;
1075 cmdret c = do_popen(kpsecmd);
1077 lyxerr[Debug::LATEX] << "kpse status = " << c.first << "\n"
1078 << "kpse result = `" << strip(c.second, '\n')
1080 return c.first != -1 ? strip(c.second, '\n') : string();