]> git.lyx.org Git - lyx.git/blob - src/support/filetools.C
the fstream/iostream changes and some small other things
[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),
275                                      name, 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 #if HAVE_PUTENV
334         // this leaks, but what can we do about it?
335         //   Is doing a getenv() and a free() of the older value 
336         //   a good idea? (JMarc)
337         int retval = putenv((new string(envstr))->c_str());
338 #else
339 #ifdef HAVE_SETENV 
340         string varname;
341         string str = envstr.split(varname,'=');
342         int retval = setenv(varname.c_str(), str.c_str(), true);
343 #endif
344 #endif
345         return retval == 0;
346 }
347
348
349 bool PutEnvPath(string const & envstr)
350 {
351         return PutEnv(envstr);
352 }
353
354
355 static
356 int DeleteAllFilesInDir (string const & path)
357 {
358         DIR * dir = opendir(path.c_str());
359         if (!dir) {
360                 WriteFSAlert (_("Error! Cannot open directory:"), path);
361                 return -1;
362         }
363         struct dirent * de;
364         while ((de = readdir(dir))) {
365                 string temp = de->d_name;
366                 if (temp == "." || temp == "..") 
367                         continue;
368                 string unlinkpath = AddName (path, temp);
369
370                 lyxerr.debug() << "Deleting file: " << unlinkpath << endl;
371
372                 if (remove(unlinkpath.c_str()))
373                         WriteFSAlert (_("Error! Could not remove file:"), 
374                                       unlinkpath);
375         }
376         closedir(dir);
377         return 0;
378 }
379
380
381 static
382 string CreateTmpDir (string const & tempdir, string const & mask)
383 {
384         string tmpfl = TmpFileName(tempdir, mask);
385         
386         if ((tmpfl.empty()) || mkdir (tmpfl.c_str(), 0777)) {
387                 WriteFSAlert(_("Error! Couldn't create temporary directory:"),
388                              tempdir);
389                 return string();
390         }
391         return MakeAbsPath(tmpfl);
392 }
393
394
395 static
396 int DestroyTmpDir (string const & tmpdir, bool Allfiles)
397 {
398 #ifdef __EMX__
399         Path p(user_lyxdir);
400 #endif
401         if (Allfiles && DeleteAllFilesInDir(tmpdir)) return -1;
402         if (rmdir(tmpdir.c_str())) { 
403                 WriteFSAlert(_("Error! Couldn't delete temporary directory:"), 
404                              tmpdir);
405                 return -1;
406         }
407         return 0; 
408
409
410
411 string CreateBufferTmpDir (string const & pathfor)
412 {
413         return CreateTmpDir(pathfor, "lyx_bufrtmp");
414 }
415
416
417 int DestroyBufferTmpDir (string const & tmpdir)
418 {
419         return DestroyTmpDir(tmpdir, true);
420 }
421
422
423 string CreateLyXTmpDir (string const & deflt)
424 {
425         if ((!deflt.empty()) && (deflt  != "/tmp")) {
426                 if (mkdir(deflt.c_str(), 0777)) {
427 #ifdef __EMX__
428                         Path p(user_lyxdir);
429 #endif
430                         string t = CreateTmpDir (deflt.c_str(), "lyx_tmp");
431                         return t;
432                 } else
433                         return deflt;
434         } else {
435 #ifdef __EMX__
436                 Path p(user_lyxdir);
437 #endif
438                 string t = CreateTmpDir ("/tmp", "lyx_tmp");
439                 return t;
440         }
441 }
442
443
444 int DestroyLyXTmpDir (string const & tmpdir)
445 {
446        return DestroyTmpDir (tmpdir, false); // Why false?
447 }
448
449
450 // Creates directory. Returns true if succesfull
451 bool createDirectory(string const & path, int permission)
452 {
453         string temp = strip(CleanupPath(path), '/');
454
455         if (temp.empty()) {
456                 WriteAlert(_("Internal error!"),
457                            _("Call to createDirectory with invalid name"));
458                 return false;
459         }
460
461         if (mkdir(temp.c_str(), permission)) {
462                 WriteFSAlert (_("Error! Couldn't create directory:"), temp);
463                 return false;
464         }
465         return true;
466 }
467
468
469 // Returns current working directory
470 string GetCWD ()
471 {
472         int n = 256;    // Assume path is less than 256 chars
473         char * err;
474         char * tbuf = new char[n];
475         
476         // Safe. Hopefully all getcwds behave this way!
477         while (((err = lyx::getcwd (tbuf, n)) == 0) && (errno == ERANGE)) {
478                 // Buffer too small, double the buffersize and try again
479                 delete[] tbuf;
480                 n = 2 * n;
481                 tbuf = new char[n];
482         }
483
484         string result;
485         if (err) result = tbuf;
486         delete[] tbuf;
487         return result;
488 }
489
490
491 // Strip filename from path name
492 string OnlyPath(string const & Filename)
493 {
494         // If empty filename, return empty
495         if (Filename.empty()) return Filename;
496
497         // Find last / or start of filename
498         string::size_type j = Filename.rfind('/');
499         if (j == string::npos)
500                 return "./";
501         return Filename.substr(0, j + 1);
502 }
503
504
505 // Convert relative path into absolute path based on a basepath.
506 // If relpath is absolute, just use that.
507 // If basepath is empty, use CWD as base.
508 string MakeAbsPath(string const & RelPath, string const & BasePath)
509 {
510         // checks for already absolute path
511         if (AbsolutePath(RelPath))
512 #ifdef __EMX__
513                 if(RelPath[0]!= '/' && RelPath[0]!= '\\')
514 #endif
515                 return RelPath;
516
517         // Copies given paths
518         string TempRel = CleanupPath(RelPath);
519
520         string TempBase;
521
522         if (!BasePath.empty()) {
523 #ifndef __EMX__
524                 TempBase = BasePath;
525 #else
526                 char * with_drive = new char[_MAX_PATH];
527                 _abspath(with_drive, BasePath.c_str(), _MAX_PATH);
528                 TempBase = with_drive;
529                 delete[] with_drive;
530 #endif
531         } else
532                 TempBase = GetCWD();
533 #ifdef __EMX__
534         if (AbsolutePath(TempRel))
535                 return TempBase.substr(0, 2) + TempRel;
536 #endif
537
538         // Handle /./ at the end of the path
539         while(suffixIs(TempBase, "/./"))
540                 TempBase.erase(TempBase.length() - 2);
541
542         // processes relative path
543         string RTemp = TempRel;
544         string Temp;
545
546         while (!RTemp.empty()) {
547                 // Split by next /
548                 RTemp = split(RTemp, Temp, '/');
549                 
550                 if (Temp == ".") continue;
551                 if (Temp == "..") {
552                         // Remove one level of TempBase
553                         int i = TempBase.length() - 2;
554 #ifndef __EMX__
555                         if (i < 0) i = 0;
556                         while (i > 0 && TempBase[i] != '/') --i;
557                         if (i > 0)
558 #else
559                         if (i < 2) i = 2;
560                         while (i > 2 && TempBase[i] != '/') --i;
561                         if (i > 2)
562 #endif
563                                 TempBase.erase(i, string::npos);
564                         else
565                                 TempBase += '/';
566                 } else {
567                         // Add this piece to TempBase
568                         if (!suffixIs(TempBase, '/'))
569                                 TempBase += '/';
570                         TempBase += Temp;
571                 }
572         }
573
574         // returns absolute path
575         return TempBase;
576 }
577
578
579 // Correctly append filename to the pathname.
580 // If pathname is '.', then don't use pathname.
581 // Chops any path of filename.
582 string AddName(string const & path, string const & fname)
583 {
584         // Get basename
585         string basename = OnlyFilename(fname);
586
587         string buf;
588
589         if (path != "." && path != "./" && !path.empty()) {
590                 buf = CleanupPath(path);
591                 if (!suffixIs(path, '/'))
592                         buf += '/';
593         }
594
595         return buf + basename;
596 }
597
598
599 // Strips path from filename
600 string OnlyFilename(string const & fname)
601 {
602         if (fname.empty())
603                 return fname;
604
605         string::size_type j = fname.rfind('/');
606         if (j == string::npos) // no '/' in fname
607                 return fname;
608
609         // Strip to basename
610         return fname.substr(j + 1);
611 }
612
613
614 // Is a filename/path absolute?
615 bool AbsolutePath(string const & path)
616 {
617 #ifndef __EMX__
618         return (!path.empty() && path[0] == '/');
619 #else
620         return (!path.empty() && (path[0] == '/' || (isalpha(static_cast<unsigned char>(path[0])) && path.length()>1 && path[1] == ':')));
621 #endif
622 }
623
624
625 // Create absolute path. If impossible, don't do anything
626 // Supports ./ and ~/. Later we can add support for ~logname/. (Asger)
627 string ExpandPath(string const & path)
628 {
629         // checks for already absolute path
630         string RTemp = ReplaceEnvironmentPath(path);
631         if (AbsolutePath(RTemp))
632                 return RTemp;
633
634         string Temp;
635         string copy(RTemp);
636
637         // Split by next /
638         RTemp= split(RTemp, Temp, '/');
639
640         if (Temp == ".") {
641                 return GetCWD() + '/' + RTemp;
642         } else if (Temp == "~") {
643                 return GetEnvPath("HOME") + '/' + RTemp;
644         } else if (Temp == "..") {
645                 return MakeAbsPath(copy);
646         } else
647                 // Don't know how to handle this
648                 return copy;
649 }
650
651
652 // Normalize a path
653 // Constracts path/../path
654 // Can't handle "../../" or "/../" (Asger)
655 string NormalizePath(string const & path)
656 {
657         string TempBase;
658         string RTemp;
659         string Temp;
660
661         if (AbsolutePath(path))
662                 RTemp = path;
663         else
664                 // Make implicit current directory explicit
665                 RTemp = "./" +path;
666
667         while (!RTemp.empty()) {
668                 // Split by next /
669                 RTemp = split(RTemp, Temp, '/');
670                 
671                 if (Temp == ".") {
672                         TempBase = "./";
673                 } else if (Temp == "..") {
674                         // Remove one level of TempBase
675                         int i = TempBase.length() - 2;
676                         while (i > 0 && TempBase[i] != '/')
677                                 --i;
678                         if (i >= 0 && TempBase[i] == '/')
679                                 TempBase.erase(i + 1, string::npos);
680                         else
681                                 TempBase = "../";
682                 } else {
683                         TempBase += Temp + '/';
684                 }
685         }
686
687         // returns absolute path
688         return TempBase;        
689 }
690
691 string CleanupPath(string const & path) 
692 {
693 #ifdef __EMX__    /* SMiyata: This should fix searchpath bug. */
694         string temppath = subst(path, '\\', '/');
695         temppath = subst(temppath, "//", "/");
696         return lowercase(temppath);
697 #else // On unix, nothing to do
698         return path;
699 #endif
700 }
701
702
703 //
704 // Search ${...} as Variable-Name inside the string and replace it with
705 // the denoted environmentvariable
706 // Allow Variables according to 
707 //  variable :=  '$' '{' [A-Za-z_]{[A-Za-z_0-9]*} '}'
708 //
709
710 string ReplaceEnvironmentPath(string const & path)
711 {
712 // 
713 // CompareChar: Environmentvariables starts with this character
714 // PathChar:    Next path component start with this character
715 // while CompareChar found do:
716 //       Split String with PathChar
717 //       Search Environmentvariable
718 //       if found: Replace Strings
719 //
720         char const CompareChar = '$';
721         char const FirstChar = '{'; 
722         char const EndChar = '}'; 
723         char const UnderscoreChar = '_'; 
724         string EndString; EndString += EndChar;
725         string FirstString; FirstString += FirstChar;
726         string CompareString; CompareString += CompareChar;
727         string const RegExp("*}*"); // Exist EndChar inside a String?
728
729 // first: Search for a '$' - Sign.
730         //string copy(path);
731         string result1; //(copy);    // for split-calls
732         string result0 = split(path, result1, CompareChar);
733         while (!result0.empty()) {
734                 string copy1(result0); // contains String after $
735                 
736                 // Check, if there is an EndChar inside original String.
737                 
738                 if (!regexMatch(copy1, RegExp)) {
739                         // No EndChar inside. So we are finished
740                         result1 += CompareString + result0;
741                         result0.clear();
742                         continue;
743                 }
744
745                 string res1;
746                 string res0 = split(copy1, res1, EndChar);
747                 // Now res1 holds the environmentvariable
748                 // First, check, if Contents is ok.
749                 if (res1.empty()) { // No environmentvariable. Continue Loop.
750                         result1 += CompareString + FirstString;
751                         result0  = res0;
752                         continue;
753                 }
754                 // check contents of res1
755                 char const * res1_contents = res1.c_str();
756                 if (*res1_contents != FirstChar) {
757                         // Again No Environmentvariable
758                         result1 += CompareString;
759                         result0 = res0;
760                 }
761
762                 // Check for variable names
763                 // Situation ${} is detected as "No Environmentvariable"
764                 char const * cp1 = res1_contents + 1;
765                 bool result = isalpha(*cp1) || (*cp1 == UnderscoreChar);
766                 ++cp1;
767                 while (*cp1 && result) {
768                         result = isalnum(*cp1) || 
769                                 (*cp1 == UnderscoreChar); 
770                         ++cp1;
771                 }
772
773                 if (!result) {
774                         // no correct variable name
775                         result1 += CompareString + res1 + EndString;
776                         result0  = split(res0, res1, CompareChar);
777                         result1 += res1;
778                         continue;
779                 }
780             
781                 string env = GetEnv(res1_contents+1);
782                 if (!env.empty()) {
783                         // Congratulations. Environmentvariable found
784                         result1 += env;
785                 } else {
786                         result1 += CompareString + res1 + EndString;
787                 }
788                 // Next $-Sign?
789                 result0  = split(res0, res1, CompareChar);
790                 result1 += res1;
791         } 
792         return result1;
793 }  // ReplaceEnvironmentPath
794
795
796 // Make relative path out of two absolute paths
797 string MakeRelPath(string const & abspath0, string const & basepath0)
798 // Makes relative path out of absolute path. If it is deeper than basepath,
799 // it's easy. If basepath and abspath share something (they are all deeper
800 // than some directory), it'll be rendered using ..'s. If they are completely
801 // different, then the absolute path will be used as relative path.
802 {
803         // This is a hack. It should probaly be done in another way. Lgb.
804         string abspath = CleanupPath(abspath0);
805         string basepath = CleanupPath(basepath0);
806         if (abspath.empty())
807                 return "<unknown_path>";
808
809         const int abslen = abspath.length();
810         const int baselen = basepath.length();
811         
812         // Find first different character
813         int i = 0;
814         while (i < abslen && i < baselen && abspath[i] == basepath[i]) ++i;
815
816         // Go back to last /
817         if (i < abslen && i < baselen
818             || (i<abslen && abspath[i] != '/' && i == baselen)
819             || (i<baselen && basepath[i] != '/' && i == abslen))
820         {
821                 if (i) --i;     // here was the last match
822                 while (i && abspath[i] != '/') --i;
823         }
824
825         if (i == 0) {
826                 // actually no match - cannot make it relative
827                 return abspath;
828         }
829
830         // Count how many dirs there are in basepath above match
831         // and append as many '..''s into relpath
832         string buf;
833         int j = i;
834         while (j < baselen) {
835                 if (basepath[j] == '/') {
836                         if (j+1 == baselen) break;
837                         buf += "../";
838                 }
839                 ++j;
840         }
841
842         // Append relative stuff from common directory to abspath
843         if (abspath[i] == '/') ++i;
844         for (; i < abslen; ++i)
845                 buf += abspath[i];
846         // Remove trailing /
847         if (suffixIs(buf, '/'))
848                 buf.erase(buf.length() - 1);
849         // Substitute empty with .
850         if (buf.empty())
851                 buf = '.';
852         return buf;
853 }
854
855
856 // Append sub-directory(ies) to a path in an intelligent way
857 string AddPath(string const & path, string const & path_2)
858 {
859         string buf;
860         string path2 = CleanupPath(path_2);
861
862         if (!path.empty() && path != "." && path != "./") {
863                 buf = CleanupPath(path);
864                 if (path[path.length() - 1] != '/')
865                         buf += '/';
866         }
867
868         if (!path2.empty()){
869                 int p2start = path2.find_first_not_of('/');
870
871                 int p2end = path2.find_last_not_of('/');
872
873                 string tmp = path2.substr(p2start, p2end - p2start + 1);
874                 buf += tmp + '/';
875         }
876         return buf;
877 }
878
879
880 /* 
881  Change extension of oldname to extension.
882  Strips path off if no_path == true.
883  If no extension on oldname, just appends.
884  */
885 string ChangeExtension(string const & oldname, string const & extension, 
886                         bool no_path) 
887 {
888         string::size_type last_slash = oldname.rfind('/');
889         string::size_type last_dot;
890         if (last_slash != string::npos)
891                 last_dot = oldname.find('.', last_slash);
892         else
893                 last_dot = oldname.rfind('.');
894
895         string ext;
896         // Make sure the extension starts with a dot
897         if (!extension.empty() && extension[0] != '.')
898                 ext= '.' + extension;
899         else
900                 ext = extension;
901         string ret_str;
902         if (no_path && last_slash != string::npos) {
903                 ++last_slash; // step it
904                 ret_str = oldname.substr(last_slash,
905                                          last_dot - last_slash) + ext;
906         } else
907                 ret_str = oldname.substr(0, last_dot) + ext;
908         return CleanupPath(ret_str);
909 }
910
911
912 // Creates a nice compact path for displaying
913 string MakeDisplayPath (string const & path, unsigned int threshold)
914 {
915         const int l1 = path.length();
916
917         // First, we try a relative path compared to home
918         string home = GetEnvPath("HOME");
919         string relhome = MakeRelPath(path, home);
920
921         unsigned int l2 = relhome.length();
922
923         string prefix;
924
925         // If we backup from home or don't have a relative path,
926         // this try is no good
927         if (prefixIs(relhome, "../") || AbsolutePath(relhome)) {
928                 // relative path was no good, just use the original path
929                 relhome = path;
930                 l2 = l1;
931         } else {
932                 prefix = "~/";
933         }
934
935         // Is the path too long?
936         if (l2 > threshold) {
937                 // Yes, shortend it
938                 prefix += ".../";
939                 
940                 string temp;
941                 
942                 while (relhome.length() > threshold)
943                         relhome = split(relhome, temp, '/');
944
945                 // Did we shortend everything away?
946                 if (relhome.empty()) {
947                         // Yes, filename in itself is too long.
948                         // Pick the start and the end of the filename.
949                         relhome = OnlyFilename(path);
950                         string head = relhome.substr(0, threshold/2 - 3);
951
952                         l2 = relhome.length();
953                         string tail =
954                                 relhome.substr(l2 - threshold/2 - 2, l2 - 1);
955                         relhome = head + "..." + tail;
956                 }
957         }
958         return prefix + relhome;
959 }
960
961
962 bool LyXReadLink(string const & File, string & Link)
963 {
964         char LinkBuffer[512];
965         // Should be PATH_MAX but that needs autconf support
966         int nRead = readlink(File.c_str(), LinkBuffer, sizeof(LinkBuffer)-1);
967         if (nRead <= 0)
968                 return false;
969         LinkBuffer[nRead] = 0;
970         Link = LinkBuffer;
971         return true;
972 }
973
974
975 typedef pair<int, string> cmdret;
976 static cmdret do_popen(string const & cmd)
977 {
978         // One question is if we should use popen or
979         // create our own popen based on fork, exec, pipe
980         // of course the best would be to have a
981         // pstream (process stream), with the
982         // variants ipstream, opstream and
983         FILE * inf = popen(cmd.c_str(), "r");
984         string ret;
985         int c = fgetc(inf);
986         while (c != EOF) {
987                 ret += static_cast<char>(c);
988                 c = fgetc(inf);
989         }
990         int pret = pclose(inf);
991         return make_pair(pret, ret);
992 }
993
994
995 string findtexfile(string const & fil, string const & format)
996 {
997         /* There is no problem to extend this function too use other
998            methods to look for files. It could be setup to look
999            in environment paths and also if wanted as a last resort
1000            to a recursive find. One of the easier extensions would
1001            perhaps be to use the LyX file lookup methods. But! I am
1002            going to implement this until I see some demand for it.
1003            Lgb
1004         */
1005         
1006         // If fil is a file with absolute path we just return it
1007         if (AbsolutePath(fil)) return fil;
1008         
1009         // Check in the current dir.
1010         if (FileInfo(OnlyFilename(fil)).exist())
1011                 return OnlyFilename(fil);
1012         
1013         // No we try to find it using kpsewhich.
1014         string kpsecmd = "kpsewhich --format= " + format + " " + OnlyFilename(fil);
1015         cmdret c = do_popen(kpsecmd);
1016         
1017         lyxerr << "kpse status = " << c.first << "\n"
1018                << "kpse result = `" << strip(c.second, '\n') << "'" << endl;
1019         return c.first != -1 ? strip(c.second, '\n') : string();
1020 }