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
29 #pragma implementation "filetools.h"
32 #include "filetools.h"
33 #include "LSubstring.h"
34 #include "lyx_gui_misc.h"
36 #include "support/path.h" // I know it's OS/2 specific (SMiyata)
40 // Which part of this is still necessary? (JMarc).
43 # define NAMLEN(dirent) strlen((dirent)->d_name)
45 # define dirent direct
46 # define NAMLEN(dirent) (dirent)->d_namlen
48 # include <sys/ndir.h>
58 extern string system_lyxdir;
59 extern string build_lyxdir;
60 extern string user_lyxdir;
61 extern string system_tempdir;
64 bool IsLyXFilename(string const & filename)
66 return contains(filename, ".lyx");
70 bool IsSGMLFilename(string const & filename)
72 return contains(filename, ".sgml");
76 // Substitutes spaces with underscores in filename (and path)
77 string MakeLatexName(string const & file)
79 string name = OnlyFilename(file);
80 string path = OnlyPath(file);
82 for (string::size_type i = 0; i < name.length(); ++i) {
83 name[i] &= 0x7f; // set 8th bit to 0
86 // ok so we scan through the string twice, but who cares.
87 string keep("abcdefghijklmnopqrstuvwxyz"
88 "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
89 "@!\"'()*+,-./0123456789:;<=>?[]`|");
91 string::size_type pos = 0;
92 while ((pos = name.find_first_not_of(keep, pos)) != string::npos) {
95 return AddName(path, name);
98 // Substitutes spaces with underscores in filename (and path)
99 string QuoteName(string const & name)
102 #warning Add proper emx support here!
105 return '\'' + name + '\'';
112 /// Returns an unique name to be used as a temporary file.
113 string 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)
160 fstream fs(path.c_str(), ios::out|ios::ate);
162 fs.open(path.c_str(), ios::in|ios::ate);
172 //returns 1: dir writeable
174 // -1: error- couldn't find out
175 int IsDirWriteable (string const & path)
177 string tmpfl = TmpFileName(path);
180 WriteFSAlert(_("LyX Internal Error!"),
181 _("Could not test if directory is writeable"));
185 if (fi.writable()) return 1;
191 // Uses a string of paths separated by ";"s to find a file to open.
192 // Can't cope with pathnames with a ';' in them. Returns full path to file.
193 // If path entry begins with $$LyX/, use system_lyxdir
194 // If path entry begins with $$User/, use user_lyxdir
195 // Example: "$$User/doc;$$LyX/doc"
196 string FileOpenSearch (string const & path, string const & name,
199 string real_file, path_element;
200 bool notfound = true;
201 string tmppath = split(path, path_element, ';');
203 while (notfound && !path_element.empty()) {
204 path_element = CleanupPath(path_element);
205 if (!suffixIs(path_element, '/'))
207 path_element = subst(path_element, "$$LyX", system_lyxdir);
208 path_element = subst(path_element, "$$User", user_lyxdir);
210 real_file = FileSearch(path_element, name, ext);
212 if (real_file.empty()) {
214 tmppath = split(tmppath, path_element, ';');
215 } while(!tmppath.empty() && path_element.empty());
221 if (ext.empty() && notfound) {
222 real_file = FileOpenSearch(path, name, "exe");
223 if (notfound) real_file = FileOpenSearch(path, name, "cmd");
230 // Returns the real name of file name in directory path, with optional
232 string FileSearch(string const & path, string const & name,
235 // if `name' is an absolute path, we ignore the setting of `path'
236 // Expand Environmentvariables in 'name'
237 string tmpname = ReplaceEnvironmentPath(name);
238 string fullname = MakeAbsPath(tmpname, path);
240 // search first without extension, then with it.
241 if (IsFileReadable(fullname))
243 else if (ext.empty())
245 else { // Is it not more reasonable to use ChangeExtension()? (SMiyata)
248 if (IsFileReadable(fullname))
256 // Search the file name.ext in the subdirectory dir of
258 // 2) build_lyxdir (if not empty)
260 string LibFileSearch(string const & dir, string const & name,
263 string fullname = FileSearch(AddPath(user_lyxdir, dir),
265 if (!fullname.empty())
268 if (!build_lyxdir.empty())
269 fullname = FileSearch(AddPath(build_lyxdir, dir),
271 if (!fullname.empty())
274 return FileSearch(AddPath(system_lyxdir, dir), name, ext);
278 string i18nLibFileSearch(string const & dir, string const & name,
281 string lang = token(string(GetEnv("LANG")), '_', 0);
283 if (lang.empty() || lang == "C")
284 return LibFileSearch(dir, name, ext);
286 string tmp = LibFileSearch(dir, lang + '_' + name,
291 return LibFileSearch(dir, name, ext);
296 string GetEnv(string const & envname)
298 // f.ex. what about error checking?
299 char const * const ch = getenv(envname.c_str());
300 string envstr = !ch ? "" : ch;
305 string GetEnvPath(string const & name)
308 string pathlist = subst(GetEnv(name), ':', ';');
310 string pathlist = subst(GetEnv(name), '\\', '/');
312 return strip(pathlist, ';');
316 bool PutEnv(string const & envstr)
319 #warning Look at and fix this.
321 // f.ex. what about error checking?
323 // this leaks, but what can we do about it?
324 // Is doing a getenv() and a free() of the older value
325 // a good idea? (JMarc)
326 // Actually we don't have to leak...calling putenv like this
327 // should be enough: ... and this is obviously not enough if putenv
328 // does not make a copy of the string. It is also not very wise to
329 // put a string on the free store. If we have to leak we should do it
332 char * leaker = new char[envstr.length() + 1];
333 envstr.copy(leaker, envstr.length());
334 leaker[envstr.length()] = '\0';
335 int retval = putenv(const_cast<PUTENV_TYPE_ARG>(leaker));
338 // If putenv does not make a copy of the char const * this
339 // is very dangerous. OTOH if it does take a copy this is the
341 int retval = putenv(const_cast<PUTENV_TYPE_ARG>(envstr.c_str()));
345 string str = envstr.split(varname,'=');
346 int retval = setenv(varname.c_str(), str.c_str(), true);
353 bool PutEnvPath(string const & envstr)
355 return PutEnv(envstr);
360 int DeleteAllFilesInDir (string const & path)
362 DIR * dir = opendir(path.c_str());
364 WriteFSAlert (_("Error! Cannot open directory:"), path);
368 while ((de = readdir(dir))) {
369 string temp = de->d_name;
370 if (temp == "." || temp == "..")
372 string unlinkpath = AddName (path, temp);
374 lyxerr.debug() << "Deleting file: " << unlinkpath << endl;
376 if (remove(unlinkpath.c_str()))
377 WriteFSAlert (_("Error! Could not remove file:"),
386 string CreateTmpDir (string const & tempdir, string const & mask)
388 string tmpfl = TmpFileName(tempdir, mask);
390 if ((tmpfl.empty()) || mkdir (tmpfl.c_str(), 0777)) {
391 WriteFSAlert(_("Error! Couldn't create temporary directory:"),
395 return MakeAbsPath(tmpfl);
400 int DestroyTmpDir (string const & tmpdir, bool Allfiles)
405 if (Allfiles && DeleteAllFilesInDir(tmpdir)) return -1;
406 if (rmdir(tmpdir.c_str())) {
407 WriteFSAlert(_("Error! Couldn't delete temporary directory:"),
415 string CreateBufferTmpDir (string const & pathfor)
417 return CreateTmpDir(pathfor, "lyx_bufrtmp");
421 int DestroyBufferTmpDir (string const & tmpdir)
423 return DestroyTmpDir(tmpdir, true);
427 string CreateLyXTmpDir (string const & deflt)
429 if ((!deflt.empty()) && (deflt != "/tmp")) {
430 if (mkdir(deflt.c_str(), 0777)) {
434 string t = CreateTmpDir (deflt.c_str(), "lyx_tmp");
442 string t = CreateTmpDir ("/tmp", "lyx_tmp");
448 int DestroyLyXTmpDir (string const & tmpdir)
450 return DestroyTmpDir (tmpdir, false); // Why false?
454 // Creates directory. Returns true if succesfull
455 bool createDirectory(string const & path, int permission)
457 string temp = strip(CleanupPath(path), '/');
460 WriteAlert(_("Internal error!"),
461 _("Call to createDirectory with invalid name"));
465 if (mkdir(temp.c_str(), permission)) {
466 WriteFSAlert (_("Error! Couldn't create directory:"), temp);
473 // Returns current working directory
476 int n = 256; // Assume path is less than 256 chars
478 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
489 if (err) result = tbuf;
495 // Strip filename from path name
496 string OnlyPath(string const & Filename)
498 // If empty filename, return empty
499 if (Filename.empty()) return Filename;
501 // Find last / or start of filename
502 string::size_type j = Filename.rfind('/');
503 if (j == string::npos)
505 return Filename.substr(0, j + 1);
509 // Convert relative path into absolute path based on a basepath.
510 // If relpath is absolute, just use that.
511 // If basepath is empty, use CWD as base.
512 string MakeAbsPath(string const & RelPath, string const & BasePath)
514 // checks for already absolute path
515 if (AbsolutePath(RelPath))
517 if(RelPath[0]!= '/' && RelPath[0]!= '\\')
521 // Copies given paths
522 string TempRel = CleanupPath(RelPath);
526 if (!BasePath.empty()) {
530 char * with_drive = new char[_MAX_PATH];
531 _abspath(with_drive, BasePath.c_str(), _MAX_PATH);
532 TempBase = with_drive;
538 if (AbsolutePath(TempRel))
539 return TempBase.substr(0, 2) + TempRel;
542 // Handle /./ at the end of the path
543 while(suffixIs(TempBase, "/./"))
544 TempBase.erase(TempBase.length() - 2);
546 // processes relative path
547 string RTemp = TempRel;
550 while (!RTemp.empty()) {
552 RTemp = split(RTemp, Temp, '/');
554 if (Temp == ".") continue;
556 // Remove one level of TempBase
557 int i = TempBase.length() - 2;
560 while (i > 0 && TempBase[i] != '/') --i;
564 while (i > 2 && TempBase[i] != '/') --i;
567 TempBase.erase(i, string::npos);
571 // Add this piece to TempBase
572 if (!suffixIs(TempBase, '/'))
578 // returns absolute path
583 // Correctly append filename to the pathname.
584 // If pathname is '.', then don't use pathname.
585 // Chops any path of filename.
586 string AddName(string const & path, string const & fname)
589 string basename = OnlyFilename(fname);
593 if (path != "." && path != "./" && !path.empty()) {
594 buf = CleanupPath(path);
595 if (!suffixIs(path, '/'))
599 return buf + basename;
603 // Strips path from filename
604 string OnlyFilename(string const & fname)
609 string::size_type j = fname.rfind('/');
610 if (j == string::npos) // no '/' in fname
614 return fname.substr(j + 1);
618 // Is a filename/path absolute?
619 bool AbsolutePath(string const & path)
622 return (!path.empty() && path[0] == '/');
624 return (!path.empty() && (path[0] == '/' || (isalpha(static_cast<unsigned char>(path[0])) && path.length()>1 && path[1] == ':')));
629 // Create absolute path. If impossible, don't do anything
630 // Supports ./ and ~/. Later we can add support for ~logname/. (Asger)
631 string ExpandPath(string const & path)
633 // checks for already absolute path
634 string RTemp = ReplaceEnvironmentPath(path);
635 if (AbsolutePath(RTemp))
642 RTemp= split(RTemp, Temp, '/');
645 return GetCWD() + '/' + RTemp;
646 } else if (Temp == "~") {
647 return GetEnvPath("HOME") + '/' + RTemp;
648 } else if (Temp == "..") {
649 return MakeAbsPath(copy);
651 // Don't know how to handle this
657 // Constracts path/../path
658 // Can't handle "../../" or "/../" (Asger)
659 string NormalizePath(string const & path)
665 if (AbsolutePath(path))
668 // Make implicit current directory explicit
671 while (!RTemp.empty()) {
673 RTemp = split(RTemp, Temp, '/');
677 } else if (Temp == "..") {
678 // Remove one level of TempBase
679 int i = TempBase.length() - 2;
680 while (i > 0 && TempBase[i] != '/')
682 if (i >= 0 && TempBase[i] == '/')
683 TempBase.erase(i + 1, string::npos);
687 TempBase += Temp + '/';
691 // returns absolute path
695 string CleanupPath(string const & path)
697 #ifdef __EMX__ /* SMiyata: This should fix searchpath bug. */
698 string temppath = subst(path, '\\', '/');
699 temppath = subst(temppath, "//", "/");
700 return lowercase(temppath);
701 #else // On unix, nothing to do
708 // Search ${...} as Variable-Name inside the string and replace it with
709 // the denoted environmentvariable
710 // Allow Variables according to
711 // variable := '$' '{' [A-Za-z_]{[A-Za-z_0-9]*} '}'
714 string ReplaceEnvironmentPath(string const & path)
717 // CompareChar: Environmentvariables starts with this character
718 // PathChar: Next path component start with this character
719 // while CompareChar found do:
720 // Split String with PathChar
721 // Search Environmentvariable
722 // if found: Replace Strings
724 char const CompareChar = '$';
725 char const FirstChar = '{';
726 char const EndChar = '}';
727 char const UnderscoreChar = '_';
728 string EndString; EndString += EndChar;
729 string FirstString; FirstString += FirstChar;
730 string CompareString; CompareString += CompareChar;
731 string const RegExp("*}*"); // Exist EndChar inside a String?
733 // first: Search for a '$' - Sign.
735 string result1; //(copy); // for split-calls
736 string result0 = split(path, result1, CompareChar);
737 while (!result0.empty()) {
738 string copy1(result0); // contains String after $
740 // Check, if there is an EndChar inside original String.
742 if (!regexMatch(copy1, RegExp)) {
743 // No EndChar inside. So we are finished
744 result1 += CompareString + result0;
750 string res0 = split(copy1, res1, EndChar);
751 // Now res1 holds the environmentvariable
752 // First, check, if Contents is ok.
753 if (res1.empty()) { // No environmentvariable. Continue Loop.
754 result1 += CompareString + FirstString;
758 // check contents of res1
759 char const * res1_contents = res1.c_str();
760 if (*res1_contents != FirstChar) {
761 // Again No Environmentvariable
762 result1 += CompareString;
766 // Check for variable names
767 // Situation ${} is detected as "No Environmentvariable"
768 char const * cp1 = res1_contents + 1;
769 bool result = isalpha(*cp1) || (*cp1 == UnderscoreChar);
771 while (*cp1 && result) {
772 result = isalnum(*cp1) ||
773 (*cp1 == UnderscoreChar);
778 // no correct variable name
779 result1 += CompareString + res1 + EndString;
780 result0 = split(res0, res1, CompareChar);
785 string env = GetEnv(res1_contents+1);
787 // Congratulations. Environmentvariable found
790 result1 += CompareString + res1 + EndString;
793 result0 = split(res0, res1, CompareChar);
797 } // ReplaceEnvironmentPath
800 // Make relative path out of two absolute paths
801 string MakeRelPath(string const & abspath0, string const & basepath0)
802 // Makes relative path out of absolute path. If it is deeper than basepath,
803 // it's easy. If basepath and abspath share something (they are all deeper
804 // than some directory), it'll be rendered using ..'s. If they are completely
805 // different, then the absolute path will be used as relative path.
807 // This is a hack. It should probaly be done in another way. Lgb.
808 string abspath = CleanupPath(abspath0);
809 string basepath = CleanupPath(basepath0);
811 return "<unknown_path>";
813 const int abslen = abspath.length();
814 const int baselen = basepath.length();
816 // Find first different character
818 while (i < abslen && i < baselen && abspath[i] == basepath[i]) ++i;
821 if (i < abslen && i < baselen
822 || (i<abslen && abspath[i] != '/' && i == baselen)
823 || (i<baselen && basepath[i] != '/' && i == abslen))
825 if (i) --i; // here was the last match
826 while (i && abspath[i] != '/') --i;
830 // actually no match - cannot make it relative
834 // Count how many dirs there are in basepath above match
835 // and append as many '..''s into relpath
838 while (j < baselen) {
839 if (basepath[j] == '/') {
840 if (j+1 == baselen) break;
846 // Append relative stuff from common directory to abspath
847 if (abspath[i] == '/') ++i;
848 for (; i < abslen; ++i)
851 if (suffixIs(buf, '/'))
852 buf.erase(buf.length() - 1);
853 // Substitute empty with .
860 // Append sub-directory(ies) to a path in an intelligent way
861 string AddPath(string const & path, string const & path_2)
864 string path2 = CleanupPath(path_2);
866 if (!path.empty() && path != "." && path != "./") {
867 buf = CleanupPath(path);
868 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);
916 // Creates a nice compact path for displaying
917 string MakeDisplayPath (string const & path, unsigned int threshold)
919 const int l1 = path.length();
921 // First, we try a relative path compared to home
922 string home = GetEnvPath("HOME");
923 string relhome = MakeRelPath(path, home);
925 unsigned int l2 = relhome.length();
929 // If we backup from home or don't have a relative path,
930 // this try is no good
931 if (prefixIs(relhome, "../") || AbsolutePath(relhome)) {
932 // relative path was no good, just use the original path
939 // Is the path too long?
940 if (l2 > threshold) {
946 while (relhome.length() > threshold)
947 relhome = split(relhome, temp, '/');
949 // Did we shortend everything away?
950 if (relhome.empty()) {
951 // Yes, filename in itself is too long.
952 // Pick the start and the end of the filename.
953 relhome = OnlyFilename(path);
954 string head = relhome.substr(0, threshold/2 - 3);
956 l2 = relhome.length();
958 relhome.substr(l2 - threshold/2 - 2, l2 - 1);
959 relhome = head + "..." + tail;
962 return prefix + relhome;
966 bool LyXReadLink(string const & File, string & Link)
968 char LinkBuffer[512];
969 // Should be PATH_MAX but that needs autconf support
970 int nRead = readlink(File.c_str(), LinkBuffer, sizeof(LinkBuffer)-1);
973 LinkBuffer[nRead] = 0;
979 typedef pair<int, string> cmdret;
980 static cmdret do_popen(string const & cmd)
982 // One question is if we should use popen or
983 // create our own popen based on fork, exec, pipe
984 // of course the best would be to have a
985 // pstream (process stream), with the
986 // variants ipstream, opstream
987 FILE * inf = popen(cmd.c_str(), "r");
991 ret += static_cast<char>(c);
994 int pret = pclose(inf);
995 return make_pair(pret, ret);
999 string findtexfile(string const & fil, string const & format)
1001 /* There is no problem to extend this function too use other
1002 methods to look for files. It could be setup to look
1003 in environment paths and also if wanted as a last resort
1004 to a recursive find. One of the easier extensions would
1005 perhaps be to use the LyX file lookup methods. But! I am
1006 going to implement this until I see some demand for it.
1010 // If fil is a file with absolute path we just return it
1011 if (AbsolutePath(fil)) return fil;
1013 // Check in the current dir.
1014 if (FileInfo(OnlyFilename(fil)).exist())
1015 return OnlyFilename(fil);
1017 // No we try to find it using kpsewhich.
1018 string kpsecmd = "kpsewhich --format= " + format + " " + OnlyFilename(fil);
1019 cmdret c = do_popen(kpsecmd);
1021 lyxerr[Debug::LATEX] << "kpse status = " << c.first << "\n"
1022 << "kpse result = `" << strip(c.second, '\n')
1024 return c.first != -1 ? strip(c.second, '\n') : string();