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