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