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
24 #pragma implementation "filetools.h"
27 #include "filetools.h"
28 #include "LSubstring.h"
29 #include "lyx_gui_misc.h"
31 #include "support/path.h" // I know it's OS/2 specific (SMiyata)
35 // Which part of this is still necessary? (JMarc).
38 # define NAMLEN(dirent) strlen((dirent)->d_name)
40 # define dirent direct
41 # define NAMLEN(dirent) (dirent)->d_namlen
43 # include <sys/ndir.h>
57 extern string system_lyxdir;
58 extern string build_lyxdir;
59 extern string user_lyxdir;
60 extern string system_tempdir;
63 bool IsLyXFilename(string const & filename)
65 return contains(filename, ".lyx");
69 // Substitutes spaces with underscores in filename (and path)
70 string MakeLatexName(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(keep, pos)) != string::npos) {
88 return AddName(path, name);
91 // Substitutes spaces with underscores in filename (and path)
92 string QuoteName(string const & name)
95 #warning Add proper emx support here!
98 return '\'' + name + '\'';
105 /// Returns an unique name to be used as a temporary file.
106 string TmpFileName(string const & dir, string const & mask)
107 {// With all these temporary variables, it should be safe enough :-) (JMarc)
110 tmpdir = system_tempdir;
113 string tmpfl = AddName(tmpdir, mask);
115 // find a uniq postfix for the filename...
116 // using the pid, and...
117 tmpfl += tostr(getpid());
121 for (int a = 'a'; a <= 'z'; ++a)
122 for (int b = 'a'; b <= 'z'; ++b)
123 for (int c = 'a'; c <= 'z'; ++c) {
124 // if this is not enough I have no idea what
126 ret = tmpfl + char(a) + char(b) + char(c);
127 // check if the file exist
128 if (!fnfo.newFile(ret).exist())
131 lyxerr << "Not able to find a uniq tmpfile name." << endl;
136 // Is a file readable ?
137 bool IsFileReadable (string const & path)
140 if (file.isOK() && file.isRegular() && file.readable())
147 // Is a file read_only?
148 // return 1 read-write
150 // -1 error (doesn't exist, no access, anything else)
151 int IsFileWriteable (string const & path)
154 if (fi.access(FileInfo::wperm|FileInfo::rperm)) // read-write
156 if (fi.readable()) // read-only
158 return -1; // everything else.
162 //returns 1: dir writeable
164 // -1: error- couldn't find out
165 int IsDirWriteable (string const & path)
167 string tmpfl = TmpFileName(path);
170 WriteFSAlert(_("LyX Internal Error!"),
171 _("Could not test if directory is writeable"));
175 if (fi.writable()) return 1;
181 // Uses a string of paths separated by ";"s to find a file to open.
182 // Can't cope with pathnames with a ';' in them. Returns full path to file.
183 // If path entry begins with $$LyX/, use system_lyxdir
184 // If path entry begins with $$User/, use user_lyxdir
185 // Example: "$$User/doc;$$LyX/doc"
186 string FileOpenSearch (string const & path, string const & name,
189 string real_file, path_element;
190 bool notfound = true;
191 string tmppath = split(path, path_element, ';');
193 while (notfound && !path_element.empty()) {
194 path_element = CleanupPath(path_element);
195 if (!suffixIs(path_element, '/'))
197 path_element = subst(path_element, "$$LyX", system_lyxdir);
198 path_element = subst(path_element, "$$User", user_lyxdir);
200 real_file = FileSearch(path_element, name, ext);
202 if (real_file.empty()) {
204 tmppath = split(tmppath, path_element, ';');
205 } while(!tmppath.empty() && path_element.empty());
211 if (ext.empty() && notfound) {
212 real_file = FileOpenSearch(path, name, "exe");
213 if (notfound) real_file = FileOpenSearch(path, name, "cmd");
220 // Returns the real name of file name in directory path, with optional
222 string FileSearch(string const & path, string const & name,
225 // if `name' is an absolute path, we ignore the setting of `path'
226 // Expand Environmentvariables in 'name'
227 string tmpname = ReplaceEnvironmentPath(name);
228 string fullname = MakeAbsPath(tmpname, path);
230 // search first without extension, then with it.
231 if (IsFileReadable(fullname))
233 else if (ext.empty())
235 else { // Is it not more reasonable to use ChangeExtension()? (SMiyata)
238 if (IsFileReadable(fullname))
246 // Search the file name.ext in the subdirectory dir of
248 // 2) build_lyxdir (if not empty)
250 string LibFileSearch(string const & dir, string const & name,
253 string fullname = FileSearch(AddPath(user_lyxdir, dir),
255 if (!fullname.empty())
258 if (!build_lyxdir.empty())
259 fullname = FileSearch(AddPath(build_lyxdir, dir),
261 if (!fullname.empty())
264 return FileSearch(AddPath(system_lyxdir, dir), name, ext);
268 string i18nLibFileSearch(string const & dir, string const & name,
271 string lang = token(string(GetEnv("LANG")), '_', 0);
273 if (lang.empty() || lang == "C")
274 return LibFileSearch(dir, name, ext);
276 string tmp = LibFileSearch(dir, lang + '_' + name,
281 return LibFileSearch(dir, name, ext);
286 string GetEnv(string const & envname)
288 // f.ex. what about error checking?
289 char const * const ch = getenv(envname.c_str());
290 string envstr = !ch ? "" : ch;
295 string GetEnvPath(string const & name)
298 string pathlist = subst(GetEnv(name), ':', ';');
300 string pathlist = subst(GetEnv(name), '\\', '/');
302 return strip(pathlist, ';');
306 bool PutEnv(string const & envstr)
309 #warning Look at and fix this.
311 // f.ex. what about error checking?
313 // this leaks, but what can we do about it?
314 // Is doing a getenv() and a free() of the older value
315 // a good idea? (JMarc)
316 // Actually we don't have to leak...calling putenv like this
317 // should be enough: ... and this is obviously not enough if putenv
318 // does not make a copy of the string. It is also not very wise to
319 // put a string on the free store. If we have to leak we should do it
321 char * leaker = new char[envstr.length() + 1];
322 envstr.copy(leaker, envstr.length());
323 leaker[envstr.length()] = '\0';
324 int retval = lyx::putenv(leaker);
326 // If putenv does not make a copy of the char const * this
327 // is very dangerous. OTOH if it does take a copy this is the
329 // The only implementation of putenv that I have seen does not
330 // allocate memory. _And_ after testing the putenv in glibc it
331 // seems that we need to make a copy of the string contents.
332 // I will enable the above.
333 //int retval = lyx::putenv(envstr.c_str());
337 string str = envstr.split(varname,'=');
338 int retval = setenv(varname.c_str(), str.c_str(), true);
345 bool PutEnvPath(string const & envstr)
347 return PutEnv(envstr);
352 int DeleteAllFilesInDir (string const & path)
354 // I have decided that we will be using parts from the boost
355 // library. Check out http://www.boost.org/
356 // For directory access we will then use the directory_iterator.
357 // Then the code will be something like:
358 // directory_iterator dit(path.c_str());
359 // if (<some way to detect failure>) {
360 // WriteFSAlert(_("Error! Cannot open directory:"), path);
363 // for (; dit != <someend>; ++dit) {
364 // if ((*dit) == 2." || (*dit) == "..")
366 // string unlinkpath = AddName(path, temp);
367 // if (remove(unlinkpath.c_str()))
368 // WriteFSAlert(_("Error! Could not remove file:"),
372 DIR * dir = opendir(path.c_str());
374 WriteFSAlert (_("Error! Cannot open directory:"), path);
378 while ((de = readdir(dir))) {
379 string temp = de->d_name;
380 if (temp == "." || temp == "..")
382 string unlinkpath = AddName (path, temp);
384 lyxerr.debug() << "Deleting file: " << unlinkpath << endl;
386 if (remove(unlinkpath.c_str()))
387 WriteFSAlert (_("Error! Could not remove file:"),
396 string CreateTmpDir (string const & tempdir, string const & mask)
398 string tmpfl = TmpFileName(tempdir, mask);
400 if ((tmpfl.empty()) || lyx::mkdir (tmpfl.c_str(), 0777)) {
401 WriteFSAlert(_("Error! Couldn't create temporary directory:"),
405 return MakeAbsPath(tmpfl);
410 int DestroyTmpDir (string const & tmpdir, bool Allfiles)
415 if (Allfiles && DeleteAllFilesInDir(tmpdir)) return -1;
416 if (rmdir(tmpdir.c_str())) {
417 WriteFSAlert(_("Error! Couldn't delete temporary directory:"),
425 string CreateBufferTmpDir (string const & pathfor)
427 return CreateTmpDir(pathfor, "lyx_bufrtmp");
431 int DestroyBufferTmpDir (string const & tmpdir)
433 return DestroyTmpDir(tmpdir, true);
437 string CreateLyXTmpDir (string const & deflt)
439 if ((!deflt.empty()) && (deflt != "/tmp")) {
440 if (lyx::mkdir(deflt.c_str(), 0777)) {
444 string t = CreateTmpDir (deflt.c_str(), "lyx_tmp");
452 string t = CreateTmpDir ("/tmp", "lyx_tmp");
458 int DestroyLyXTmpDir (string const & tmpdir)
460 return DestroyTmpDir (tmpdir, false); // Why false?
464 // Creates directory. Returns true if succesfull
465 bool createDirectory(string const & path, int permission)
467 string temp = strip(CleanupPath(path), '/');
470 WriteAlert(_("Internal error!"),
471 _("Call to createDirectory with invalid name"));
475 if (lyx::mkdir(temp.c_str(), permission)) {
476 WriteFSAlert (_("Error! Couldn't create directory:"), temp);
483 // Returns current working directory
486 int n = 256; // Assume path is less than 256 chars
488 char * tbuf = new char[n];
490 // Safe. Hopefully all getcwds behave this way!
491 while (((err = lyx::getcwd (tbuf, n)) == 0) && (errno == ERANGE)) {
492 // Buffer too small, double the buffersize and try again
499 if (err) result = tbuf;
505 // Strip filename from path name
506 string OnlyPath(string const & Filename)
508 // If empty filename, return empty
509 if (Filename.empty()) return Filename;
511 // Find last / or start of filename
512 string::size_type j = Filename.rfind('/');
513 if (j == string::npos)
515 return Filename.substr(0, j + 1);
519 // Convert relative path into absolute path based on a basepath.
520 // If relpath is absolute, just use that.
521 // If basepath is empty, use CWD as base.
522 string MakeAbsPath(string const & RelPath, string const & BasePath)
524 // checks for already absolute path
525 if (AbsolutePath(RelPath))
527 if(RelPath[0]!= '/' && RelPath[0]!= '\\')
531 // Copies given paths
532 string TempRel = CleanupPath(RelPath);
536 if (!BasePath.empty()) {
540 char * with_drive = new char[_MAX_PATH];
541 _abspath(with_drive, BasePath.c_str(), _MAX_PATH);
542 TempBase = with_drive;
548 if (AbsolutePath(TempRel))
549 return TempBase.substr(0, 2) + TempRel;
552 // Handle /./ at the end of the path
553 while(suffixIs(TempBase, "/./"))
554 TempBase.erase(TempBase.length() - 2);
556 // processes relative path
557 string RTemp = TempRel;
560 while (!RTemp.empty()) {
562 RTemp = split(RTemp, Temp, '/');
564 if (Temp == ".") continue;
566 // Remove one level of TempBase
567 int i = TempBase.length() - 2;
570 while (i > 0 && TempBase[i] != '/') --i;
574 while (i > 2 && TempBase[i] != '/') --i;
577 TempBase.erase(i, string::npos);
581 // Add this piece to TempBase
582 if (!suffixIs(TempBase, '/'))
588 // returns absolute path
593 // Correctly append filename to the pathname.
594 // If pathname is '.', then don't use pathname.
595 // Chops any path of filename.
596 string AddName(string const & path, string const & fname)
599 string basename = OnlyFilename(fname);
603 if (path != "." && path != "./" && !path.empty()) {
604 buf = CleanupPath(path);
605 if (!suffixIs(path, '/'))
609 return buf + basename;
613 // Strips path from filename
614 string OnlyFilename(string const & fname)
619 string::size_type j = fname.rfind('/');
620 if (j == string::npos) // no '/' in fname
624 return fname.substr(j + 1);
628 // Is a filename/path absolute?
629 bool AbsolutePath(string const & path)
632 return (!path.empty() && path[0] == '/');
634 return (!path.empty() && (path[0] == '/' || (isalpha(static_cast<unsigned char>(path[0])) && path.length()>1 && path[1] == ':')));
639 // Create absolute path. If impossible, don't do anything
640 // Supports ./ and ~/. Later we can add support for ~logname/. (Asger)
641 string ExpandPath(string const & path)
643 // checks for already absolute path
644 string RTemp = ReplaceEnvironmentPath(path);
645 if (AbsolutePath(RTemp))
652 RTemp= split(RTemp, Temp, '/');
655 return GetCWD() + '/' + RTemp;
656 } else if (Temp == "~") {
657 return GetEnvPath("HOME") + '/' + RTemp;
658 } else if (Temp == "..") {
659 return MakeAbsPath(copy);
661 // Don't know how to handle this
667 // Constracts path/../path
668 // Can't handle "../../" or "/../" (Asger)
669 string NormalizePath(string const & path)
675 if (AbsolutePath(path))
678 // Make implicit current directory explicit
681 while (!RTemp.empty()) {
683 RTemp = split(RTemp, Temp, '/');
687 } else if (Temp == "..") {
688 // Remove one level of TempBase
689 int i = TempBase.length() - 2;
690 while (i > 0 && TempBase[i] != '/')
692 if (i >= 0 && TempBase[i] == '/')
693 TempBase.erase(i + 1, string::npos);
697 TempBase += Temp + '/';
701 // returns absolute path
705 string CleanupPath(string const & path)
707 #ifdef __EMX__ /* SMiyata: This should fix searchpath bug. */
708 string temppath = subst(path, '\\', '/');
709 temppath = subst(temppath, "//", "/");
710 return lowercase(temppath);
711 #else // On unix, nothing to do
718 // Search ${...} as Variable-Name inside the string and replace it with
719 // the denoted environmentvariable
720 // Allow Variables according to
721 // variable := '$' '{' [A-Za-z_]{[A-Za-z_0-9]*} '}'
724 string ReplaceEnvironmentPath(string const & path)
727 // CompareChar: Environmentvariables starts with this character
728 // PathChar: Next path component start with this character
729 // while CompareChar found do:
730 // Split String with PathChar
731 // Search Environmentvariable
732 // if found: Replace Strings
734 char const CompareChar = '$';
735 char const FirstChar = '{';
736 char const EndChar = '}';
737 char const UnderscoreChar = '_';
738 string EndString; EndString += EndChar;
739 string FirstString; FirstString += FirstChar;
740 string CompareString; CompareString += CompareChar;
741 string const RegExp("*}*"); // Exist EndChar inside a String?
743 // first: Search for a '$' - Sign.
745 string result1; //(copy); // for split-calls
746 string result0 = split(path, result1, CompareChar);
747 while (!result0.empty()) {
748 string copy1(result0); // contains String after $
750 // Check, if there is an EndChar inside original String.
752 if (!regexMatch(copy1, RegExp)) {
753 // No EndChar inside. So we are finished
754 result1 += CompareString + result0;
760 string res0 = split(copy1, res1, EndChar);
761 // Now res1 holds the environmentvariable
762 // First, check, if Contents is ok.
763 if (res1.empty()) { // No environmentvariable. Continue Loop.
764 result1 += CompareString + FirstString;
768 // check contents of res1
769 char const * res1_contents = res1.c_str();
770 if (*res1_contents != FirstChar) {
771 // Again No Environmentvariable
772 result1 += CompareString;
776 // Check for variable names
777 // Situation ${} is detected as "No Environmentvariable"
778 char const * cp1 = res1_contents + 1;
779 bool result = isalpha(*cp1) || (*cp1 == UnderscoreChar);
781 while (*cp1 && result) {
782 result = isalnum(*cp1) ||
783 (*cp1 == UnderscoreChar);
788 // no correct variable name
789 result1 += CompareString + res1 + EndString;
790 result0 = split(res0, res1, CompareChar);
795 string env = GetEnv(res1_contents+1);
797 // Congratulations. Environmentvariable found
800 result1 += CompareString + res1 + EndString;
803 result0 = split(res0, res1, CompareChar);
807 } // ReplaceEnvironmentPath
810 // Make relative path out of two absolute paths
811 string MakeRelPath(string const & abspath0, string const & basepath0)
812 // Makes relative path out of absolute path. If it is deeper than basepath,
813 // it's easy. If basepath and abspath share something (they are all deeper
814 // than some directory), it'll be rendered using ..'s. If they are completely
815 // different, then the absolute path will be used as relative path.
817 // This is a hack. It should probaly be done in another way. Lgb.
818 string abspath = CleanupPath(abspath0);
819 string basepath = CleanupPath(basepath0);
821 return "<unknown_path>";
823 const int abslen = abspath.length();
824 const int baselen = basepath.length();
826 // Find first different character
828 while (i < abslen && i < baselen && abspath[i] == basepath[i]) ++i;
831 if (i < abslen && i < baselen
832 || (i<abslen && abspath[i] != '/' && i == baselen)
833 || (i<baselen && basepath[i] != '/' && i == abslen))
835 if (i) --i; // here was the last match
836 while (i && abspath[i] != '/') --i;
840 // actually no match - cannot make it relative
844 // Count how many dirs there are in basepath above match
845 // and append as many '..''s into relpath
848 while (j < baselen) {
849 if (basepath[j] == '/') {
850 if (j + 1 == baselen) break;
856 // Append relative stuff from common directory to abspath
857 if (abspath[i] == '/') ++i;
858 for (; i < abslen; ++i)
861 if (suffixIs(buf, '/'))
862 buf.erase(buf.length() - 1);
863 // Substitute empty with .
870 // Append sub-directory(ies) to a path in an intelligent way
871 string AddPath(string const & path, string const & path_2)
874 string path2 = CleanupPath(path_2);
876 if (!path.empty() && path != "." && path != "./") {
877 buf = CleanupPath(path);
878 if (path[path.length() - 1] != '/')
883 int p2start = path2.find_first_not_of('/');
885 int p2end = path2.find_last_not_of('/');
887 string tmp = path2.substr(p2start, p2end - p2start + 1);
895 Change extension of oldname to extension.
896 Strips path off if no_path == true.
897 If no extension on oldname, just appends.
899 string ChangeExtension(string const & oldname, string const & extension,
902 string::size_type last_slash = oldname.rfind('/');
903 string::size_type last_dot = oldname.rfind('.');
904 if (last_dot < last_slash && last_slash != string::npos)
905 last_dot = string::npos;
908 // Make sure the extension starts with a dot
909 if (!extension.empty() && extension[0] != '.')
910 ext= '.' + extension;
914 if (no_path && last_slash != string::npos) {
915 ++last_slash; // step it
916 ret_str = oldname.substr(last_slash,
917 last_dot - last_slash) + ext;
919 ret_str = oldname.substr(0, last_dot) + ext;
920 return CleanupPath(ret_str);
924 // Creates a nice compact path for displaying
925 string MakeDisplayPath (string const & path, unsigned int threshold)
927 const int l1 = path.length();
929 // First, we try a relative path compared to home
930 string home = GetEnvPath("HOME");
931 string relhome = MakeRelPath(path, home);
933 unsigned int l2 = relhome.length();
937 // If we backup from home or don't have a relative path,
938 // this try is no good
939 if (prefixIs(relhome, "../") || AbsolutePath(relhome)) {
940 // relative path was no good, just use the original path
947 // Is the path too long?
948 if (l2 > threshold) {
954 while (relhome.length() > threshold)
955 relhome = split(relhome, temp, '/');
957 // Did we shortend everything away?
958 if (relhome.empty()) {
959 // Yes, filename in itself is too long.
960 // Pick the start and the end of the filename.
961 relhome = OnlyFilename(path);
962 string head = relhome.substr(0, threshold/2 - 3);
964 l2 = relhome.length();
966 relhome.substr(l2 - threshold/2 - 2, l2 - 1);
967 relhome = head + "..." + tail;
970 return prefix + relhome;
974 bool LyXReadLink(string const & File, string & Link)
976 char LinkBuffer[512];
977 // Should be PATH_MAX but that needs autconf support
978 int nRead = readlink(File.c_str(), LinkBuffer, sizeof(LinkBuffer)-1);
981 LinkBuffer[nRead] = 0;
987 typedef pair<int, string> cmdret;
989 cmdret do_popen(string const & cmd)
991 // One question is if we should use popen or
992 // create our own popen based on fork, exec, pipe
993 // of course the best would be to have a
994 // pstream (process stream), with the
995 // variants ipstream, opstream
996 FILE * inf = popen(cmd.c_str(), "r");
1000 ret += static_cast<char>(c);
1003 int pret = pclose(inf);
1004 return make_pair(pret, ret);
1008 string findtexfile(string const & fil, string const & /*format*/)
1010 /* There is no problem to extend this function too use other
1011 methods to look for files. It could be setup to look
1012 in environment paths and also if wanted as a last resort
1013 to a recursive find. One of the easier extensions would
1014 perhaps be to use the LyX file lookup methods. But! I am
1015 going to implement this until I see some demand for it.
1019 // If the file can be found directly, we just return a
1020 // absolute path version of it.
1021 if (FileInfo(fil).exist())
1022 return MakeAbsPath(fil);
1024 // No we try to find it using kpsewhich.
1025 // It seems from the kpsewhich manual page that it is safe to use
1026 // kpsewhich without --format: "When the --format option is not
1027 // given, the search path used when looking for a file is inferred
1028 // from the name given, by looking for a known extension. If no
1029 // known extension is found, the search path for TeX source files
1031 // However, we want to take advantage of the format sine almost all
1032 // the different formats has environment variables that can be used
1033 // to controll which paths to search. f.ex. bib looks in
1034 // BIBINPUTS and TEXBIB. Small list follows:
1035 // bib - BIBINPUTS, TEXBIB
1037 // graphic/figure - TEXPICTS, TEXINPUTS
1038 // ist - TEXINDEXSTYLE, INDEXSTYLE
1039 // pk - PROGRAMFONTS, PKFONTS, TEXPKS, GLYPHFONTS, TEXFONTS
1041 // tfm - TFMFONTS, TEXFONTS
1042 // This means that to use kpsewhich in the best possible way we
1043 // should help it by setting additional path in the approp. envir.var.
1044 string kpsecmd = "kpsewhich " + fil;
1046 cmdret c = do_popen(kpsecmd);
1048 lyxerr[Debug::LATEX] << "kpse status = " << c.first << "\n"
1049 << "kpse result = `" << strip(c.second, '\n')
1051 return c.first != -1 ? strip(c.second, '\n') : string();