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)
157 fstream fs(path.c_str(), ios::out|ios::ate);
159 fs.open(path.c_str(), ios::in|ios::ate);
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"));
182 if (fi.writable()) return 1;
188 // Uses a string of paths separated by ";"s to find a file to open.
189 // Can't cope with pathnames with a ';' in them. Returns full path to file.
190 // If path entry begins with $$LyX/, use system_lyxdir
191 // If path entry begins with $$User/, use user_lyxdir
192 // Example: "$$User/doc;$$LyX/doc"
193 string FileOpenSearch (string const & path, string const & name,
196 string real_file, path_element;
197 bool notfound = true;
198 string tmppath = split(path, path_element, ';');
200 while (notfound && !path_element.empty()) {
201 path_element = CleanupPath(path_element);
202 if (!suffixIs(path_element, '/'))
204 path_element = subst(path_element, "$$LyX", system_lyxdir);
205 path_element = subst(path_element, "$$User", user_lyxdir);
207 real_file = FileSearch(path_element, name, ext);
209 if (real_file.empty()) {
211 tmppath = split(tmppath, path_element, ';');
212 } while(!tmppath.empty() && path_element.empty());
218 if (ext.empty() && notfound) {
219 real_file = FileOpenSearch(path, name, "exe");
220 if (notfound) real_file = FileOpenSearch(path, name, "cmd");
227 // Returns the real name of file name in directory path, with optional
229 string FileSearch(string const & path, string const & name,
232 // if `name' is an absolute path, we ignore the setting of `path'
233 // Expand Environmentvariables in 'name'
234 string tmpname = ReplaceEnvironmentPath(name);
235 string fullname = MakeAbsPath(tmpname, path);
237 // search first without extension, then with it.
238 if (IsFileReadable(fullname))
240 else if (ext.empty())
242 else { // Is it not more reasonable to use ChangeExtension()? (SMiyata)
245 if (IsFileReadable(fullname))
253 // Search the file name.ext in the subdirectory dir of
255 // 2) build_lyxdir (if not empty)
257 string LibFileSearch(string const & dir, string const & name,
260 string fullname = FileSearch(AddPath(user_lyxdir, dir),
262 if (!fullname.empty())
265 if (!build_lyxdir.empty())
266 fullname = FileSearch(AddPath(build_lyxdir, dir),
268 if (!fullname.empty())
271 return FileSearch(AddPath(system_lyxdir, dir), name, ext);
275 string i18nLibFileSearch(string const & dir, string const & name,
278 string lang = token(string(GetEnv("LANG")), '_', 0);
280 if (lang.empty() || lang == "C")
281 return LibFileSearch(dir, name, ext);
283 string tmp = LibFileSearch(dir, lang + '_' + name,
288 return LibFileSearch(dir, name, ext);
293 string GetEnv(string const & envname)
295 // f.ex. what about error checking?
296 char const * const ch = getenv(envname.c_str());
297 string envstr = !ch ? "" : ch;
302 string GetEnvPath(string const & name)
305 string pathlist = subst(GetEnv(name), ':', ';');
307 string pathlist = subst(GetEnv(name), '\\', '/');
309 return strip(pathlist, ';');
313 bool PutEnv(string const & envstr)
316 #warning Look at and fix this.
318 // f.ex. what about error checking?
320 // this leaks, but what can we do about it?
321 // Is doing a getenv() and a free() of the older value
322 // a good idea? (JMarc)
323 // Actually we don't have to leak...calling putenv like this
325 int retval = putenv(const_cast<PUTENV_TYPE_ARG>(envstr.c_str()));
326 //int retval = putenv(const_cast<PUTENV_TYPE_ARG>((new string(envstr))->c_str()));
330 string str = envstr.split(varname,'=');
331 int retval = setenv(varname.c_str(), str.c_str(), true);
338 bool PutEnvPath(string const & envstr)
340 return PutEnv(envstr);
345 int DeleteAllFilesInDir (string const & path)
347 DIR * dir = opendir(path.c_str());
349 WriteFSAlert (_("Error! Cannot open directory:"), path);
353 while ((de = readdir(dir))) {
354 string temp = de->d_name;
355 if (temp == "." || temp == "..")
357 string unlinkpath = AddName (path, temp);
359 lyxerr.debug() << "Deleting file: " << unlinkpath << endl;
361 if (remove(unlinkpath.c_str()))
362 WriteFSAlert (_("Error! Could not remove file:"),
371 string CreateTmpDir (string const & tempdir, string const & mask)
373 string tmpfl = TmpFileName(tempdir, mask);
375 if ((tmpfl.empty()) || mkdir (tmpfl.c_str(), 0777)) {
376 WriteFSAlert(_("Error! Couldn't create temporary directory:"),
380 return MakeAbsPath(tmpfl);
385 int DestroyTmpDir (string const & tmpdir, bool Allfiles)
390 if (Allfiles && DeleteAllFilesInDir(tmpdir)) return -1;
391 if (rmdir(tmpdir.c_str())) {
392 WriteFSAlert(_("Error! Couldn't delete temporary directory:"),
400 string CreateBufferTmpDir (string const & pathfor)
402 return CreateTmpDir(pathfor, "lyx_bufrtmp");
406 int DestroyBufferTmpDir (string const & tmpdir)
408 return DestroyTmpDir(tmpdir, true);
412 string CreateLyXTmpDir (string const & deflt)
414 if ((!deflt.empty()) && (deflt != "/tmp")) {
415 if (mkdir(deflt.c_str(), 0777)) {
419 string t = CreateTmpDir (deflt.c_str(), "lyx_tmp");
427 string t = CreateTmpDir ("/tmp", "lyx_tmp");
433 int DestroyLyXTmpDir (string const & tmpdir)
435 return DestroyTmpDir (tmpdir, false); // Why false?
439 // Creates directory. Returns true if succesfull
440 bool createDirectory(string const & path, int permission)
442 string temp = strip(CleanupPath(path), '/');
445 WriteAlert(_("Internal error!"),
446 _("Call to createDirectory with invalid name"));
450 if (mkdir(temp.c_str(), permission)) {
451 WriteFSAlert (_("Error! Couldn't create directory:"), temp);
458 // Returns current working directory
461 int n = 256; // Assume path is less than 256 chars
463 char * tbuf = new char[n];
465 // Safe. Hopefully all getcwds behave this way!
466 while (((err = lyx::getcwd (tbuf, n)) == 0) && (errno == ERANGE)) {
467 // Buffer too small, double the buffersize and try again
474 if (err) result = tbuf;
480 // Strip filename from path name
481 string OnlyPath(string const & Filename)
483 // If empty filename, return empty
484 if (Filename.empty()) return Filename;
486 // Find last / or start of filename
487 string::size_type j = Filename.rfind('/');
488 if (j == string::npos)
490 return Filename.substr(0, j + 1);
494 // Convert relative path into absolute path based on a basepath.
495 // If relpath is absolute, just use that.
496 // If basepath is empty, use CWD as base.
497 string MakeAbsPath(string const & RelPath, string const & BasePath)
499 // checks for already absolute path
500 if (AbsolutePath(RelPath))
502 if(RelPath[0]!= '/' && RelPath[0]!= '\\')
506 // Copies given paths
507 string TempRel = CleanupPath(RelPath);
511 if (!BasePath.empty()) {
515 char * with_drive = new char[_MAX_PATH];
516 _abspath(with_drive, BasePath.c_str(), _MAX_PATH);
517 TempBase = with_drive;
523 if (AbsolutePath(TempRel))
524 return TempBase.substr(0, 2) + TempRel;
527 // Handle /./ at the end of the path
528 while(suffixIs(TempBase, "/./"))
529 TempBase.erase(TempBase.length() - 2);
531 // processes relative path
532 string RTemp = TempRel;
535 while (!RTemp.empty()) {
537 RTemp = split(RTemp, Temp, '/');
539 if (Temp == ".") continue;
541 // Remove one level of TempBase
542 int i = TempBase.length() - 2;
545 while (i > 0 && TempBase[i] != '/') --i;
549 while (i > 2 && TempBase[i] != '/') --i;
552 TempBase.erase(i, string::npos);
556 // Add this piece to TempBase
557 if (!suffixIs(TempBase, '/'))
563 // returns absolute path
568 // Correctly append filename to the pathname.
569 // If pathname is '.', then don't use pathname.
570 // Chops any path of filename.
571 string AddName(string const & path, string const & fname)
574 string basename = OnlyFilename(fname);
578 if (path != "." && path != "./" && !path.empty()) {
579 buf = CleanupPath(path);
580 if (!suffixIs(path, '/'))
584 return buf + basename;
588 // Strips path from filename
589 string OnlyFilename(string const & fname)
594 string::size_type j = fname.rfind('/');
595 if (j == string::npos) // no '/' in fname
599 return fname.substr(j + 1);
603 // Is a filename/path absolute?
604 bool AbsolutePath(string const & path)
607 return (!path.empty() && path[0] == '/');
609 return (!path.empty() && (path[0] == '/' || (isalpha(static_cast<unsigned char>(path[0])) && path.length()>1 && path[1] == ':')));
614 // Create absolute path. If impossible, don't do anything
615 // Supports ./ and ~/. Later we can add support for ~logname/. (Asger)
616 string ExpandPath(string const & path)
618 // checks for already absolute path
619 string RTemp = ReplaceEnvironmentPath(path);
620 if (AbsolutePath(RTemp))
627 RTemp= split(RTemp, Temp, '/');
630 return GetCWD() + '/' + RTemp;
631 } else if (Temp == "~") {
632 return GetEnvPath("HOME") + '/' + RTemp;
633 } else if (Temp == "..") {
634 return MakeAbsPath(copy);
636 // Don't know how to handle this
642 // Constracts path/../path
643 // Can't handle "../../" or "/../" (Asger)
644 string NormalizePath(string const & path)
650 if (AbsolutePath(path))
653 // Make implicit current directory explicit
656 while (!RTemp.empty()) {
658 RTemp = split(RTemp, Temp, '/');
662 } else if (Temp == "..") {
663 // Remove one level of TempBase
664 int i = TempBase.length() - 2;
665 while (i > 0 && TempBase[i] != '/')
667 if (i >= 0 && TempBase[i] == '/')
668 TempBase.erase(i + 1, string::npos);
672 TempBase += Temp + '/';
676 // returns absolute path
680 string CleanupPath(string const & path)
682 #ifdef __EMX__ /* SMiyata: This should fix searchpath bug. */
683 string temppath = subst(path, '\\', '/');
684 temppath = subst(temppath, "//", "/");
685 return lowercase(temppath);
686 #else // On unix, nothing to do
693 // Search ${...} as Variable-Name inside the string and replace it with
694 // the denoted environmentvariable
695 // Allow Variables according to
696 // variable := '$' '{' [A-Za-z_]{[A-Za-z_0-9]*} '}'
699 string ReplaceEnvironmentPath(string const & path)
702 // CompareChar: Environmentvariables starts with this character
703 // PathChar: Next path component start with this character
704 // while CompareChar found do:
705 // Split String with PathChar
706 // Search Environmentvariable
707 // if found: Replace Strings
709 char const CompareChar = '$';
710 char const FirstChar = '{';
711 char const EndChar = '}';
712 char const UnderscoreChar = '_';
713 string EndString; EndString += EndChar;
714 string FirstString; FirstString += FirstChar;
715 string CompareString; CompareString += CompareChar;
716 string const RegExp("*}*"); // Exist EndChar inside a String?
718 // first: Search for a '$' - Sign.
720 string result1; //(copy); // for split-calls
721 string result0 = split(path, result1, CompareChar);
722 while (!result0.empty()) {
723 string copy1(result0); // contains String after $
725 // Check, if there is an EndChar inside original String.
727 if (!regexMatch(copy1, RegExp)) {
728 // No EndChar inside. So we are finished
729 result1 += CompareString + result0;
735 string res0 = split(copy1, res1, EndChar);
736 // Now res1 holds the environmentvariable
737 // First, check, if Contents is ok.
738 if (res1.empty()) { // No environmentvariable. Continue Loop.
739 result1 += CompareString + FirstString;
743 // check contents of res1
744 char const * res1_contents = res1.c_str();
745 if (*res1_contents != FirstChar) {
746 // Again No Environmentvariable
747 result1 += CompareString;
751 // Check for variable names
752 // Situation ${} is detected as "No Environmentvariable"
753 char const * cp1 = res1_contents + 1;
754 bool result = isalpha(*cp1) || (*cp1 == UnderscoreChar);
756 while (*cp1 && result) {
757 result = isalnum(*cp1) ||
758 (*cp1 == UnderscoreChar);
763 // no correct variable name
764 result1 += CompareString + res1 + EndString;
765 result0 = split(res0, res1, CompareChar);
770 string env = GetEnv(res1_contents+1);
772 // Congratulations. Environmentvariable found
775 result1 += CompareString + res1 + EndString;
778 result0 = split(res0, res1, CompareChar);
782 } // ReplaceEnvironmentPath
785 // Make relative path out of two absolute paths
786 string MakeRelPath(string const & abspath0, string const & basepath0)
787 // Makes relative path out of absolute path. If it is deeper than basepath,
788 // it's easy. If basepath and abspath share something (they are all deeper
789 // than some directory), it'll be rendered using ..'s. If they are completely
790 // different, then the absolute path will be used as relative path.
792 // This is a hack. It should probaly be done in another way. Lgb.
793 string abspath = CleanupPath(abspath0);
794 string basepath = CleanupPath(basepath0);
796 return "<unknown_path>";
798 const int abslen = abspath.length();
799 const int baselen = basepath.length();
801 // Find first different character
803 while (i < abslen && i < baselen && abspath[i] == basepath[i]) ++i;
806 if (i < abslen && i < baselen
807 || (i<abslen && abspath[i] != '/' && i == baselen)
808 || (i<baselen && basepath[i] != '/' && i == abslen))
810 if (i) --i; // here was the last match
811 while (i && abspath[i] != '/') --i;
815 // actually no match - cannot make it relative
819 // Count how many dirs there are in basepath above match
820 // and append as many '..''s into relpath
823 while (j < baselen) {
824 if (basepath[j] == '/') {
825 if (j+1 == baselen) break;
831 // Append relative stuff from common directory to abspath
832 if (abspath[i] == '/') ++i;
833 for (; i < abslen; ++i)
836 if (suffixIs(buf, '/'))
837 buf.erase(buf.length() - 1);
838 // Substitute empty with .
845 // Append sub-directory(ies) to a path in an intelligent way
846 string AddPath(string const & path, string const & path_2)
849 string path2 = CleanupPath(path_2);
851 if (!path.empty() && path != "." && path != "./") {
852 buf = CleanupPath(path);
853 if (path[path.length() - 1] != '/')
858 int p2start = path2.find_first_not_of('/');
860 int p2end = path2.find_last_not_of('/');
862 string tmp = path2.substr(p2start, p2end - p2start + 1);
870 Change extension of oldname to extension.
871 Strips path off if no_path == true.
872 If no extension on oldname, just appends.
874 string ChangeExtension(string const & oldname, string const & extension,
877 string::size_type last_slash = oldname.rfind('/');
878 string::size_type last_dot;
879 if (last_slash != string::npos)
880 last_dot = oldname.find('.', last_slash);
882 last_dot = oldname.rfind('.');
885 // Make sure the extension starts with a dot
886 if (!extension.empty() && extension[0] != '.')
887 ext= '.' + extension;
891 if (no_path && last_slash != string::npos) {
892 ++last_slash; // step it
893 ret_str = oldname.substr(last_slash,
894 last_dot - last_slash) + ext;
896 ret_str = oldname.substr(0, last_dot) + ext;
897 return CleanupPath(ret_str);
901 // Creates a nice compact path for displaying
902 string MakeDisplayPath (string const & path, unsigned int threshold)
904 const int l1 = path.length();
906 // First, we try a relative path compared to home
907 string home = GetEnvPath("HOME");
908 string relhome = MakeRelPath(path, home);
910 unsigned int l2 = relhome.length();
914 // If we backup from home or don't have a relative path,
915 // this try is no good
916 if (prefixIs(relhome, "../") || AbsolutePath(relhome)) {
917 // relative path was no good, just use the original path
924 // Is the path too long?
925 if (l2 > threshold) {
931 while (relhome.length() > threshold)
932 relhome = split(relhome, temp, '/');
934 // Did we shortend everything away?
935 if (relhome.empty()) {
936 // Yes, filename in itself is too long.
937 // Pick the start and the end of the filename.
938 relhome = OnlyFilename(path);
939 string head = relhome.substr(0, threshold/2 - 3);
941 l2 = relhome.length();
943 relhome.substr(l2 - threshold/2 - 2, l2 - 1);
944 relhome = head + "..." + tail;
947 return prefix + relhome;
951 bool LyXReadLink(string const & File, string & Link)
953 char LinkBuffer[512];
954 // Should be PATH_MAX but that needs autconf support
955 int nRead = readlink(File.c_str(), LinkBuffer, sizeof(LinkBuffer)-1);
958 LinkBuffer[nRead] = 0;
964 typedef pair<int, string> cmdret;
965 static cmdret do_popen(string const & cmd)
967 // One question is if we should use popen or
968 // create our own popen based on fork, exec, pipe
969 // of course the best would be to have a
970 // pstream (process stream), with the
971 // variants ipstream, opstream
972 FILE * inf = popen(cmd.c_str(), "r");
976 ret += static_cast<char>(c);
979 int pret = pclose(inf);
980 return make_pair(pret, ret);
984 string findtexfile(string const & fil, string const & format)
986 /* There is no problem to extend this function too use other
987 methods to look for files. It could be setup to look
988 in environment paths and also if wanted as a last resort
989 to a recursive find. One of the easier extensions would
990 perhaps be to use the LyX file lookup methods. But! I am
991 going to implement this until I see some demand for it.
995 // If fil is a file with absolute path we just return it
996 if (AbsolutePath(fil)) return fil;
998 // Check in the current dir.
999 if (FileInfo(OnlyFilename(fil)).exist())
1000 return OnlyFilename(fil);
1002 // No we try to find it using kpsewhich.
1003 string kpsecmd = "kpsewhich --format= " + format + " " + OnlyFilename(fil);
1004 cmdret c = do_popen(kpsecmd);
1006 lyxerr[Debug::LATEX] << "kpse status = " << c.first << "\n"
1007 << "kpse result = `" << strip(c.second, '\n')
1009 return c.first != -1 ? strip(c.second, '\n') : string();