]> git.lyx.org Git - lyx.git/blob - src/support/filetools.C
noncopyable + read ChangeLog
[lyx.git] / src / support / filetools.C
1 /*
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
8         
9         See also filetools.H.
10
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
14
15 */
16
17 #include <config.h>
18
19 #include <cctype>
20
21 #include <utility>
22 #include <fstream>
23
24 #ifdef HAVE_SSTREAM
25 #include <sstream>
26 #else
27 #include <strstream>
28 #endif
29
30 #ifdef __GNUG__
31 #pragma implementation "filetools.h"
32 #endif
33
34 #include "filetools.h"
35 #include "LSubstring.h"
36 #include "lyx_gui_misc.h"
37 #include "FileInfo.h"
38 #include "support/path.h"        // I know it's OS/2 specific (SMiyata)
39 #include "gettext.h"
40 #include "lyxlib.h"
41
42 // Which part of this is still necessary? (JMarc).
43 #if HAVE_DIRENT_H
44 # include <dirent.h>
45 # define NAMLEN(dirent) strlen((dirent)->d_name)
46 #else
47 # define dirent direct
48 # define NAMLEN(dirent) (dirent)->d_namlen
49 # if HAVE_SYS_NDIR_H
50 #  include <sys/ndir.h>
51 # endif
52 # if HAVE_SYS_DIR_H
53 #  include <sys/dir.h>
54 # endif
55 # if HAVE_NDIR_H
56 #  include <ndir.h>
57 # endif
58 #endif
59
60 using std::make_pair;
61 using std::pair;
62 using std::endl;
63 using std::ifstream;
64
65 extern string system_lyxdir;
66 extern string build_lyxdir;
67 extern string user_lyxdir;
68 extern string system_tempdir;
69
70
71 bool IsLyXFilename(string const & filename)
72 {
73         return contains(filename, ".lyx");
74 }
75
76
77 // Substitutes spaces with underscores in filename (and path)
78 string MakeLatexName(string const & file)
79 {
80         string name = OnlyFilename(file);
81         string path = OnlyPath(file);
82         
83         for (string::size_type i = 0; i < name.length(); ++i) {
84                 name[i] &= 0x7f; // set 8th bit to 0
85         };
86
87         // ok so we scan through the string twice, but who cares.
88         string keep("abcdefghijklmnopqrstuvwxyz"
89                 "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
90                 "@!\"'()*+,-./0123456789:;<=>?[]`|");
91         
92         string::size_type pos = 0;
93         while ((pos = name.find_first_not_of(keep, pos)) != string::npos) {
94                 name[pos++] = '_';
95         }
96         return AddName(path, name);
97 }
98
99 // Substitutes spaces with underscores in filename (and path)
100 string QuoteName(string const & name)
101 {
102         // CHECK Add proper emx support here!
103 #ifndef __EMX__
104         return '\'' + name + '\'';
105 #else
106         return name; 
107 #endif
108 }
109
110
111 /// Returns an unique name to be used as a temporary file. 
112 string TmpFileName(string const & dir, string const & mask)
113 {// With all these temporary variables, it should be safe enough :-) (JMarc)
114         string tmpdir;  
115         if (dir.empty())
116                 tmpdir = system_tempdir;
117         else
118                 tmpdir = dir;
119         string tmpfl = AddName(tmpdir, mask);
120
121         // find a uniq postfix for the filename...
122         // using the pid, and...
123         tmpfl += tostr(getpid());
124         // a short string...
125         string ret;
126         FileInfo fnfo;
127         for (int a = 'a'; a <= 'z'; ++a)
128                 for (int b = 'a'; b <= 'z'; ++b)
129                         for (int c = 'a'; c <= 'z'; ++c) {
130                                 // if this is not enough I have no idea what
131                                 // to do.
132                                 ret = tmpfl + char(a) + char(b) + char(c);
133                                 // check if the file exist
134                                 if (!fnfo.newFile(ret).exist())
135                                         return ret;
136                         }
137         lyxerr << "Not able to find a uniq tmpfile name." << endl;
138         return string();
139 }
140
141
142 // Is a file readable ?
143 bool IsFileReadable (string const & path)
144 {
145         FileInfo file(path);
146         if (file.isOK() && file.isRegular() && file.readable())
147                 return true;
148         else
149                 return false;
150 }
151
152
153 // Is a file read_only?
154 // return 1 read-write
155 //        0 read_only
156 //       -1 error (doesn't exist, no access, anything else) 
157 int IsFileWriteable (string const & path)
158 {
159         FileInfo fi(path);
160         if (fi.access(FileInfo::wperm|FileInfo::rperm)) // read-write
161                 return 1;
162         if (fi.readable()) // read-only
163                 return 0;
164         return -1; // everything else.
165 }
166
167
168 //returns 1: dir writeable
169 //        0: not writeable
170 //       -1: error- couldn't find out
171 int IsDirWriteable (string const & path)
172 {
173         string tmpfl = TmpFileName(path);
174
175         if (tmpfl.empty()) {
176                 WriteFSAlert(_("LyX Internal Error!"), 
177                              _("Could not test if directory is writeable"));
178                 return -1;
179         } else {
180                 FileInfo fi(path);
181                 if (fi.writable()) return 1;
182                 return 0;
183         }
184 }
185
186
187 // Uses a string of paths separated by ";"s to find a file to open.
188 // Can't cope with pathnames with a ';' in them. Returns full path to file.
189 // If path entry begins with $$LyX/, use system_lyxdir
190 // If path entry begins with $$User/, use user_lyxdir
191 // Example: "$$User/doc;$$LyX/doc"
192 string FileOpenSearch (string const & path, string const & name, 
193                        string const & ext)
194 {
195         string real_file, path_element;
196         bool notfound = true;
197         string tmppath = split(path, path_element, ';');
198         
199         while (notfound && !path_element.empty()) {
200                 path_element = CleanupPath(path_element);
201                 if (!suffixIs(path_element, '/'))
202                         path_element+= '/';
203                 path_element = subst(path_element, "$$LyX", system_lyxdir);
204                 path_element = subst(path_element, "$$User", user_lyxdir);
205                 
206                 real_file = FileSearch(path_element, name, ext);
207                 
208                 if (real_file.empty()) {
209                         do {
210                                 tmppath = split(tmppath, path_element, ';');
211                         } while(!tmppath.empty() && path_element.empty());
212                 } else {
213                         notfound = false;
214                 }
215         }
216 #ifdef __EMX__
217         if (ext.empty() && notfound) {
218                 real_file = FileOpenSearch(path, name, "exe");
219                 if (notfound) real_file = FileOpenSearch(path, name, "cmd");
220         }
221 #endif
222         return real_file;
223 }
224
225
226 // Returns the real name of file name in directory path, with optional
227 // extension ext.  
228 string FileSearch(string const & path, string const & name, 
229                   string const & ext)
230 {
231         // if `name' is an absolute path, we ignore the setting of `path'
232         // Expand Environmentvariables in 'name'
233         string tmpname = ReplaceEnvironmentPath(name);
234         string fullname = MakeAbsPath(tmpname, path);
235         
236         // search first without extension, then with it.
237         if (IsFileReadable(fullname))
238                 return fullname;
239         else if (ext.empty()) 
240                 return string();
241         else { // Is it not more reasonable to use ChangeExtension()? (SMiyata)
242                 fullname += '.';
243                 fullname += ext;
244                 if (IsFileReadable(fullname))
245                         return fullname;
246                 else 
247                         return string();
248         }
249 }
250
251
252 // Search the file name.ext in the subdirectory dir of
253 //   1) user_lyxdir
254 //   2) build_lyxdir (if not empty)
255 //   3) system_lyxdir
256 string LibFileSearch(string const & dir, string const & name, 
257                      string const & ext)
258 {
259         string fullname = FileSearch(AddPath(user_lyxdir, dir),
260                                      name, ext); 
261         if (!fullname.empty())
262                 return fullname;
263         
264         if (!build_lyxdir.empty()) 
265                 fullname = FileSearch(AddPath(build_lyxdir, dir), 
266                                       name, ext);
267         if (!fullname.empty())
268                 return fullname;
269         
270         return FileSearch(AddPath(system_lyxdir, dir), name, ext);
271 }
272
273
274 string i18nLibFileSearch(string const & dir, string const & name, 
275                          string const & ext)
276 {
277         string lang = token(string(GetEnv("LANG")), '_', 0);
278         
279         if (lang.empty() || lang == "C")
280                 return LibFileSearch(dir, name, ext);
281         else {
282                 string tmp = LibFileSearch(dir, lang + '_' + name,
283                                            ext);
284                 if (!tmp.empty())
285                         return tmp;
286                 else
287                         return LibFileSearch(dir, name, ext);
288         }
289 }
290
291
292 string GetEnv(string const & envname)
293 {
294         // f.ex. what about error checking?
295         char const * const ch = getenv(envname.c_str());
296         string envstr = !ch ? "" : ch;
297         return envstr;
298 }
299
300
301 string GetEnvPath(string const & name)
302 {
303 #ifndef __EMX__
304         string pathlist = subst(GetEnv(name), ':', ';');
305 #else
306         string pathlist = subst(GetEnv(name), '\\', '/');
307 #endif
308         return strip(pathlist, ';');
309 }
310
311
312 bool PutEnv(string const & envstr)
313 {
314         // CHECK Look at and fix this.
315         // f.ex. what about error checking?
316
317 #if HAVE_PUTENV
318         // this leaks, but what can we do about it?
319         //   Is doing a getenv() and a free() of the older value 
320         //   a good idea? (JMarc)
321         // Actually we don't have to leak...calling putenv like this
322         // should be enough: ... and this is obviously not enough if putenv
323         // does not make a copy of the string. It is also not very wise to
324         // put a string on the free store. If we have to leak we should do it
325         // like this:
326         char * leaker = new char[envstr.length() + 1];
327         envstr.copy(leaker, envstr.length());
328         leaker[envstr.length()] = '\0';
329         int retval = lyx::putenv(leaker);
330
331         // If putenv does not make a copy of the char const * this
332         // is very dangerous. OTOH if it does take a copy this is the
333         // best solution.
334         // The  only implementation of putenv that I have seen does not
335         // allocate memory. _And_ after testing the putenv in glibc it
336         // seems that we need to make a copy of the string contents.
337         // I will enable the above.
338         //int retval = lyx::putenv(envstr.c_str());
339 #else
340 #ifdef HAVE_SETENV 
341         string varname;
342         string str = envstr.split(varname,'=');
343         int retval = setenv(varname.c_str(), str.c_str(), true);
344 #else
345         // No environment setting function. Can this happen?
346         int retval = 1; //return an error condition.
347 #endif
348 #endif
349         return retval == 0;
350 }
351
352
353 bool PutEnvPath(string const & envstr)
354 {
355         return PutEnv(envstr);
356 }
357
358
359 static
360 int DeleteAllFilesInDir (string const & path)
361 {
362         // I have decided that we will be using parts from the boost
363         // library. Check out http://www.boost.org/
364         // For directory access we will then use the directory_iterator.
365         // Then the code will be something like:
366         // directory_iterator dit(path.c_str());
367         // if (<some way to detect failure>) {
368         //         WriteFSAlert(_("Error! Cannot open directory:"), path);
369         //         return -1;
370         // }
371         // for (; dit != <someend>; ++dit) {
372         //         if ((*dit) == 2." || (*dit) == "..")
373         //                 continue;
374         //         string unlinkpath = AddName(path, temp);
375         //         if (remove(unlinkpath.c_str()))
376         //                 WriteFSAlert(_("Error! Could not remove file:"),
377         //                              unlinkpath);
378         // }
379         // return 0;
380         DIR * dir = opendir(path.c_str());
381         if (!dir) {
382                 WriteFSAlert (_("Error! Cannot open directory:"), path);
383                 return -1;
384         }
385         struct dirent * de;
386         while ((de = readdir(dir))) {
387                 string temp = de->d_name;
388                 if (temp == "." || temp == "..") 
389                         continue;
390                 string unlinkpath = AddName (path, temp);
391
392                 lyxerr.debug() << "Deleting file: " << unlinkpath << endl;
393
394                 if (remove(unlinkpath.c_str()))
395                         WriteFSAlert (_("Error! Could not remove file:"), 
396                                       unlinkpath);
397         }
398         closedir(dir);
399         return 0;
400 }
401
402
403 static
404 string CreateTmpDir (string const & tempdir, string const & mask)
405 {
406         string tmpfl = TmpFileName(tempdir, mask);
407         
408         if ((tmpfl.empty()) || lyx::mkdir (tmpfl.c_str(), 0777)) {
409                 WriteFSAlert(_("Error! Couldn't create temporary directory:"),
410                              tempdir);
411                 return string();
412         }
413         return MakeAbsPath(tmpfl);
414 }
415
416
417 static
418 int DestroyTmpDir (string const & tmpdir, bool Allfiles)
419 {
420 #ifdef __EMX__
421         Path p(user_lyxdir);
422 #endif
423         if (Allfiles && DeleteAllFilesInDir(tmpdir)) return -1;
424         if (rmdir(tmpdir.c_str())) { 
425                 WriteFSAlert(_("Error! Couldn't delete temporary directory:"), 
426                              tmpdir);
427                 return -1;
428         }
429         return 0; 
430
431
432
433 string CreateBufferTmpDir (string const & pathfor)
434 {
435         return CreateTmpDir(pathfor, "lyx_bufrtmp");
436 }
437
438
439 int DestroyBufferTmpDir (string const & tmpdir)
440 {
441         return DestroyTmpDir(tmpdir, true);
442 }
443
444
445 string CreateLyXTmpDir (string const & deflt)
446 {
447         if ((!deflt.empty()) && (deflt  != "/tmp")) {
448                 if (lyx::mkdir(deflt.c_str(), 0777)) {
449 #ifdef __EMX__
450                         Path p(user_lyxdir);
451 #endif
452                         string t = CreateTmpDir (deflt.c_str(), "lyx_tmp");
453                         return t;
454                 } else
455                         return deflt;
456         } else {
457 #ifdef __EMX__
458                 Path p(user_lyxdir);
459 #endif
460                 string t = CreateTmpDir ("/tmp", "lyx_tmp");
461                 return t;
462         }
463 }
464
465
466 int DestroyLyXTmpDir (string const & tmpdir)
467 {
468        return DestroyTmpDir (tmpdir, false); // Why false?
469 }
470
471
472 // Creates directory. Returns true if succesfull
473 bool createDirectory(string const & path, int permission)
474 {
475         string temp = strip(CleanupPath(path), '/');
476
477         if (temp.empty()) {
478                 WriteAlert(_("Internal error!"),
479                            _("Call to createDirectory with invalid name"));
480                 return false;
481         }
482
483         if (lyx::mkdir(temp.c_str(), permission)) {
484                 WriteFSAlert (_("Error! Couldn't create directory:"), temp);
485                 return false;
486         }
487         return true;
488 }
489
490
491 // Returns current working directory
492 string GetCWD ()
493 {
494         int n = 256;    // Assume path is less than 256 chars
495         char * err;
496         char * tbuf = new char[n];
497         
498         // Safe. Hopefully all getcwds behave this way!
499         while (((err = lyx::getcwd (tbuf, n)) == 0) && (errno == ERANGE)) {
500                 // Buffer too small, double the buffersize and try again
501                 delete[] tbuf;
502                 n = 2 * n;
503                 tbuf = new char[n];
504         }
505
506         string result;
507         if (err) result = tbuf;
508         delete[] tbuf;
509         return result;
510 }
511
512
513 // Strip filename from path name
514 string OnlyPath(string const & Filename)
515 {
516         // If empty filename, return empty
517         if (Filename.empty()) return Filename;
518
519         // Find last / or start of filename
520         string::size_type j = Filename.rfind('/');
521         if (j == string::npos)
522                 return "./";
523         return Filename.substr(0, j + 1);
524 }
525
526
527 // Convert relative path into absolute path based on a basepath.
528 // If relpath is absolute, just use that.
529 // If basepath is empty, use CWD as base.
530 string MakeAbsPath(string const & RelPath, string const & BasePath)
531 {
532         // checks for already absolute path
533         if (AbsolutePath(RelPath))
534 #ifdef __EMX__
535                 if(RelPath[0]!= '/' && RelPath[0]!= '\\')
536 #endif
537                 return RelPath;
538
539         // Copies given paths
540         string TempRel = CleanupPath(RelPath);
541
542         string TempBase;
543
544         if (!BasePath.empty()) {
545 #ifndef __EMX__
546                 TempBase = BasePath;
547 #else
548                 char * with_drive = new char[_MAX_PATH];
549                 _abspath(with_drive, BasePath.c_str(), _MAX_PATH);
550                 TempBase = with_drive;
551                 delete[] with_drive;
552 #endif
553         } else
554                 TempBase = GetCWD();
555 #ifdef __EMX__
556         if (AbsolutePath(TempRel))
557                 return TempBase.substr(0, 2) + TempRel;
558 #endif
559
560         // Handle /./ at the end of the path
561         while(suffixIs(TempBase, "/./"))
562                 TempBase.erase(TempBase.length() - 2);
563
564         // processes relative path
565         string RTemp = TempRel;
566         string Temp;
567
568         while (!RTemp.empty()) {
569                 // Split by next /
570                 RTemp = split(RTemp, Temp, '/');
571                 
572                 if (Temp == ".") continue;
573                 if (Temp == "..") {
574                         // Remove one level of TempBase
575                         int i = TempBase.length() - 2;
576 #ifndef __EMX__
577                         if (i < 0) i = 0;
578                         while (i > 0 && TempBase[i] != '/') --i;
579                         if (i > 0)
580 #else
581                         if (i < 2) i = 2;
582                         while (i > 2 && TempBase[i] != '/') --i;
583                         if (i > 2)
584 #endif
585                                 TempBase.erase(i, string::npos);
586                         else
587                                 TempBase += '/';
588                 } else {
589                         // Add this piece to TempBase
590                         if (!suffixIs(TempBase, '/'))
591                                 TempBase += '/';
592                         TempBase += Temp;
593                 }
594         }
595
596         // returns absolute path
597         return TempBase;
598 }
599
600
601 // Correctly append filename to the pathname.
602 // If pathname is '.', then don't use pathname.
603 // Chops any path of filename.
604 string AddName(string const & path, string const & fname)
605 {
606         // Get basename
607         string basename = OnlyFilename(fname);
608
609         string buf;
610
611         if (path != "." && path != "./" && !path.empty()) {
612                 buf = CleanupPath(path);
613                 if (!suffixIs(path, '/'))
614                         buf += '/';
615         }
616
617         return buf + basename;
618 }
619
620
621 // Strips path from filename
622 string OnlyFilename(string const & fname)
623 {
624         if (fname.empty())
625                 return fname;
626
627         string::size_type j = fname.rfind('/');
628         if (j == string::npos) // no '/' in fname
629                 return fname;
630
631         // Strip to basename
632         return fname.substr(j + 1);
633 }
634
635
636 // Is a filename/path absolute?
637 bool AbsolutePath(string const & path)
638 {
639 #ifndef __EMX__
640         return (!path.empty() && path[0] == '/');
641 #else
642         return (!path.empty() && (path[0] == '/' || (isalpha(static_cast<unsigned char>(path[0])) && path.length()>1 && path[1] == ':')));
643 #endif
644 }
645
646
647 // Create absolute path. If impossible, don't do anything
648 // Supports ./ and ~/. Later we can add support for ~logname/. (Asger)
649 string ExpandPath(string const & path)
650 {
651         // checks for already absolute path
652         string RTemp = ReplaceEnvironmentPath(path);
653         if (AbsolutePath(RTemp))
654                 return RTemp;
655
656         string Temp;
657         string copy(RTemp);
658
659         // Split by next /
660         RTemp= split(RTemp, Temp, '/');
661
662         if (Temp == ".") {
663                 return GetCWD() + '/' + RTemp;
664         } else if (Temp == "~") {
665                 return GetEnvPath("HOME") + '/' + RTemp;
666         } else if (Temp == "..") {
667                 return MakeAbsPath(copy);
668         } else
669                 // Don't know how to handle this
670                 return copy;
671 }
672
673
674 // Normalize a path
675 // Constracts path/../path
676 // Can't handle "../../" or "/../" (Asger)
677 string NormalizePath(string const & path)
678 {
679         string TempBase;
680         string RTemp;
681         string Temp;
682
683         if (AbsolutePath(path))
684                 RTemp = path;
685         else
686                 // Make implicit current directory explicit
687                 RTemp = "./" +path;
688
689         while (!RTemp.empty()) {
690                 // Split by next /
691                 RTemp = split(RTemp, Temp, '/');
692                 
693                 if (Temp == ".") {
694                         TempBase = "./";
695                 } else if (Temp == "..") {
696                         // Remove one level of TempBase
697                         int i = TempBase.length() - 2;
698                         while (i > 0 && TempBase[i] != '/')
699                                 --i;
700                         if (i >= 0 && TempBase[i] == '/')
701                                 TempBase.erase(i + 1, string::npos);
702                         else
703                                 TempBase = "../";
704                 } else {
705                         TempBase += Temp + '/';
706                 }
707         }
708
709         // returns absolute path
710         return TempBase;        
711 }
712
713 string CleanupPath(string const & path) 
714 {
715 #ifdef __EMX__    /* SMiyata: This should fix searchpath bug. */
716         string temppath = subst(path, '\\', '/');
717         temppath = subst(temppath, "//", "/");
718         return lowercase(temppath);
719 #else // On unix, nothing to do
720         return path;
721 #endif
722 }
723
724 string GetFileContents(string const & fname) {
725         FileInfo finfo(fname);
726         if (finfo.exist()) {
727                 ifstream ifs(fname.c_str());
728 #ifdef HAVE_SSTREAM
729                 std::ostringstream ofs;
730 #else
731 #warning The rumour goes that this might leak, but who really cares?
732                 ostrstream ofs;
733 #endif
734                 if (ifs && ofs) {
735                         ofs << ifs.rdbuf();
736                         ifs.close();
737 #ifdef HAVE_SSTREAM
738                         return ofs.str().c_str();
739 #else
740                         ofs << '\0';
741                         char const * tmp = ofs.str();
742                         string ret(tmp);
743                         delete[] tmp;
744                         return ret;
745 #endif
746                 }
747         }
748         lyxerr << "LyX was not able to read file '" << fname << "'" << endl;
749         return string();
750 }
751
752
753 //
754 // Search ${...} as Variable-Name inside the string and replace it with
755 // the denoted environmentvariable
756 // Allow Variables according to 
757 //  variable :=  '$' '{' [A-Za-z_]{[A-Za-z_0-9]*} '}'
758 //
759
760 string ReplaceEnvironmentPath(string const & path)
761 {
762 // 
763 // CompareChar: Environmentvariables starts with this character
764 // PathChar:    Next path component start with this character
765 // while CompareChar found do:
766 //       Split String with PathChar
767 //       Search Environmentvariable
768 //       if found: Replace Strings
769 //
770         char const CompareChar = '$';
771         char const FirstChar = '{'; 
772         char const EndChar = '}'; 
773         char const UnderscoreChar = '_'; 
774         string EndString; EndString += EndChar;
775         string FirstString; FirstString += FirstChar;
776         string CompareString; CompareString += CompareChar;
777         string const RegExp("*}*"); // Exist EndChar inside a String?
778
779 // first: Search for a '$' - Sign.
780         //string copy(path);
781         string result1; //(copy);    // for split-calls
782         string result0 = split(path, result1, CompareChar);
783         while (!result0.empty()) {
784                 string copy1(result0); // contains String after $
785                 
786                 // Check, if there is an EndChar inside original String.
787                 
788                 if (!regexMatch(copy1, RegExp)) {
789                         // No EndChar inside. So we are finished
790                         result1 += CompareString + result0;
791                         result0.erase();
792                         continue;
793                 }
794
795                 string res1;
796                 string res0 = split(copy1, res1, EndChar);
797                 // Now res1 holds the environmentvariable
798                 // First, check, if Contents is ok.
799                 if (res1.empty()) { // No environmentvariable. Continue Loop.
800                         result1 += CompareString + FirstString;
801                         result0  = res0;
802                         continue;
803                 }
804                 // check contents of res1
805                 char const * res1_contents = res1.c_str();
806                 if (*res1_contents != FirstChar) {
807                         // Again No Environmentvariable
808                         result1 += CompareString;
809                         result0 = res0;
810                 }
811
812                 // Check for variable names
813                 // Situation ${} is detected as "No Environmentvariable"
814                 char const * cp1 = res1_contents + 1;
815                 bool result = isalpha(*cp1) || (*cp1 == UnderscoreChar);
816                 ++cp1;
817                 while (*cp1 && result) {
818                         result = isalnum(*cp1) || 
819                                 (*cp1 == UnderscoreChar); 
820                         ++cp1;
821                 }
822
823                 if (!result) {
824                         // no correct variable name
825                         result1 += CompareString + res1 + EndString;
826                         result0  = split(res0, res1, CompareChar);
827                         result1 += res1;
828                         continue;
829                 }
830             
831                 string env = GetEnv(res1_contents+1);
832                 if (!env.empty()) {
833                         // Congratulations. Environmentvariable found
834                         result1 += env;
835                 } else {
836                         result1 += CompareString + res1 + EndString;
837                 }
838                 // Next $-Sign?
839                 result0  = split(res0, res1, CompareChar);
840                 result1 += res1;
841         } 
842         return result1;
843 }  // ReplaceEnvironmentPath
844
845
846 // Make relative path out of two absolute paths
847 string MakeRelPath(string const & abspath0, string const & basepath0)
848 // Makes relative path out of absolute path. If it is deeper than basepath,
849 // it's easy. If basepath and abspath share something (they are all deeper
850 // than some directory), it'll be rendered using ..'s. If they are completely
851 // different, then the absolute path will be used as relative path.
852 {
853         // This is a hack. It should probaly be done in another way. Lgb.
854         string abspath = CleanupPath(abspath0);
855         string basepath = CleanupPath(basepath0);
856         if (abspath.empty())
857                 return "<unknown_path>";
858
859         const int abslen = abspath.length();
860         const int baselen = basepath.length();
861         
862         // Find first different character
863         int i = 0;
864         while (i < abslen && i < baselen && abspath[i] == basepath[i]) ++i;
865
866         // Go back to last /
867         if (i < abslen && i < baselen
868             || (i<abslen && abspath[i] != '/' && i == baselen)
869             || (i<baselen && basepath[i] != '/' && i == abslen))
870         {
871                 if (i) --i;     // here was the last match
872                 while (i && abspath[i] != '/') --i;
873         }
874
875         if (i == 0) {
876                 // actually no match - cannot make it relative
877                 return abspath;
878         }
879
880         // Count how many dirs there are in basepath above match
881         // and append as many '..''s into relpath
882         string buf;
883         int j = i;
884         while (j < baselen) {
885                 if (basepath[j] == '/') {
886                         if (j + 1 == baselen) break;
887                         buf += "../";
888                 }
889                 ++j;
890         }
891
892         // Append relative stuff from common directory to abspath
893         if (abspath[i] == '/') ++i;
894         for (; i < abslen; ++i)
895                 buf += abspath[i];
896         // Remove trailing /
897         if (suffixIs(buf, '/'))
898                 buf.erase(buf.length() - 1);
899         // Substitute empty with .
900         if (buf.empty())
901                 buf = '.';
902         return buf;
903 }
904
905
906 // Append sub-directory(ies) to a path in an intelligent way
907 string AddPath(string const & path, string const & path_2)
908 {
909         string buf;
910         string path2 = CleanupPath(path_2);
911
912         if (!path.empty() && path != "." && path != "./") {
913                 buf = CleanupPath(path);
914                 if (path[path.length() - 1] != '/')
915                         buf += '/';
916         }
917
918         if (!path2.empty()){
919                 int p2start = path2.find_first_not_of('/');
920
921                 int p2end = path2.find_last_not_of('/');
922
923                 string tmp = path2.substr(p2start, p2end - p2start + 1);
924                 buf += tmp + '/';
925         }
926         return buf;
927 }
928
929
930 /* 
931  Change extension of oldname to extension.
932  Strips path off if no_path == true.
933  If no extension on oldname, just appends.
934  */
935 string ChangeExtension(string const & oldname, string const & extension)
936 {
937         string::size_type last_slash = oldname.rfind('/');
938         string::size_type last_dot = oldname.rfind('.');
939         if (last_dot < last_slash && last_slash != string::npos)
940                 last_dot = string::npos;
941         
942         string ext;
943         // Make sure the extension starts with a dot
944         if (!extension.empty() && extension[0] != '.')
945                 ext= '.' + extension;
946         else
947                 ext = extension;
948
949         return CleanupPath(oldname.substr(0, last_dot) + ext);
950 }
951
952
953 // Creates a nice compact path for displaying
954 string MakeDisplayPath (string const & path, unsigned int threshold)
955 {
956         const int l1 = path.length();
957
958         // First, we try a relative path compared to home
959         string home = GetEnvPath("HOME");
960         string relhome = MakeRelPath(path, home);
961
962         unsigned int l2 = relhome.length();
963
964         string prefix;
965
966         // If we backup from home or don't have a relative path,
967         // this try is no good
968         if (prefixIs(relhome, "../") || AbsolutePath(relhome)) {
969                 // relative path was no good, just use the original path
970                 relhome = path;
971                 l2 = l1;
972         } else {
973                 prefix = "~/";
974         }
975
976         // Is the path too long?
977         if (l2 > threshold) {
978                 // Yes, shortend it
979                 prefix += ".../";
980                 
981                 string temp;
982                 
983                 while (relhome.length() > threshold)
984                         relhome = split(relhome, temp, '/');
985
986                 // Did we shortend everything away?
987                 if (relhome.empty()) {
988                         // Yes, filename in itself is too long.
989                         // Pick the start and the end of the filename.
990                         relhome = OnlyFilename(path);
991                         string head = relhome.substr(0, threshold/2 - 3);
992
993                         l2 = relhome.length();
994                         string tail =
995                                 relhome.substr(l2 - threshold/2 - 2, l2 - 1);
996                         relhome = head + "..." + tail;
997                 }
998         }
999         return prefix + relhome;
1000 }
1001
1002
1003 bool LyXReadLink(string const & File, string & Link)
1004 {
1005         char LinkBuffer[512];
1006         // Should be PATH_MAX but that needs autconf support
1007         int nRead = readlink(File.c_str(), LinkBuffer, sizeof(LinkBuffer)-1);
1008         if (nRead <= 0)
1009                 return false;
1010         LinkBuffer[nRead] = 0;
1011         Link = LinkBuffer;
1012         return true;
1013 }
1014
1015
1016 typedef pair<int, string> cmdret;
1017 static
1018 cmdret do_popen(string const & cmd)
1019 {
1020         // One question is if we should use popen or
1021         // create our own popen based on fork, exec, pipe
1022         // of course the best would be to have a
1023         // pstream (process stream), with the
1024         // variants ipstream, opstream
1025         FILE * inf = popen(cmd.c_str(), "r");
1026         string ret;
1027         int c = fgetc(inf);
1028         while (c != EOF) {
1029                 ret += static_cast<char>(c);
1030                 c = fgetc(inf);
1031         }
1032         int pret = pclose(inf);
1033         return make_pair(pret, ret);
1034 }
1035
1036
1037 string findtexfile(string const & fil, string const & /*format*/)
1038 {
1039         /* There is no problem to extend this function too use other
1040            methods to look for files. It could be setup to look
1041            in environment paths and also if wanted as a last resort
1042            to a recursive find. One of the easier extensions would
1043            perhaps be to use the LyX file lookup methods. But! I am
1044            going to implement this until I see some demand for it.
1045            Lgb
1046         */
1047         
1048         // If the file can be found directly, we just return a
1049         // absolute path version of it. 
1050         if (FileInfo(fil).exist())
1051                 return MakeAbsPath(fil);
1052
1053         // No we try to find it using kpsewhich.
1054         // It seems from the kpsewhich manual page that it is safe to use
1055         // kpsewhich without --format: "When the --format option is not
1056         // given, the search path used when looking for a file is inferred
1057         // from the name given, by looking for a known extension. If no
1058         // known extension is found, the search path for TeX source files
1059         // is used."
1060         // However, we want to take advantage of the format sine almost all
1061         // the different formats has environment variables that can be used
1062         // to controll which paths to search. f.ex. bib looks in
1063         // BIBINPUTS and TEXBIB. Small list follows:
1064         // bib - BIBINPUTS, TEXBIB
1065         // bst - BSTINPUTS
1066         // graphic/figure - TEXPICTS, TEXINPUTS
1067         // ist - TEXINDEXSTYLE, INDEXSTYLE
1068         // pk - PROGRAMFONTS, PKFONTS, TEXPKS, GLYPHFONTS, TEXFONTS
1069         // tex - TEXINPUTS
1070         // tfm - TFMFONTS, TEXFONTS
1071         // This means that to use kpsewhich in the best possible way we
1072         // should help it by setting additional path in the approp. envir.var.
1073         string kpsecmd = "kpsewhich " + fil;
1074
1075         cmdret c = do_popen(kpsecmd);
1076         
1077         lyxerr[Debug::LATEX] << "kpse status = " << c.first << "\n"
1078                              << "kpse result = `" << strip(c.second, '\n') 
1079                              << "'" << endl;
1080         return c.first != -1 ? strip(c.second, '\n') : string();
1081 }