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
23 #pragma implementation "filetools.h"
26 #include "filetools.h"
27 #include "lyx_gui_misc.h"
29 #include "support/path.h" // I know it's OS/2 specific (SMiyata)
34 // Which part of this is still necessary? (JMarc).
37 # define NAMLEN(dirent) strlen((dirent)->d_name)
39 # define dirent direct
40 # define NAMLEN(dirent) (dirent)->d_namlen
42 # include <sys/ndir.h>
54 extern string system_lyxdir;
55 extern string build_lyxdir;
56 extern string user_lyxdir;
57 extern string system_tempdir;
60 bool IsLyXFilename(string const & filename)
62 return contains(filename, ".lyx");
66 bool IsSGMLFilename(string const & filename)
68 return contains(filename, ".sgml");
72 // Substitutes spaces with underscores in filename (and path)
73 string SpaceLess(string const & file)
75 string name = OnlyFilename(file);
76 string path = OnlyPath(file);
78 for (string::size_type i = 0; i < name.length(); ++i) {
79 name[i] &= 0x7f; // set 8th bit to 0
82 // ok so we scan through the string twice, but who cares.
83 string keep("abcdefghijklmnopqrstuvwxyz"
84 "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
85 "@!\"'()*+,-./0123456789:;<=>?[]`|");
87 string::size_type pos = 0;
88 while ((pos = name.find_first_not_of(keep, pos)) != string::npos) {
91 return AddName(path, name);
95 /// Returns an unique name to be used as a temporary file.
96 string TmpFileName(string const & dir, string const & mask)
97 {// With all these temporary variables, it should be safe enough :-) (JMarc)
100 tmpdir = system_tempdir;
103 string tmpfl = AddName(tmpdir, mask);
105 // find a uniq postfix for the filename...
106 // using the pid, and...
107 tmpfl += tostr(getpid());
111 for (int a= 'a'; a<= 'z'; ++a)
112 for (int b= 'a'; b<= 'z'; ++b)
113 for (int c= 'a'; c<= 'z'; ++c) {
114 // if this is not enough I have no idea what
116 ret = tmpfl + char(a) + char(b) + char(c);
117 // check if the file exist
118 if (!fnfo.newFile(ret).exist())
121 lyxerr << "Not able to find a uniq tmpfile name." << endl;
126 // Is a file readable ?
127 bool IsFileReadable (string const & path)
130 if (file.isOK() && file.isRegular() && file.readable())
137 // Is a file read_only?
138 // return 1 read-write
140 // -1 error (doesn't exist, no access, anything else)
141 int IsFileWriteable (string const & path)
143 FilePtr fp(path, FilePtr::update);
145 if ((errno == EACCES) || (errno == EROFS)) {
146 fp.reopen(path, FilePtr::read);
157 //returns 1: dir writeable
159 // -1: error- couldn't find out
160 int IsDirWriteable (string const & path)
162 string tmpfl = TmpFileName(path);
165 WriteFSAlert(_("LyX Internal Error!"),
166 _("Could not test if directory is writeable"));
169 FilePtr fp(tmpfl, FilePtr::truncate);
171 if (errno == EACCES) {
174 WriteFSAlert(_("LyX Internal Error!"),
175 _("Cannot open directory test file"));
180 if (remove (tmpfl.c_str())) {
181 WriteFSAlert(_("LyX Internal Error!"),
182 _("Created test file but cannot remove it?"));
189 // Uses a string of paths separated by ";"s to find a file to open.
190 // Can't cope with pathnames with a ';' in them. Returns full path to file.
191 // If path entry begins with $$LyX/, use system_lyxdir
192 // If path entry begins with $$User/, use user_lyxdir
193 // Example: "$$User/doc;$$LyX/doc"
194 string FileOpenSearch (string const & path, string const & name,
197 string real_file, path_element;
198 bool notfound = true;
199 string tmppath = split(path, path_element, ';');
201 while (notfound && !path_element.empty()) {
202 path_element = CleanupPath(path_element);
203 if (!suffixIs(path_element, '/'))
205 path_element = subst(path_element, "$$LyX", system_lyxdir);
206 path_element = subst(path_element, "$$User", user_lyxdir);
208 real_file = FileSearch(path_element, name, ext);
210 if (real_file.empty()) {
212 tmppath = split(tmppath, path_element, ';');
213 } while(!tmppath.empty() && path_element.empty());
219 if (ext.empty() && notfound) {
220 real_file = FileOpenSearch(path, name, "exe");
221 if (notfound) real_file = FileOpenSearch(path, name, "cmd");
228 // Returns the real name of file name in directory path, with optional
230 string FileSearch(string const & path, string const & name,
233 // if `name' is an absolute path, we ignore the setting of `path'
234 // Expand Environmentvariables in 'name'
235 string tmpname = ReplaceEnvironmentPath(name);
236 string fullname = MakeAbsPath(tmpname, path);
238 // search first without extension, then with it.
239 if (IsFileReadable(fullname))
241 else if (ext.empty())
243 else { // Is it not more reasonable to use ChangeExtension()? (SMiyata)
246 if (IsFileReadable(fullname))
254 // Search the file name.ext in the subdirectory dir of
256 // 2) build_lyxdir (if not empty)
258 string LibFileSearch(string const & dir, string const & name,
261 string fullname = FileSearch(AddPath(user_lyxdir, dir), name,
263 if (!fullname.empty())
266 if (!build_lyxdir.empty())
267 fullname = FileSearch(AddPath(build_lyxdir, dir),
269 if (!fullname.empty())
272 return FileSearch(AddPath(system_lyxdir, dir), name, ext);
276 string 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 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 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)
317 #warning Look at and fix this.
319 // f.ex. what about error checking?
322 // this leaks, but what can we do about it?
323 // Is doing a getenv() and a free() of the older value
324 // a good idea? (JMarc)
325 retval = putenv((new string(envstr))->c_str());
329 string str = envstr.split(varname,'=');
330 retval = setenv(varname.c_str(), str.c_str(), true);
337 bool PutEnvPath(string const & envstr)
339 return PutEnv(envstr);
344 int DeleteAllFilesInDir (string const & path)
347 DIR * dir = opendir(path.c_str());
349 WriteFSAlert (_("Error! Cannot open directory:"), path);
352 while ((de = readdir(dir))) {
353 string temp = de->d_name;
354 if (temp == "." || temp == "..")
356 string unlinkpath = AddName (path, temp);
358 lyxerr.debug() << "Deleting file: " << unlinkpath << endl;
360 if (remove (unlinkpath.c_str()))
361 WriteFSAlert (_("Error! Could not remove file:"),
370 string CreateTmpDir (string const & tempdir, string const & mask)
372 string tmpfl = TmpFileName(tempdir, mask);
374 if ((tmpfl.empty()) || mkdir (tmpfl.c_str(), 0777)) {
375 WriteFSAlert(_("Error! Couldn't create temporary directory:"),
379 return MakeAbsPath(tmpfl);
384 int DestroyTmpDir (string const & tmpdir, bool Allfiles)
389 if (Allfiles && DeleteAllFilesInDir(tmpdir)) return -1;
390 if (rmdir(tmpdir.c_str())) {
391 WriteFSAlert(_("Error! Couldn't delete temporary directory:"),
399 string CreateBufferTmpDir (string const & pathfor)
401 return CreateTmpDir (pathfor, "lyx_bufrtmp");
405 int DestroyBufferTmpDir (string const & tmpdir)
407 return DestroyTmpDir (tmpdir, true);
411 string CreateLyXTmpDir (string const & deflt)
415 if ((!deflt.empty()) && (deflt != "/tmp")) {
416 if (mkdir (deflt.c_str(), 0777)) {
420 t = CreateTmpDir (deflt.c_str(), "lyx_tmp");
428 t = CreateTmpDir ("/tmp", "lyx_tmp");
434 int DestroyLyXTmpDir (string const & tmpdir)
436 return DestroyTmpDir (tmpdir, false); // Why false?
440 // Creates directory. Returns true if succesfull
441 bool createDirectory(string const & path, int permission)
443 string temp = strip(CleanupPath(path), '/');
446 WriteAlert(_("Internal error!"),
447 _("Call to createDirectory with invalid name"));
451 if (mkdir(temp.c_str(), permission)) {
452 WriteFSAlert (_("Error! Couldn't create directory:"), temp);
459 // Returns current working directory
462 int n = 256; // Assume path is less than 256 chars
464 char * tbuf = new char[n];
467 // Safe. Hopefully all getcwds behave this way!
468 while (((err = lyx::getcwd (tbuf, n)) == 0) && (errno == ERANGE)) {
469 // Buffer too small, double the buffersize and try again
475 if (err) result = tbuf;
481 // Strip filename from path name
482 string OnlyPath(string const & Filename)
484 // If empty filename, return empty
485 if (Filename.empty()) return Filename;
487 // Find last / or start of filename
488 string::size_type j = Filename.rfind('/');
489 if (j == string::npos)
491 return Filename.substr(0, j + 1);
495 // Convert relative path into absolute path based on a basepath.
496 // If relpath is absolute, just use that.
497 // If basepath is empty, use CWD as base.
498 string MakeAbsPath(string const & RelPath, string const & BasePath)
500 // checks for already absolute path
501 if (AbsolutePath(RelPath))
503 if(RelPath[0]!= '/' && RelPath[0]!= '\\')
507 // Copies given paths
508 string TempRel = CleanupPath(RelPath);
512 if (!BasePath.empty()) {
516 char * with_drive = new char[_MAX_PATH];
517 _abspath(with_drive, BasePath.c_str(), _MAX_PATH);
518 TempBase = with_drive;
524 if (AbsolutePath(TempRel))
525 return TempBase.substr(0, 2) + TempRel;
528 // Handle /./ at the end of the path
529 while(suffixIs(TempBase, "/./"))
530 TempBase.erase(TempBase.length() - 2);
532 // processes relative path
533 string RTemp = TempRel;
536 while (!RTemp.empty()) {
538 RTemp = split(RTemp, Temp, '/');
540 if (Temp == ".") continue;
542 // Remove one level of TempBase
543 int i = TempBase.length()-2;
546 while (i > 0 && TempBase[i] != '/') --i;
550 while (i > 2 && TempBase[i] != '/') --i;
553 TempBase.erase(i, string::npos);
557 // Add this piece to TempBase
558 if (!suffixIs(TempBase, '/'))
564 // returns absolute path
569 // Correctly append filename to the pathname.
570 // If pathname is '.', then don't use pathname.
571 // Chops any path of filename.
572 string AddName(string const & path, string const & fname)
575 string basename = OnlyFilename(fname);
579 if (path != "." && path != "./" && !path.empty()) {
580 buf = CleanupPath(path);
581 if (!suffixIs(path, '/'))
585 return buf + basename;
589 // Strips path from filename
590 string OnlyFilename(string const & fname)
595 string::size_type j = fname.rfind('/');
596 if (j == string::npos) // no '/' in fname
600 return fname.substr(j + 1);
604 // Is a filename/path absolute?
605 bool AbsolutePath(string const & path)
608 return (!path.empty() && path[0] == '/');
610 return (!path.empty() && (path[0] == '/' || (isalpha(static_cast<unsigned char>(path[0])) && path.length()>1 && path[1] == ':')));
615 // Create absolute path. If impossible, don't do anything
616 // Supports ./ and ~/. Later we can add support for ~logname/. (Asger)
617 string ExpandPath(string const & path)
619 Assert(!path.empty()); // We don't allow empty path. (Lgb)
620 // checks for already absolute path
621 string RTemp = ReplaceEnvironmentPath(path);
622 if (AbsolutePath(RTemp))
629 RTemp= split(RTemp, Temp, '/');
632 return GetCWD() + '/' + RTemp;
633 } else if (Temp == "~") {
634 return GetEnvPath("HOME") + '/' + RTemp;
635 } else if (Temp == "..") {
636 return MakeAbsPath(copy);
638 // Don't know how to handle this
644 // Constracts path/../path
645 // Can't handle "../../" or "/../" (Asger)
646 string NormalizePath(string const & path)
652 if (AbsolutePath(path))
655 // Make implicit current directory explicit
658 while (!RTemp.empty()) {
660 RTemp = split(RTemp, Temp, '/');
664 } else if (Temp == "..") {
665 // Remove one level of TempBase
666 int i = TempBase.length()-2;
667 while (i>0 && TempBase[i] != '/')
669 if (i>= 0 && TempBase[i] == '/')
670 TempBase.erase(i+1, string::npos);
674 TempBase += Temp + '/';
678 // returns absolute path
682 string CleanupPath(string const & path)
684 #ifdef __EMX__ /* SMiyata: This should fix searchpath bug. */
685 string temppath = subst(path, '\\', '/');
686 temppath = subst(temppath, "//", "/");
687 return lowercase(temppath);
688 #else // On unix, nothing to do
695 // Search ${...} as Variable-Name inside the string and replace it with
696 // the denoted environmentvariable
697 // Allow Variables according to
698 // variable := '$' '{' [A-Za-z_]{[A-Za-z_0-9]*} '}'
701 string ReplaceEnvironmentPath(string const & path)
703 Assert(!path.empty()); // We don't allow empty path. (Lgb)
705 // CompareChar: Environmentvariables starts with this character
706 // PathChar: Next path component start with this character
707 // while CompareChar found do:
708 // Split String with PathChar
709 // Search Environmentvariable
710 // if found: Replace Strings
712 char const CompareChar = '$';
713 char const FirstChar = '{';
714 char const EndChar = '}';
715 char const UnderscoreChar = '_';
716 string EndString; EndString += EndChar;
717 string FirstString; FirstString += FirstChar;
718 string CompareString; CompareString += CompareChar;
719 string const RegExp("*}*"); // Exist EndChar inside a String?
721 // first: Search for a '$' - Sign.
723 string result1; //(copy); // for split-calls
724 string result0 = split(path, result1, CompareChar);
725 while (!result0.empty()) {
726 string copy1(result0); // contains String after $
728 // Check, if there is an EndChar inside original String.
730 if (!regexMatch(copy1, RegExp)) {
731 // No EndChar inside. So we are finished
732 result1 += CompareString + result0;
738 string res0 = split(copy1, res1, EndChar);
739 // Now res1 holds the environmentvariable
740 // First, check, if Contents is ok.
741 if (res1.empty()) { // No environmentvariable. Continue Loop.
742 result1 += CompareString + FirstString;
746 // check contents of res1
747 char const * res1_contents = res1.c_str();
748 if (*res1_contents != FirstChar) {
749 // Again No Environmentvariable
750 result1 += CompareString;
754 // Check for variable names
755 // Situation ${} is detected as "No Environmentvariable"
756 char const * cp1 = res1_contents+1;
757 bool result = isalpha(*cp1) || (*cp1 == UnderscoreChar);
759 while (*cp1 && result) {
760 result = isalnum(*cp1) ||
761 (*cp1 == UnderscoreChar);
766 // no correct variable name
767 result1 += CompareString + res1 + EndString;
768 result0 = split(res0, res1, CompareChar);
773 string env = GetEnv(res1_contents+1);
775 // Congratulations. Environmentvariable found
778 result1 += CompareString + res1 + EndString;
781 result0 = split(res0, res1, CompareChar);
785 } // ReplaceEnvironmentPath
788 // Make relative path out of two absolute paths
789 string MakeRelPath(string const & abspath0, string const & basepath0)
790 // Makes relative path out of absolute path. If it is deeper than basepath,
791 // it's easy. If basepath and abspath share something (they are all deeper
792 // than some directory), it'll be rendered using ..'s. If they are completely
793 // different, then the absolute path will be used as relative path.
795 // This is a hack. It should probaly be done in another way. Lgb.
796 string abspath = CleanupPath(abspath0);
797 string basepath = CleanupPath(basepath0);
799 return "<unknown_path>";
801 const int abslen = abspath.length();
802 const int baselen = basepath.length();
804 // Find first different character
806 while (i < abslen && i < baselen && abspath[i] == basepath[i]) ++i;
809 if (i < abslen && i < baselen
810 || (i<abslen && abspath[i] != '/' && i == baselen)
811 || (i<baselen && basepath[i] != '/' && i == abslen))
813 if (i) --i; // here was the last match
814 while (i && abspath[i] != '/') --i;
818 // actually no match - cannot make it relative
822 // Count how many dirs there are in basepath above match
823 // and append as many '..''s into relpath
826 while (j < baselen) {
827 if (basepath[j] == '/') {
828 if (j+1 == baselen) break;
834 // Append relative stuff from common directory to abspath
835 if (abspath[i] == '/') ++i;
836 for (; i<abslen; ++i)
839 if (suffixIs(buf, '/'))
840 buf.erase(buf.length() - 1);
841 // Substitute empty with .
848 // Append sub-directory(ies) to a path in an intelligent way
849 string AddPath(string const & path, string const & path_2)
852 string path2 = CleanupPath(path_2);
854 if (!path.empty() && path != "." && path != "./") {
855 buf = CleanupPath(path);
856 if (path[path.length() - 1] != '/')
862 int p2start = path2.find_first_not_of('/');
864 int p2end = path2.find_last_not_of('/');
866 string tmp = path2.substr(p2start, p2end - p2start + 1);
874 Change extension of oldname to extension.
875 Strips path off if no_path == true.
876 If no extension on oldname, just appends.
878 string ChangeExtension(string const & oldname, string const & extension,
881 string::size_type last_slash = oldname.rfind('/');
882 string::size_type last_dot;
883 if (last_slash != string::npos)
884 last_dot = oldname.find('.', last_slash);
886 last_dot = oldname.rfind('.');
889 // Make sure the extension starts with a dot
890 if (!extension.empty() && extension[0] != '.')
891 ext= '.' + extension;
895 if (no_path && last_slash != string::npos) {
896 ++last_slash; // step it
897 ret_str = oldname.substr(last_slash,
898 last_dot - last_slash) + ext;
900 ret_str = oldname.substr(0, last_dot) + ext;
901 return CleanupPath(ret_str);
906 // Creates a nice compact path for displaying
907 string MakeDisplayPath (string const & path, unsigned int threshold)
909 const int l1 = path.length();
911 // First, we try a relative path compared to home
912 string home = GetEnvPath("HOME");
913 string relhome = MakeRelPath(path, home);
915 unsigned int l2 = relhome.length();
919 // If we backup from home or don't have a relative path,
920 // this try is no good
921 if (prefixIs(relhome, "../") || AbsolutePath(relhome)) {
922 // relative path was no good, just use the original path
929 // Is the path too long?
930 if (l2 > threshold) {
936 while (relhome.length() > threshold)
937 relhome = split(relhome, temp, '/');
939 // Did we shortend everything away?
940 if (relhome.empty()) {
941 // Yes, filename in itself is too long.
942 // Pick the start and the end of the filename.
943 relhome = OnlyFilename(path);
944 string head = relhome.substr(0, threshold/2 - 3);
946 l2 = relhome.length();
948 relhome.substr(l2 - threshold/2 - 2, l2 - 1);
949 relhome = head + "..." + tail;
952 return prefix + relhome;
956 bool LyXReadLink(string const & File, string & Link)
958 char LinkBuffer[512];
959 // Should be PATH_MAX but that needs autconf support
961 nRead = readlink(File.c_str(), LinkBuffer, sizeof(LinkBuffer)-1);
964 LinkBuffer[nRead] = 0;
970 typedef pair<int, string> cmdret;
971 static cmdret do_popen(string const & cmd)
973 // One question is if we should use popen or
974 // create our own popen based on fork, exec, pipe
975 // of course the best would be to have a
976 // pstream (process stream), with the
977 // variants ipstream, opstream and
978 FILE * inf = popen(cmd.c_str(), "r");
982 ret += static_cast<char>(c);
985 int pret = pclose(inf);
986 return make_pair(pret, ret);
990 string findtexfile(string const & fil, string const & format)
992 /* There is no problem to extend this function too use other
993 methods to look for files. It could be setup to look
994 in environment paths and also if wanted as a last resort
995 to a recursive find. One of the easier extensions would
996 perhaps be to use the LyX file lookup methods. But! I am
997 going to implement this until I see some demand for it.
1001 // If fil is a file with absolute path we just return it
1002 if (AbsolutePath(fil)) return fil;
1004 // Check in the current dir.
1005 if (FileInfo(OnlyFilename(fil)).exist())
1006 return OnlyFilename(fil);
1008 // No we try to find it using kpsewhich.
1009 string kpsecmd = "kpsewhich --format= " + format + " " + OnlyFilename(fil);
1010 cmdret c = do_popen(kpsecmd);
1012 lyxerr << "kpse status = " << c.first << "\n"
1013 << "kpse result = `" << strip(c.second, '\n') << "'" << endl;
1014 return c.first != -1 ? strip(c.second, '\n') : string();