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