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 "lyx_gui_misc.h"
31 #include "support/path.h" // I know it's OS/2 specific (SMiyata)
36 // Which part of this is still necessary? (JMarc).
39 # define NAMLEN(dirent) strlen((dirent)->d_name)
41 # define dirent direct
42 # define NAMLEN(dirent) (dirent)->d_namlen
44 # 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 // checks for already absolute path
620 string RTemp = ReplaceEnvironmentPath(path);
621 if (AbsolutePath(RTemp))
628 RTemp= split(RTemp, Temp, '/');
631 return GetCWD() + '/' + RTemp;
632 } else if (Temp == "~") {
633 return GetEnvPath("HOME") + '/' + RTemp;
634 } else if (Temp == "..") {
635 return MakeAbsPath(copy);
637 // Don't know how to handle this
643 // Constracts path/../path
644 // Can't handle "../../" or "/../" (Asger)
645 string NormalizePath(string const & path)
651 if (AbsolutePath(path))
654 // Make implicit current directory explicit
657 while (!RTemp.empty()) {
659 RTemp = split(RTemp, Temp, '/');
663 } else if (Temp == "..") {
664 // Remove one level of TempBase
665 int i = TempBase.length()-2;
666 while (i>0 && TempBase[i] != '/')
668 if (i>= 0 && TempBase[i] == '/')
669 TempBase.erase(i+1, string::npos);
673 TempBase += Temp + '/';
677 // returns absolute path
681 string CleanupPath(string const & path)
683 #ifdef __EMX__ /* SMiyata: This should fix searchpath bug. */
684 string temppath = subst(path, '\\', '/');
685 temppath = subst(temppath, "//", "/");
686 return lowercase(temppath);
687 #else // On unix, nothing to do
694 // Search ${...} as Variable-Name inside the string and replace it with
695 // the denoted environmentvariable
696 // Allow Variables according to
697 // variable := '$' '{' [A-Za-z_]{[A-Za-z_0-9]*} '}'
700 string ReplaceEnvironmentPath(string const & path)
703 // CompareChar: Environmentvariables starts with this character
704 // PathChar: Next path component start with this character
705 // while CompareChar found do:
706 // Split String with PathChar
707 // Search Environmentvariable
708 // if found: Replace Strings
710 char const CompareChar = '$';
711 char const FirstChar = '{';
712 char const EndChar = '}';
713 char const UnderscoreChar = '_';
714 string EndString; EndString += EndChar;
715 string FirstString; FirstString += FirstChar;
716 string CompareString; CompareString += CompareChar;
717 string const RegExp("*}*"); // Exist EndChar inside a String?
719 // first: Search for a '$' - Sign.
721 string result1; //(copy); // for split-calls
722 string result0 = split(path, result1, CompareChar);
723 while (!result0.empty()) {
724 string copy1(result0); // contains String after $
726 // Check, if there is an EndChar inside original String.
728 if (!regexMatch(copy1, RegExp)) {
729 // No EndChar inside. So we are finished
730 result1 += CompareString + result0;
736 string res0 = split(copy1, res1, EndChar);
737 // Now res1 holds the environmentvariable
738 // First, check, if Contents is ok.
739 if (res1.empty()) { // No environmentvariable. Continue Loop.
740 result1 += CompareString + FirstString;
744 // check contents of res1
745 char const * res1_contents = res1.c_str();
746 if (*res1_contents != FirstChar) {
747 // Again No Environmentvariable
748 result1 += CompareString;
752 // Check for variable names
753 // Situation ${} is detected as "No Environmentvariable"
754 char const * cp1 = res1_contents+1;
755 bool result = isalpha(*cp1) || (*cp1 == UnderscoreChar);
757 while (*cp1 && result) {
758 result = isalnum(*cp1) ||
759 (*cp1 == UnderscoreChar);
764 // no correct variable name
765 result1 += CompareString + res1 + EndString;
766 result0 = split(res0, res1, CompareChar);
771 string env = GetEnv(res1_contents+1);
773 // Congratulations. Environmentvariable found
776 result1 += CompareString + res1 + EndString;
779 result0 = split(res0, res1, CompareChar);
783 } // ReplaceEnvironmentPath
786 // Make relative path out of two absolute paths
787 string MakeRelPath(string const & abspath0, string const & basepath0)
788 // Makes relative path out of absolute path. If it is deeper than basepath,
789 // it's easy. If basepath and abspath share something (they are all deeper
790 // than some directory), it'll be rendered using ..'s. If they are completely
791 // different, then the absolute path will be used as relative path.
793 // This is a hack. It should probaly be done in another way. Lgb.
794 string abspath = CleanupPath(abspath0);
795 string basepath = CleanupPath(basepath0);
797 return "<unknown_path>";
799 const int abslen = abspath.length();
800 const int baselen = basepath.length();
802 // Find first different character
804 while (i < abslen && i < baselen && abspath[i] == basepath[i]) ++i;
807 if (i < abslen && i < baselen
808 || (i<abslen && abspath[i] != '/' && i == baselen)
809 || (i<baselen && basepath[i] != '/' && i == abslen))
811 if (i) --i; // here was the last match
812 while (i && abspath[i] != '/') --i;
816 // actually no match - cannot make it relative
820 // Count how many dirs there are in basepath above match
821 // and append as many '..''s into relpath
824 while (j < baselen) {
825 if (basepath[j] == '/') {
826 if (j+1 == baselen) break;
832 // Append relative stuff from common directory to abspath
833 if (abspath[i] == '/') ++i;
834 for (; i<abslen; ++i)
837 if (suffixIs(buf, '/'))
838 buf.erase(buf.length() - 1);
839 // Substitute empty with .
846 // Append sub-directory(ies) to a path in an intelligent way
847 string AddPath(string const & path, string const & path_2)
850 string path2 = CleanupPath(path_2);
852 if (!path.empty() && path != "." && path != "./") {
853 buf = CleanupPath(path);
854 if (path[path.length() - 1] != '/')
860 int p2start = path2.find_first_not_of('/');
862 int p2end = path2.find_last_not_of('/');
864 string tmp = path2.substr(p2start, p2end - p2start + 1);
872 Change extension of oldname to extension.
873 Strips path off if no_path == true.
874 If no extension on oldname, just appends.
876 string ChangeExtension(string const & oldname, string const & extension,
879 string::size_type last_slash = oldname.rfind('/');
880 string::size_type last_dot;
881 if (last_slash != string::npos)
882 last_dot = oldname.find('.', last_slash);
884 last_dot = oldname.rfind('.');
887 // Make sure the extension starts with a dot
888 if (!extension.empty() && extension[0] != '.')
889 ext= '.' + extension;
893 if (no_path && last_slash != string::npos) {
894 ++last_slash; // step it
895 ret_str = oldname.substr(last_slash,
896 last_dot - last_slash) + ext;
898 ret_str = oldname.substr(0, last_dot) + ext;
899 return CleanupPath(ret_str);
904 // Creates a nice compact path for displaying
905 string MakeDisplayPath (string const & path, unsigned int threshold)
907 const int l1 = path.length();
909 // First, we try a relative path compared to home
910 string home = GetEnvPath("HOME");
911 string relhome = MakeRelPath(path, home);
913 unsigned int l2 = relhome.length();
917 // If we backup from home or don't have a relative path,
918 // this try is no good
919 if (prefixIs(relhome, "../") || AbsolutePath(relhome)) {
920 // relative path was no good, just use the original path
927 // Is the path too long?
928 if (l2 > threshold) {
934 while (relhome.length() > threshold)
935 relhome = split(relhome, temp, '/');
937 // Did we shortend everything away?
938 if (relhome.empty()) {
939 // Yes, filename in itself is too long.
940 // Pick the start and the end of the filename.
941 relhome = OnlyFilename(path);
942 string head = relhome.substr(0, threshold/2 - 3);
944 l2 = relhome.length();
946 relhome.substr(l2 - threshold/2 - 2, l2 - 1);
947 relhome = head + "..." + tail;
950 return prefix + relhome;
954 bool LyXReadLink(string const & File, string & Link)
956 char LinkBuffer[512];
957 // Should be PATH_MAX but that needs autconf support
959 nRead = readlink(File.c_str(), LinkBuffer, sizeof(LinkBuffer)-1);
962 LinkBuffer[nRead] = 0;
968 typedef pair<int, string> cmdret;
969 static cmdret do_popen(string const & cmd)
971 // One question is if we should use popen or
972 // create our own popen based on fork, exec, pipe
973 // of course the best would be to have a
974 // pstream (process stream), with the
975 // variants ipstream, opstream and
976 FILE * inf = popen(cmd.c_str(), "r");
980 ret += static_cast<char>(c);
983 int pret = pclose(inf);
984 return make_pair(pret, ret);
988 string findtexfile(string const & fil, string const & format)
990 /* There is no problem to extend this function too use other
991 methods to look for files. It could be setup to look
992 in environment paths and also if wanted as a last resort
993 to a recursive find. One of the easier extensions would
994 perhaps be to use the LyX file lookup methods. But! I am
995 going to implement this until I see some demand for it.
999 // If fil is a file with absolute path we just return it
1000 if (AbsolutePath(fil)) return fil;
1002 // Check in the current dir.
1003 if (FileInfo(OnlyFilename(fil)).exist())
1004 return OnlyFilename(fil);
1006 // No we try to find it using kpsewhich.
1007 string kpsecmd = "kpsewhich --format= " + format + " " + OnlyFilename(fil);
1008 cmdret c = do_popen(kpsecmd);
1010 lyxerr << "kpse status = " << c.first << "\n"
1011 << "kpse result = `" << strip(c.second, '\n') << "'" << endl;
1012 return c.first != -1 ? strip(c.second, '\n') : string();