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