]> git.lyx.org Git - lyx.git/blob - src/support/filetools.C
Fixed a BUG where if in a PATH was something like ;; the scan for the
[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 << "Not able to find a uniq tmpfile name." << endl;
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                   do {
205                     tmppath = split(tmppath, path_element, ';');
206                   } while(!tmppath.empty() && path_element.empty());
207                 } else {
208                   notfound = false;
209                 }
210         }
211 #ifdef __EMX__
212         if (ext.empty() && notfound) {
213                 real_file = FileOpenSearch(path, name, "exe");
214                 if (notfound) real_file = FileOpenSearch(path, name, "cmd");
215         }
216 #endif
217         return real_file;
218 }
219
220
221 // Returns the real name of file name in directory path, with optional
222 // extension ext.  
223 string FileSearch(string const & path, string const & name, 
224                   string const & ext)
225 {
226         // if `name' is an absolute path, we ignore the setting of `path'
227         // Expand Environmentvariables in 'name'
228         string tmpname = ReplaceEnvironmentPath(name);
229         string fullname = MakeAbsPath(tmpname, path);
230         
231         // search first without extension, then with it.
232         if (IsFileReadable(fullname))
233                 return fullname;
234         else if (ext.empty()) 
235                 return string();
236         else { // Is it not more reasonable to use ChangeExtension()? (SMiyata)
237                 fullname += '.';
238                 fullname += ext;
239                 if (IsFileReadable(fullname))
240                         return fullname;
241                 else 
242                         return string();
243         }
244 }
245
246
247 // Search the file name.ext in the subdirectory dir of
248 //   1) user_lyxdir
249 //   2) build_lyxdir (if not empty)
250 //   3) system_lyxdir
251 string LibFileSearch(string const & dir, string const & name, 
252                       string const & ext)
253 {
254         string fullname = FileSearch(AddPath(user_lyxdir,dir), name,
255                                       ext); 
256         if (!fullname.empty())
257                 return fullname;
258
259         if (!build_lyxdir.empty()) 
260                 fullname = FileSearch(AddPath(build_lyxdir, dir), 
261                                       name, ext);
262         if (!fullname.empty())
263                 return fullname;
264
265         return FileSearch(AddPath(system_lyxdir,dir), name, ext);
266 }
267
268
269 string i18nLibFileSearch(string const & dir, string const & name, 
270                           string const & ext)
271 {
272         string lang = token(string(GetEnv("LANG")), '_', 0);
273
274         if (lang.empty() || lang == "C")
275                 return LibFileSearch(dir, name, ext);
276         else {
277                 string tmp = LibFileSearch(dir, lang + '_' + name,
278                                             ext);
279                 if (!tmp.empty())
280                         return tmp;
281                 else
282                         return LibFileSearch(dir, name, ext);
283         }
284 }
285
286
287 string GetEnv(string const & envname)
288 {
289         // f.ex. what about error checking?
290         char const * const ch = getenv(envname.c_str());
291         string envstr = !ch ? "" : ch;
292         return envstr;
293 }
294
295
296 string GetEnvPath(string const & name)
297 {
298 #ifndef __EMX__
299         string pathlist = subst(GetEnv(name), ':', ';');
300 #else
301         string pathlist = subst(GetEnv(name), '\\', '/');
302 #endif
303         return strip(pathlist, ';');
304 }
305
306
307 bool PutEnv(string const & envstr)
308 {
309 #warning Look at and fix this.
310         // f.ex. what about error checking?
311         int retval = 0;
312 #if HAVE_PUTENV
313         // this leaks, but what can we do about it?
314         //   Is doing a getenv() and a free() of the older value 
315         //   a good idea? (JMarc)
316         retval = putenv((new string(envstr))->c_str());
317 #else
318 #ifdef HAVE_SETENV 
319         string varname;
320         string str = envstr.split(varname,'=');
321         retval = setenv(varname.c_str(), str.c_str(), true);
322 #endif
323 #endif
324         return retval == 0;
325 }
326
327
328 bool PutEnvPath(string const & envstr)
329 {
330         string pathlist = envstr;
331 #warning Verify that this is correct.
332 #ifdef __EMX__
333         pathlist.subst(':', ';');
334         pathlist.subst('/', '\\');
335 #endif
336         return PutEnv(pathlist);
337 }
338
339
340 static
341 int DeleteAllFilesInDir (string const & path)
342 {
343         DIR * dir;
344         struct dirent * de;
345         dir = opendir(path.c_str());
346         if (!dir) {
347                 WriteFSAlert (_("Error! Cannot open directory:"), path);
348                 return -1;
349         }
350         while ((de = readdir(dir))) {
351                 string temp = de->d_name;
352                 if (temp=="." || temp=="..") 
353                         continue;
354                 string unlinkpath = AddName (path, temp);
355
356                 lyxerr.debug() << "Deleting file: " << unlinkpath << endl;
357
358                 if (remove (unlinkpath.c_str()))
359                         WriteFSAlert (_("Error! Could not remove file:"), 
360                                       unlinkpath);
361         }
362         closedir (dir);
363         return 0;
364 }
365
366
367 static
368 string CreateTmpDir (string const & tempdir, string const & mask)
369 {
370         string tmpfl = TmpFileName(tempdir, mask);
371         
372         if ((tmpfl.empty()) || mkdir (tmpfl.c_str(), 0777)) {
373                 WriteFSAlert(_("Error! Couldn't create temporary directory:"),
374                              tempdir);
375                 return string();
376         }
377         return MakeAbsPath(tmpfl);
378 }
379
380
381 static
382 int DestroyTmpDir (string const & tmpdir, bool Allfiles)
383 {
384         if ((Allfiles) && (DeleteAllFilesInDir (tmpdir))) return -1;
385         if (rmdir(tmpdir.c_str())) { 
386 #ifdef __EMX__
387                 if (errno == EBUSY) {
388                         chdir(user_lyxdir.c_str()); // They are in the same drive.
389                         if (!rmdir(tmpdir.c_str())) return 0;
390                 }
391 #endif
392                 WriteFSAlert(_("Error! Couldn't delete temporary directory:"), 
393                              tmpdir);
394                 return -1;
395         }
396         return 0; 
397
398
399
400 string CreateBufferTmpDir (string const & pathfor)
401 {
402         return CreateTmpDir (pathfor, "lyx_bufrtmp");
403 }
404
405
406 int DestroyBufferTmpDir (string const & tmpdir)
407 {
408         return DestroyTmpDir (tmpdir, true);
409 }
410
411
412 string CreateLyXTmpDir (string const & deflt)
413 {
414         string t;        
415
416         if ((!deflt.empty()) && (deflt  != "/tmp")) {
417                 if (mkdir (deflt.c_str(), 0777)) {
418 #ifdef __EMX__
419                         PathPush(user_lyxdir);
420 #endif
421                         t = CreateTmpDir (deflt.c_str(), "lyx_tmp");
422 #ifdef __EMX__
423                         PathPop();
424 #endif
425                         return t;
426                 } else
427                         return deflt;
428         } else {
429 #ifdef __EMX__
430                 PathPush(user_lyxdir);
431 #endif
432                 t = CreateTmpDir ("/tmp", "lyx_tmp");
433 #ifdef __EMX__
434                 PathPop();
435 #endif
436                 return t;
437         }
438 }
439
440
441 int DestroyLyXTmpDir (string const & tmpdir)
442 {
443        return DestroyTmpDir (tmpdir, false); // Why false?
444 }
445
446
447 // Creates directory. Returns true if succesfull
448 bool createDirectory(string const & path, int permission)
449 {
450         string temp = strip(CleanupPath(path), '/');
451
452         if (temp.empty()) {
453                 WriteAlert(_("Internal error!"),
454                            _("Call to createDirectory with invalid name"));
455                 return false;
456         }
457
458         if (mkdir(temp.c_str(), permission)) {
459                 WriteFSAlert (_("Error! Couldn't create directory:"), temp);
460                 return false;
461         }
462         return true;
463 }
464
465
466 // Returns current working directory
467 string GetCWD ()
468 {
469         int n = 256;    // Assume path is less than 256 chars
470         char * err;
471         char * tbuf = new char [n];
472         string result;
473         
474         // Safe. Hopefully all getcwds behave this way!
475         while (((err = getcwd (tbuf, n)) == 0) && (errno == ERANGE)) {
476                 // Buffer too small, double the buffersize and try again
477                 delete[] tbuf;
478                 n = 2*n;
479                 tbuf = new char [n];
480         }
481
482         if (err) result = tbuf;
483         delete[] tbuf;
484         return result;
485 }
486
487
488 // Strip filename from path name
489 string OnlyPath(string const & Filename)
490 {
491         // If empty filename, return empty
492         if (Filename.empty()) return Filename;
493
494         // Find last / or start of filename
495         string::size_type j = Filename.rfind('/');
496         if (j==string::npos)
497                 return "./";
498         return Filename.substr(0, j + 1);
499 }
500
501
502 // Convert relative path into absolute path based on a basepath.
503 // If relpath is absolute, just use that.
504 // If basepath is empty, use CWD as base.
505 string MakeAbsPath(string const & RelPath, string const & BasePath)
506 {
507         // checks for already absolute path
508         if (AbsolutePath(RelPath))
509 #ifdef __EMX__
510                 if(RelPath[0]!='/' || RelPath[0]!='\\')
511 #endif
512                 return RelPath;
513
514         // Copies given paths
515         string TempRel = CleanupPath(RelPath);
516
517         string TempBase;
518
519         if (!BasePath.empty()) {
520 #ifndef __EMX__
521                 TempBase = BasePath;
522 #else
523                 char * with_drive = new char[_MAX_PATH];
524                 _abspath(with_drive, BasePath.c_str(), _MAX_PATH);
525                 TempBase = with_drive;
526                 delete[] with_drive;
527 #endif
528         } else
529                 TempBase = GetCWD();
530 #ifdef __EMX__
531         if (AbsolutePath(TempRel))
532                 return TempBase[0] + TempRel;
533 #endif
534
535         // Handle /./ at the end of the path
536         while(suffixIs(TempBase, "/./"))
537                 TempBase.erase(TempBase.length() - 2);
538
539         // processes relative path
540         string RTemp = TempRel;
541         string Temp;
542
543         while (!RTemp.empty()) {
544                 // Split by next /
545                 RTemp = split(RTemp, Temp, '/');
546                 
547                 if (Temp==".") continue;
548                 if (Temp=="..") {
549                         // Remove one level of TempBase
550                         int i = TempBase.length()-2;
551 #ifndef __EMX__
552                         if (i<0) i=0;
553                         while (i>0 && TempBase[i] != '/') --i;
554                         if (i>0)
555 #else
556                                 if (i<2) i=2;
557                         while (i>2 && TempBase[i] != '/') --i;
558                         if (i>2)
559 #endif
560                                 TempBase.erase(i, string::npos);
561                         else
562                                 TempBase += '/';
563                 } else {
564                         // Add this piece to TempBase
565                         if (!suffixIs(TempBase, '/'))
566                                 TempBase += '/';
567                         TempBase += Temp;
568                 }
569         }
570
571         // returns absolute path
572         return TempBase;        
573 }
574
575
576 // Correctly append filename to the pathname.
577 // If pathname is '.', then don't use pathname.
578 // Chops any path of filename.
579 string AddName(string const & path, string const & fname)
580 {
581         // Get basename
582         string basename = OnlyFilename(fname);
583
584         string buf;
585
586         if (path != "." && path != "./" && !path.empty()) {
587                 buf = CleanupPath(path);
588                 if (!suffixIs(path, '/'))
589                         buf += '/';
590         }
591
592         return buf + basename;
593 }
594
595
596 // Strips path from filename
597 string OnlyFilename(string const & fname)
598 {
599         Assert(!fname.empty()); // We don't allow empty filename. (Lgb)
600
601         string::size_type j = fname.rfind('/');
602         if (j == string::npos) // no '/' in fname
603                 return fname;
604
605         // Strip to basename
606         return fname.substr(j + 1);
607 }
608
609
610 // Is a filename/path absolute?
611 bool AbsolutePath(string const & path)
612 {
613 #ifndef __EMX__
614         return (!path.empty() && path[0] == '/');
615 #else
616         return (!path.empty() && path[0]=='/' || (isalpha((unsigned char) path[0]) && path[1]==':'));
617 #endif
618 }
619
620
621 // Create absolute path. If impossible, don't do anything
622 // Supports ./ and ~/. Later we can add support for ~logname/. (Asger)
623 string ExpandPath(string const & path)
624 {
625         Assert(!path.empty()); // We don't allow empty path. (Lgb)
626         // checks for already absolute path
627         string RTemp = ReplaceEnvironmentPath(path);
628         if (AbsolutePath(RTemp))
629                 return RTemp;
630
631         string Temp;
632         string copy(RTemp);
633
634         // Split by next /
635         RTemp=split(RTemp, Temp, '/');
636
637         if (Temp==".") {
638                 return GetCWD() + '/' + RTemp;
639         } else if (Temp=="~") {
640                 return GetEnvPath("HOME") + '/' + RTemp;
641         } else if (Temp=="..") {
642                 return MakeAbsPath(copy);
643         } else
644                 // Don't know how to handle this
645                 return copy;
646 }
647
648
649 // Normalize a path
650 // Constracts path/../path
651 // Can't handle "../../" or "/../" (Asger)
652 string NormalizePath(string const & path)
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 }