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), name,
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?
335 // this leaks, but what can we do about it?
336 // Is doing a getenv() and a free() of the older value
337 // a good idea? (JMarc)
338 retval = putenv((new string(envstr))->c_str());
342 string str = envstr.split(varname,'=');
343 retval = setenv(varname.c_str(), str.c_str(), true);
350 bool PutEnvPath(string const & envstr)
352 return PutEnv(envstr);
357 int DeleteAllFilesInDir (string const & path)
360 DIR * dir = opendir(path.c_str());
362 WriteFSAlert (_("Error! Cannot open directory:"), path);
365 while ((de = readdir(dir))) {
366 string temp = de->d_name;
367 if (temp == "." || temp == "..")
369 string unlinkpath = AddName (path, temp);
371 lyxerr.debug() << "Deleting file: " << unlinkpath << endl;
373 if (remove (unlinkpath.c_str()))
374 WriteFSAlert (_("Error! Could not remove file:"),
383 string CreateTmpDir (string const & tempdir, string const & mask)
385 string tmpfl = TmpFileName(tempdir, mask);
387 if ((tmpfl.empty()) || mkdir (tmpfl.c_str(), 0777)) {
388 WriteFSAlert(_("Error! Couldn't create temporary directory:"),
392 return MakeAbsPath(tmpfl);
397 int DestroyTmpDir (string const & tmpdir, bool Allfiles)
402 if (Allfiles && DeleteAllFilesInDir(tmpdir)) return -1;
403 if (rmdir(tmpdir.c_str())) {
404 WriteFSAlert(_("Error! Couldn't delete temporary directory:"),
412 string CreateBufferTmpDir (string const & pathfor)
414 return CreateTmpDir (pathfor, "lyx_bufrtmp");
418 int DestroyBufferTmpDir (string const & tmpdir)
420 return DestroyTmpDir (tmpdir, true);
424 string CreateLyXTmpDir (string const & deflt)
428 if ((!deflt.empty()) && (deflt != "/tmp")) {
429 if (mkdir (deflt.c_str(), 0777)) {
433 t = CreateTmpDir (deflt.c_str(), "lyx_tmp");
441 t = CreateTmpDir ("/tmp", "lyx_tmp");
447 int DestroyLyXTmpDir (string const & tmpdir)
449 return DestroyTmpDir (tmpdir, false); // Why false?
453 // Creates directory. Returns true if succesfull
454 bool createDirectory(string const & path, int permission)
456 string temp = strip(CleanupPath(path), '/');
459 WriteAlert(_("Internal error!"),
460 _("Call to createDirectory with invalid name"));
464 if (mkdir(temp.c_str(), permission)) {
465 WriteFSAlert (_("Error! Couldn't create directory:"), temp);
472 // Returns current working directory
475 int n = 256; // Assume path is less than 256 chars
477 char * tbuf = new char[n];
480 // Safe. Hopefully all getcwds behave this way!
481 while (((err = lyx::getcwd (tbuf, n)) == 0) && (errno == ERANGE)) {
482 // Buffer too small, double the buffersize and try again
488 if (err) result = tbuf;
494 // Strip filename from path name
495 string OnlyPath(string const & Filename)
497 // If empty filename, return empty
498 if (Filename.empty()) return Filename;
500 // Find last / or start of filename
501 string::size_type j = Filename.rfind('/');
502 if (j == string::npos)
504 return Filename.substr(0, j + 1);
508 // Convert relative path into absolute path based on a basepath.
509 // If relpath is absolute, just use that.
510 // If basepath is empty, use CWD as base.
511 string MakeAbsPath(string const & RelPath, string const & BasePath)
513 // checks for already absolute path
514 if (AbsolutePath(RelPath))
516 if(RelPath[0]!= '/' && RelPath[0]!= '\\')
520 // Copies given paths
521 string TempRel = CleanupPath(RelPath);
525 if (!BasePath.empty()) {
529 char * with_drive = new char[_MAX_PATH];
530 _abspath(with_drive, BasePath.c_str(), _MAX_PATH);
531 TempBase = with_drive;
537 if (AbsolutePath(TempRel))
538 return TempBase.substr(0, 2) + TempRel;
541 // Handle /./ at the end of the path
542 while(suffixIs(TempBase, "/./"))
543 TempBase.erase(TempBase.length() - 2);
545 // processes relative path
546 string RTemp = TempRel;
549 while (!RTemp.empty()) {
551 RTemp = split(RTemp, Temp, '/');
553 if (Temp == ".") continue;
555 // Remove one level of TempBase
556 int i = TempBase.length()-2;
559 while (i > 0 && TempBase[i] != '/') --i;
563 while (i > 2 && TempBase[i] != '/') --i;
566 TempBase.erase(i, string::npos);
570 // Add this piece to TempBase
571 if (!suffixIs(TempBase, '/'))
577 // returns absolute path
582 // Correctly append filename to the pathname.
583 // If pathname is '.', then don't use pathname.
584 // Chops any path of filename.
585 string AddName(string const & path, string const & fname)
588 string basename = OnlyFilename(fname);
592 if (path != "." && path != "./" && !path.empty()) {
593 buf = CleanupPath(path);
594 if (!suffixIs(path, '/'))
598 return buf + basename;
602 // Strips path from filename
603 string OnlyFilename(string const & fname)
608 string::size_type j = fname.rfind('/');
609 if (j == string::npos) // no '/' in fname
613 return fname.substr(j + 1);
617 // Is a filename/path absolute?
618 bool AbsolutePath(string const & path)
621 return (!path.empty() && path[0] == '/');
623 return (!path.empty() && (path[0] == '/' || (isalpha(static_cast<unsigned char>(path[0])) && path.length()>1 && path[1] == ':')));
628 // Create absolute path. If impossible, don't do anything
629 // Supports ./ and ~/. Later we can add support for ~logname/. (Asger)
630 string ExpandPath(string const & path)
632 // checks for already absolute path
633 string RTemp = ReplaceEnvironmentPath(path);
634 if (AbsolutePath(RTemp))
641 RTemp= split(RTemp, Temp, '/');
644 return GetCWD() + '/' + RTemp;
645 } else if (Temp == "~") {
646 return GetEnvPath("HOME") + '/' + RTemp;
647 } else if (Temp == "..") {
648 return MakeAbsPath(copy);
650 // Don't know how to handle this
656 // Constracts path/../path
657 // Can't handle "../../" or "/../" (Asger)
658 string NormalizePath(string const & path)
664 if (AbsolutePath(path))
667 // Make implicit current directory explicit
670 while (!RTemp.empty()) {
672 RTemp = split(RTemp, Temp, '/');
676 } else if (Temp == "..") {
677 // Remove one level of TempBase
678 int i = TempBase.length()-2;
679 while (i>0 && TempBase[i] != '/')
681 if (i>= 0 && TempBase[i] == '/')
682 TempBase.erase(i+1, string::npos);
686 TempBase += Temp + '/';
690 // returns absolute path
694 string CleanupPath(string const & path)
696 #ifdef __EMX__ /* SMiyata: This should fix searchpath bug. */
697 string temppath = subst(path, '\\', '/');
698 temppath = subst(temppath, "//", "/");
699 return lowercase(temppath);
700 #else // On unix, nothing to do
707 // Search ${...} as Variable-Name inside the string and replace it with
708 // the denoted environmentvariable
709 // Allow Variables according to
710 // variable := '$' '{' [A-Za-z_]{[A-Za-z_0-9]*} '}'
713 string ReplaceEnvironmentPath(string const & path)
716 // CompareChar: Environmentvariables starts with this character
717 // PathChar: Next path component start with this character
718 // while CompareChar found do:
719 // Split String with PathChar
720 // Search Environmentvariable
721 // if found: Replace Strings
723 char const CompareChar = '$';
724 char const FirstChar = '{';
725 char const EndChar = '}';
726 char const UnderscoreChar = '_';
727 string EndString; EndString += EndChar;
728 string FirstString; FirstString += FirstChar;
729 string CompareString; CompareString += CompareChar;
730 string const RegExp("*}*"); // Exist EndChar inside a String?
732 // first: Search for a '$' - Sign.
734 string result1; //(copy); // for split-calls
735 string result0 = split(path, result1, CompareChar);
736 while (!result0.empty()) {
737 string copy1(result0); // contains String after $
739 // Check, if there is an EndChar inside original String.
741 if (!regexMatch(copy1, RegExp)) {
742 // No EndChar inside. So we are finished
743 result1 += CompareString + result0;
749 string res0 = split(copy1, res1, EndChar);
750 // Now res1 holds the environmentvariable
751 // First, check, if Contents is ok.
752 if (res1.empty()) { // No environmentvariable. Continue Loop.
753 result1 += CompareString + FirstString;
757 // check contents of res1
758 char const * res1_contents = res1.c_str();
759 if (*res1_contents != FirstChar) {
760 // Again No Environmentvariable
761 result1 += CompareString;
765 // Check for variable names
766 // Situation ${} is detected as "No Environmentvariable"
767 char const * cp1 = res1_contents+1;
768 bool result = isalpha(*cp1) || (*cp1 == UnderscoreChar);
770 while (*cp1 && result) {
771 result = isalnum(*cp1) ||
772 (*cp1 == UnderscoreChar);
777 // no correct variable name
778 result1 += CompareString + res1 + EndString;
779 result0 = split(res0, res1, CompareChar);
784 string env = GetEnv(res1_contents+1);
786 // Congratulations. Environmentvariable found
789 result1 += CompareString + res1 + EndString;
792 result0 = split(res0, res1, CompareChar);
796 } // ReplaceEnvironmentPath
799 // Make relative path out of two absolute paths
800 string MakeRelPath(string const & abspath0, string const & basepath0)
801 // Makes relative path out of absolute path. If it is deeper than basepath,
802 // it's easy. If basepath and abspath share something (they are all deeper
803 // than some directory), it'll be rendered using ..'s. If they are completely
804 // different, then the absolute path will be used as relative path.
806 // This is a hack. It should probaly be done in another way. Lgb.
807 string abspath = CleanupPath(abspath0);
808 string basepath = CleanupPath(basepath0);
810 return "<unknown_path>";
812 const int abslen = abspath.length();
813 const int baselen = basepath.length();
815 // Find first different character
817 while (i < abslen && i < baselen && abspath[i] == basepath[i]) ++i;
820 if (i < abslen && i < baselen
821 || (i<abslen && abspath[i] != '/' && i == baselen)
822 || (i<baselen && basepath[i] != '/' && i == abslen))
824 if (i) --i; // here was the last match
825 while (i && abspath[i] != '/') --i;
829 // actually no match - cannot make it relative
833 // Count how many dirs there are in basepath above match
834 // and append as many '..''s into relpath
837 while (j < baselen) {
838 if (basepath[j] == '/') {
839 if (j+1 == baselen) break;
845 // Append relative stuff from common directory to abspath
846 if (abspath[i] == '/') ++i;
847 for (; i<abslen; ++i)
850 if (suffixIs(buf, '/'))
851 buf.erase(buf.length() - 1);
852 // Substitute empty with .
859 // Append sub-directory(ies) to a path in an intelligent way
860 string AddPath(string const & path, string const & path_2)
863 string path2 = CleanupPath(path_2);
865 if (!path.empty() && path != "." && path != "./") {
866 buf = CleanupPath(path);
867 if (path[path.length() - 1] != '/')
873 int p2start = path2.find_first_not_of('/');
875 int p2end = path2.find_last_not_of('/');
877 string tmp = path2.substr(p2start, p2end - p2start + 1);
885 Change extension of oldname to extension.
886 Strips path off if no_path == true.
887 If no extension on oldname, just appends.
889 string ChangeExtension(string const & oldname, string const & extension,
892 string::size_type last_slash = oldname.rfind('/');
893 string::size_type last_dot;
894 if (last_slash != string::npos)
895 last_dot = oldname.find('.', last_slash);
897 last_dot = oldname.rfind('.');
900 // Make sure the extension starts with a dot
901 if (!extension.empty() && extension[0] != '.')
902 ext= '.' + extension;
906 if (no_path && last_slash != string::npos) {
907 ++last_slash; // step it
908 ret_str = oldname.substr(last_slash,
909 last_dot - last_slash) + ext;
911 ret_str = oldname.substr(0, last_dot) + ext;
912 return CleanupPath(ret_str);
917 // Creates a nice compact path for displaying
918 string MakeDisplayPath (string const & path, unsigned int threshold)
920 const int l1 = path.length();
922 // First, we try a relative path compared to home
923 string home = GetEnvPath("HOME");
924 string relhome = MakeRelPath(path, home);
926 unsigned int l2 = relhome.length();
930 // If we backup from home or don't have a relative path,
931 // this try is no good
932 if (prefixIs(relhome, "../") || AbsolutePath(relhome)) {
933 // relative path was no good, just use the original path
940 // Is the path too long?
941 if (l2 > threshold) {
947 while (relhome.length() > threshold)
948 relhome = split(relhome, temp, '/');
950 // Did we shortend everything away?
951 if (relhome.empty()) {
952 // Yes, filename in itself is too long.
953 // Pick the start and the end of the filename.
954 relhome = OnlyFilename(path);
955 string head = relhome.substr(0, threshold/2 - 3);
957 l2 = relhome.length();
959 relhome.substr(l2 - threshold/2 - 2, l2 - 1);
960 relhome = head + "..." + tail;
963 return prefix + relhome;
967 bool LyXReadLink(string const & File, string & Link)
969 char LinkBuffer[512];
970 // Should be PATH_MAX but that needs autconf support
972 nRead = readlink(File.c_str(), LinkBuffer, sizeof(LinkBuffer)-1);
975 LinkBuffer[nRead] = 0;
981 typedef pair<int, string> cmdret;
982 static cmdret do_popen(string const & cmd)
984 // One question is if we should use popen or
985 // create our own popen based on fork, exec, pipe
986 // of course the best would be to have a
987 // pstream (process stream), with the
988 // variants ipstream, opstream and
989 FILE * inf = popen(cmd.c_str(), "r");
993 ret += static_cast<char>(c);
996 int pret = pclose(inf);
997 return make_pair(pret, ret);
1001 string findtexfile(string const & fil, string const & format)
1003 /* There is no problem to extend this function too use other
1004 methods to look for files. It could be setup to look
1005 in environment paths and also if wanted as a last resort
1006 to a recursive find. One of the easier extensions would
1007 perhaps be to use the LyX file lookup methods. But! I am
1008 going to implement this until I see some demand for it.
1012 // If fil is a file with absolute path we just return it
1013 if (AbsolutePath(fil)) return fil;
1015 // Check in the current dir.
1016 if (FileInfo(OnlyFilename(fil)).exist())
1017 return OnlyFilename(fil);
1019 // No we try to find it using kpsewhich.
1020 string kpsecmd = "kpsewhich --format= " + format + " " + OnlyFilename(fil);
1021 cmdret c = do_popen(kpsecmd);
1023 lyxerr << "kpse status = " << c.first << "\n"
1024 << "kpse result = `" << strip(c.second, '\n') << "'" << endl;
1025 return c.first != -1 ? strip(c.second, '\n') : string();