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
25 #pragma implementation "filetools.h"
28 #include "filetools.h"
29 #include "LSubstring.h"
30 #include "lyx_gui_misc.h"
32 #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 while (qname.find("'") != string::npos)
103 LSubstring(qname, "'") = "\\'";
104 return '\'' + qname + '\'';
108 /// Returns an unique name to be used as a temporary file.
109 string TmpFileName(string const & dir, string const & mask)
110 {// With all these temporary variables, it should be safe enough :-) (JMarc)
113 tmpdir = system_tempdir;
116 string tmpfl = AddName(tmpdir, mask);
118 // find a uniq postfix for the filename...
119 // using the pid, and...
120 tmpfl += tostr(getpid());
124 for (int a = 'a'; a <= 'z'; ++a)
125 for (int b = 'a'; b <= 'z'; ++b)
126 for (int c = 'a'; c <= 'z'; ++c) {
127 // if this is not enough I have no idea what
129 ret = tmpfl + char(a) + char(b) + char(c);
130 // check if the file exist
131 if (!fnfo.newFile(ret).exist())
134 lyxerr << "Not able to find a uniq tmpfile name." << endl;
139 // Is a file readable ?
140 bool IsFileReadable (string const & path)
143 if (file.isOK() && file.isRegular() && file.readable())
150 // Is a file read_only?
151 // return 1 read-write
153 // -1 error (doesn't exist, no access, anything else)
154 int IsFileWriteable (string const & path)
156 FilePtr fp(path, FilePtr::update);
158 if ((errno == EACCES) || (errno == EROFS)) {
159 fp.reopen(path, FilePtr::read);
170 //returns 1: dir writeable
172 // -1: error- couldn't find out
173 int IsDirWriteable (string const & path)
175 string tmpfl = TmpFileName(path);
178 WriteFSAlert(_("LyX Internal Error!"),
179 _("Could not test if directory is writeable"));
182 FilePtr fp(tmpfl, FilePtr::truncate);
184 if (errno == EACCES) {
187 WriteFSAlert(_("LyX Internal Error!"),
188 _("Cannot open directory test file"));
193 if (remove(tmpfl.c_str())) {
194 WriteFSAlert(_("LyX Internal Error!"),
195 _("Created test file but cannot remove it?"));
202 // Uses a string of paths separated by ";"s to find a file to open.
203 // Can't cope with pathnames with a ';' in them. Returns full path to file.
204 // If path entry begins with $$LyX/, use system_lyxdir
205 // If path entry begins with $$User/, use user_lyxdir
206 // Example: "$$User/doc;$$LyX/doc"
207 string FileOpenSearch (string const & path, string const & name,
210 string real_file, path_element;
211 bool notfound = true;
212 string tmppath = split(path, path_element, ';');
214 while (notfound && !path_element.empty()) {
215 path_element = CleanupPath(path_element);
216 if (!suffixIs(path_element, '/'))
218 path_element = subst(path_element, "$$LyX", system_lyxdir);
219 path_element = subst(path_element, "$$User", user_lyxdir);
221 real_file = FileSearch(path_element, name, ext);
223 if (real_file.empty()) {
225 tmppath = split(tmppath, path_element, ';');
226 } while(!tmppath.empty() && path_element.empty());
232 if (ext.empty() && notfound) {
233 real_file = FileOpenSearch(path, name, "exe");
234 if (notfound) real_file = FileOpenSearch(path, name, "cmd");
241 // Returns the real name of file name in directory path, with optional
243 string FileSearch(string const & path, string const & name,
246 // if `name' is an absolute path, we ignore the setting of `path'
247 // Expand Environmentvariables in 'name'
248 string tmpname = ReplaceEnvironmentPath(name);
249 string fullname = MakeAbsPath(tmpname, path);
251 // search first without extension, then with it.
252 if (IsFileReadable(fullname))
254 else if (ext.empty())
256 else { // Is it not more reasonable to use ChangeExtension()? (SMiyata)
259 if (IsFileReadable(fullname))
267 // Search the file name.ext in the subdirectory dir of
269 // 2) build_lyxdir (if not empty)
271 string LibFileSearch(string const & dir, string const & name,
274 string fullname = FileSearch(AddPath(user_lyxdir, dir),
276 if (!fullname.empty())
279 if (!build_lyxdir.empty())
280 fullname = FileSearch(AddPath(build_lyxdir, dir),
282 if (!fullname.empty())
285 return FileSearch(AddPath(system_lyxdir, dir), name, ext);
289 string i18nLibFileSearch(string const & dir, string const & name,
292 string lang = token(string(GetEnv("LANG")), '_', 0);
294 if (lang.empty() || lang == "C")
295 return LibFileSearch(dir, name, ext);
297 string tmp = LibFileSearch(dir, lang + '_' + name,
302 return LibFileSearch(dir, name, ext);
307 string GetEnv(string const & envname)
309 // f.ex. what about error checking?
310 char const * const ch = getenv(envname.c_str());
311 string envstr = !ch ? "" : ch;
316 string GetEnvPath(string const & name)
319 string pathlist = subst(GetEnv(name), ':', ';');
321 string pathlist = subst(GetEnv(name), '\\', '/');
323 return strip(pathlist, ';');
327 bool PutEnv(string const & envstr)
330 #warning Look at and fix this.
332 // f.ex. what about error checking?
334 // this leaks, but what can we do about it?
335 // Is doing a getenv() and a free() of the older value
336 // a good idea? (JMarc)
337 int retval = putenv((new string(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 DIR * dir = opendir(path.c_str());
360 WriteFSAlert (_("Error! Cannot open directory:"), path);
364 while ((de = readdir(dir))) {
365 string temp = de->d_name;
366 if (temp == "." || temp == "..")
368 string unlinkpath = AddName (path, temp);
370 lyxerr.debug() << "Deleting file: " << unlinkpath << endl;
372 if (remove(unlinkpath.c_str()))
373 WriteFSAlert (_("Error! Could not remove file:"),
382 string CreateTmpDir (string const & tempdir, string const & mask)
384 string tmpfl = TmpFileName(tempdir, mask);
386 if ((tmpfl.empty()) || mkdir (tmpfl.c_str(), 0777)) {
387 WriteFSAlert(_("Error! Couldn't create temporary directory:"),
391 return MakeAbsPath(tmpfl);
396 int DestroyTmpDir (string const & tmpdir, bool Allfiles)
401 if (Allfiles && DeleteAllFilesInDir(tmpdir)) return -1;
402 if (rmdir(tmpdir.c_str())) {
403 WriteFSAlert(_("Error! Couldn't delete temporary directory:"),
411 string CreateBufferTmpDir (string const & pathfor)
413 return CreateTmpDir(pathfor, "lyx_bufrtmp");
417 int DestroyBufferTmpDir (string const & tmpdir)
419 return DestroyTmpDir(tmpdir, true);
423 string CreateLyXTmpDir (string const & deflt)
425 if ((!deflt.empty()) && (deflt != "/tmp")) {
426 if (mkdir(deflt.c_str(), 0777)) {
430 string t = CreateTmpDir (deflt.c_str(), "lyx_tmp");
438 string t = CreateTmpDir ("/tmp", "lyx_tmp");
444 int DestroyLyXTmpDir (string const & tmpdir)
446 return DestroyTmpDir (tmpdir, false); // Why false?
450 // Creates directory. Returns true if succesfull
451 bool createDirectory(string const & path, int permission)
453 string temp = strip(CleanupPath(path), '/');
456 WriteAlert(_("Internal error!"),
457 _("Call to createDirectory with invalid name"));
461 if (mkdir(temp.c_str(), permission)) {
462 WriteFSAlert (_("Error! Couldn't create directory:"), temp);
469 // Returns current working directory
472 int n = 256; // Assume path is less than 256 chars
474 char * tbuf = new char[n];
476 // Safe. Hopefully all getcwds behave this way!
477 while (((err = lyx::getcwd (tbuf, n)) == 0) && (errno == ERANGE)) {
478 // Buffer too small, double the buffersize and try again
485 if (err) result = tbuf;
491 // Strip filename from path name
492 string OnlyPath(string const & Filename)
494 // If empty filename, return empty
495 if (Filename.empty()) return Filename;
497 // Find last / or start of filename
498 string::size_type j = Filename.rfind('/');
499 if (j == string::npos)
501 return Filename.substr(0, j + 1);
505 // Convert relative path into absolute path based on a basepath.
506 // If relpath is absolute, just use that.
507 // If basepath is empty, use CWD as base.
508 string MakeAbsPath(string const & RelPath, string const & BasePath)
510 // checks for already absolute path
511 if (AbsolutePath(RelPath))
513 if(RelPath[0]!= '/' && RelPath[0]!= '\\')
517 // Copies given paths
518 string TempRel = CleanupPath(RelPath);
522 if (!BasePath.empty()) {
526 char * with_drive = new char[_MAX_PATH];
527 _abspath(with_drive, BasePath.c_str(), _MAX_PATH);
528 TempBase = with_drive;
534 if (AbsolutePath(TempRel))
535 return TempBase.substr(0, 2) + TempRel;
538 // Handle /./ at the end of the path
539 while(suffixIs(TempBase, "/./"))
540 TempBase.erase(TempBase.length() - 2);
542 // processes relative path
543 string RTemp = TempRel;
546 while (!RTemp.empty()) {
548 RTemp = split(RTemp, Temp, '/');
550 if (Temp == ".") continue;
552 // Remove one level of TempBase
553 int i = TempBase.length() - 2;
556 while (i > 0 && TempBase[i] != '/') --i;
560 while (i > 2 && TempBase[i] != '/') --i;
563 TempBase.erase(i, string::npos);
567 // Add this piece to TempBase
568 if (!suffixIs(TempBase, '/'))
574 // returns absolute path
579 // Correctly append filename to the pathname.
580 // If pathname is '.', then don't use pathname.
581 // Chops any path of filename.
582 string AddName(string const & path, string const & fname)
585 string basename = OnlyFilename(fname);
589 if (path != "." && path != "./" && !path.empty()) {
590 buf = CleanupPath(path);
591 if (!suffixIs(path, '/'))
595 return buf + basename;
599 // Strips path from filename
600 string OnlyFilename(string const & fname)
605 string::size_type j = fname.rfind('/');
606 if (j == string::npos) // no '/' in fname
610 return fname.substr(j + 1);
614 // Is a filename/path absolute?
615 bool AbsolutePath(string const & path)
618 return (!path.empty() && path[0] == '/');
620 return (!path.empty() && (path[0] == '/' || (isalpha(static_cast<unsigned char>(path[0])) && path.length()>1 && path[1] == ':')));
625 // Create absolute path. If impossible, don't do anything
626 // Supports ./ and ~/. Later we can add support for ~logname/. (Asger)
627 string ExpandPath(string const & path)
629 // checks for already absolute path
630 string RTemp = ReplaceEnvironmentPath(path);
631 if (AbsolutePath(RTemp))
638 RTemp= split(RTemp, Temp, '/');
641 return GetCWD() + '/' + RTemp;
642 } else if (Temp == "~") {
643 return GetEnvPath("HOME") + '/' + RTemp;
644 } else if (Temp == "..") {
645 return MakeAbsPath(copy);
647 // Don't know how to handle this
653 // Constracts path/../path
654 // Can't handle "../../" or "/../" (Asger)
655 string NormalizePath(string const & path)
661 if (AbsolutePath(path))
664 // Make implicit current directory explicit
667 while (!RTemp.empty()) {
669 RTemp = split(RTemp, Temp, '/');
673 } else if (Temp == "..") {
674 // Remove one level of TempBase
675 int i = TempBase.length() - 2;
676 while (i > 0 && TempBase[i] != '/')
678 if (i >= 0 && TempBase[i] == '/')
679 TempBase.erase(i + 1, string::npos);
683 TempBase += Temp + '/';
687 // returns absolute path
691 string CleanupPath(string const & path)
693 #ifdef __EMX__ /* SMiyata: This should fix searchpath bug. */
694 string temppath = subst(path, '\\', '/');
695 temppath = subst(temppath, "//", "/");
696 return lowercase(temppath);
697 #else // On unix, nothing to do
704 // Search ${...} as Variable-Name inside the string and replace it with
705 // the denoted environmentvariable
706 // Allow Variables according to
707 // variable := '$' '{' [A-Za-z_]{[A-Za-z_0-9]*} '}'
710 string ReplaceEnvironmentPath(string const & path)
713 // CompareChar: Environmentvariables starts with this character
714 // PathChar: Next path component start with this character
715 // while CompareChar found do:
716 // Split String with PathChar
717 // Search Environmentvariable
718 // if found: Replace Strings
720 char const CompareChar = '$';
721 char const FirstChar = '{';
722 char const EndChar = '}';
723 char const UnderscoreChar = '_';
724 string EndString; EndString += EndChar;
725 string FirstString; FirstString += FirstChar;
726 string CompareString; CompareString += CompareChar;
727 string const RegExp("*}*"); // Exist EndChar inside a String?
729 // first: Search for a '$' - Sign.
731 string result1; //(copy); // for split-calls
732 string result0 = split(path, result1, CompareChar);
733 while (!result0.empty()) {
734 string copy1(result0); // contains String after $
736 // Check, if there is an EndChar inside original String.
738 if (!regexMatch(copy1, RegExp)) {
739 // No EndChar inside. So we are finished
740 result1 += CompareString + result0;
746 string res0 = split(copy1, res1, EndChar);
747 // Now res1 holds the environmentvariable
748 // First, check, if Contents is ok.
749 if (res1.empty()) { // No environmentvariable. Continue Loop.
750 result1 += CompareString + FirstString;
754 // check contents of res1
755 char const * res1_contents = res1.c_str();
756 if (*res1_contents != FirstChar) {
757 // Again No Environmentvariable
758 result1 += CompareString;
762 // Check for variable names
763 // Situation ${} is detected as "No Environmentvariable"
764 char const * cp1 = res1_contents + 1;
765 bool result = isalpha(*cp1) || (*cp1 == UnderscoreChar);
767 while (*cp1 && result) {
768 result = isalnum(*cp1) ||
769 (*cp1 == UnderscoreChar);
774 // no correct variable name
775 result1 += CompareString + res1 + EndString;
776 result0 = split(res0, res1, CompareChar);
781 string env = GetEnv(res1_contents+1);
783 // Congratulations. Environmentvariable found
786 result1 += CompareString + res1 + EndString;
789 result0 = split(res0, res1, CompareChar);
793 } // ReplaceEnvironmentPath
796 // Make relative path out of two absolute paths
797 string MakeRelPath(string const & abspath0, string const & basepath0)
798 // Makes relative path out of absolute path. If it is deeper than basepath,
799 // it's easy. If basepath and abspath share something (they are all deeper
800 // than some directory), it'll be rendered using ..'s. If they are completely
801 // different, then the absolute path will be used as relative path.
803 // This is a hack. It should probaly be done in another way. Lgb.
804 string abspath = CleanupPath(abspath0);
805 string basepath = CleanupPath(basepath0);
807 return "<unknown_path>";
809 const int abslen = abspath.length();
810 const int baselen = basepath.length();
812 // Find first different character
814 while (i < abslen && i < baselen && abspath[i] == basepath[i]) ++i;
817 if (i < abslen && i < baselen
818 || (i<abslen && abspath[i] != '/' && i == baselen)
819 || (i<baselen && basepath[i] != '/' && i == abslen))
821 if (i) --i; // here was the last match
822 while (i && abspath[i] != '/') --i;
826 // actually no match - cannot make it relative
830 // Count how many dirs there are in basepath above match
831 // and append as many '..''s into relpath
834 while (j < baselen) {
835 if (basepath[j] == '/') {
836 if (j+1 == baselen) break;
842 // Append relative stuff from common directory to abspath
843 if (abspath[i] == '/') ++i;
844 for (; i < abslen; ++i)
847 if (suffixIs(buf, '/'))
848 buf.erase(buf.length() - 1);
849 // Substitute empty with .
856 // Append sub-directory(ies) to a path in an intelligent way
857 string AddPath(string const & path, string const & path_2)
860 string path2 = CleanupPath(path_2);
862 if (!path.empty() && path != "." && path != "./") {
863 buf = CleanupPath(path);
864 if (path[path.length() - 1] != '/')
869 int p2start = path2.find_first_not_of('/');
871 int p2end = path2.find_last_not_of('/');
873 string tmp = path2.substr(p2start, p2end - p2start + 1);
881 Change extension of oldname to extension.
882 Strips path off if no_path == true.
883 If no extension on oldname, just appends.
885 string ChangeExtension(string const & oldname, string const & extension,
888 string::size_type last_slash = oldname.rfind('/');
889 string::size_type last_dot;
890 if (last_slash != string::npos)
891 last_dot = oldname.find('.', last_slash);
893 last_dot = oldname.rfind('.');
896 // Make sure the extension starts with a dot
897 if (!extension.empty() && extension[0] != '.')
898 ext= '.' + extension;
902 if (no_path && last_slash != string::npos) {
903 ++last_slash; // step it
904 ret_str = oldname.substr(last_slash,
905 last_dot - last_slash) + ext;
907 ret_str = oldname.substr(0, last_dot) + ext;
908 return CleanupPath(ret_str);
912 // Creates a nice compact path for displaying
913 string MakeDisplayPath (string const & path, unsigned int threshold)
915 const int l1 = path.length();
917 // First, we try a relative path compared to home
918 string home = GetEnvPath("HOME");
919 string relhome = MakeRelPath(path, home);
921 unsigned int l2 = relhome.length();
925 // If we backup from home or don't have a relative path,
926 // this try is no good
927 if (prefixIs(relhome, "../") || AbsolutePath(relhome)) {
928 // relative path was no good, just use the original path
935 // Is the path too long?
936 if (l2 > threshold) {
942 while (relhome.length() > threshold)
943 relhome = split(relhome, temp, '/');
945 // Did we shortend everything away?
946 if (relhome.empty()) {
947 // Yes, filename in itself is too long.
948 // Pick the start and the end of the filename.
949 relhome = OnlyFilename(path);
950 string head = relhome.substr(0, threshold/2 - 3);
952 l2 = relhome.length();
954 relhome.substr(l2 - threshold/2 - 2, l2 - 1);
955 relhome = head + "..." + tail;
958 return prefix + relhome;
962 bool LyXReadLink(string const & File, string & Link)
964 char LinkBuffer[512];
965 // Should be PATH_MAX but that needs autconf support
966 int nRead = readlink(File.c_str(), LinkBuffer, sizeof(LinkBuffer)-1);
969 LinkBuffer[nRead] = 0;
975 typedef pair<int, string> cmdret;
976 static cmdret do_popen(string const & cmd)
978 // One question is if we should use popen or
979 // create our own popen based on fork, exec, pipe
980 // of course the best would be to have a
981 // pstream (process stream), with the
982 // variants ipstream, opstream and
983 FILE * inf = popen(cmd.c_str(), "r");
987 ret += static_cast<char>(c);
990 int pret = pclose(inf);
991 return make_pair(pret, ret);
995 string findtexfile(string const & fil, string const & format)
997 /* There is no problem to extend this function too use other
998 methods to look for files. It could be setup to look
999 in environment paths and also if wanted as a last resort
1000 to a recursive find. One of the easier extensions would
1001 perhaps be to use the LyX file lookup methods. But! I am
1002 going to implement this until I see some demand for it.
1006 // If fil is a file with absolute path we just return it
1007 if (AbsolutePath(fil)) return fil;
1009 // Check in the current dir.
1010 if (FileInfo(OnlyFilename(fil)).exist())
1011 return OnlyFilename(fil);
1013 // No we try to find it using kpsewhich.
1014 string kpsecmd = "kpsewhich --format= " + format + " " + OnlyFilename(fil);
1015 cmdret c = do_popen(kpsecmd);
1017 lyxerr << "kpse status = " << c.first << "\n"
1018 << "kpse result = `" << strip(c.second, '\n') << "'" << endl;
1019 return c.first != -1 ? strip(c.second, '\n') : string();