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