]> git.lyx.org Git - lyx.git/blob - src/support/filetools.cpp
grammar
[lyx.git] / src / support / filetools.cpp
1 /**
2  * \file filetools.cpp
3  * This file is part of LyX, the document processor.
4  * Licence details can be found in the file COPYING.
5  *
6  * parts Copyright 1985, 1990, 1993 Free Software Foundation, Inc.
7  *
8  * \author Ivan Schreter
9  * \author Dirk Niggemann
10  * \author Asger Alstrup
11  * \author Lars Gullik Bjønnes
12  * \author Jean-Marc Lasgouttes
13  * \author Angus Leeming
14  * \author John Levon
15  * \author Herbert Voß
16  *
17  * Full author contact details are available in file CREDITS.
18  *
19  * General path-mangling functions
20  */
21
22 #include <config.h>
23
24 #include "support/filetools.h"
25
26 #include "support/debug.h"
27 #include "support/environment.h"
28 #include "support/gettext.h"
29 #include "support/lstrings.h"
30 #include "support/os.h"
31 #include "support/Package.h"
32 #include "support/Path.h"
33 #include "support/Systemcall.h"
34 #include "support/qstring_helpers.h"
35
36 #include <QDir>
37
38 #include "support/lassert.h"
39 #include <boost/regex.hpp>
40
41 #include <fcntl.h>
42
43 #include <cerrno>
44 #include <cstdlib>
45 #include <cstdio>
46
47 #include <utility>
48 #include <fstream>
49 #include <sstream>
50
51 using namespace std;
52
53 #define USE_QPROCESS
54
55 namespace lyx {
56 namespace support {
57
58 bool isLyXFilename(string const & filename)
59 {
60         return suffixIs(ascii_lowercase(filename), ".lyx");
61 }
62
63
64 bool isSGMLFilename(string const & filename)
65 {
66         return suffixIs(ascii_lowercase(filename), ".sgml");
67 }
68
69
70 bool isValidLaTeXFilename(string const & filename)
71 {
72         string const invalid_chars("#$%{}()[]\"^");
73         return filename.find_first_of(invalid_chars) == string::npos;
74 }
75
76
77 string const latex_path(string const & original_path,
78                 latex_path_extension extension,
79                 latex_path_dots dots)
80 {
81         // On cygwin, we may need windows or posix style paths.
82         string path = os::latex_path(original_path);
83         path = subst(path, "~", "\\string~");
84         if (path.find(' ') != string::npos) {
85                 // We can't use '"' because " is sometimes active (e.g. if
86                 // babel is loaded with the "german" option)
87                 if (extension == EXCLUDE_EXTENSION) {
88                         // ChangeExtension calls os::internal_path internally
89                         // so don't use it to remove the extension.
90                         string const ext = getExtension(path);
91                         string const base = ext.empty() ?
92                                 path :
93                                 path.substr(0, path.length() - ext.length() - 1);
94                         // ChangeExtension calls os::internal_path internally
95                         // so don't use it to re-add the extension.
96                         path = "\\string\"" + base + "\\string\"." + ext;
97                 } else {
98                         path = "\\string\"" + path + "\\string\"";
99                 }
100         }
101
102         return dots == ESCAPE_DOTS ? subst(path, ".", "\\lyxdot ") : path;
103 }
104
105
106 // Substitutes spaces with underscores in filename (and path)
107 FileName const makeLatexName(FileName const & file)
108 {
109         string name = file.onlyFileName();
110         string const path = file.onlyPath().absFilename() + "/";
111
112         // ok so we scan through the string twice, but who cares.
113         // FIXME: in Unicode time this will break for sure! There is
114         // a non-latin world out there...
115         string const keep = "abcdefghijklmnopqrstuvwxyz"
116                 "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
117                 "@!'()*+,-./0123456789:;<=>?[]`|";
118
119         string::size_type pos = 0;
120         while ((pos = name.find_first_not_of(keep, pos)) != string::npos)
121                 name[pos++] = '_';
122
123         FileName latex_name(path + name);
124         latex_name.changeExtension(".tex");
125         return latex_name;
126 }
127
128
129 string const quoteName(string const & name, quote_style style)
130 {
131         switch(style) {
132         case quote_shell:
133                 // This does not work for filenames containing " (windows)
134                 // or ' (all other OSes). This can't be changed easily, since
135                 // we would need to adapt the command line parser in
136                 // Forkedcall::generateChild. Therefore we don't pass user
137                 // filenames to child processes if possible. We store them in
138                 // a python script instead, where we don't have these
139                 // limitations.
140 #ifndef USE_QPROCESS
141                 return (os::shell() == os::UNIX) ?
142                         '\'' + name + '\'':
143                         '"' + name + '"';
144 #else
145                 return '"' + name + '"';
146 #endif
147         case quote_python:
148                 return "\"" + subst(subst(name, "\\", "\\\\"), "\"", "\\\"")
149                      + "\"";
150         }
151         // shut up stupid compiler
152         return string();
153 }
154
155
156 #if 0
157 // Uses a string of paths separated by ";"s to find a file to open.
158 // Can't cope with pathnames with a ';' in them. Returns full path to file.
159 // If path entry begins with $$LyX/, use system_lyxdir
160 // If path entry begins with $$User/, use user_lyxdir
161 // Example: "$$User/doc;$$LyX/doc"
162 FileName const fileOpenSearch(string const & path, string const & name,
163                              string const & ext)
164 {
165         FileName real_file;
166         string path_element;
167         bool notfound = true;
168         string tmppath = split(path, path_element, ';');
169
170         while (notfound && !path_element.empty()) {
171                 path_element = os::internal_path(path_element);
172                 if (!suffixIs(path_element, '/'))
173                         path_element += '/';
174                 path_element = subst(path_element, "$$LyX",
175                                      package().system_support().absFilename());
176                 path_element = subst(path_element, "$$User",
177                                      package().user_support().absFilename());
178
179                 real_file = fileSearch(path_element, name, ext);
180
181                 if (real_file.empty()) {
182                         do {
183                                 tmppath = split(tmppath, path_element, ';');
184                         } while (!tmppath.empty() && path_element.empty());
185                 } else {
186                         notfound = false;
187                 }
188         }
189         return real_file;
190 }
191 #endif
192
193
194 // Returns the real name of file name in directory path, with optional
195 // extension ext.
196 FileName const fileSearch(string const & path, string const & name,
197                           string const & ext, search_mode mode)
198 {
199         // if `name' is an absolute path, we ignore the setting of `path'
200         // Expand Environmentvariables in 'name'
201         string const tmpname = replaceEnvironmentPath(name);
202         FileName fullname(makeAbsPath(tmpname, path));
203         // search first without extension, then with it.
204         if (fullname.isReadableFile())
205                 return fullname;
206         if (ext.empty())
207                 // We are done.
208                 return mode == may_not_exist ? fullname : FileName();
209         // Only add the extension if it is not already the extension of
210         // fullname.
211         if (getExtension(fullname.absFilename()) != ext)
212                 fullname = FileName(addExtension(fullname.absFilename(), ext));
213         if (fullname.isReadableFile() || mode == may_not_exist)
214                 return fullname;
215         return FileName();
216 }
217
218
219 // Search the file name.ext in the subdirectory dir of
220 //   1) user_lyxdir
221 //   2) build_lyxdir (if not empty)
222 //   3) system_lyxdir
223 FileName const libFileSearch(string const & dir, string const & name,
224                            string const & ext)
225 {
226         FileName fullname = fileSearch(addPath(package().user_support().absFilename(), dir),
227                                      name, ext);
228         if (!fullname.empty())
229                 return fullname;
230
231         if (!package().build_support().empty())
232                 fullname = fileSearch(addPath(package().build_support().absFilename(), dir),
233                                       name, ext);
234         if (!fullname.empty())
235                 return fullname;
236
237         return fileSearch(addPath(package().system_support().absFilename(), dir), name, ext);
238 }
239
240
241 FileName const i18nLibFileSearch(string const & dir, string const & name,
242                   string const & ext)
243 {
244         /* The highest priority value is the `LANGUAGE' environment
245            variable. But we don't use the value if the currently
246            selected locale is the C locale. This is a GNU extension.
247
248            Otherwise, w use a trick to guess what support/gettext.has done:
249            each po file is able to tell us its name. (JMarc)
250         */
251
252         string lang = to_ascii(_("[[Replace with the code of your language]]"));
253         string const language = getEnv("LANGUAGE");
254         if (!lang.empty() && !language.empty())
255                 lang = language;
256
257         string l;
258         lang = split(lang, l, ':');
259         while (!l.empty()) {
260                 FileName tmp;
261                 // First try with the full name
262                 tmp = libFileSearch(addPath(dir, l), name, ext);
263                 if (!tmp.empty())
264                         return tmp;
265
266                 // Then the name without country code
267                 string const shortl = token(l, '_', 0);
268                 if (shortl != l) {
269                         tmp = libFileSearch(addPath(dir, shortl), name, ext);
270                         if (!tmp.empty())
271                                 return tmp;
272                 }
273
274 #if 1
275                 // For compatibility, to be removed later (JMarc)
276                 tmp = libFileSearch(dir, token(l, '_', 0) + '_' + name,
277                                     ext);
278                 if (!tmp.empty()) {
279                         lyxerr << "i18nLibFileSearch: File `" << tmp
280                                << "' has been found by the old method" <<endl;
281                         return tmp;
282                 }
283 #endif
284                 lang = split(lang, l, ':');
285         }
286
287         return libFileSearch(dir, name, ext);
288 }
289
290
291 string const libScriptSearch(string const & command_in, quote_style style)
292 {
293         static string const token_scriptpath = "$$s/";
294
295         string command = command_in;
296         // Find the starting position of "$$s/"
297         string::size_type const pos1 = command.find(token_scriptpath);
298         if (pos1 == string::npos)
299                 return command;
300         // Find the end of the "$$s/some_subdir/some_script" word within
301         // command. Assumes that the script name does not contain spaces.
302         string::size_type const start_script = pos1 + 4;
303         string::size_type const pos2 = command.find(' ', start_script);
304         string::size_type const size_script = pos2 == string::npos?
305                 (command.size() - start_script) : pos2 - start_script;
306
307         // Does this script file exist?
308         string const script =
309                 libFileSearch(".", command.substr(start_script, size_script)).absFilename();
310
311         if (script.empty()) {
312                 // Replace "$$s/" with ""
313                 command.erase(pos1, 4);
314         } else {
315                 // Replace "$$s/foo/some_script" with "<path to>/some_script".
316                 string::size_type const size_replace = size_script + 4;
317                 command.replace(pos1, size_replace, quoteName(script, style));
318         }
319
320         return command;
321 }
322
323
324 static FileName createTmpDir(FileName const & tempdir, string const & mask)
325 {
326         LYXERR(Debug::FILES, "createTmpDir: tempdir=`" << tempdir << "'\n"
327                 << "createTmpDir:    mask=`" << mask << '\'');
328
329         FileName const tmpfl = FileName::tempName(tempdir, mask);
330
331         if (tmpfl.empty() || !tmpfl.createDirectory(0700)) {
332                 LYXERR0("LyX could not create temporary directory in " << tempdir
333                         << "'");
334                 return FileName();
335         }
336
337         return tmpfl;
338 }
339
340
341 FileName const createLyXTmpDir(FileName const & deflt)
342 {
343         if (deflt.empty() || deflt == package().system_temp_dir())
344                 return createTmpDir(package().system_temp_dir(), "lyx_tmpdir");
345
346         if (deflt.createDirectory(0777)) 
347                 return deflt;
348
349         if (deflt.isDirWritable()) {
350                 // deflt could not be created because it
351                 // did exist already, so let's create our own
352                 // dir inside deflt.
353                 return createTmpDir(deflt, "lyx_tmpdir");
354         } else {
355                 // some other error occured.
356                 return createTmpDir(package().system_temp_dir(), "lyx_tmpdir");
357         }
358 }
359
360
361 // Strip filename from path name
362 string const onlyPath(string const & filename)
363 {
364         // If empty filename, return empty
365         if (filename.empty())
366                 return filename;
367
368         // Find last / or start of filename
369         size_t j = filename.rfind('/');
370         return j == string::npos ? "./" : filename.substr(0, j + 1);
371 }
372
373
374 // Convert relative path into absolute path based on a basepath.
375 // If relpath is absolute, just use that.
376 // If basepath is empty, use CWD as base.
377 // Note that basePath can be a relative path, in the sense that it may
378 // not begin with "/" (e.g.), but it should NOT contain such constructs
379 // as "/../".
380 // FIXME It might be nice if the code didn't simply assume that.
381 FileName const makeAbsPath(string const & relPath, string const & basePath)
382 {
383         // checks for already absolute path
384         if (FileName::isAbsolute(relPath))
385                 return FileName(relPath);
386
387         // Copies given paths
388         string tempRel = os::internal_path(relPath);
389         // Since TempRel is NOT absolute, we can safely replace "//" with "/"
390         tempRel = subst(tempRel, "//", "/");
391
392         string tempBase;
393
394         if (FileName::isAbsolute(basePath))
395                 tempBase = basePath;
396         else
397                 tempBase = addPath(FileName::getcwd().absFilename(), basePath);
398
399         // Handle /./ at the end of the path
400         while (suffixIs(tempBase, "/./"))
401                 tempBase.erase(tempBase.length() - 2);
402
403         // processes relative path
404         string rTemp = tempRel;
405         string temp;
406
407         // Check for a leading "~"
408         // Split by first /
409         rTemp = split(rTemp, temp, '/');
410         if (temp == "~") {
411                 tempBase = package().home_dir().absFilename();
412                 tempRel = rTemp;
413         }
414
415         rTemp = tempRel;
416         while (!rTemp.empty()) {
417                 // Split by next /
418                 rTemp = split(rTemp, temp, '/');
419
420                 if (temp == ".") continue;
421                 if (temp == "..") {
422                         // Remove one level of TempBase
423                         if (tempBase.length() <= 1) {
424                                 //this is supposed to be an absolute path, so...
425                                 tempBase = "/";
426                                 continue;
427                         }
428                         //erase a trailing slash if there is one
429                         if (suffixIs(tempBase, "/"))
430                                 tempBase.erase(tempBase.length() - 1, string::npos);
431
432                         string::size_type i = tempBase.length() - 1;
433                         while (i > 0 && tempBase[i] != '/')
434                                 --i;
435                         if (i > 0)
436                                 tempBase.erase(i, string::npos);
437                         else
438                                 tempBase = '/';
439                 } else if (temp.empty() && !rTemp.empty()) {
440                                 tempBase = os::current_root() + rTemp;
441                                 rTemp.erase();
442                 } else {
443                         // Add this piece to TempBase
444                         if (!suffixIs(tempBase, '/'))
445                                 tempBase += '/';
446                         tempBase += temp;
447                 }
448         }
449
450         // returns absolute path
451         return FileName(tempBase);
452 }
453
454
455 // Correctly append filename to the pathname.
456 // If pathname is '.', then don't use pathname.
457 // Chops any path of filename.
458 string const addName(string const & path, string const & fname)
459 {
460         string const basename = onlyFilename(fname);
461         string buf;
462
463         if (path != "." && path != "./" && !path.empty()) {
464                 buf = os::internal_path(path);
465                 if (!suffixIs(path, '/'))
466                         buf += '/';
467         }
468
469         return buf + basename;
470 }
471
472
473 // Strips path from filename
474 string const onlyFilename(string const & fname)
475 {
476         if (fname.empty())
477                 return fname;
478
479         string::size_type j = fname.rfind('/');
480         if (j == string::npos) // no '/' in fname
481                 return fname;
482
483         // Strip to basename
484         return fname.substr(j + 1);
485 }
486
487
488 // Create absolute path. If impossible, don't do anything
489 // Supports ./ and ~/. Later we can add support for ~logname/. (Asger)
490 string const expandPath(string const & path)
491 {
492         // checks for already absolute path
493         string rTemp = replaceEnvironmentPath(path);
494         if (FileName::isAbsolute(rTemp))
495                 return rTemp;
496
497         string temp;
498         string const copy = rTemp;
499
500         // Split by next /
501         rTemp = split(rTemp, temp, '/');
502
503         if (temp == ".")
504                 return FileName::getcwd().absFilename() + '/' + rTemp;
505
506         if (temp == "~")
507                 return package().home_dir().absFilename() + '/' + rTemp;
508
509         if (temp == "..")
510                 return makeAbsPath(copy).absFilename();
511
512         // Don't know how to handle this
513         return copy;
514 }
515
516
517 // Search the string for ${VAR} and $VAR and replace VAR using getenv.
518 string const replaceEnvironmentPath(string const & path)
519 {
520         // ${VAR} is defined as
521         // $\{[A-Za-z_][A-Za-z_0-9]*\}
522         static string const envvar_br = "[$]\\{([A-Za-z_][A-Za-z_0-9]*)\\}";
523
524         // $VAR is defined as:
525         // $\{[A-Za-z_][A-Za-z_0-9]*\}
526         static string const envvar = "[$]([A-Za-z_][A-Za-z_0-9]*)";
527
528         static boost::regex envvar_br_re("(.*)" + envvar_br + "(.*)");
529         static boost::regex envvar_re("(.*)" + envvar + "(.*)");
530         boost::smatch what;
531         string result;
532         string remaining = path;
533         while (1) {
534                 regex_match(remaining, what, envvar_br_re);
535                 if (!what[0].matched) {
536                         regex_match(remaining, what, envvar_re);
537                         if (!what[0].matched) {
538                                 result += remaining;
539                                 break;
540                         }
541                 }
542                 string env_var = getEnv(what.str(2));
543                 if (!env_var.empty())
544                         result += what.str(1) + env_var;
545                 else
546                         result += what.str(1) + "$" + what.str(2);
547                 remaining = what.str(3);
548         }
549         return result;
550 }
551
552
553 // Make relative path out of two absolute paths
554 docstring const makeRelPath(docstring const & abspath, docstring const & basepath)
555 // Makes relative path out of absolute path. If it is deeper than basepath,
556 // it's easy. If basepath and abspath share something (they are all deeper
557 // than some directory), it'll be rendered using ..'s. If they are completely
558 // different, then the absolute path will be used as relative path.
559 {
560         docstring::size_type const abslen = abspath.length();
561         docstring::size_type const baselen = basepath.length();
562
563         docstring::size_type i = os::common_path(abspath, basepath);
564
565         if (i == 0) {
566                 // actually no match - cannot make it relative
567                 return abspath;
568         }
569
570         // Count how many dirs there are in basepath above match
571         // and append as many '..''s into relpath
572         docstring buf;
573         docstring::size_type j = i;
574         while (j < baselen) {
575                 if (basepath[j] == '/') {
576                         if (j + 1 == baselen)
577                                 break;
578                         buf += "../";
579                 }
580                 ++j;
581         }
582
583         // Append relative stuff from common directory to abspath
584         if (abspath[i] == '/')
585                 ++i;
586         for (; i < abslen; ++i)
587                 buf += abspath[i];
588         // Remove trailing /
589         if (suffixIs(buf, '/'))
590                 buf.erase(buf.length() - 1);
591         // Substitute empty with .
592         if (buf.empty())
593                 buf = '.';
594         return buf;
595 }
596
597
598 // Append sub-directory(ies) to a path in an intelligent way
599 string const addPath(string const & path, string const & path_2)
600 {
601         string buf;
602         string const path2 = os::internal_path(path_2);
603
604         if (!path.empty() && path != "." && path != "./") {
605                 buf = os::internal_path(path);
606                 if (path[path.length() - 1] != '/')
607                         buf += '/';
608         }
609
610         if (!path2.empty()) {
611                 string::size_type const p2start = path2.find_first_not_of('/');
612                 string::size_type const p2end = path2.find_last_not_of('/');
613                 string const tmp = path2.substr(p2start, p2end - p2start + 1);
614                 buf += tmp + '/';
615         }
616         return buf;
617 }
618
619
620 string const changeExtension(string const & oldname, string const & extension)
621 {
622         string::size_type const last_slash = oldname.rfind('/');
623         string::size_type last_dot = oldname.rfind('.');
624         if (last_dot < last_slash && last_slash != string::npos)
625                 last_dot = string::npos;
626
627         string ext;
628         // Make sure the extension starts with a dot
629         if (!extension.empty() && extension[0] != '.')
630                 ext= '.' + extension;
631         else
632                 ext = extension;
633
634         return os::internal_path(oldname.substr(0, last_dot) + ext);
635 }
636
637
638 string const removeExtension(string const & name)
639 {
640         return changeExtension(name, string());
641 }
642
643
644 string const addExtension(string const & name, string const & extension)
645 {
646         if (!extension.empty() && extension[0] != '.')
647                 return name + '.' + extension;
648         return name + extension;
649 }
650
651
652 /// Return the extension of the file (not including the .)
653 string const getExtension(string const & name)
654 {
655         string::size_type const last_slash = name.rfind('/');
656         string::size_type const last_dot = name.rfind('.');
657         if (last_dot != string::npos &&
658             (last_slash == string::npos || last_dot > last_slash))
659                 return name.substr(last_dot + 1,
660                                    name.length() - (last_dot + 1));
661         else
662                 return string();
663 }
664
665
666 string const unzippedFileName(string const & zipped_file)
667 {
668         string const ext = getExtension(zipped_file);
669         if (ext == "gz" || ext == "z" || ext == "Z")
670                 return changeExtension(zipped_file, string());
671         return onlyPath(zipped_file) + "unzipped_" + onlyFilename(zipped_file);
672 }
673
674
675 FileName const unzipFile(FileName const & zipped_file, string const & unzipped_file)
676 {
677         FileName const tempfile = FileName(unzipped_file.empty() ?
678                 unzippedFileName(zipped_file.toFilesystemEncoding()) :
679                 unzipped_file);
680         // Run gunzip
681         string const command = "gunzip -c " +
682                 zipped_file.toFilesystemEncoding() + " > " +
683                 tempfile.toFilesystemEncoding();
684         Systemcall one;
685         one.startscript(Systemcall::Wait, command);
686         // test that command was executed successfully (anon)
687         // yes, please do. (Lgb)
688         return tempfile;
689 }
690
691
692 docstring const makeDisplayPath(string const & path, unsigned int threshold)
693 {
694         string str = path;
695
696         // If file is from LyXDir, display it as if it were relative.
697         string const system = package().system_support().absFilename();
698         if (prefixIs(str, system) && str != system)
699                 return from_utf8("[" + str.erase(0, system.length()) + "]");
700
701         // replace /home/blah with ~/
702         string const home = package().home_dir().absFilename();
703         if (!home.empty() && prefixIs(str, home))
704                 str = subst(str, home, "~");
705
706         if (str.length() <= threshold)
707                 return from_utf8(os::external_path(str));
708
709         string const prefix = ".../";
710         string temp;
711
712         while (str.length() > threshold)
713                 str = split(str, temp, '/');
714
715         // Did we shorten everything away?
716         if (str.empty()) {
717                 // Yes, filename itself is too long.
718                 // Pick the start and the end of the filename.
719                 str = onlyFilename(path);
720                 string const head = str.substr(0, threshold / 2 - 3);
721
722                 string::size_type len = str.length();
723                 string const tail =
724                         str.substr(len - threshold / 2 - 2, len - 1);
725                 str = head + "..." + tail;
726         }
727
728         return from_utf8(os::external_path(prefix + str));
729 }
730
731
732 bool readLink(FileName const & file, FileName & link)
733 {
734 #ifdef HAVE_READLINK
735         char linkbuffer[512];
736         // Should be PATH_MAX but that needs autconf support
737         string const encoded = file.toFilesystemEncoding();
738         int const nRead = ::readlink(encoded.c_str(),
739                                      linkbuffer, sizeof(linkbuffer) - 1);
740         if (nRead <= 0)
741                 return false;
742         linkbuffer[nRead] = '\0'; // terminator
743         link = makeAbsPath(linkbuffer, onlyPath(file.absFilename()));
744         return true;
745 #else
746         return false;
747 #endif
748 }
749
750
751 cmd_ret const runCommand(string const & cmd)
752 {
753         // FIXME: replace all calls to RunCommand with ForkedCall
754         // (if the output is not needed) or the code in ISpell.cpp
755         // (if the output is needed).
756
757         // One question is if we should use popen or
758         // create our own popen based on fork, exec, pipe
759         // of course the best would be to have a
760         // pstream (process stream), with the
761         // variants ipstream, opstream
762
763 #if defined (HAVE_POPEN)
764         FILE * inf = ::popen(cmd.c_str(), os::popen_read_mode());
765 #elif defined (HAVE__POPEN)
766         FILE * inf = ::_popen(cmd.c_str(), os::popen_read_mode());
767 #else
768 #error No popen() function.
769 #endif
770
771         // (Claus Hentschel) Check if popen was succesful ;-)
772         if (!inf) {
773                 lyxerr << "RunCommand:: could not start child process" << endl;
774                 return make_pair(-1, string());
775         }
776
777         string ret;
778         int c = fgetc(inf);
779         while (c != EOF) {
780                 ret += static_cast<char>(c);
781                 c = fgetc(inf);
782         }
783
784 #if defined (HAVE_PCLOSE)
785         int const pret = pclose(inf);
786 #elif defined (HAVE__PCLOSE)
787         int const pret = _pclose(inf);
788 #else
789 #error No pclose() function.
790 #endif
791
792         if (pret == -1)
793                 perror("RunCommand:: could not terminate child process");
794
795         return make_pair(pret, ret);
796 }
797
798
799 FileName const findtexfile(string const & fil, string const & /*format*/)
800 {
801         /* There is no problem to extend this function too use other
802            methods to look for files. It could be setup to look
803            in environment paths and also if wanted as a last resort
804            to a recursive find. One of the easier extensions would
805            perhaps be to use the LyX file lookup methods. But! I am
806            going to implement this until I see some demand for it.
807            Lgb
808         */
809
810         // If the file can be found directly, we just return a
811         // absolute path version of it.
812         FileName const absfile(makeAbsPath(fil));
813         if (absfile.exists())
814                 return absfile;
815
816         // Now we try to find it using kpsewhich.
817         // It seems from the kpsewhich manual page that it is safe to use
818         // kpsewhich without --format: "When the --format option is not
819         // given, the search path used when looking for a file is inferred
820         // from the name given, by looking for a known extension. If no
821         // known extension is found, the search path for TeX source files
822         // is used."
823         // However, we want to take advantage of the format sine almost all
824         // the different formats has environment variables that can be used
825         // to controll which paths to search. f.ex. bib looks in
826         // BIBINPUTS and TEXBIB. Small list follows:
827         // bib - BIBINPUTS, TEXBIB
828         // bst - BSTINPUTS
829         // graphic/figure - TEXPICTS, TEXINPUTS
830         // ist - TEXINDEXSTYLE, INDEXSTYLE
831         // pk - PROGRAMFONTS, PKFONTS, TEXPKS, GLYPHFONTS, TEXFONTS
832         // tex - TEXINPUTS
833         // tfm - TFMFONTS, TEXFONTS
834         // This means that to use kpsewhich in the best possible way we
835         // should help it by setting additional path in the approp. envir.var.
836         string const kpsecmd = "kpsewhich " + fil;
837
838         cmd_ret const c = runCommand(kpsecmd);
839
840         LYXERR(Debug::LATEX, "kpse status = " << c.first << '\n'
841                  << "kpse result = `" << rtrim(c.second, "\n\r") << '\'');
842         if (c.first != -1)
843                 return FileName(rtrim(to_utf8(from_filesystem8bit(c.second)), "\n\r"));
844         else
845                 return FileName();
846 }
847
848
849 void readBB_lyxerrMessage(FileName const & file, bool & zipped,
850         string const & message)
851 {
852         LYXERR(Debug::GRAPHICS, "[readBB_from_PSFile] " << message);
853         // FIXME: Why is this func deleting a file? (Lgb)
854         if (zipped)
855                 file.removeFile();
856 }
857
858
859 string const readBB_from_PSFile(FileName const & file)
860 {
861         // in a (e)ps-file it's an entry like %%BoundingBox:23 45 321 345
862         // It seems that every command in the header has an own line,
863         // getline() should work for all files.
864         // On the other hand some plot programs write the bb at the
865         // end of the file. Than we have in the header:
866         // %%BoundingBox: (atend)
867         // In this case we must check the end.
868         bool zipped = file.isZippedFile();
869         FileName const file_ = zipped ? unzipFile(file) : file;
870         string const format = file_.guessFormatFromContents();
871
872         if (format != "eps" && format != "ps") {
873                 readBB_lyxerrMessage(file_, zipped,"no(e)ps-format");
874                 return string();
875         }
876
877         static boost::regex bbox_re(
878                 "^%%BoundingBox:\\s*([[:digit:]]+)\\s+([[:digit:]]+)\\s+([[:digit:]]+)\\s+([[:digit:]]+)");
879         ifstream is(file_.toFilesystemEncoding().c_str());
880         while (is) {
881                 string s;
882                 getline(is,s);
883                 boost::smatch what;
884                 if (regex_match(s, what, bbox_re)) {
885                         // Our callers expect the tokens in the string
886                         // separated by single spaces.
887                         // FIXME: change return type from string to something
888                         // sensible
889                         ostringstream os;
890                         os << what.str(1) << ' ' << what.str(2) << ' '
891                            << what.str(3) << ' ' << what.str(4);
892                         string const bb = os.str();
893                         readBB_lyxerrMessage(file_, zipped, bb);
894                         return bb;
895                 }
896         }
897         readBB_lyxerrMessage(file_, zipped, "no bb found");
898         return string();
899 }
900
901
902 int compare_timestamps(FileName const & file1, FileName const & file2)
903 {
904         // If the original is newer than the copy, then copy the original
905         // to the new directory.
906
907         int cmp = 0;
908         if (file1.exists() && file2.exists()) {
909                 double const tmp = difftime(file1.lastModified(), file2.lastModified());
910                 if (tmp != 0)
911                         cmp = tmp > 0 ? 1 : -1;
912
913         } else if (file1.exists()) {
914                 cmp = 1;
915         } else if (file2.exists()) {
916                 cmp = -1;
917         }
918
919         return cmp;
920 }
921
922
923 } //namespace support
924 } // namespace lyx