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
26 #pragma implementation "filetools.h"
29 #include "filetools.h"
30 #include "LSubstring.h"
31 #include "lyx_gui_misc.h"
33 #include "support/path.h" // I know it's OS/2 specific (SMiyata)
37 // Which part of this is still necessary? (JMarc).
40 # define NAMLEN(dirent) strlen((dirent)->d_name)
42 # define dirent direct
43 # define NAMLEN(dirent) (dirent)->d_namlen
45 # include <sys/ndir.h>
55 extern string system_lyxdir;
56 extern string build_lyxdir;
57 extern string user_lyxdir;
58 extern string system_tempdir;
61 bool IsLyXFilename(string const & filename)
63 return contains(filename, ".lyx");
67 bool IsSGMLFilename(string const & filename)
69 return contains(filename, ".sgml");
73 // Substitutes spaces with underscores in filename (and path)
74 string MakeLatexName(string const & file)
76 string name = OnlyFilename(file);
77 string path = OnlyPath(file);
79 for (string::size_type i = 0; i < name.length(); ++i) {
80 name[i] &= 0x7f; // set 8th bit to 0
83 // ok so we scan through the string twice, but who cares.
84 string keep("abcdefghijklmnopqrstuvwxyz"
85 "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
86 "@!\"'()*+,-./0123456789:;<=>?[]`|");
88 string::size_type pos = 0;
89 while ((pos = name.find_first_not_of(keep, pos)) != string::npos) {
92 return AddName(path, name);
95 // Substitutes spaces with underscores in filename (and path)
96 string QuoteName(string const & name)
99 #warning Add proper emx support here!
102 return '\'' + name + '\'';
109 /// Returns an unique name to be used as a temporary file.
110 string TmpFileName(string const & dir, string const & mask)
111 {// With all these temporary variables, it should be safe enough :-) (JMarc)
114 tmpdir = system_tempdir;
117 string tmpfl = AddName(tmpdir, mask);
119 // find a uniq postfix for the filename...
120 // using the pid, and...
121 tmpfl += tostr(getpid());
125 for (int a = 'a'; a <= 'z'; ++a)
126 for (int b = 'a'; b <= 'z'; ++b)
127 for (int c = 'a'; c <= 'z'; ++c) {
128 // if this is not enough I have no idea what
130 ret = tmpfl + char(a) + char(b) + char(c);
131 // check if the file exist
132 if (!fnfo.newFile(ret).exist())
135 lyxerr << "Not able to find a uniq tmpfile name." << endl;
140 // Is a file readable ?
141 bool IsFileReadable (string const & path)
144 if (file.isOK() && file.isRegular() && file.readable())
151 // Is a file read_only?
152 // return 1 read-write
154 // -1 error (doesn't exist, no access, anything else)
155 int IsFileWriteable (string const & path)
158 if (fi.access(FileInfo::wperm|FileInfo::rperm)) // read-write
160 if (fi.readable()) // read-only
162 return -1; // everything else.
166 //returns 1: dir writeable
168 // -1: error- couldn't find out
169 int IsDirWriteable (string const & path)
171 string tmpfl = TmpFileName(path);
174 WriteFSAlert(_("LyX Internal Error!"),
175 _("Could not test if directory is writeable"));
179 if (fi.writable()) return 1;
185 // Uses a string of paths separated by ";"s to find a file to open.
186 // Can't cope with pathnames with a ';' in them. Returns full path to file.
187 // If path entry begins with $$LyX/, use system_lyxdir
188 // If path entry begins with $$User/, use user_lyxdir
189 // Example: "$$User/doc;$$LyX/doc"
190 string FileOpenSearch (string const & path, string const & name,
193 string real_file, path_element;
194 bool notfound = true;
195 string tmppath = split(path, path_element, ';');
197 while (notfound && !path_element.empty()) {
198 path_element = CleanupPath(path_element);
199 if (!suffixIs(path_element, '/'))
201 path_element = subst(path_element, "$$LyX", system_lyxdir);
202 path_element = subst(path_element, "$$User", user_lyxdir);
204 real_file = FileSearch(path_element, name, ext);
206 if (real_file.empty()) {
208 tmppath = split(tmppath, path_element, ';');
209 } while(!tmppath.empty() && path_element.empty());
215 if (ext.empty() && notfound) {
216 real_file = FileOpenSearch(path, name, "exe");
217 if (notfound) real_file = FileOpenSearch(path, name, "cmd");
224 // Returns the real name of file name in directory path, with optional
226 string FileSearch(string const & path, string const & name,
229 // if `name' is an absolute path, we ignore the setting of `path'
230 // Expand Environmentvariables in 'name'
231 string tmpname = ReplaceEnvironmentPath(name);
232 string fullname = MakeAbsPath(tmpname, path);
234 // search first without extension, then with it.
235 if (IsFileReadable(fullname))
237 else if (ext.empty())
239 else { // Is it not more reasonable to use ChangeExtension()? (SMiyata)
242 if (IsFileReadable(fullname))
250 // Search the file name.ext in the subdirectory dir of
252 // 2) build_lyxdir (if not empty)
254 string LibFileSearch(string const & dir, string const & name,
257 string fullname = FileSearch(AddPath(user_lyxdir, dir),
259 if (!fullname.empty())
262 if (!build_lyxdir.empty())
263 fullname = FileSearch(AddPath(build_lyxdir, dir),
265 if (!fullname.empty())
268 return FileSearch(AddPath(system_lyxdir, dir), name, ext);
272 string i18nLibFileSearch(string const & dir, string const & name,
275 string lang = token(string(GetEnv("LANG")), '_', 0);
277 if (lang.empty() || lang == "C")
278 return LibFileSearch(dir, name, ext);
280 string tmp = LibFileSearch(dir, lang + '_' + name,
285 return LibFileSearch(dir, name, ext);
290 string GetEnv(string const & envname)
292 // f.ex. what about error checking?
293 char const * const ch = getenv(envname.c_str());
294 string envstr = !ch ? "" : ch;
299 string GetEnvPath(string const & name)
302 string pathlist = subst(GetEnv(name), ':', ';');
304 string pathlist = subst(GetEnv(name), '\\', '/');
306 return strip(pathlist, ';');
310 bool PutEnv(string const & envstr)
313 #warning Look at and fix this.
315 // f.ex. what about error checking?
317 // this leaks, but what can we do about it?
318 // Is doing a getenv() and a free() of the older value
319 // a good idea? (JMarc)
320 // Actually we don't have to leak...calling putenv like this
321 // should be enough: ... and this is obviously not enough if putenv
322 // does not make a copy of the string. It is also not very wise to
323 // put a string on the free store. If we have to leak we should do it
325 char * leaker = new char[envstr.length() + 1];
326 envstr.copy(leaker, envstr.length());
327 leaker[envstr.length()] = '\0';
328 int retval = lyx::putenv(leaker);
330 // If putenv does not make a copy of the char const * this
331 // is very dangerous. OTOH if it does take a copy this is the
333 // The only implementation of putenv that I have seen does not
334 // allocate memory. _And_ after testing the putenv in glibc it
335 // seems that we need to make a copy of the string contents.
336 // I will enable the above.
337 //int retval = lyx::putenv(envstr.c_str());
341 string str = envstr.split(varname,'=');
342 int retval = setenv(varname.c_str(), str.c_str(), true);
349 bool PutEnvPath(string const & envstr)
351 return PutEnv(envstr);
356 int DeleteAllFilesInDir (string const & path)
358 // I have decided that we will be using parts from the boost
359 // library. Check out http://www.boost.org/
360 // For directory access we will then use the directory_iterator.
361 // Then the code will be something like:
362 // directory_iterator dit(path.c_str());
363 // if (<some way to detect failure>) {
364 // WriteFSAlert(_("Error! Cannot open directory:"), path);
367 // for (; dit != <someend>; ++dit) {
368 // if ((*dit) == 2." || (*dit) == "..")
370 // string unlinkpath = AddName(path, temp);
371 // if (remove(unlinkpath.c_str()))
372 // WriteFSAlert(_("Error! Could not remove file:"),
376 DIR * dir = opendir(path.c_str());
378 WriteFSAlert (_("Error! Cannot open directory:"), path);
382 while ((de = readdir(dir))) {
383 string temp = de->d_name;
384 if (temp == "." || temp == "..")
386 string unlinkpath = AddName (path, temp);
388 lyxerr.debug() << "Deleting file: " << unlinkpath << endl;
390 if (remove(unlinkpath.c_str()))
391 WriteFSAlert (_("Error! Could not remove file:"),
400 string CreateTmpDir (string const & tempdir, string const & mask)
402 string tmpfl = TmpFileName(tempdir, mask);
404 if ((tmpfl.empty()) || lyx::mkdir (tmpfl.c_str(), 0777)) {
405 WriteFSAlert(_("Error! Couldn't create temporary directory:"),
409 return MakeAbsPath(tmpfl);
414 int DestroyTmpDir (string const & tmpdir, bool Allfiles)
419 if (Allfiles && DeleteAllFilesInDir(tmpdir)) return -1;
420 if (rmdir(tmpdir.c_str())) {
421 WriteFSAlert(_("Error! Couldn't delete temporary directory:"),
429 string CreateBufferTmpDir (string const & pathfor)
431 return CreateTmpDir(pathfor, "lyx_bufrtmp");
435 int DestroyBufferTmpDir (string const & tmpdir)
437 return DestroyTmpDir(tmpdir, true);
441 string CreateLyXTmpDir (string const & deflt)
443 if ((!deflt.empty()) && (deflt != "/tmp")) {
444 if (lyx::mkdir(deflt.c_str(), 0777)) {
448 string t = CreateTmpDir (deflt.c_str(), "lyx_tmp");
456 string t = CreateTmpDir ("/tmp", "lyx_tmp");
462 int DestroyLyXTmpDir (string const & tmpdir)
464 return DestroyTmpDir (tmpdir, false); // Why false?
468 // Creates directory. Returns true if succesfull
469 bool createDirectory(string const & path, int permission)
471 string temp = strip(CleanupPath(path), '/');
474 WriteAlert(_("Internal error!"),
475 _("Call to createDirectory with invalid name"));
479 if (lyx::mkdir(temp.c_str(), permission)) {
480 WriteFSAlert (_("Error! Couldn't create directory:"), temp);
487 // Returns current working directory
490 int n = 256; // Assume path is less than 256 chars
492 char * tbuf = new char[n];
494 // Safe. Hopefully all getcwds behave this way!
495 while (((err = lyx::getcwd (tbuf, n)) == 0) && (errno == ERANGE)) {
496 // Buffer too small, double the buffersize and try again
503 if (err) result = tbuf;
509 // Strip filename from path name
510 string OnlyPath(string const & Filename)
512 // If empty filename, return empty
513 if (Filename.empty()) return Filename;
515 // Find last / or start of filename
516 string::size_type j = Filename.rfind('/');
517 if (j == string::npos)
519 return Filename.substr(0, j + 1);
523 // Convert relative path into absolute path based on a basepath.
524 // If relpath is absolute, just use that.
525 // If basepath is empty, use CWD as base.
526 string MakeAbsPath(string const & RelPath, string const & BasePath)
528 // checks for already absolute path
529 if (AbsolutePath(RelPath))
531 if(RelPath[0]!= '/' && RelPath[0]!= '\\')
535 // Copies given paths
536 string TempRel = CleanupPath(RelPath);
540 if (!BasePath.empty()) {
544 char * with_drive = new char[_MAX_PATH];
545 _abspath(with_drive, BasePath.c_str(), _MAX_PATH);
546 TempBase = with_drive;
552 if (AbsolutePath(TempRel))
553 return TempBase.substr(0, 2) + TempRel;
556 // Handle /./ at the end of the path
557 while(suffixIs(TempBase, "/./"))
558 TempBase.erase(TempBase.length() - 2);
560 // processes relative path
561 string RTemp = TempRel;
564 while (!RTemp.empty()) {
566 RTemp = split(RTemp, Temp, '/');
568 if (Temp == ".") continue;
570 // Remove one level of TempBase
571 int i = TempBase.length() - 2;
574 while (i > 0 && TempBase[i] != '/') --i;
578 while (i > 2 && TempBase[i] != '/') --i;
581 TempBase.erase(i, string::npos);
585 // Add this piece to TempBase
586 if (!suffixIs(TempBase, '/'))
592 // returns absolute path
597 // Correctly append filename to the pathname.
598 // If pathname is '.', then don't use pathname.
599 // Chops any path of filename.
600 string AddName(string const & path, string const & fname)
603 string basename = OnlyFilename(fname);
607 if (path != "." && path != "./" && !path.empty()) {
608 buf = CleanupPath(path);
609 if (!suffixIs(path, '/'))
613 return buf + basename;
617 // Strips path from filename
618 string OnlyFilename(string const & fname)
623 string::size_type j = fname.rfind('/');
624 if (j == string::npos) // no '/' in fname
628 return fname.substr(j + 1);
632 // Is a filename/path absolute?
633 bool AbsolutePath(string const & path)
636 return (!path.empty() && path[0] == '/');
638 return (!path.empty() && (path[0] == '/' || (isalpha(static_cast<unsigned char>(path[0])) && path.length()>1 && path[1] == ':')));
643 // Create absolute path. If impossible, don't do anything
644 // Supports ./ and ~/. Later we can add support for ~logname/. (Asger)
645 string ExpandPath(string const & path)
647 // checks for already absolute path
648 string RTemp = ReplaceEnvironmentPath(path);
649 if (AbsolutePath(RTemp))
656 RTemp= split(RTemp, Temp, '/');
659 return GetCWD() + '/' + RTemp;
660 } else if (Temp == "~") {
661 return GetEnvPath("HOME") + '/' + RTemp;
662 } else if (Temp == "..") {
663 return MakeAbsPath(copy);
665 // Don't know how to handle this
671 // Constracts path/../path
672 // Can't handle "../../" or "/../" (Asger)
673 string NormalizePath(string const & path)
679 if (AbsolutePath(path))
682 // Make implicit current directory explicit
685 while (!RTemp.empty()) {
687 RTemp = split(RTemp, Temp, '/');
691 } else if (Temp == "..") {
692 // Remove one level of TempBase
693 int i = TempBase.length() - 2;
694 while (i > 0 && TempBase[i] != '/')
696 if (i >= 0 && TempBase[i] == '/')
697 TempBase.erase(i + 1, string::npos);
701 TempBase += Temp + '/';
705 // returns absolute path
709 string CleanupPath(string const & path)
711 #ifdef __EMX__ /* SMiyata: This should fix searchpath bug. */
712 string temppath = subst(path, '\\', '/');
713 temppath = subst(temppath, "//", "/");
714 return lowercase(temppath);
715 #else // On unix, nothing to do
722 // Search ${...} as Variable-Name inside the string and replace it with
723 // the denoted environmentvariable
724 // Allow Variables according to
725 // variable := '$' '{' [A-Za-z_]{[A-Za-z_0-9]*} '}'
728 string ReplaceEnvironmentPath(string const & path)
731 // CompareChar: Environmentvariables starts with this character
732 // PathChar: Next path component start with this character
733 // while CompareChar found do:
734 // Split String with PathChar
735 // Search Environmentvariable
736 // if found: Replace Strings
738 char const CompareChar = '$';
739 char const FirstChar = '{';
740 char const EndChar = '}';
741 char const UnderscoreChar = '_';
742 string EndString; EndString += EndChar;
743 string FirstString; FirstString += FirstChar;
744 string CompareString; CompareString += CompareChar;
745 string const RegExp("*}*"); // Exist EndChar inside a String?
747 // first: Search for a '$' - Sign.
749 string result1; //(copy); // for split-calls
750 string result0 = split(path, result1, CompareChar);
751 while (!result0.empty()) {
752 string copy1(result0); // contains String after $
754 // Check, if there is an EndChar inside original String.
756 if (!regexMatch(copy1, RegExp)) {
757 // No EndChar inside. So we are finished
758 result1 += CompareString + result0;
764 string res0 = split(copy1, res1, EndChar);
765 // Now res1 holds the environmentvariable
766 // First, check, if Contents is ok.
767 if (res1.empty()) { // No environmentvariable. Continue Loop.
768 result1 += CompareString + FirstString;
772 // check contents of res1
773 char const * res1_contents = res1.c_str();
774 if (*res1_contents != FirstChar) {
775 // Again No Environmentvariable
776 result1 += CompareString;
780 // Check for variable names
781 // Situation ${} is detected as "No Environmentvariable"
782 char const * cp1 = res1_contents + 1;
783 bool result = isalpha(*cp1) || (*cp1 == UnderscoreChar);
785 while (*cp1 && result) {
786 result = isalnum(*cp1) ||
787 (*cp1 == UnderscoreChar);
792 // no correct variable name
793 result1 += CompareString + res1 + EndString;
794 result0 = split(res0, res1, CompareChar);
799 string env = GetEnv(res1_contents+1);
801 // Congratulations. Environmentvariable found
804 result1 += CompareString + res1 + EndString;
807 result0 = split(res0, res1, CompareChar);
811 } // ReplaceEnvironmentPath
814 // Make relative path out of two absolute paths
815 string MakeRelPath(string const & abspath0, string const & basepath0)
816 // Makes relative path out of absolute path. If it is deeper than basepath,
817 // it's easy. If basepath and abspath share something (they are all deeper
818 // than some directory), it'll be rendered using ..'s. If they are completely
819 // different, then the absolute path will be used as relative path.
821 // This is a hack. It should probaly be done in another way. Lgb.
822 string abspath = CleanupPath(abspath0);
823 string basepath = CleanupPath(basepath0);
825 return "<unknown_path>";
827 const int abslen = abspath.length();
828 const int baselen = basepath.length();
830 // Find first different character
832 while (i < abslen && i < baselen && abspath[i] == basepath[i]) ++i;
835 if (i < abslen && i < baselen
836 || (i<abslen && abspath[i] != '/' && i == baselen)
837 || (i<baselen && basepath[i] != '/' && i == abslen))
839 if (i) --i; // here was the last match
840 while (i && abspath[i] != '/') --i;
844 // actually no match - cannot make it relative
848 // Count how many dirs there are in basepath above match
849 // and append as many '..''s into relpath
852 while (j < baselen) {
853 if (basepath[j] == '/') {
854 if (j + 1 == baselen) break;
860 // Append relative stuff from common directory to abspath
861 if (abspath[i] == '/') ++i;
862 for (; i < abslen; ++i)
865 if (suffixIs(buf, '/'))
866 buf.erase(buf.length() - 1);
867 // Substitute empty with .
874 // Append sub-directory(ies) to a path in an intelligent way
875 string AddPath(string const & path, string const & path_2)
878 string path2 = CleanupPath(path_2);
880 if (!path.empty() && path != "." && path != "./") {
881 buf = CleanupPath(path);
882 if (path[path.length() - 1] != '/')
887 int p2start = path2.find_first_not_of('/');
889 int p2end = path2.find_last_not_of('/');
891 string tmp = path2.substr(p2start, p2end - p2start + 1);
899 Change extension of oldname to extension.
900 Strips path off if no_path == true.
901 If no extension on oldname, just appends.
903 string ChangeExtension(string const & oldname, string const & extension,
906 string::size_type last_slash = oldname.rfind('/');
907 string::size_type last_dot = oldname.rfind('.');
908 if (last_dot < last_slash && last_slash != string::npos)
909 last_dot = string::npos;
912 // Make sure the extension starts with a dot
913 if (!extension.empty() && extension[0] != '.')
914 ext= '.' + extension;
918 if (no_path && last_slash != string::npos) {
919 ++last_slash; // step it
920 ret_str = oldname.substr(last_slash,
921 last_dot - last_slash) + ext;
923 ret_str = oldname.substr(0, last_dot) + ext;
924 return CleanupPath(ret_str);
928 // Creates a nice compact path for displaying
929 string MakeDisplayPath (string const & path, unsigned int threshold)
931 const int l1 = path.length();
933 // First, we try a relative path compared to home
934 string home = GetEnvPath("HOME");
935 string relhome = MakeRelPath(path, home);
937 unsigned int l2 = relhome.length();
941 // If we backup from home or don't have a relative path,
942 // this try is no good
943 if (prefixIs(relhome, "../") || AbsolutePath(relhome)) {
944 // relative path was no good, just use the original path
951 // Is the path too long?
952 if (l2 > threshold) {
958 while (relhome.length() > threshold)
959 relhome = split(relhome, temp, '/');
961 // Did we shortend everything away?
962 if (relhome.empty()) {
963 // Yes, filename in itself is too long.
964 // Pick the start and the end of the filename.
965 relhome = OnlyFilename(path);
966 string head = relhome.substr(0, threshold/2 - 3);
968 l2 = relhome.length();
970 relhome.substr(l2 - threshold/2 - 2, l2 - 1);
971 relhome = head + "..." + tail;
974 return prefix + relhome;
978 bool LyXReadLink(string const & File, string & Link)
980 char LinkBuffer[512];
981 // Should be PATH_MAX but that needs autconf support
982 int nRead = readlink(File.c_str(), LinkBuffer, sizeof(LinkBuffer)-1);
985 LinkBuffer[nRead] = 0;
991 typedef pair<int, string> cmdret;
992 static cmdret do_popen(string const & cmd)
994 // One question is if we should use popen or
995 // create our own popen based on fork, exec, pipe
996 // of course the best would be to have a
997 // pstream (process stream), with the
998 // variants ipstream, opstream
999 FILE * inf = popen(cmd.c_str(), "r");
1003 ret += static_cast<char>(c);
1006 int pret = pclose(inf);
1007 return make_pair(pret, ret);
1011 string findtexfile(string const & fil, string const & /*format*/)
1013 /* There is no problem to extend this function too use other
1014 methods to look for files. It could be setup to look
1015 in environment paths and also if wanted as a last resort
1016 to a recursive find. One of the easier extensions would
1017 perhaps be to use the LyX file lookup methods. But! I am
1018 going to implement this until I see some demand for it.
1022 // If the file can be found directly, we just return a
1023 // absolute path version of it.
1024 if (FileInfo(fil).exist())
1025 return MakeAbsPath(fil);
1027 // No we try to find it using kpsewhich.
1028 // It seems from the kpsewhich manual page that it is safe to use
1029 // kpsewhich without --format: "When the --format option is not
1030 // given, the search path used when looking for a file is inferred
1031 // from the name given, by looking for a known extension. If no
1032 // known extension is found, the search path for TeX source files
1034 // However, we want to take advantage of the format sine almost all
1035 // the different formats has environment variables that can be used
1036 // to controll which paths to search. f.ex. bib looks in
1037 // BIBINPUTS and TEXBIB. Small list follows:
1038 // bib - BIBINPUTS, TEXBIB
1040 // graphic/figure - TEXPICTS, TEXINPUTS
1041 // ist - TEXINDEXSTYLE, INDEXSTYLE
1042 // pk - PROGRAMFONTS, PKFONTS, TEXPKS, GLYPHFONTS, TEXFONTS
1044 // tfm - TFMFONTS, TEXFONTS
1045 // This means that to use kpsewhich in the best possible way we
1046 // should help it by setting additional path in the approp. envir.var.
1047 string kpsecmd = "kpsewhich " + fil;
1049 cmdret c = do_popen(kpsecmd);
1051 lyxerr[Debug::LATEX] << "kpse status = " << c.first << "\n"
1052 << "kpse result = `" << strip(c.second, '\n')
1054 return c.first != -1 ? strip(c.second, '\n') : string();