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 const 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);
100 // Substitutes spaces with underscores in filename (and path)
101 string const QuoteName(string const & name)
103 // CHECK Add proper emx support here!
105 return '\'' + name + '\'';
112 // Returns an unique name to be used as a temporary file.
113 string const TmpFileName(string const & dir, string const & mask)
114 {// With all these temporary variables, it should be safe enough :-) (JMarc)
117 tmpdir = system_tempdir;
120 string tmpfl(AddName(tmpdir, mask));
122 // find a uniq postfix for the filename...
123 // using the pid, and...
124 tmpfl += tostr(getpid());
128 for (int a = 'a'; a <= 'z'; ++a)
129 for (int b = 'a'; b <= 'z'; ++b)
130 for (int c = 'a'; c <= 'z'; ++c) {
131 // if this is not enough I have no idea what
133 ret = tmpfl + char(a) + char(b) + char(c);
134 // check if the file exist
135 if (!fnfo.newFile(ret).exist())
138 lyxerr << "Not able to find a uniq tmpfile name." << endl;
143 // Is a file readable ?
144 bool IsFileReadable (string const & path)
147 if (file.isOK() && file.isRegular() && file.readable())
154 // Is a file read_only?
155 // return 1 read-write
157 // -1 error (doesn't exist, no access, anything else)
158 int IsFileWriteable (string const & path)
161 if (fi.access(FileInfo::wperm|FileInfo::rperm)) // read-write
163 if (fi.readable()) // read-only
165 return -1; // everything else.
169 //returns 1: dir writeable
171 // -1: error- couldn't find out
172 int IsDirWriteable (string const & path)
174 string tmpfl(TmpFileName(path));
177 WriteFSAlert(_("LyX Internal Error!"),
178 _("Could not test if directory is writeable"));
182 if (fi.writable()) return 1;
188 // Uses a string of paths separated by ";"s to find a file to open.
189 // Can't cope with pathnames with a ';' in them. Returns full path to file.
190 // If path entry begins with $$LyX/, use system_lyxdir
191 // If path entry begins with $$User/, use user_lyxdir
192 // Example: "$$User/doc;$$LyX/doc"
193 string const FileOpenSearch (string const & path, string const & name,
196 string real_file, path_element;
197 bool notfound = true;
198 string tmppath = split(path, path_element, ';');
200 while (notfound && !path_element.empty()) {
201 path_element = CleanupPath(path_element);
202 if (!suffixIs(path_element, '/'))
204 path_element = subst(path_element, "$$LyX", system_lyxdir);
205 path_element = subst(path_element, "$$User", user_lyxdir);
207 real_file = FileSearch(path_element, name, ext);
209 if (real_file.empty()) {
211 tmppath = split(tmppath, path_element, ';');
212 } while(!tmppath.empty() && path_element.empty());
218 if (ext.empty() && notfound) {
219 real_file = FileOpenSearch(path, name, "exe");
220 if (notfound) real_file = FileOpenSearch(path, name, "cmd");
227 // Returns the real name of file name in directory path, with optional
229 string const FileSearch(string const & path, string const & name,
232 // if `name' is an absolute path, we ignore the setting of `path'
233 // Expand Environmentvariables in 'name'
234 string tmpname = ReplaceEnvironmentPath(name);
235 string fullname = MakeAbsPath(tmpname, path);
237 // search first without extension, then with it.
238 if (IsFileReadable(fullname))
240 else if (ext.empty())
242 else { // Is it not more reasonable to use ChangeExtension()? (SMiyata)
245 if (IsFileReadable(fullname))
253 // Search the file name.ext in the subdirectory dir of
255 // 2) build_lyxdir (if not empty)
257 string const LibFileSearch(string const & dir, string const & name,
260 string fullname = FileSearch(AddPath(user_lyxdir, dir),
262 if (!fullname.empty())
265 if (!build_lyxdir.empty())
266 fullname = FileSearch(AddPath(build_lyxdir, dir),
268 if (!fullname.empty())
271 return FileSearch(AddPath(system_lyxdir, dir), name, ext);
276 i18nLibFileSearch(string const & dir, string const & name,
279 string lang = token(string(GetEnv("LANG")), '_', 0);
281 if (lang.empty() || lang == "C")
282 return LibFileSearch(dir, name, ext);
284 string tmp = LibFileSearch(dir, lang + '_' + name,
289 return LibFileSearch(dir, name, ext);
294 string const GetEnv(string const & envname)
296 // f.ex. what about error checking?
297 char const * const ch = getenv(envname.c_str());
298 string envstr = !ch ? "" : ch;
303 string const GetEnvPath(string const & name)
306 string pathlist = subst(GetEnv(name), ':', ';');
308 string pathlist = subst(GetEnv(name), '\\', '/');
310 return strip(pathlist, ';');
314 bool PutEnv(string const & envstr)
316 // CHECK Look at and fix this.
317 // f.ex. what about error checking?
320 // this leaks, but what can we do about it?
321 // Is doing a getenv() and a free() of the older value
322 // a good idea? (JMarc)
323 // Actually we don't have to leak...calling putenv like this
324 // should be enough: ... and this is obviously not enough if putenv
325 // does not make a copy of the string. It is also not very wise to
326 // put a string on the free store. If we have to leak we should do it
328 char * leaker = new char[envstr.length() + 1];
329 envstr.copy(leaker, envstr.length());
330 leaker[envstr.length()] = '\0';
331 int retval = lyx::putenv(leaker);
333 // If putenv does not make a copy of the char const * this
334 // is very dangerous. OTOH if it does take a copy this is the
336 // The only implementation of putenv that I have seen does not
337 // allocate memory. _And_ after testing the putenv in glibc it
338 // seems that we need to make a copy of the string contents.
339 // I will enable the above.
340 //int retval = lyx::putenv(envstr.c_str());
344 string str = envstr.split(varname,'=');
345 int retval = setenv(varname.c_str(), str.c_str(), true);
347 // No environment setting function. Can this happen?
348 int retval = 1; //return an error condition.
355 bool PutEnvPath(string const & envstr)
357 return PutEnv(envstr);
362 int DeleteAllFilesInDir (string const & path)
364 // I have decided that we will be using parts from the boost
365 // library. Check out http://www.boost.org/
366 // For directory access we will then use the directory_iterator.
367 // Then the code will be something like:
368 // directory_iterator dit(path);
369 // directory_iterator dend;
370 // if (dit == dend) {
371 // WriteFSAlert(_("Error! Cannot open directory:"), path);
374 // for (; dit != dend; ++dit) {
375 // string filename(*dit);
376 // if (filename == "." || filename == "..")
378 // string unlinkpath(AddName(path, filename));
379 // if (remove(unlinkpath.c_str()))
380 // WriteFSAlert(_("Error! Could not remove file:"),
384 DIR * dir = opendir(path.c_str());
386 WriteFSAlert (_("Error! Cannot open directory:"), path);
390 while ((de = readdir(dir))) {
391 string temp = de->d_name;
392 if (temp == "." || temp == "..")
394 string unlinkpath = AddName (path, temp);
396 lyxerr.debug() << "Deleting file: " << unlinkpath << endl;
398 if (remove(unlinkpath.c_str()))
399 WriteFSAlert (_("Error! Could not remove file:"),
408 string const CreateTmpDir (string const & tempdir, string const & mask)
410 string tmpfl(TmpFileName(tempdir, mask));
412 if ((tmpfl.empty()) || lyx::mkdir (tmpfl.c_str(), 0777)) {
413 WriteFSAlert(_("Error! Couldn't create temporary directory:"),
417 return MakeAbsPath(tmpfl);
422 int DestroyTmpDir (string const & tmpdir, bool Allfiles)
427 if (Allfiles && DeleteAllFilesInDir(tmpdir)) return -1;
428 if (rmdir(tmpdir.c_str())) {
429 WriteFSAlert(_("Error! Couldn't delete temporary directory:"),
437 string const CreateBufferTmpDir (string const & pathfor)
439 return CreateTmpDir(pathfor, "lyx_bufrtmp");
443 int DestroyBufferTmpDir (string const & tmpdir)
445 return DestroyTmpDir(tmpdir, true);
449 string const CreateLyXTmpDir (string const & deflt)
451 if ((!deflt.empty()) && (deflt != "/tmp")) {
452 if (lyx::mkdir(deflt.c_str(), 0777)) {
456 string t(CreateTmpDir (deflt.c_str(), "lyx_tmp"));
464 string t(CreateTmpDir ("/tmp", "lyx_tmp"));
470 int DestroyLyXTmpDir (string const & tmpdir)
472 return DestroyTmpDir (tmpdir, false); // Why false?
476 // Creates directory. Returns true if succesfull
477 bool createDirectory(string const & path, int permission)
479 string temp(strip(CleanupPath(path), '/'));
482 WriteAlert(_("Internal error!"),
483 _("Call to createDirectory with invalid name"));
487 if (lyx::mkdir(temp.c_str(), permission)) {
488 WriteFSAlert (_("Error! Couldn't create directory:"), temp);
495 // Returns current working directory
496 string const GetCWD ()
498 int n = 256; // Assume path is less than 256 chars
500 char * tbuf = new char[n];
502 // Safe. Hopefully all getcwds behave this way!
503 while (((err = lyx::getcwd (tbuf, n)) == 0) && (errno == ERANGE)) {
504 // Buffer too small, double the buffersize and try again
511 if (err) result = tbuf;
517 // Strip filename from path name
518 string const OnlyPath(string const & Filename)
520 // If empty filename, return empty
521 if (Filename.empty()) return Filename;
523 // Find last / or start of filename
524 string::size_type j = Filename.rfind('/');
525 if (j == string::npos)
527 return Filename.substr(0, j + 1);
531 // Convert relative path into absolute path based on a basepath.
532 // If relpath is absolute, just use that.
533 // If basepath is empty, use CWD as base.
534 string const MakeAbsPath(string const & RelPath, string const & BasePath)
536 // checks for already absolute path
537 if (AbsolutePath(RelPath))
539 if(RelPath[0]!= '/' && RelPath[0]!= '\\')
543 // Copies given paths
544 string TempRel(CleanupPath(RelPath));
548 if (!BasePath.empty()) {
552 char * with_drive = new char[_MAX_PATH];
553 _abspath(with_drive, BasePath.c_str(), _MAX_PATH);
554 TempBase = with_drive;
560 if (AbsolutePath(TempRel))
561 return TempBase.substr(0, 2) + TempRel;
564 // Handle /./ at the end of the path
565 while(suffixIs(TempBase, "/./"))
566 TempBase.erase(TempBase.length() - 2);
568 // processes relative path
569 string RTemp(TempRel);
572 while (!RTemp.empty()) {
574 RTemp = split(RTemp, Temp, '/');
576 if (Temp == ".") continue;
578 // Remove one level of TempBase
579 int i = TempBase.length() - 2;
582 while (i > 0 && TempBase[i] != '/') --i;
586 while (i > 2 && TempBase[i] != '/') --i;
589 TempBase.erase(i, string::npos);
593 // Add this piece to TempBase
594 if (!suffixIs(TempBase, '/'))
600 // returns absolute path
605 // Correctly append filename to the pathname.
606 // If pathname is '.', then don't use pathname.
607 // Chops any path of filename.
608 string const AddName(string const & path, string const & fname)
611 string basename(OnlyFilename(fname));
615 if (path != "." && path != "./" && !path.empty()) {
616 buf = CleanupPath(path);
617 if (!suffixIs(path, '/'))
621 return buf + basename;
625 // Strips path from filename
626 string const OnlyFilename(string const & fname)
631 string::size_type j = fname.rfind('/');
632 if (j == string::npos) // no '/' in fname
636 return fname.substr(j + 1);
640 // Is a filename/path absolute?
641 bool AbsolutePath(string const & path)
644 return (!path.empty() && path[0] == '/');
646 return (!path.empty() && (path[0] == '/' || (isalpha(static_cast<unsigned char>(path[0])) && path.length()>1 && path[1] == ':')));
651 // Create absolute path. If impossible, don't do anything
652 // Supports ./ and ~/. Later we can add support for ~logname/. (Asger)
653 string const ExpandPath(string const & path)
655 // checks for already absolute path
656 string RTemp(ReplaceEnvironmentPath(path));
657 if (AbsolutePath(RTemp))
664 RTemp = split(RTemp, Temp, '/');
667 return GetCWD() + '/' + RTemp;
668 } else if (Temp == "~") {
669 return GetEnvPath("HOME") + '/' + RTemp;
670 } else if (Temp == "..") {
671 return MakeAbsPath(copy);
673 // Don't know how to handle this
679 // Constracts path/../path
680 // Can't handle "../../" or "/../" (Asger)
681 string const NormalizePath(string const & path)
687 if (AbsolutePath(path))
690 // Make implicit current directory explicit
693 while (!RTemp.empty()) {
695 RTemp = split(RTemp, Temp, '/');
699 } else if (Temp == "..") {
700 // Remove one level of TempBase
701 int i = TempBase.length() - 2;
702 while (i > 0 && TempBase[i] != '/')
704 if (i >= 0 && TempBase[i] == '/')
705 TempBase.erase(i + 1, string::npos);
709 TempBase += Temp + '/';
713 // returns absolute path
718 string const CleanupPath(string const & path)
720 #ifdef __EMX__ /* SMiyata: This should fix searchpath bug. */
721 string temppath = subst(path, '\\', '/');
722 temppath = subst(temppath, "//", "/");
723 return lowercase(temppath);
724 #else // On unix, nothing to do
730 string const GetFileContents(string const & fname)
732 FileInfo finfo(fname);
734 ifstream ifs(fname.c_str());
736 std::ostringstream ofs;
738 #warning The rumour goes that this might leak, but who really cares?
745 return ofs.str().c_str();
748 char const * tmp = ofs.str();
755 lyxerr << "LyX was not able to read file '" << fname << "'" << endl;
761 // Search ${...} as Variable-Name inside the string and replace it with
762 // the denoted environmentvariable
763 // Allow Variables according to
764 // variable := '$' '{' [A-Za-z_]{[A-Za-z_0-9]*} '}'
767 string const ReplaceEnvironmentPath(string const & path)
770 // CompareChar: Environmentvariables starts with this character
771 // PathChar: Next path component start with this character
772 // while CompareChar found do:
773 // Split String with PathChar
774 // Search Environmentvariable
775 // if found: Replace Strings
777 char const CompareChar = '$';
778 char const FirstChar = '{';
779 char const EndChar = '}';
780 char const UnderscoreChar = '_';
781 string EndString; EndString += EndChar;
782 string FirstString; FirstString += FirstChar;
783 string CompareString; CompareString += CompareChar;
784 string const RegExp("*}*"); // Exist EndChar inside a String?
786 // first: Search for a '$' - Sign.
788 string result1; //(copy); // for split-calls
789 string result0 = split(path, result1, CompareChar);
790 while (!result0.empty()) {
791 string copy1(result0); // contains String after $
793 // Check, if there is an EndChar inside original String.
795 if (!regexMatch(copy1, RegExp)) {
796 // No EndChar inside. So we are finished
797 result1 += CompareString + result0;
803 string res0 = split(copy1, res1, EndChar);
804 // Now res1 holds the environmentvariable
805 // First, check, if Contents is ok.
806 if (res1.empty()) { // No environmentvariable. Continue Loop.
807 result1 += CompareString + FirstString;
811 // check contents of res1
812 char const * res1_contents = res1.c_str();
813 if (*res1_contents != FirstChar) {
814 // Again No Environmentvariable
815 result1 += CompareString;
819 // Check for variable names
820 // Situation ${} is detected as "No Environmentvariable"
821 char const * cp1 = res1_contents + 1;
822 bool result = isalpha(*cp1) || (*cp1 == UnderscoreChar);
824 while (*cp1 && result) {
825 result = isalnum(*cp1) ||
826 (*cp1 == UnderscoreChar);
831 // no correct variable name
832 result1 += CompareString + res1 + EndString;
833 result0 = split(res0, res1, CompareChar);
838 string env(GetEnv(res1_contents + 1));
840 // Congratulations. Environmentvariable found
843 result1 += CompareString + res1 + EndString;
846 result0 = split(res0, res1, CompareChar);
850 } // ReplaceEnvironmentPath
853 // Make relative path out of two absolute paths
854 string const MakeRelPath(string const & abspath0, string const & basepath0)
855 // Makes relative path out of absolute path. If it is deeper than basepath,
856 // it's easy. If basepath and abspath share something (they are all deeper
857 // than some directory), it'll be rendered using ..'s. If they are completely
858 // different, then the absolute path will be used as relative path.
860 // This is a hack. It should probaly be done in another way. Lgb.
861 string abspath = CleanupPath(abspath0);
862 string basepath = CleanupPath(basepath0);
864 return "<unknown_path>";
866 int const abslen = abspath.length();
867 int const baselen = basepath.length();
869 // Find first different character
871 while (i < abslen && i < baselen && abspath[i] == basepath[i]) ++i;
874 if (i < abslen && i < baselen
875 || (i<abslen && abspath[i] != '/' && i == baselen)
876 || (i<baselen && basepath[i] != '/' && i == abslen))
878 if (i) --i; // here was the last match
879 while (i && abspath[i] != '/') --i;
883 // actually no match - cannot make it relative
887 // Count how many dirs there are in basepath above match
888 // and append as many '..''s into relpath
891 while (j < baselen) {
892 if (basepath[j] == '/') {
893 if (j + 1 == baselen) break;
899 // Append relative stuff from common directory to abspath
900 if (abspath[i] == '/') ++i;
901 for (; i < abslen; ++i)
904 if (suffixIs(buf, '/'))
905 buf.erase(buf.length() - 1);
906 // Substitute empty with .
913 // Append sub-directory(ies) to a path in an intelligent way
914 string const AddPath(string const & path, string const & path_2)
917 string path2 = CleanupPath(path_2);
919 if (!path.empty() && path != "." && path != "./") {
920 buf = CleanupPath(path);
921 if (path[path.length() - 1] != '/')
926 int p2start = path2.find_first_not_of('/');
928 int p2end = path2.find_last_not_of('/');
930 string tmp = path2.substr(p2start, p2end - p2start + 1);
938 Change extension of oldname to extension.
939 Strips path off if no_path == true.
940 If no extension on oldname, just appends.
943 ChangeExtension(string const & oldname, string const & extension)
945 string::size_type last_slash = oldname.rfind('/');
946 string::size_type last_dot = oldname.rfind('.');
947 if (last_dot < last_slash && last_slash != string::npos)
948 last_dot = string::npos;
951 // Make sure the extension starts with a dot
952 if (!extension.empty() && extension[0] != '.')
953 ext= '.' + extension;
957 return CleanupPath(oldname.substr(0, last_dot) + ext);
961 // Creates a nice compact path for displaying
963 MakeDisplayPath (string const & path, unsigned int threshold)
965 int const l1 = path.length();
967 // First, we try a relative path compared to home
968 string const home(GetEnvPath("HOME"));
969 string relhome = MakeRelPath(path, home);
971 unsigned int l2 = relhome.length();
975 // If we backup from home or don't have a relative path,
976 // this try is no good
977 if (prefixIs(relhome, "../") || AbsolutePath(relhome)) {
978 // relative path was no good, just use the original path
985 // Is the path too long?
986 if (l2 > threshold) {
992 while (relhome.length() > threshold)
993 relhome = split(relhome, temp, '/');
995 // Did we shortend everything away?
996 if (relhome.empty()) {
997 // Yes, filename in itself is too long.
998 // Pick the start and the end of the filename.
999 relhome = OnlyFilename(path);
1000 string head = relhome.substr(0, threshold/2 - 3);
1002 l2 = relhome.length();
1004 relhome.substr(l2 - threshold/2 - 2, l2 - 1);
1005 relhome = head + "..." + tail;
1008 return prefix + relhome;
1012 bool LyXReadLink(string const & File, string & Link)
1014 char LinkBuffer[512];
1015 // Should be PATH_MAX but that needs autconf support
1016 int nRead = readlink(File.c_str(), LinkBuffer, sizeof(LinkBuffer)-1);
1019 LinkBuffer[nRead] = 0;
1025 typedef pair<int, string> cmdret;
1027 cmdret const do_popen(string const & cmd)
1029 // One question is if we should use popen or
1030 // create our own popen based on fork, exec, pipe
1031 // of course the best would be to have a
1032 // pstream (process stream), with the
1033 // variants ipstream, opstream
1034 FILE * inf = popen(cmd.c_str(), "r");
1038 ret += static_cast<char>(c);
1041 int pret = pclose(inf);
1042 return make_pair(pret, ret);
1047 findtexfile(string const & fil, string const & /*format*/)
1049 /* There is no problem to extend this function too use other
1050 methods to look for files. It could be setup to look
1051 in environment paths and also if wanted as a last resort
1052 to a recursive find. One of the easier extensions would
1053 perhaps be to use the LyX file lookup methods. But! I am
1054 going to implement this until I see some demand for it.
1058 // If the file can be found directly, we just return a
1059 // absolute path version of it.
1060 if (FileInfo(fil).exist())
1061 return MakeAbsPath(fil);
1063 // No we try to find it using kpsewhich.
1064 // It seems from the kpsewhich manual page that it is safe to use
1065 // kpsewhich without --format: "When the --format option is not
1066 // given, the search path used when looking for a file is inferred
1067 // from the name given, by looking for a known extension. If no
1068 // known extension is found, the search path for TeX source files
1070 // However, we want to take advantage of the format sine almost all
1071 // the different formats has environment variables that can be used
1072 // to controll which paths to search. f.ex. bib looks in
1073 // BIBINPUTS and TEXBIB. Small list follows:
1074 // bib - BIBINPUTS, TEXBIB
1076 // graphic/figure - TEXPICTS, TEXINPUTS
1077 // ist - TEXINDEXSTYLE, INDEXSTYLE
1078 // pk - PROGRAMFONTS, PKFONTS, TEXPKS, GLYPHFONTS, TEXFONTS
1080 // tfm - TFMFONTS, TEXFONTS
1081 // This means that to use kpsewhich in the best possible way we
1082 // should help it by setting additional path in the approp. envir.var.
1083 string const kpsecmd = "kpsewhich " + fil;
1085 cmdret const c = do_popen(kpsecmd);
1087 lyxerr[Debug::LATEX] << "kpse status = " << c.first << "\n"
1088 << "kpse result = `" << strip(c.second, '\n')
1090 return c.first != -1 ? strip(c.second, '\n') : string();