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