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