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