]> git.lyx.org Git - lyx.git/blob - src/support/filetools.C
John's latest patch frenzy
[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 <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         //lyxerr << "fi : " << fi << endl; 
138         //lyxerr << "fi.exists" << fi.exist() << endl;
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         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         // search first without extension, then with it.
254         if (IsFileReadable(fullname))
255                 return fullname;
256         else if (ext.empty()) 
257                 return string();
258         else { // Is it not more reasonable to use ChangeExtension()? (SMiyata)
259                 fullname += '.';
260                 fullname += ext;
261                 if (IsFileReadable(fullname))
262                         return fullname;
263                 else 
264                         return string();
265         }
266 }
267
268
269 // Search the file name.ext in the subdirectory dir of
270 //   1) user_lyxdir
271 //   2) build_lyxdir (if not empty)
272 //   3) system_lyxdir
273 string const LibFileSearch(string const & dir, string const & name, 
274                            string const & ext)
275 {
276         string fullname = FileSearch(AddPath(user_lyxdir, dir),
277                                      name, ext); 
278         if (!fullname.empty())
279                 return fullname;
280         
281         if (!build_lyxdir.empty()) 
282                 fullname = FileSearch(AddPath(build_lyxdir, dir), 
283                                       name, ext);
284         if (!fullname.empty())
285                 return fullname;
286         
287         return FileSearch(AddPath(system_lyxdir, dir), name, ext);
288 }
289
290
291 string const
292 i18nLibFileSearch(string const & dir, string const & name, 
293                   string const & ext)
294 {
295         string const lang = token(string(GetEnv("LANG")), '_', 0);
296         
297         if (lang.empty() || lang == "C")
298                 return LibFileSearch(dir, name, ext);
299         else {
300                 string const tmp = LibFileSearch(dir, lang + '_' + name,
301                                                  ext);
302                 if (!tmp.empty())
303                         return tmp;
304                 else
305                         return LibFileSearch(dir, name, ext);
306         }
307 }
308
309
310 string const GetEnv(string const & envname)
311 {
312         // f.ex. what about error checking?
313         char const * const ch = getenv(envname.c_str());
314         string const envstr = !ch ? "" : ch;
315         return envstr;
316 }
317
318
319 string const GetEnvPath(string const & name)
320 {
321 #ifndef __EMX__
322         string const pathlist = subst(GetEnv(name), ':', ';');
323 #else
324         string const pathlist = os::slashify_path(GetEnv(name));
325 #endif
326         return strip(pathlist, ';');
327 }
328
329
330 bool PutEnv(string const & envstr)
331 {
332         // CHECK Look at and fix this.
333         // f.ex. what about error checking?
334
335 #if HAVE_PUTENV
336         // this leaks, but what can we do about it?
337         //   Is doing a getenv() and a free() of the older value 
338         //   a good idea? (JMarc)
339         // Actually we don't have to leak...calling putenv like this
340         // should be enough: ... and this is obviously not enough if putenv
341         // does not make a copy of the string. It is also not very wise to
342         // put a string on the free store. If we have to leak we should do it
343         // like this:
344         char * leaker = new char[envstr.length() + 1];
345         envstr.copy(leaker, envstr.length());
346         leaker[envstr.length()] = '\0';
347         int const retval = lyx::putenv(leaker);
348
349         // If putenv does not make a copy of the char const * this
350         // is very dangerous. OTOH if it does take a copy this is the
351         // best solution.
352         // The  only implementation of putenv that I have seen does not
353         // allocate memory. _And_ after testing the putenv in glibc it
354         // seems that we need to make a copy of the string contents.
355         // I will enable the above.
356         //int retval = lyx::putenv(envstr.c_str());
357 #else
358 #ifdef HAVE_SETENV 
359         string varname;
360         string const str = envstr.split(varname,'=');
361         int const retval = ::setenv(varname.c_str(), str.c_str(), true);
362 #else
363         // No environment setting function. Can this happen?
364         int const retval = 1; //return an error condition.
365 #endif
366 #endif
367         return retval == 0;
368 }
369
370
371 bool PutEnvPath(string const & envstr)
372 {
373         return PutEnv(envstr);
374 }
375
376
377 namespace {
378
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::FILES] << "Deleting file: " << unlinkpath 
415                                      << endl;
416
417                 bool deleted = true;
418                 if (FileInfo(unlinkpath).isDir())
419                         deleted = (DeleteAllFilesInDir(unlinkpath) == 0);
420                 deleted &= (lyx::unlink(unlinkpath) == 0);
421                 if (!deleted) {
422                         WriteFSAlert (_("Error! Could not remove file:"), 
423                                       unlinkpath);
424                         return_value = -1;
425                 }
426         }
427         closedir(dir);
428         return return_value;
429 }
430
431
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 int DestroyTmpDir(string const & tmpdir, bool Allfiles)
455 {
456 #ifdef __EMX__
457         Path p(user_lyxdir);
458 #endif
459         if (Allfiles && DeleteAllFilesInDir(tmpdir)) return -1;
460         if (lyx::rmdir(tmpdir)) { 
461                 WriteFSAlert(_("Error! Couldn't delete temporary directory:"), 
462                              tmpdir);
463                 return -1;
464         }
465         return 0; 
466 }
467
468 } // namespace anon
469
470
471 string const CreateBufferTmpDir(string const & pathfor)
472 {
473         static int count;
474         static string const tmpdir(pathfor.empty() ? os::getTmpDir() : pathfor);
475         // We are in our own directory.  Why bother to mangle name?
476         // In fact I wrote this code to circumvent a problematic behaviour (bug?)
477         // of EMX mkstemp().
478         string const tmpfl = tmpdir + "/lyx_tmpbuf" + tostr(count++);
479         if (lyx::mkdir(tmpfl, 0777)) {
480                 WriteFSAlert(_("Error! Couldn't create temporary directory:"),
481                              tmpdir);
482                 return string();
483         }
484         return tmpfl;
485 }
486
487
488 int DestroyBufferTmpDir(string const & tmpdir)
489 {
490         return DestroyTmpDir(tmpdir, true);
491 }
492
493
494 string const CreateLyXTmpDir(string const & deflt)
495 {
496         if ((!deflt.empty()) && (deflt  != "/tmp")) {
497                 if (lyx::mkdir(deflt, 0777)) {
498 #ifdef __EMX__
499                         Path p(user_lyxdir);
500 #endif
501                         string const t(CreateTmpDir(deflt, "lyx_tmpdir"));
502                         return t;
503                 } else
504                         return deflt;
505         } else {
506 #ifdef __EMX__
507                 Path p(user_lyxdir);
508 #endif
509                 string const t(CreateTmpDir("/tmp", "lyx_tmpdir"));
510                 return t;
511         }
512 }
513
514
515 int DestroyLyXTmpDir(string const & tmpdir)
516 {
517        return DestroyTmpDir (tmpdir, false); // Why false?
518 }
519
520
521 // Creates directory. Returns true if succesfull
522 bool createDirectory(string const & path, int permission)
523 {
524         string temp(strip(os::slashify_path(path), '/'));
525
526         if (temp.empty()) {
527                 WriteAlert(_("Internal error!"),
528                            _("Call to createDirectory with invalid name"));
529                 return false;
530         }
531
532         if (lyx::mkdir(temp, permission)) {
533                 WriteFSAlert (_("Error! Couldn't create directory:"), temp);
534                 return false;
535         }
536         return true;
537 }
538
539
540 // Strip filename from path name
541 string const OnlyPath(string const & Filename)
542 {
543         // If empty filename, return empty
544         if (Filename.empty()) return Filename;
545
546         // Find last / or start of filename
547         string::size_type j = Filename.rfind('/');
548         if (j == string::npos)
549                 return "./";
550         return Filename.substr(0, j + 1);
551 }
552
553
554 // Convert relative path into absolute path based on a basepath.
555 // If relpath is absolute, just use that.
556 // If basepath is empty, use CWD as base.
557 string const MakeAbsPath(string const & RelPath, string const & BasePath)
558 {
559         // checks for already absolute path
560         if (AbsolutePath(RelPath))
561                 return RelPath;
562
563         // Copies given paths
564         string TempRel(os::slashify_path(RelPath));
565         // Since TempRel is NOT absolute, we can safely replace "//" with "/"
566         TempRel = subst(TempRel, "//", "/");
567
568         string TempBase;
569
570         if (AbsolutePath(BasePath))
571                 TempBase = BasePath;
572         else
573                 TempBase = AddPath(lyx::getcwd(), BasePath);
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 if (Temp.empty() && !RTemp.empty()) {
604                                 TempBase = os::current_root() + RTemp;
605                                 RTemp.erase();
606                 } else {
607                         // Add this piece to TempBase
608                         if (!suffixIs(TempBase, '/'))
609                                 TempBase += '/';
610                         TempBase += Temp;
611                 }
612         }
613
614         // returns absolute path
615         return os::slashify_path(TempBase);
616 }
617
618
619 // Correctly append filename to the pathname.
620 // If pathname is '.', then don't use pathname.
621 // Chops any path of filename.
622 string const AddName(string const & path, string const & fname)
623 {
624         // Get basename
625         string const basename(OnlyFilename(fname));
626
627         string buf;
628
629         if (path != "." && path != "./" && !path.empty()) {
630                 buf = os::slashify_path(path);
631                 if (!suffixIs(path, '/'))
632                         buf += '/';
633         }
634
635         return buf + basename;
636 }
637
638
639 // Strips path from filename
640 string const OnlyFilename(string const & fname)
641 {
642         if (fname.empty())
643                 return fname;
644
645         string::size_type j = fname.rfind('/');
646         if (j == string::npos) // no '/' in fname
647                 return fname;
648
649         // Strip to basename
650         return fname.substr(j + 1);
651 }
652
653
654 // Is a filename/path absolute?
655 bool AbsolutePath(string const & path)
656 {
657 #ifndef __EMX__
658         return (!path.empty() && path[0] == '/');
659 #else
660         return (!path.empty() && isalpha(static_cast<unsigned char>(path[0])) && path.length() > 1 && path[1] == ':');
661 #endif
662 }
663
664
665 // Create absolute path. If impossible, don't do anything
666 // Supports ./ and ~/. Later we can add support for ~logname/. (Asger)
667 string const ExpandPath(string const & path)
668 {
669         // checks for already absolute path
670         string RTemp(ReplaceEnvironmentPath(path));
671         if (AbsolutePath(RTemp))
672                 return RTemp;
673
674         string Temp;
675         string const copy(RTemp);
676
677         // Split by next /
678         RTemp = split(RTemp, Temp, '/');
679
680         if (Temp == ".") {
681                 return lyx::getcwd() /*GetCWD()*/ + '/' + RTemp;
682         } else if (Temp == "~") {
683                 return GetEnvPath("HOME") + '/' + RTemp;
684         } else if (Temp == "..") {
685                 return MakeAbsPath(copy);
686         } else
687                 // Don't know how to handle this
688                 return copy;
689 }
690
691
692 // Normalize a path
693 // Constracts path/../path
694 // Can't handle "../../" or "/../" (Asger)
695 string const NormalizePath(string const & path)
696 {
697         string TempBase;
698         string RTemp;
699         string Temp;
700
701         if (AbsolutePath(path))
702                 RTemp = path;
703         else
704                 // Make implicit current directory explicit
705                 RTemp = "./" +path;
706
707         while (!RTemp.empty()) {
708                 // Split by next /
709                 RTemp = split(RTemp, Temp, '/');
710                 
711                 if (Temp == ".") {
712                         TempBase = "./";
713                 } else if (Temp == "..") {
714                         // Remove one level of TempBase
715                         string::difference_type i = TempBase.length() - 2;
716                         while (i > 0 && TempBase[i] != '/')
717                                 --i;
718                         if (i >= 0 && TempBase[i] == '/')
719                                 TempBase.erase(i + 1, string::npos);
720                         else
721                                 TempBase = "../";
722                 } else {
723                         TempBase += Temp + '/';
724                 }
725         }
726
727         // returns absolute path
728         return TempBase;        
729 }
730
731
732 string const GetFileContents(string const & fname)
733 {
734         FileInfo finfo(fname);
735         if (finfo.exist()) {
736                 ifstream ifs(fname.c_str());
737                 ostringstream ofs;
738                 if (ifs && ofs) {
739                         ofs << ifs.rdbuf();
740                         ifs.close();
741                         return ofs.str().c_str();
742                 }
743         }
744         lyxerr << "LyX was not able to read file '" << fname << "'" << endl;
745         return string();
746 }
747
748
749 //
750 // Search ${...} as Variable-Name inside the string and replace it with
751 // the denoted environmentvariable
752 // Allow Variables according to 
753 //  variable :=  '$' '{' [A-Za-z_]{[A-Za-z_0-9]*} '}'
754 //
755
756 string const ReplaceEnvironmentPath(string const & path)
757 {
758 // 
759 // CompareChar: Environmentvariables starts with this character
760 // PathChar:    Next path component start with this character
761 // while CompareChar found do:
762 //       Split String with PathChar
763 //       Search Environmentvariable
764 //       if found: Replace Strings
765 //
766         char const CompareChar = '$';
767         char const FirstChar = '{'; 
768         char const EndChar = '}'; 
769         char const UnderscoreChar = '_'; 
770         string EndString; EndString += EndChar;
771         string FirstString; FirstString += FirstChar;
772         string CompareString; CompareString += CompareChar;
773         string const RegExp("*}*"); // Exist EndChar inside a String?
774
775 // first: Search for a '$' - Sign.
776         //string copy(path);
777         string result1; //(copy);    // for split-calls
778         string result0 = split(path, result1, CompareChar);
779         while (!result0.empty()) {
780                 string copy1(result0); // contains String after $
781                 
782                 // Check, if there is an EndChar inside original String.
783                 
784                 if (!regexMatch(copy1, RegExp)) {
785                         // No EndChar inside. So we are finished
786                         result1 += CompareString + result0;
787                         result0.erase();
788                         continue;
789                 }
790
791                 string res1;
792                 string res0 = split(copy1, res1, EndChar);
793                 // Now res1 holds the environmentvariable
794                 // First, check, if Contents is ok.
795                 if (res1.empty()) { // No environmentvariable. Continue Loop.
796                         result1 += CompareString + FirstString;
797                         result0  = res0;
798                         continue;
799                 }
800                 // check contents of res1
801                 char const * res1_contents = res1.c_str();
802                 if (*res1_contents != FirstChar) {
803                         // Again No Environmentvariable
804                         result1 += CompareString;
805                         result0 = res0;
806                 }
807
808                 // Check for variable names
809                 // Situation ${} is detected as "No Environmentvariable"
810                 char const * cp1 = res1_contents + 1;
811                 bool result = isalpha(*cp1) || (*cp1 == UnderscoreChar);
812                 ++cp1;
813                 while (*cp1 && result) {
814                         result = isalnum(*cp1) || 
815                                 (*cp1 == UnderscoreChar); 
816                         ++cp1;
817                 }
818
819                 if (!result) {
820                         // no correct variable name
821                         result1 += CompareString + res1 + EndString;
822                         result0  = split(res0, res1, CompareChar);
823                         result1 += res1;
824                         continue;
825                 }
826             
827                 string env(GetEnv(res1_contents + 1));
828                 if (!env.empty()) {
829                         // Congratulations. Environmentvariable found
830                         result1 += env;
831                 } else {
832                         result1 += CompareString + res1 + EndString;
833                 }
834                 // Next $-Sign?
835                 result0  = split(res0, res1, CompareChar);
836                 result1 += res1;
837         } 
838         return result1;
839 }  // ReplaceEnvironmentPath
840
841
842 // Make relative path out of two absolute paths
843 string const MakeRelPath(string const & abspath, string const & basepath)
844 // Makes relative path out of absolute path. If it is deeper than basepath,
845 // it's easy. If basepath and abspath share something (they are all deeper
846 // than some directory), it'll be rendered using ..'s. If they are completely
847 // different, then the absolute path will be used as relative path.
848 {
849         string::size_type const abslen = abspath.length();
850         string::size_type const baselen = basepath.length();
851
852         string::size_type i = os::common_path(abspath, basepath);
853
854         if (i == 0) {
855                 // actually no match - cannot make it relative
856                 return abspath;
857         }
858
859         // Count how many dirs there are in basepath above match
860         // and append as many '..''s into relpath
861         string buf;
862         string::size_type j = i;
863         while (j < baselen) {
864                 if (basepath[j] == '/') {
865                         if (j + 1 == baselen) break;
866                         buf += "../";
867                 }
868                 ++j;
869         }
870
871         // Append relative stuff from common directory to abspath
872         if (abspath[i] == '/') ++i;
873         for (; i < abslen; ++i)
874                 buf += abspath[i];
875         // Remove trailing /
876         if (suffixIs(buf, '/'))
877                 buf.erase(buf.length() - 1);
878         // Substitute empty with .
879         if (buf.empty())
880                 buf = '.';
881         return buf;
882 }
883
884
885 // Append sub-directory(ies) to a path in an intelligent way
886 string const AddPath(string const & path, string const & path_2)
887 {
888         string buf;
889         string const path2 = os::slashify_path(path_2);
890
891         if (!path.empty() && path != "." && path != "./") {
892                 buf = os::slashify_path(path);
893                 if (path[path.length() - 1] != '/')
894                         buf += '/';
895         }
896
897         if (!path2.empty()) {
898                 string::size_type const p2start = path2.find_first_not_of('/');
899                 string::size_type const p2end = path2.find_last_not_of('/');
900                 string const tmp = path2.substr(p2start, p2end - p2start + 1);
901                 buf += tmp + '/';
902         }
903         return buf;
904 }
905
906
907 /* 
908  Change extension of oldname to extension.
909  Strips path off if no_path == true.
910  If no extension on oldname, just appends.
911  */
912 string const
913 ChangeExtension(string const & oldname, string const & extension)
914 {
915         string::size_type const last_slash = oldname.rfind('/');
916         string::size_type last_dot = oldname.rfind('.');
917         if (last_dot < last_slash && last_slash != string::npos)
918                 last_dot = string::npos;
919         
920         string ext;
921         // Make sure the extension starts with a dot
922         if (!extension.empty() && extension[0] != '.')
923                 ext= "." + extension;
924         else
925                 ext = extension;
926
927         return os::slashify_path(oldname.substr(0, last_dot) + ext);
928 }
929
930
931 /// Return the extension of the file (not including the .)
932 string const GetExtension(string const & name)
933 {
934         string::size_type const last_slash = name.rfind('/');
935         string::size_type const last_dot = name.rfind('.');
936         if (last_dot != string::npos &&
937             (last_slash == string::npos || last_dot > last_slash))
938                 return name.substr(last_dot + 1,
939                                    name.length() - (last_dot + 1));
940         else
941                 return string();
942 }
943
944
945 // Creates a nice compact path for displaying
946 string const
947 MakeDisplayPath (string const & path, unsigned int threshold)
948 {
949         string::size_type const l1 = path.length();
950
951         // First, we try a relative path compared to home
952         string const home(GetEnvPath("HOME"));
953         string relhome = MakeRelPath(path, home);
954
955         string::size_type l2 = relhome.length();
956
957         string prefix;
958
959         // If we backup from home or don't have a relative path,
960         // this try is no good
961         if (prefixIs(relhome, "../") || AbsolutePath(relhome)) {
962                 // relative path was no good, just use the original path
963                 relhome = path;
964                 l2 = l1;
965         } else {
966                 prefix = "~/";
967         }
968
969         // Is the path too long?
970         if (l2 > threshold) {
971                 // Yes, shortend it
972                 prefix += ".../";
973                 
974                 string temp;
975                 
976                 while (relhome.length() > threshold)
977                         relhome = split(relhome, temp, '/');
978
979                 // Did we shortend everything away?
980                 if (relhome.empty()) {
981                         // Yes, filename in itself is too long.
982                         // Pick the start and the end of the filename.
983                         relhome = OnlyFilename(path);
984                         string const head = relhome.substr(0, threshold/2 - 3);
985
986                         l2 = relhome.length();
987                         string const tail =
988                                 relhome.substr(l2 - threshold/2 - 2, l2 - 1);
989                         relhome = head + "..." + tail;
990                 }
991         }
992         return prefix + relhome;
993 }
994
995
996 bool LyXReadLink(string const & File, string & Link)
997 {
998         char LinkBuffer[512];
999         // Should be PATH_MAX but that needs autconf support
1000         int const nRead = ::readlink(File.c_str(),
1001                                      LinkBuffer, sizeof(LinkBuffer) - 1);
1002         if (nRead <= 0)
1003                 return false;
1004         LinkBuffer[nRead] = '\0'; // terminator
1005         Link = LinkBuffer;
1006         return true;
1007 }
1008
1009
1010 namespace {
1011
1012 typedef pair<int, string> cmdret;
1013
1014 cmdret const do_popen(string const & cmd)
1015 {
1016         // One question is if we should use popen or
1017         // create our own popen based on fork, exec, pipe
1018         // of course the best would be to have a
1019         // pstream (process stream), with the
1020         // variants ipstream, opstream
1021         FILE * inf = ::popen(cmd.c_str(), "r");
1022         string ret;
1023         int c = fgetc(inf);
1024         while (c != EOF) {
1025                 ret += static_cast<char>(c);
1026                 c = fgetc(inf);
1027         }
1028         int const pret = pclose(inf);
1029         return make_pair(pret, ret);
1030 }
1031
1032 } // namespace anon
1033
1034
1035 string const
1036 findtexfile(string const & fil, string const & /*format*/)
1037 {
1038         /* There is no problem to extend this function too use other
1039            methods to look for files. It could be setup to look
1040            in environment paths and also if wanted as a last resort
1041            to a recursive find. One of the easier extensions would
1042            perhaps be to use the LyX file lookup methods. But! I am
1043            going to implement this until I see some demand for it.
1044            Lgb
1045         */
1046         
1047         // If the file can be found directly, we just return a
1048         // absolute path version of it. 
1049         if (FileInfo(fil).exist())
1050                 return MakeAbsPath(fil);
1051
1052         // No we try to find it using kpsewhich.
1053         // It seems from the kpsewhich manual page that it is safe to use
1054         // kpsewhich without --format: "When the --format option is not
1055         // given, the search path used when looking for a file is inferred
1056         // from the name given, by looking for a known extension. If no
1057         // known extension is found, the search path for TeX source files
1058         // is used."
1059         // However, we want to take advantage of the format sine almost all
1060         // the different formats has environment variables that can be used
1061         // to controll which paths to search. f.ex. bib looks in
1062         // BIBINPUTS and TEXBIB. Small list follows:
1063         // bib - BIBINPUTS, TEXBIB
1064         // bst - BSTINPUTS
1065         // graphic/figure - TEXPICTS, TEXINPUTS
1066         // ist - TEXINDEXSTYLE, INDEXSTYLE
1067         // pk - PROGRAMFONTS, PKFONTS, TEXPKS, GLYPHFONTS, TEXFONTS
1068         // tex - TEXINPUTS
1069         // tfm - TFMFONTS, TEXFONTS
1070         // This means that to use kpsewhich in the best possible way we
1071         // should help it by setting additional path in the approp. envir.var.
1072         string const kpsecmd = "kpsewhich " + fil;
1073
1074         cmdret const c = do_popen(kpsecmd);
1075         
1076         lyxerr[Debug::LATEX] << "kpse status = " << c.first << "\n"
1077                              << "kpse result = `" << strip(c.second, '\n') 
1078                              << "'" << endl;
1079         if (c.first != -1) 
1080                 return strip(strip(c.second, '\n'), '\r');
1081         else
1082                 return string();
1083 }
1084
1085
1086 void removeAutosaveFile(string const & filename)
1087 {
1088         string a = OnlyPath(filename);
1089         a += '#';
1090         a += OnlyFilename(filename);
1091         a += '#';
1092         FileInfo const fileinfo(a);
1093         if (fileinfo.exist()) {
1094                 if (lyx::unlink(a) != 0) {
1095                         WriteFSAlert(_("Could not delete auto-save file!"), a);
1096                 }
1097         }
1098 }