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