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
26 #pragma implementation "filetools.h"
29 #include "filetools.h"
30 #include "LSubstring.h"
31 #include "lyx_gui_misc.h"
33 #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 return '\'' + name + '\'';
109 /// Returns an unique name to be used as a temporary file.
110 string TmpFileName(string const & dir, string const & mask)
111 {// With all these temporary variables, it should be safe enough :-) (JMarc)
114 tmpdir = system_tempdir;
117 string tmpfl = AddName(tmpdir, mask);
119 // find a uniq postfix for the filename...
120 // using the pid, and...
121 tmpfl += tostr(getpid());
125 for (int a = 'a'; a <= 'z'; ++a)
126 for (int b = 'a'; b <= 'z'; ++b)
127 for (int c = 'a'; c <= 'z'; ++c) {
128 // if this is not enough I have no idea what
130 ret = tmpfl + char(a) + char(b) + char(c);
131 // check if the file exist
132 if (!fnfo.newFile(ret).exist())
135 lyxerr << "Not able to find a uniq tmpfile name." << endl;
140 // Is a file readable ?
141 bool IsFileReadable (string const & path)
144 if (file.isOK() && file.isRegular() && file.readable())
151 // Is a file read_only?
152 // return 1 read-write
154 // -1 error (doesn't exist, no access, anything else)
155 int IsFileWriteable (string const & path)
158 if (fi.access(FileInfo::wperm|FileInfo::rperm)) // read-write
160 if (fi.readable()) // read-only
162 return -1; // everything else.
166 //returns 1: dir writeable
168 // -1: error- couldn't find out
169 int IsDirWriteable (string const & path)
171 string tmpfl = TmpFileName(path);
174 WriteFSAlert(_("LyX Internal Error!"),
175 _("Could not test if directory is writeable"));
179 if (fi.writable()) return 1;
185 // Uses a string of paths separated by ";"s to find a file to open.
186 // Can't cope with pathnames with a ';' in them. Returns full path to file.
187 // If path entry begins with $$LyX/, use system_lyxdir
188 // If path entry begins with $$User/, use user_lyxdir
189 // Example: "$$User/doc;$$LyX/doc"
190 string FileOpenSearch (string const & path, string const & name,
193 string real_file, path_element;
194 bool notfound = true;
195 string tmppath = split(path, path_element, ';');
197 while (notfound && !path_element.empty()) {
198 path_element = CleanupPath(path_element);
199 if (!suffixIs(path_element, '/'))
201 path_element = subst(path_element, "$$LyX", system_lyxdir);
202 path_element = subst(path_element, "$$User", user_lyxdir);
204 real_file = FileSearch(path_element, name, ext);
206 if (real_file.empty()) {
208 tmppath = split(tmppath, path_element, ';');
209 } while(!tmppath.empty() && path_element.empty());
215 if (ext.empty() && notfound) {
216 real_file = FileOpenSearch(path, name, "exe");
217 if (notfound) real_file = FileOpenSearch(path, name, "cmd");
224 // Returns the real name of file name in directory path, with optional
226 string FileSearch(string const & path, string const & name,
229 // if `name' is an absolute path, we ignore the setting of `path'
230 // Expand Environmentvariables in 'name'
231 string tmpname = ReplaceEnvironmentPath(name);
232 string fullname = MakeAbsPath(tmpname, path);
234 // search first without extension, then with it.
235 if (IsFileReadable(fullname))
237 else if (ext.empty())
239 else { // Is it not more reasonable to use ChangeExtension()? (SMiyata)
242 if (IsFileReadable(fullname))
250 // Search the file name.ext in the subdirectory dir of
252 // 2) build_lyxdir (if not empty)
254 string LibFileSearch(string const & dir, string const & name,
257 string fullname = FileSearch(AddPath(user_lyxdir, dir),
259 if (!fullname.empty())
262 if (!build_lyxdir.empty())
263 fullname = FileSearch(AddPath(build_lyxdir, dir),
265 if (!fullname.empty())
268 return FileSearch(AddPath(system_lyxdir, dir), name, ext);
272 string i18nLibFileSearch(string const & dir, string const & name,
275 string lang = token(string(GetEnv("LANG")), '_', 0);
277 if (lang.empty() || lang == "C")
278 return LibFileSearch(dir, name, ext);
280 string tmp = LibFileSearch(dir, lang + '_' + name,
285 return LibFileSearch(dir, name, ext);
290 string GetEnv(string const & envname)
292 // f.ex. what about error checking?
293 char const * const ch = getenv(envname.c_str());
294 string envstr = !ch ? "" : ch;
299 string GetEnvPath(string const & name)
302 string pathlist = subst(GetEnv(name), ':', ';');
304 string pathlist = subst(GetEnv(name), '\\', '/');
306 return strip(pathlist, ';');
310 bool PutEnv(string const & envstr)
313 #warning Look at and fix this.
315 // f.ex. what about error checking?
317 // this leaks, but what can we do about it?
318 // Is doing a getenv() and a free() of the older value
319 // a good idea? (JMarc)
320 // Actually we don't have to leak...calling putenv like this
321 // should be enough: ... and this is obviously not enough if putenv
322 // does not make a copy of the string. It is also not very wise to
323 // put a string on the free store. If we have to leak we should do it
326 char * leaker = new char[envstr.length() + 1];
327 envstr.copy(leaker, envstr.length());
328 leaker[envstr.length()] = '\0';
329 int retval = putenv(const_cast<PUTENV_TYPE_ARG>(leaker));
332 // If putenv does not make a copy of the char const * this
333 // is very dangerous. OTOH if it does take a copy this is the
335 int retval = putenv(const_cast<PUTENV_TYPE_ARG>(envstr.c_str()));
339 string str = envstr.split(varname,'=');
340 int retval = setenv(varname.c_str(), str.c_str(), true);
347 bool PutEnvPath(string const & envstr)
349 return PutEnv(envstr);
354 int DeleteAllFilesInDir (string const & path)
356 DIR * dir = opendir(path.c_str());
358 WriteFSAlert (_("Error! Cannot open directory:"), path);
362 while ((de = readdir(dir))) {
363 string temp = de->d_name;
364 if (temp == "." || temp == "..")
366 string unlinkpath = AddName (path, temp);
368 lyxerr.debug() << "Deleting file: " << unlinkpath << endl;
370 if (remove(unlinkpath.c_str()))
371 WriteFSAlert (_("Error! Could not remove file:"),
380 string CreateTmpDir (string const & tempdir, string const & mask)
382 string tmpfl = TmpFileName(tempdir, mask);
384 if ((tmpfl.empty()) || mkdir (tmpfl.c_str(), 0777)) {
385 WriteFSAlert(_("Error! Couldn't create temporary directory:"),
389 return MakeAbsPath(tmpfl);
394 int DestroyTmpDir (string const & tmpdir, bool Allfiles)
399 if (Allfiles && DeleteAllFilesInDir(tmpdir)) return -1;
400 if (rmdir(tmpdir.c_str())) {
401 WriteFSAlert(_("Error! Couldn't delete temporary directory:"),
409 string CreateBufferTmpDir (string const & pathfor)
411 return CreateTmpDir(pathfor, "lyx_bufrtmp");
415 int DestroyBufferTmpDir (string const & tmpdir)
417 return DestroyTmpDir(tmpdir, true);
421 string CreateLyXTmpDir (string const & deflt)
423 if ((!deflt.empty()) && (deflt != "/tmp")) {
424 if (mkdir(deflt.c_str(), 0777)) {
428 string t = CreateTmpDir (deflt.c_str(), "lyx_tmp");
436 string t = CreateTmpDir ("/tmp", "lyx_tmp");
442 int DestroyLyXTmpDir (string const & tmpdir)
444 return DestroyTmpDir (tmpdir, false); // Why false?
448 // Creates directory. Returns true if succesfull
449 bool createDirectory(string const & path, int permission)
451 string temp = strip(CleanupPath(path), '/');
454 WriteAlert(_("Internal error!"),
455 _("Call to createDirectory with invalid name"));
459 if (mkdir(temp.c_str(), permission)) {
460 WriteFSAlert (_("Error! Couldn't create directory:"), temp);
467 // Returns current working directory
470 int n = 256; // Assume path is less than 256 chars
472 char * tbuf = new char[n];
474 // Safe. Hopefully all getcwds behave this way!
475 while (((err = lyx::getcwd (tbuf, n)) == 0) && (errno == ERANGE)) {
476 // Buffer too small, double the buffersize and try again
483 if (err) result = tbuf;
489 // Strip filename from path name
490 string OnlyPath(string const & Filename)
492 // If empty filename, return empty
493 if (Filename.empty()) return Filename;
495 // Find last / or start of filename
496 string::size_type j = Filename.rfind('/');
497 if (j == string::npos)
499 return Filename.substr(0, j + 1);
503 // Convert relative path into absolute path based on a basepath.
504 // If relpath is absolute, just use that.
505 // If basepath is empty, use CWD as base.
506 string MakeAbsPath(string const & RelPath, string const & BasePath)
508 // checks for already absolute path
509 if (AbsolutePath(RelPath))
511 if(RelPath[0]!= '/' && RelPath[0]!= '\\')
515 // Copies given paths
516 string TempRel = CleanupPath(RelPath);
520 if (!BasePath.empty()) {
524 char * with_drive = new char[_MAX_PATH];
525 _abspath(with_drive, BasePath.c_str(), _MAX_PATH);
526 TempBase = with_drive;
532 if (AbsolutePath(TempRel))
533 return TempBase.substr(0, 2) + TempRel;
536 // Handle /./ at the end of the path
537 while(suffixIs(TempBase, "/./"))
538 TempBase.erase(TempBase.length() - 2);
540 // processes relative path
541 string RTemp = TempRel;
544 while (!RTemp.empty()) {
546 RTemp = split(RTemp, Temp, '/');
548 if (Temp == ".") continue;
550 // Remove one level of TempBase
551 int i = TempBase.length() - 2;
554 while (i > 0 && TempBase[i] != '/') --i;
558 while (i > 2 && TempBase[i] != '/') --i;
561 TempBase.erase(i, string::npos);
565 // Add this piece to TempBase
566 if (!suffixIs(TempBase, '/'))
572 // returns absolute path
577 // Correctly append filename to the pathname.
578 // If pathname is '.', then don't use pathname.
579 // Chops any path of filename.
580 string AddName(string const & path, string const & fname)
583 string basename = OnlyFilename(fname);
587 if (path != "." && path != "./" && !path.empty()) {
588 buf = CleanupPath(path);
589 if (!suffixIs(path, '/'))
593 return buf + basename;
597 // Strips path from filename
598 string OnlyFilename(string const & fname)
603 string::size_type j = fname.rfind('/');
604 if (j == string::npos) // no '/' in fname
608 return fname.substr(j + 1);
612 // Is a filename/path absolute?
613 bool AbsolutePath(string const & path)
616 return (!path.empty() && path[0] == '/');
618 return (!path.empty() && (path[0] == '/' || (isalpha(static_cast<unsigned char>(path[0])) && path.length()>1 && path[1] == ':')));
623 // Create absolute path. If impossible, don't do anything
624 // Supports ./ and ~/. Later we can add support for ~logname/. (Asger)
625 string ExpandPath(string const & path)
627 // checks for already absolute path
628 string RTemp = ReplaceEnvironmentPath(path);
629 if (AbsolutePath(RTemp))
636 RTemp= split(RTemp, Temp, '/');
639 return GetCWD() + '/' + RTemp;
640 } else if (Temp == "~") {
641 return GetEnvPath("HOME") + '/' + RTemp;
642 } else if (Temp == "..") {
643 return MakeAbsPath(copy);
645 // Don't know how to handle this
651 // Constracts path/../path
652 // Can't handle "../../" or "/../" (Asger)
653 string NormalizePath(string const & path)
659 if (AbsolutePath(path))
662 // Make implicit current directory explicit
665 while (!RTemp.empty()) {
667 RTemp = split(RTemp, Temp, '/');
671 } else if (Temp == "..") {
672 // Remove one level of TempBase
673 int i = TempBase.length() - 2;
674 while (i > 0 && TempBase[i] != '/')
676 if (i >= 0 && TempBase[i] == '/')
677 TempBase.erase(i + 1, string::npos);
681 TempBase += Temp + '/';
685 // returns absolute path
689 string CleanupPath(string const & path)
691 #ifdef __EMX__ /* SMiyata: This should fix searchpath bug. */
692 string temppath = subst(path, '\\', '/');
693 temppath = subst(temppath, "//", "/");
694 return lowercase(temppath);
695 #else // On unix, nothing to do
702 // Search ${...} as Variable-Name inside the string and replace it with
703 // the denoted environmentvariable
704 // Allow Variables according to
705 // variable := '$' '{' [A-Za-z_]{[A-Za-z_0-9]*} '}'
708 string ReplaceEnvironmentPath(string const & path)
711 // CompareChar: Environmentvariables starts with this character
712 // PathChar: Next path component start with this character
713 // while CompareChar found do:
714 // Split String with PathChar
715 // Search Environmentvariable
716 // if found: Replace Strings
718 char const CompareChar = '$';
719 char const FirstChar = '{';
720 char const EndChar = '}';
721 char const UnderscoreChar = '_';
722 string EndString; EndString += EndChar;
723 string FirstString; FirstString += FirstChar;
724 string CompareString; CompareString += CompareChar;
725 string const RegExp("*}*"); // Exist EndChar inside a String?
727 // first: Search for a '$' - Sign.
729 string result1; //(copy); // for split-calls
730 string result0 = split(path, result1, CompareChar);
731 while (!result0.empty()) {
732 string copy1(result0); // contains String after $
734 // Check, if there is an EndChar inside original String.
736 if (!regexMatch(copy1, RegExp)) {
737 // No EndChar inside. So we are finished
738 result1 += CompareString + result0;
744 string res0 = split(copy1, res1, EndChar);
745 // Now res1 holds the environmentvariable
746 // First, check, if Contents is ok.
747 if (res1.empty()) { // No environmentvariable. Continue Loop.
748 result1 += CompareString + FirstString;
752 // check contents of res1
753 char const * res1_contents = res1.c_str();
754 if (*res1_contents != FirstChar) {
755 // Again No Environmentvariable
756 result1 += CompareString;
760 // Check for variable names
761 // Situation ${} is detected as "No Environmentvariable"
762 char const * cp1 = res1_contents + 1;
763 bool result = isalpha(*cp1) || (*cp1 == UnderscoreChar);
765 while (*cp1 && result) {
766 result = isalnum(*cp1) ||
767 (*cp1 == UnderscoreChar);
772 // no correct variable name
773 result1 += CompareString + res1 + EndString;
774 result0 = split(res0, res1, CompareChar);
779 string env = GetEnv(res1_contents+1);
781 // Congratulations. Environmentvariable found
784 result1 += CompareString + res1 + EndString;
787 result0 = split(res0, res1, CompareChar);
791 } // ReplaceEnvironmentPath
794 // Make relative path out of two absolute paths
795 string MakeRelPath(string const & abspath0, string const & basepath0)
796 // Makes relative path out of absolute path. If it is deeper than basepath,
797 // it's easy. If basepath and abspath share something (they are all deeper
798 // than some directory), it'll be rendered using ..'s. If they are completely
799 // different, then the absolute path will be used as relative path.
801 // This is a hack. It should probaly be done in another way. Lgb.
802 string abspath = CleanupPath(abspath0);
803 string basepath = CleanupPath(basepath0);
805 return "<unknown_path>";
807 const int abslen = abspath.length();
808 const int baselen = basepath.length();
810 // Find first different character
812 while (i < abslen && i < baselen && abspath[i] == basepath[i]) ++i;
815 if (i < abslen && i < baselen
816 || (i<abslen && abspath[i] != '/' && i == baselen)
817 || (i<baselen && basepath[i] != '/' && i == abslen))
819 if (i) --i; // here was the last match
820 while (i && abspath[i] != '/') --i;
824 // actually no match - cannot make it relative
828 // Count how many dirs there are in basepath above match
829 // and append as many '..''s into relpath
832 while (j < baselen) {
833 if (basepath[j] == '/') {
834 if (j + 1 == baselen) break;
840 // Append relative stuff from common directory to abspath
841 if (abspath[i] == '/') ++i;
842 for (; i < abslen; ++i)
845 if (suffixIs(buf, '/'))
846 buf.erase(buf.length() - 1);
847 // Substitute empty with .
854 // Append sub-directory(ies) to a path in an intelligent way
855 string AddPath(string const & path, string const & path_2)
858 string path2 = CleanupPath(path_2);
860 if (!path.empty() && path != "." && path != "./") {
861 buf = CleanupPath(path);
862 if (path[path.length() - 1] != '/')
867 int p2start = path2.find_first_not_of('/');
869 int p2end = path2.find_last_not_of('/');
871 string tmp = path2.substr(p2start, p2end - p2start + 1);
879 Change extension of oldname to extension.
880 Strips path off if no_path == true.
881 If no extension on oldname, just appends.
883 string ChangeExtension(string const & oldname, string const & extension,
886 string::size_type last_slash = oldname.rfind('/');
887 string::size_type last_dot;
888 if (last_slash != string::npos)
889 last_dot = oldname.find('.', last_slash);
891 last_dot = oldname.rfind('.');
894 // Make sure the extension starts with a dot
895 if (!extension.empty() && extension[0] != '.')
896 ext= '.' + extension;
900 if (no_path && last_slash != string::npos) {
901 ++last_slash; // step it
902 ret_str = oldname.substr(last_slash,
903 last_dot - last_slash) + ext;
905 ret_str = oldname.substr(0, last_dot) + ext;
906 return CleanupPath(ret_str);
910 // Creates a nice compact path for displaying
911 string MakeDisplayPath (string const & path, unsigned int threshold)
913 const int l1 = path.length();
915 // First, we try a relative path compared to home
916 string home = GetEnvPath("HOME");
917 string relhome = MakeRelPath(path, home);
919 unsigned int l2 = relhome.length();
923 // If we backup from home or don't have a relative path,
924 // this try is no good
925 if (prefixIs(relhome, "../") || AbsolutePath(relhome)) {
926 // relative path was no good, just use the original path
933 // Is the path too long?
934 if (l2 > threshold) {
940 while (relhome.length() > threshold)
941 relhome = split(relhome, temp, '/');
943 // Did we shortend everything away?
944 if (relhome.empty()) {
945 // Yes, filename in itself is too long.
946 // Pick the start and the end of the filename.
947 relhome = OnlyFilename(path);
948 string head = relhome.substr(0, threshold/2 - 3);
950 l2 = relhome.length();
952 relhome.substr(l2 - threshold/2 - 2, l2 - 1);
953 relhome = head + "..." + tail;
956 return prefix + relhome;
960 bool LyXReadLink(string const & File, string & Link)
962 char LinkBuffer[512];
963 // Should be PATH_MAX but that needs autconf support
964 int nRead = readlink(File.c_str(), LinkBuffer, sizeof(LinkBuffer)-1);
967 LinkBuffer[nRead] = 0;
973 typedef pair<int, string> cmdret;
974 static cmdret do_popen(string const & cmd)
976 // One question is if we should use popen or
977 // create our own popen based on fork, exec, pipe
978 // of course the best would be to have a
979 // pstream (process stream), with the
980 // variants ipstream, opstream
981 FILE * inf = popen(cmd.c_str(), "r");
985 ret += static_cast<char>(c);
988 int pret = pclose(inf);
989 return make_pair(pret, ret);
993 string findtexfile(string const & fil, string const & format)
995 /* There is no problem to extend this function too use other
996 methods to look for files. It could be setup to look
997 in environment paths and also if wanted as a last resort
998 to a recursive find. One of the easier extensions would
999 perhaps be to use the LyX file lookup methods. But! I am
1000 going to implement this until I see some demand for it.
1004 // If fil is a file with absolute path we just return it
1005 if (AbsolutePath(fil)) return fil;
1007 // Check in the current dir.
1008 if (FileInfo(OnlyFilename(fil)).exist())
1009 return OnlyFilename(fil);
1011 // No we try to find it using kpsewhich.
1012 string kpsecmd = "kpsewhich --format=" + format + " " + OnlyFilename(fil);
1014 cmdret c = do_popen(kpsecmd);
1016 lyxerr[Debug::LATEX] << "kpse status = " << c.first << "\n"
1017 << "kpse result = `" << strip(c.second, '\n')
1019 return c.first != -1 ? strip(c.second, '\n') : string();