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