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)
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 MakeLatexName(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);
94 // Substitutes spaces with underscores in filename (and path)
95 string QuoteName(string const & name)
98 #warning Add proper emx support here!
101 while (qname.find("'") != string::npos)
102 LSubstring(qname, "'") = "\\'";
103 return '\'' + qname + '\'';
107 /// Returns an unique name to be used as a temporary file.
108 string TmpFileName(string const & dir, string const & mask)
109 {// With all these temporary variables, it should be safe enough :-) (JMarc)
112 tmpdir = system_tempdir;
115 string tmpfl = AddName(tmpdir, mask);
117 // find a uniq postfix for the filename...
118 // using the pid, and...
119 tmpfl += tostr(getpid());
123 for (int a = 'a'; a <= 'z'; ++a)
124 for (int b = 'a'; b <= 'z'; ++b)
125 for (int c = 'a'; c <= 'z'; ++c) {
126 // if this is not enough I have no idea what
128 ret = tmpfl + char(a) + char(b) + char(c);
129 // check if the file exist
130 if (!fnfo.newFile(ret).exist())
133 lyxerr << "Not able to find a uniq tmpfile name." << endl;
138 // Is a file readable ?
139 bool IsFileReadable (string const & path)
142 if (file.isOK() && file.isRegular() && file.readable())
149 // Is a file read_only?
150 // return 1 read-write
152 // -1 error (doesn't exist, no access, anything else)
153 int IsFileWriteable (string const & path)
155 FilePtr fp(path, FilePtr::update);
157 if ((errno == EACCES) || (errno == EROFS)) {
158 fp.reopen(path, FilePtr::read);
169 //returns 1: dir writeable
171 // -1: error- couldn't find out
172 int IsDirWriteable (string const & path)
174 string tmpfl = TmpFileName(path);
177 WriteFSAlert(_("LyX Internal Error!"),
178 _("Could not test if directory is writeable"));
181 FilePtr fp(tmpfl, FilePtr::truncate);
183 if (errno == EACCES) {
186 WriteFSAlert(_("LyX Internal Error!"),
187 _("Cannot open directory test file"));
192 if (remove(tmpfl.c_str())) {
193 WriteFSAlert(_("LyX Internal Error!"),
194 _("Created test file but cannot remove it?"));
201 // Uses a string of paths separated by ";"s to find a file to open.
202 // Can't cope with pathnames with a ';' in them. Returns full path to file.
203 // If path entry begins with $$LyX/, use system_lyxdir
204 // If path entry begins with $$User/, use user_lyxdir
205 // Example: "$$User/doc;$$LyX/doc"
206 string FileOpenSearch (string const & path, string const & name,
209 string real_file, path_element;
210 bool notfound = true;
211 string tmppath = split(path, path_element, ';');
213 while (notfound && !path_element.empty()) {
214 path_element = CleanupPath(path_element);
215 if (!suffixIs(path_element, '/'))
217 path_element = subst(path_element, "$$LyX", system_lyxdir);
218 path_element = subst(path_element, "$$User", user_lyxdir);
220 real_file = FileSearch(path_element, name, ext);
222 if (real_file.empty()) {
224 tmppath = split(tmppath, path_element, ';');
225 } while(!tmppath.empty() && path_element.empty());
231 if (ext.empty() && notfound) {
232 real_file = FileOpenSearch(path, name, "exe");
233 if (notfound) real_file = FileOpenSearch(path, name, "cmd");
240 // Returns the real name of file name in directory path, with optional
242 string FileSearch(string const & path, string const & name,
245 // if `name' is an absolute path, we ignore the setting of `path'
246 // Expand Environmentvariables in 'name'
247 string tmpname = ReplaceEnvironmentPath(name);
248 string fullname = MakeAbsPath(tmpname, path);
250 // search first without extension, then with it.
251 if (IsFileReadable(fullname))
253 else if (ext.empty())
255 else { // Is it not more reasonable to use ChangeExtension()? (SMiyata)
258 if (IsFileReadable(fullname))
266 // Search the file name.ext in the subdirectory dir of
268 // 2) build_lyxdir (if not empty)
270 string LibFileSearch(string const & dir, string const & name,
273 string fullname = FileSearch(AddPath(user_lyxdir, dir),
275 if (!fullname.empty())
278 if (!build_lyxdir.empty())
279 fullname = FileSearch(AddPath(build_lyxdir, dir),
281 if (!fullname.empty())
284 return FileSearch(AddPath(system_lyxdir, dir), name, ext);
288 string i18nLibFileSearch(string const & dir, string const & name,
291 string lang = token(string(GetEnv("LANG")), '_', 0);
293 if (lang.empty() || lang == "C")
294 return LibFileSearch(dir, name, ext);
296 string tmp = LibFileSearch(dir, lang + '_' + name,
301 return LibFileSearch(dir, name, ext);
306 string GetEnv(string const & envname)
308 // f.ex. what about error checking?
309 char const * const ch = getenv(envname.c_str());
310 string envstr = !ch ? "" : ch;
315 string GetEnvPath(string const & name)
318 string pathlist = subst(GetEnv(name), ':', ';');
320 string pathlist = subst(GetEnv(name), '\\', '/');
322 return strip(pathlist, ';');
326 bool PutEnv(string const & envstr)
329 #warning Look at and fix this.
331 // f.ex. what about error checking?
333 // this leaks, but what can we do about it?
334 // Is doing a getenv() and a free() of the older value
335 // a good idea? (JMarc)
336 int retval = putenv((new string(envstr))->c_str());
340 string str = envstr.split(varname,'=');
341 int retval = setenv(varname.c_str(), str.c_str(), true);
348 bool PutEnvPath(string const & envstr)
350 return PutEnv(envstr);
355 int DeleteAllFilesInDir (string const & path)
357 DIR * dir = opendir(path.c_str());
359 WriteFSAlert (_("Error! Cannot open directory:"), path);
363 while ((de = readdir(dir))) {
364 string temp = de->d_name;
365 if (temp == "." || temp == "..")
367 string unlinkpath = AddName (path, temp);
369 lyxerr.debug() << "Deleting file: " << unlinkpath << endl;
371 if (remove(unlinkpath.c_str()))
372 WriteFSAlert (_("Error! Could not remove file:"),
381 string CreateTmpDir (string const & tempdir, string const & mask)
383 string tmpfl = TmpFileName(tempdir, mask);
385 if ((tmpfl.empty()) || mkdir (tmpfl.c_str(), 0777)) {
386 WriteFSAlert(_("Error! Couldn't create temporary directory:"),
390 return MakeAbsPath(tmpfl);
395 int DestroyTmpDir (string const & tmpdir, bool Allfiles)
400 if (Allfiles && DeleteAllFilesInDir(tmpdir)) return -1;
401 if (rmdir(tmpdir.c_str())) {
402 WriteFSAlert(_("Error! Couldn't delete temporary directory:"),
410 string CreateBufferTmpDir (string const & pathfor)
412 return CreateTmpDir(pathfor, "lyx_bufrtmp");
416 int DestroyBufferTmpDir (string const & tmpdir)
418 return DestroyTmpDir(tmpdir, true);
422 string CreateLyXTmpDir (string const & deflt)
424 if ((!deflt.empty()) && (deflt != "/tmp")) {
425 if (mkdir(deflt.c_str(), 0777)) {
429 string t = CreateTmpDir (deflt.c_str(), "lyx_tmp");
437 string t = CreateTmpDir ("/tmp", "lyx_tmp");
443 int DestroyLyXTmpDir (string const & tmpdir)
445 return DestroyTmpDir (tmpdir, false); // Why false?
449 // Creates directory. Returns true if succesfull
450 bool createDirectory(string const & path, int permission)
452 string temp = strip(CleanupPath(path), '/');
455 WriteAlert(_("Internal error!"),
456 _("Call to createDirectory with invalid name"));
460 if (mkdir(temp.c_str(), permission)) {
461 WriteFSAlert (_("Error! Couldn't create directory:"), temp);
468 // Returns current working directory
471 int n = 256; // Assume path is less than 256 chars
473 char * tbuf = new char[n];
475 // Safe. Hopefully all getcwds behave this way!
476 while (((err = lyx::getcwd (tbuf, n)) == 0) && (errno == ERANGE)) {
477 // Buffer too small, double the buffersize and try again
484 if (err) result = tbuf;
490 // Strip filename from path name
491 string OnlyPath(string const & Filename)
493 // If empty filename, return empty
494 if (Filename.empty()) return Filename;
496 // Find last / or start of filename
497 string::size_type j = Filename.rfind('/');
498 if (j == string::npos)
500 return Filename.substr(0, j + 1);
504 // Convert relative path into absolute path based on a basepath.
505 // If relpath is absolute, just use that.
506 // If basepath is empty, use CWD as base.
507 string MakeAbsPath(string const & RelPath, string const & BasePath)
509 // checks for already absolute path
510 if (AbsolutePath(RelPath))
512 if(RelPath[0]!= '/' && RelPath[0]!= '\\')
516 // Copies given paths
517 string TempRel = CleanupPath(RelPath);
521 if (!BasePath.empty()) {
525 char * with_drive = new char[_MAX_PATH];
526 _abspath(with_drive, BasePath.c_str(), _MAX_PATH);
527 TempBase = with_drive;
533 if (AbsolutePath(TempRel))
534 return TempBase.substr(0, 2) + TempRel;
537 // Handle /./ at the end of the path
538 while(suffixIs(TempBase, "/./"))
539 TempBase.erase(TempBase.length() - 2);
541 // processes relative path
542 string RTemp = TempRel;
545 while (!RTemp.empty()) {
547 RTemp = split(RTemp, Temp, '/');
549 if (Temp == ".") continue;
551 // Remove one level of TempBase
552 int i = TempBase.length() - 2;
555 while (i > 0 && TempBase[i] != '/') --i;
559 while (i > 2 && TempBase[i] != '/') --i;
562 TempBase.erase(i, string::npos);
566 // Add this piece to TempBase
567 if (!suffixIs(TempBase, '/'))
573 // returns absolute path
578 // Correctly append filename to the pathname.
579 // If pathname is '.', then don't use pathname.
580 // Chops any path of filename.
581 string AddName(string const & path, string const & fname)
584 string basename = OnlyFilename(fname);
588 if (path != "." && path != "./" && !path.empty()) {
589 buf = CleanupPath(path);
590 if (!suffixIs(path, '/'))
594 return buf + basename;
598 // Strips path from filename
599 string OnlyFilename(string const & fname)
604 string::size_type j = fname.rfind('/');
605 if (j == string::npos) // no '/' in fname
609 return fname.substr(j + 1);
613 // Is a filename/path absolute?
614 bool AbsolutePath(string const & path)
617 return (!path.empty() && path[0] == '/');
619 return (!path.empty() && (path[0] == '/' || (isalpha(static_cast<unsigned char>(path[0])) && path.length()>1 && path[1] == ':')));
624 // Create absolute path. If impossible, don't do anything
625 // Supports ./ and ~/. Later we can add support for ~logname/. (Asger)
626 string ExpandPath(string const & path)
628 // checks for already absolute path
629 string RTemp = ReplaceEnvironmentPath(path);
630 if (AbsolutePath(RTemp))
637 RTemp= split(RTemp, Temp, '/');
640 return GetCWD() + '/' + RTemp;
641 } else if (Temp == "~") {
642 return GetEnvPath("HOME") + '/' + RTemp;
643 } else if (Temp == "..") {
644 return MakeAbsPath(copy);
646 // Don't know how to handle this
652 // Constracts path/../path
653 // Can't handle "../../" or "/../" (Asger)
654 string NormalizePath(string const & path)
660 if (AbsolutePath(path))
663 // Make implicit current directory explicit
666 while (!RTemp.empty()) {
668 RTemp = split(RTemp, Temp, '/');
672 } else if (Temp == "..") {
673 // Remove one level of TempBase
674 int i = TempBase.length() - 2;
675 while (i > 0 && TempBase[i] != '/')
677 if (i >= 0 && TempBase[i] == '/')
678 TempBase.erase(i + 1, string::npos);
682 TempBase += Temp + '/';
686 // returns absolute path
690 string CleanupPath(string const & path)
692 #ifdef __EMX__ /* SMiyata: This should fix searchpath bug. */
693 string temppath = subst(path, '\\', '/');
694 temppath = subst(temppath, "//", "/");
695 return lowercase(temppath);
696 #else // On unix, nothing to do
703 // Search ${...} as Variable-Name inside the string and replace it with
704 // the denoted environmentvariable
705 // Allow Variables according to
706 // variable := '$' '{' [A-Za-z_]{[A-Za-z_0-9]*} '}'
709 string ReplaceEnvironmentPath(string const & path)
712 // CompareChar: Environmentvariables starts with this character
713 // PathChar: Next path component start with this character
714 // while CompareChar found do:
715 // Split String with PathChar
716 // Search Environmentvariable
717 // if found: Replace Strings
719 char const CompareChar = '$';
720 char const FirstChar = '{';
721 char const EndChar = '}';
722 char const UnderscoreChar = '_';
723 string EndString; EndString += EndChar;
724 string FirstString; FirstString += FirstChar;
725 string CompareString; CompareString += CompareChar;
726 string const RegExp("*}*"); // Exist EndChar inside a String?
728 // first: Search for a '$' - Sign.
730 string result1; //(copy); // for split-calls
731 string result0 = split(path, result1, CompareChar);
732 while (!result0.empty()) {
733 string copy1(result0); // contains String after $
735 // Check, if there is an EndChar inside original String.
737 if (!regexMatch(copy1, RegExp)) {
738 // No EndChar inside. So we are finished
739 result1 += CompareString + result0;
745 string res0 = split(copy1, res1, EndChar);
746 // Now res1 holds the environmentvariable
747 // First, check, if Contents is ok.
748 if (res1.empty()) { // No environmentvariable. Continue Loop.
749 result1 += CompareString + FirstString;
753 // check contents of res1
754 char const * res1_contents = res1.c_str();
755 if (*res1_contents != FirstChar) {
756 // Again No Environmentvariable
757 result1 += CompareString;
761 // Check for variable names
762 // Situation ${} is detected as "No Environmentvariable"
763 char const * cp1 = res1_contents + 1;
764 bool result = isalpha(*cp1) || (*cp1 == UnderscoreChar);
766 while (*cp1 && result) {
767 result = isalnum(*cp1) ||
768 (*cp1 == UnderscoreChar);
773 // no correct variable name
774 result1 += CompareString + res1 + EndString;
775 result0 = split(res0, res1, CompareChar);
780 string env = GetEnv(res1_contents+1);
782 // Congratulations. Environmentvariable found
785 result1 += CompareString + res1 + EndString;
788 result0 = split(res0, res1, CompareChar);
792 } // ReplaceEnvironmentPath
795 // Make relative path out of two absolute paths
796 string MakeRelPath(string const & abspath0, string const & basepath0)
797 // Makes relative path out of absolute path. If it is deeper than basepath,
798 // it's easy. If basepath and abspath share something (they are all deeper
799 // than some directory), it'll be rendered using ..'s. If they are completely
800 // different, then the absolute path will be used as relative path.
802 // This is a hack. It should probaly be done in another way. Lgb.
803 string abspath = CleanupPath(abspath0);
804 string basepath = CleanupPath(basepath0);
806 return "<unknown_path>";
808 const int abslen = abspath.length();
809 const int baselen = basepath.length();
811 // Find first different character
813 while (i < abslen && i < baselen && abspath[i] == basepath[i]) ++i;
816 if (i < abslen && i < baselen
817 || (i<abslen && abspath[i] != '/' && i == baselen)
818 || (i<baselen && basepath[i] != '/' && i == abslen))
820 if (i) --i; // here was the last match
821 while (i && abspath[i] != '/') --i;
825 // actually no match - cannot make it relative
829 // Count how many dirs there are in basepath above match
830 // and append as many '..''s into relpath
833 while (j < baselen) {
834 if (basepath[j] == '/') {
835 if (j+1 == baselen) break;
841 // Append relative stuff from common directory to abspath
842 if (abspath[i] == '/') ++i;
843 for (; i < abslen; ++i)
846 if (suffixIs(buf, '/'))
847 buf.erase(buf.length() - 1);
848 // Substitute empty with .
855 // Append sub-directory(ies) to a path in an intelligent way
856 string AddPath(string const & path, string const & path_2)
859 string path2 = CleanupPath(path_2);
861 if (!path.empty() && path != "." && path != "./") {
862 buf = CleanupPath(path);
863 if (path[path.length() - 1] != '/')
868 int p2start = path2.find_first_not_of('/');
870 int p2end = path2.find_last_not_of('/');
872 string tmp = path2.substr(p2start, p2end - p2start + 1);
880 Change extension of oldname to extension.
881 Strips path off if no_path == true.
882 If no extension on oldname, just appends.
884 string ChangeExtension(string const & oldname, string const & extension,
887 string::size_type last_slash = oldname.rfind('/');
888 string::size_type last_dot;
889 if (last_slash != string::npos)
890 last_dot = oldname.find('.', last_slash);
892 last_dot = oldname.rfind('.');
895 // Make sure the extension starts with a dot
896 if (!extension.empty() && extension[0] != '.')
897 ext= '.' + extension;
901 if (no_path && last_slash != string::npos) {
902 ++last_slash; // step it
903 ret_str = oldname.substr(last_slash,
904 last_dot - last_slash) + ext;
906 ret_str = oldname.substr(0, last_dot) + ext;
907 return CleanupPath(ret_str);
911 // Creates a nice compact path for displaying
912 string MakeDisplayPath (string const & path, unsigned int threshold)
914 const int l1 = path.length();
916 // First, we try a relative path compared to home
917 string home = GetEnvPath("HOME");
918 string relhome = MakeRelPath(path, home);
920 unsigned int l2 = relhome.length();
924 // If we backup from home or don't have a relative path,
925 // this try is no good
926 if (prefixIs(relhome, "../") || AbsolutePath(relhome)) {
927 // relative path was no good, just use the original path
934 // Is the path too long?
935 if (l2 > threshold) {
941 while (relhome.length() > threshold)
942 relhome = split(relhome, temp, '/');
944 // Did we shortend everything away?
945 if (relhome.empty()) {
946 // Yes, filename in itself is too long.
947 // Pick the start and the end of the filename.
948 relhome = OnlyFilename(path);
949 string head = relhome.substr(0, threshold/2 - 3);
951 l2 = relhome.length();
953 relhome.substr(l2 - threshold/2 - 2, l2 - 1);
954 relhome = head + "..." + tail;
957 return prefix + relhome;
961 bool LyXReadLink(string const & File, string & Link)
963 char LinkBuffer[512];
964 // Should be PATH_MAX but that needs autconf support
965 int nRead = readlink(File.c_str(), LinkBuffer, sizeof(LinkBuffer)-1);
968 LinkBuffer[nRead] = 0;
974 typedef pair<int, string> cmdret;
975 static cmdret do_popen(string const & cmd)
977 // One question is if we should use popen or
978 // create our own popen based on fork, exec, pipe
979 // of course the best would be to have a
980 // pstream (process stream), with the
981 // variants ipstream, opstream and
982 FILE * inf = popen(cmd.c_str(), "r");
986 ret += static_cast<char>(c);
989 int pret = pclose(inf);
990 return make_pair(pret, ret);
994 string findtexfile(string const & fil, string const & format)
996 /* There is no problem to extend this function too use other
997 methods to look for files. It could be setup to look
998 in environment paths and also if wanted as a last resort
999 to a recursive find. One of the easier extensions would
1000 perhaps be to use the LyX file lookup methods. But! I am
1001 going to implement this until I see some demand for it.
1005 // If fil is a file with absolute path we just return it
1006 if (AbsolutePath(fil)) return fil;
1008 // Check in the current dir.
1009 if (FileInfo(OnlyFilename(fil)).exist())
1010 return OnlyFilename(fil);
1012 // No we try to find it using kpsewhich.
1013 string kpsecmd = "kpsewhich --format= " + format + " " + OnlyFilename(fil);
1014 cmdret c = do_popen(kpsecmd);
1016 lyxerr << "kpse status = " << c.first << "\n"
1017 << "kpse result = `" << strip(c.second, '\n') << "'" << endl;
1018 return c.first != -1 ? strip(c.second, '\n') : string();