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