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