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)
94 // CHECK Add proper emx support here!
96 return '\'' + name + '\'';
103 /// Returns an unique name to be used as a temporary file.
104 string TmpFileName(string const & dir, string const & mask)
105 {// With all these temporary variables, it should be safe enough :-) (JMarc)
108 tmpdir = system_tempdir;
111 string tmpfl = AddName(tmpdir, mask);
113 // find a uniq postfix for the filename...
114 // using the pid, and...
115 tmpfl += tostr(getpid());
119 for (int a = 'a'; a <= 'z'; ++a)
120 for (int b = 'a'; b <= 'z'; ++b)
121 for (int c = 'a'; c <= 'z'; ++c) {
122 // if this is not enough I have no idea what
124 ret = tmpfl + char(a) + char(b) + char(c);
125 // check if the file exist
126 if (!fnfo.newFile(ret).exist())
129 lyxerr << "Not able to find a uniq tmpfile name." << endl;
134 // Is a file readable ?
135 bool IsFileReadable (string const & path)
138 if (file.isOK() && file.isRegular() && file.readable())
145 // Is a file read_only?
146 // return 1 read-write
148 // -1 error (doesn't exist, no access, anything else)
149 int IsFileWriteable (string const & path)
152 if (fi.access(FileInfo::wperm|FileInfo::rperm)) // read-write
154 if (fi.readable()) // read-only
156 return -1; // everything else.
160 //returns 1: dir writeable
162 // -1: error- couldn't find out
163 int IsDirWriteable (string const & path)
165 string tmpfl = TmpFileName(path);
168 WriteFSAlert(_("LyX Internal Error!"),
169 _("Could not test if directory is writeable"));
173 if (fi.writable()) return 1;
179 // Uses a string of paths separated by ";"s to find a file to open.
180 // Can't cope with pathnames with a ';' in them. Returns full path to file.
181 // If path entry begins with $$LyX/, use system_lyxdir
182 // If path entry begins with $$User/, use user_lyxdir
183 // Example: "$$User/doc;$$LyX/doc"
184 string FileOpenSearch (string const & path, string const & name,
187 string real_file, path_element;
188 bool notfound = true;
189 string tmppath = split(path, path_element, ';');
191 while (notfound && !path_element.empty()) {
192 path_element = CleanupPath(path_element);
193 if (!suffixIs(path_element, '/'))
195 path_element = subst(path_element, "$$LyX", system_lyxdir);
196 path_element = subst(path_element, "$$User", user_lyxdir);
198 real_file = FileSearch(path_element, name, ext);
200 if (real_file.empty()) {
202 tmppath = split(tmppath, path_element, ';');
203 } while(!tmppath.empty() && path_element.empty());
209 if (ext.empty() && notfound) {
210 real_file = FileOpenSearch(path, name, "exe");
211 if (notfound) real_file = FileOpenSearch(path, name, "cmd");
218 // Returns the real name of file name in directory path, with optional
220 string FileSearch(string const & path, string const & name,
223 // if `name' is an absolute path, we ignore the setting of `path'
224 // Expand Environmentvariables in 'name'
225 string tmpname = ReplaceEnvironmentPath(name);
226 string fullname = MakeAbsPath(tmpname, path);
228 // search first without extension, then with it.
229 if (IsFileReadable(fullname))
231 else if (ext.empty())
233 else { // Is it not more reasonable to use ChangeExtension()? (SMiyata)
236 if (IsFileReadable(fullname))
244 // Search the file name.ext in the subdirectory dir of
246 // 2) build_lyxdir (if not empty)
248 string LibFileSearch(string const & dir, string const & name,
251 string fullname = FileSearch(AddPath(user_lyxdir, dir),
253 if (!fullname.empty())
256 if (!build_lyxdir.empty())
257 fullname = FileSearch(AddPath(build_lyxdir, dir),
259 if (!fullname.empty())
262 return FileSearch(AddPath(system_lyxdir, dir), name, ext);
266 string i18nLibFileSearch(string const & dir, string const & name,
269 string lang = token(string(GetEnv("LANG")), '_', 0);
271 if (lang.empty() || lang == "C")
272 return LibFileSearch(dir, name, ext);
274 string tmp = LibFileSearch(dir, lang + '_' + name,
279 return LibFileSearch(dir, name, ext);
284 string GetEnv(string const & envname)
286 // f.ex. what about error checking?
287 char const * const ch = getenv(envname.c_str());
288 string envstr = !ch ? "" : ch;
293 string GetEnvPath(string const & name)
296 string pathlist = subst(GetEnv(name), ':', ';');
298 string pathlist = subst(GetEnv(name), '\\', '/');
300 return strip(pathlist, ';');
304 bool PutEnv(string const & envstr)
306 // CHECK Look at and fix this.
307 // f.ex. what about error checking?
309 // this leaks, but what can we do about it?
310 // Is doing a getenv() and a free() of the older value
311 // a good idea? (JMarc)
312 // Actually we don't have to leak...calling putenv like this
313 // should be enough: ... and this is obviously not enough if putenv
314 // does not make a copy of the string. It is also not very wise to
315 // put a string on the free store. If we have to leak we should do it
317 char * leaker = new char[envstr.length() + 1];
318 envstr.copy(leaker, envstr.length());
319 leaker[envstr.length()] = '\0';
320 int retval = lyx::putenv(leaker);
322 // If putenv does not make a copy of the char const * this
323 // is very dangerous. OTOH if it does take a copy this is the
325 // The only implementation of putenv that I have seen does not
326 // allocate memory. _And_ after testing the putenv in glibc it
327 // seems that we need to make a copy of the string contents.
328 // I will enable the above.
329 //int retval = lyx::putenv(envstr.c_str());
333 string str = envstr.split(varname,'=');
334 int retval = setenv(varname.c_str(), str.c_str(), true);
341 bool PutEnvPath(string const & envstr)
343 return PutEnv(envstr);
348 int DeleteAllFilesInDir (string const & path)
350 // I have decided that we will be using parts from the boost
351 // library. Check out http://www.boost.org/
352 // For directory access we will then use the directory_iterator.
353 // Then the code will be something like:
354 // directory_iterator dit(path.c_str());
355 // if (<some way to detect failure>) {
356 // WriteFSAlert(_("Error! Cannot open directory:"), path);
359 // for (; dit != <someend>; ++dit) {
360 // if ((*dit) == 2." || (*dit) == "..")
362 // string unlinkpath = AddName(path, temp);
363 // if (remove(unlinkpath.c_str()))
364 // WriteFSAlert(_("Error! Could not remove file:"),
368 DIR * dir = opendir(path.c_str());
370 WriteFSAlert (_("Error! Cannot open directory:"), path);
374 while ((de = readdir(dir))) {
375 string temp = de->d_name;
376 if (temp == "." || temp == "..")
378 string unlinkpath = AddName (path, temp);
380 lyxerr.debug() << "Deleting file: " << unlinkpath << endl;
382 if (remove(unlinkpath.c_str()))
383 WriteFSAlert (_("Error! Could not remove file:"),
392 string CreateTmpDir (string const & tempdir, string const & mask)
394 string tmpfl = TmpFileName(tempdir, mask);
396 if ((tmpfl.empty()) || lyx::mkdir (tmpfl.c_str(), 0777)) {
397 WriteFSAlert(_("Error! Couldn't create temporary directory:"),
401 return MakeAbsPath(tmpfl);
406 int DestroyTmpDir (string const & tmpdir, bool Allfiles)
411 if (Allfiles && DeleteAllFilesInDir(tmpdir)) return -1;
412 if (rmdir(tmpdir.c_str())) {
413 WriteFSAlert(_("Error! Couldn't delete temporary directory:"),
421 string CreateBufferTmpDir (string const & pathfor)
423 return CreateTmpDir(pathfor, "lyx_bufrtmp");
427 int DestroyBufferTmpDir (string const & tmpdir)
429 return DestroyTmpDir(tmpdir, true);
433 string CreateLyXTmpDir (string const & deflt)
435 if ((!deflt.empty()) && (deflt != "/tmp")) {
436 if (lyx::mkdir(deflt.c_str(), 0777)) {
440 string t = CreateTmpDir (deflt.c_str(), "lyx_tmp");
448 string t = CreateTmpDir ("/tmp", "lyx_tmp");
454 int DestroyLyXTmpDir (string const & tmpdir)
456 return DestroyTmpDir (tmpdir, false); // Why false?
460 // Creates directory. Returns true if succesfull
461 bool createDirectory(string const & path, int permission)
463 string temp = strip(CleanupPath(path), '/');
466 WriteAlert(_("Internal error!"),
467 _("Call to createDirectory with invalid name"));
471 if (lyx::mkdir(temp.c_str(), permission)) {
472 WriteFSAlert (_("Error! Couldn't create directory:"), temp);
479 // Returns current working directory
482 int n = 256; // Assume path is less than 256 chars
484 char * tbuf = new char[n];
486 // Safe. Hopefully all getcwds behave this way!
487 while (((err = lyx::getcwd (tbuf, n)) == 0) && (errno == ERANGE)) {
488 // Buffer too small, double the buffersize and try again
495 if (err) result = tbuf;
501 // Strip filename from path name
502 string OnlyPath(string const & Filename)
504 // If empty filename, return empty
505 if (Filename.empty()) return Filename;
507 // Find last / or start of filename
508 string::size_type j = Filename.rfind('/');
509 if (j == string::npos)
511 return Filename.substr(0, j + 1);
515 // Convert relative path into absolute path based on a basepath.
516 // If relpath is absolute, just use that.
517 // If basepath is empty, use CWD as base.
518 string MakeAbsPath(string const & RelPath, string const & BasePath)
520 // checks for already absolute path
521 if (AbsolutePath(RelPath))
523 if(RelPath[0]!= '/' && RelPath[0]!= '\\')
527 // Copies given paths
528 string TempRel = CleanupPath(RelPath);
532 if (!BasePath.empty()) {
536 char * with_drive = new char[_MAX_PATH];
537 _abspath(with_drive, BasePath.c_str(), _MAX_PATH);
538 TempBase = with_drive;
544 if (AbsolutePath(TempRel))
545 return TempBase.substr(0, 2) + TempRel;
548 // Handle /./ at the end of the path
549 while(suffixIs(TempBase, "/./"))
550 TempBase.erase(TempBase.length() - 2);
552 // processes relative path
553 string RTemp = TempRel;
556 while (!RTemp.empty()) {
558 RTemp = split(RTemp, Temp, '/');
560 if (Temp == ".") continue;
562 // Remove one level of TempBase
563 int i = TempBase.length() - 2;
566 while (i > 0 && TempBase[i] != '/') --i;
570 while (i > 2 && TempBase[i] != '/') --i;
573 TempBase.erase(i, string::npos);
577 // Add this piece to TempBase
578 if (!suffixIs(TempBase, '/'))
584 // returns absolute path
589 // Correctly append filename to the pathname.
590 // If pathname is '.', then don't use pathname.
591 // Chops any path of filename.
592 string AddName(string const & path, string const & fname)
595 string basename = OnlyFilename(fname);
599 if (path != "." && path != "./" && !path.empty()) {
600 buf = CleanupPath(path);
601 if (!suffixIs(path, '/'))
605 return buf + basename;
609 // Strips path from filename
610 string OnlyFilename(string const & fname)
615 string::size_type j = fname.rfind('/');
616 if (j == string::npos) // no '/' in fname
620 return fname.substr(j + 1);
624 // Is a filename/path absolute?
625 bool AbsolutePath(string const & path)
628 return (!path.empty() && path[0] == '/');
630 return (!path.empty() && (path[0] == '/' || (isalpha(static_cast<unsigned char>(path[0])) && path.length()>1 && path[1] == ':')));
635 // Create absolute path. If impossible, don't do anything
636 // Supports ./ and ~/. Later we can add support for ~logname/. (Asger)
637 string ExpandPath(string const & path)
639 // checks for already absolute path
640 string RTemp = ReplaceEnvironmentPath(path);
641 if (AbsolutePath(RTemp))
648 RTemp= split(RTemp, Temp, '/');
651 return GetCWD() + '/' + RTemp;
652 } else if (Temp == "~") {
653 return GetEnvPath("HOME") + '/' + RTemp;
654 } else if (Temp == "..") {
655 return MakeAbsPath(copy);
657 // Don't know how to handle this
663 // Constracts path/../path
664 // Can't handle "../../" or "/../" (Asger)
665 string NormalizePath(string const & path)
671 if (AbsolutePath(path))
674 // Make implicit current directory explicit
677 while (!RTemp.empty()) {
679 RTemp = split(RTemp, Temp, '/');
683 } else if (Temp == "..") {
684 // Remove one level of TempBase
685 int i = TempBase.length() - 2;
686 while (i > 0 && TempBase[i] != '/')
688 if (i >= 0 && TempBase[i] == '/')
689 TempBase.erase(i + 1, string::npos);
693 TempBase += Temp + '/';
697 // returns absolute path
701 string CleanupPath(string const & path)
703 #ifdef __EMX__ /* SMiyata: This should fix searchpath bug. */
704 string temppath = subst(path, '\\', '/');
705 temppath = subst(temppath, "//", "/");
706 return lowercase(temppath);
707 #else // On unix, nothing to do
714 // Search ${...} as Variable-Name inside the string and replace it with
715 // the denoted environmentvariable
716 // Allow Variables according to
717 // variable := '$' '{' [A-Za-z_]{[A-Za-z_0-9]*} '}'
720 string ReplaceEnvironmentPath(string const & path)
723 // CompareChar: Environmentvariables starts with this character
724 // PathChar: Next path component start with this character
725 // while CompareChar found do:
726 // Split String with PathChar
727 // Search Environmentvariable
728 // if found: Replace Strings
730 char const CompareChar = '$';
731 char const FirstChar = '{';
732 char const EndChar = '}';
733 char const UnderscoreChar = '_';
734 string EndString; EndString += EndChar;
735 string FirstString; FirstString += FirstChar;
736 string CompareString; CompareString += CompareChar;
737 string const RegExp("*}*"); // Exist EndChar inside a String?
739 // first: Search for a '$' - Sign.
741 string result1; //(copy); // for split-calls
742 string result0 = split(path, result1, CompareChar);
743 while (!result0.empty()) {
744 string copy1(result0); // contains String after $
746 // Check, if there is an EndChar inside original String.
748 if (!regexMatch(copy1, RegExp)) {
749 // No EndChar inside. So we are finished
750 result1 += CompareString + result0;
756 string res0 = split(copy1, res1, EndChar);
757 // Now res1 holds the environmentvariable
758 // First, check, if Contents is ok.
759 if (res1.empty()) { // No environmentvariable. Continue Loop.
760 result1 += CompareString + FirstString;
764 // check contents of res1
765 char const * res1_contents = res1.c_str();
766 if (*res1_contents != FirstChar) {
767 // Again No Environmentvariable
768 result1 += CompareString;
772 // Check for variable names
773 // Situation ${} is detected as "No Environmentvariable"
774 char const * cp1 = res1_contents + 1;
775 bool result = isalpha(*cp1) || (*cp1 == UnderscoreChar);
777 while (*cp1 && result) {
778 result = isalnum(*cp1) ||
779 (*cp1 == UnderscoreChar);
784 // no correct variable name
785 result1 += CompareString + res1 + EndString;
786 result0 = split(res0, res1, CompareChar);
791 string env = GetEnv(res1_contents+1);
793 // Congratulations. Environmentvariable found
796 result1 += CompareString + res1 + EndString;
799 result0 = split(res0, res1, CompareChar);
803 } // ReplaceEnvironmentPath
806 // Make relative path out of two absolute paths
807 string MakeRelPath(string const & abspath0, string const & basepath0)
808 // Makes relative path out of absolute path. If it is deeper than basepath,
809 // it's easy. If basepath and abspath share something (they are all deeper
810 // than some directory), it'll be rendered using ..'s. If they are completely
811 // different, then the absolute path will be used as relative path.
813 // This is a hack. It should probaly be done in another way. Lgb.
814 string abspath = CleanupPath(abspath0);
815 string basepath = CleanupPath(basepath0);
817 return "<unknown_path>";
819 const int abslen = abspath.length();
820 const int baselen = basepath.length();
822 // Find first different character
824 while (i < abslen && i < baselen && abspath[i] == basepath[i]) ++i;
827 if (i < abslen && i < baselen
828 || (i<abslen && abspath[i] != '/' && i == baselen)
829 || (i<baselen && basepath[i] != '/' && i == abslen))
831 if (i) --i; // here was the last match
832 while (i && abspath[i] != '/') --i;
836 // actually no match - cannot make it relative
840 // Count how many dirs there are in basepath above match
841 // and append as many '..''s into relpath
844 while (j < baselen) {
845 if (basepath[j] == '/') {
846 if (j + 1 == baselen) break;
852 // Append relative stuff from common directory to abspath
853 if (abspath[i] == '/') ++i;
854 for (; i < abslen; ++i)
857 if (suffixIs(buf, '/'))
858 buf.erase(buf.length() - 1);
859 // Substitute empty with .
866 // Append sub-directory(ies) to a path in an intelligent way
867 string AddPath(string const & path, string const & path_2)
870 string path2 = CleanupPath(path_2);
872 if (!path.empty() && path != "." && path != "./") {
873 buf = CleanupPath(path);
874 if (path[path.length() - 1] != '/')
879 int p2start = path2.find_first_not_of('/');
881 int p2end = path2.find_last_not_of('/');
883 string tmp = path2.substr(p2start, p2end - p2start + 1);
891 Change extension of oldname to extension.
892 Strips path off if no_path == true.
893 If no extension on oldname, just appends.
895 string ChangeExtension(string const & oldname, string const & extension,
898 string::size_type last_slash = oldname.rfind('/');
899 string::size_type last_dot = oldname.rfind('.');
900 if (last_dot < last_slash && last_slash != string::npos)
901 last_dot = string::npos;
904 // Make sure the extension starts with a dot
905 if (!extension.empty() && extension[0] != '.')
906 ext= '.' + extension;
910 if (no_path && last_slash != string::npos) {
911 ++last_slash; // step it
912 ret_str = oldname.substr(last_slash,
913 last_dot - last_slash) + ext;
915 ret_str = oldname.substr(0, last_dot) + ext;
916 return CleanupPath(ret_str);
920 // Creates a nice compact path for displaying
921 string MakeDisplayPath (string const & path, unsigned int threshold)
923 const int l1 = path.length();
925 // First, we try a relative path compared to home
926 string home = GetEnvPath("HOME");
927 string relhome = MakeRelPath(path, home);
929 unsigned int l2 = relhome.length();
933 // If we backup from home or don't have a relative path,
934 // this try is no good
935 if (prefixIs(relhome, "../") || AbsolutePath(relhome)) {
936 // relative path was no good, just use the original path
943 // Is the path too long?
944 if (l2 > threshold) {
950 while (relhome.length() > threshold)
951 relhome = split(relhome, temp, '/');
953 // Did we shortend everything away?
954 if (relhome.empty()) {
955 // Yes, filename in itself is too long.
956 // Pick the start and the end of the filename.
957 relhome = OnlyFilename(path);
958 string head = relhome.substr(0, threshold/2 - 3);
960 l2 = relhome.length();
962 relhome.substr(l2 - threshold/2 - 2, l2 - 1);
963 relhome = head + "..." + tail;
966 return prefix + relhome;
970 bool LyXReadLink(string const & File, string & Link)
972 char LinkBuffer[512];
973 // Should be PATH_MAX but that needs autconf support
974 int nRead = readlink(File.c_str(), LinkBuffer, sizeof(LinkBuffer)-1);
977 LinkBuffer[nRead] = 0;
983 typedef pair<int, string> cmdret;
985 cmdret do_popen(string const & cmd)
987 // One question is if we should use popen or
988 // create our own popen based on fork, exec, pipe
989 // of course the best would be to have a
990 // pstream (process stream), with the
991 // variants ipstream, opstream
992 FILE * inf = popen(cmd.c_str(), "r");
996 ret += static_cast<char>(c);
999 int pret = pclose(inf);
1000 return make_pair(pret, ret);
1004 string findtexfile(string const & fil, string const & /*format*/)
1006 /* There is no problem to extend this function too use other
1007 methods to look for files. It could be setup to look
1008 in environment paths and also if wanted as a last resort
1009 to a recursive find. One of the easier extensions would
1010 perhaps be to use the LyX file lookup methods. But! I am
1011 going to implement this until I see some demand for it.
1015 // If the file can be found directly, we just return a
1016 // absolute path version of it.
1017 if (FileInfo(fil).exist())
1018 return MakeAbsPath(fil);
1020 // No we try to find it using kpsewhich.
1021 // It seems from the kpsewhich manual page that it is safe to use
1022 // kpsewhich without --format: "When the --format option is not
1023 // given, the search path used when looking for a file is inferred
1024 // from the name given, by looking for a known extension. If no
1025 // known extension is found, the search path for TeX source files
1027 // However, we want to take advantage of the format sine almost all
1028 // the different formats has environment variables that can be used
1029 // to controll which paths to search. f.ex. bib looks in
1030 // BIBINPUTS and TEXBIB. Small list follows:
1031 // bib - BIBINPUTS, TEXBIB
1033 // graphic/figure - TEXPICTS, TEXINPUTS
1034 // ist - TEXINDEXSTYLE, INDEXSTYLE
1035 // pk - PROGRAMFONTS, PKFONTS, TEXPKS, GLYPHFONTS, TEXFONTS
1037 // tfm - TFMFONTS, TEXFONTS
1038 // This means that to use kpsewhich in the best possible way we
1039 // should help it by setting additional path in the approp. envir.var.
1040 string kpsecmd = "kpsewhich " + fil;
1042 cmdret c = do_popen(kpsecmd);
1044 lyxerr[Debug::LATEX] << "kpse status = " << c.first << "\n"
1045 << "kpse result = `" << strip(c.second, '\n')
1047 return c.first != -1 ? strip(c.second, '\n') : string();