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