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