]> git.lyx.org Git - features.git/blob - src/support/filetools.cpp
Introducing FileName::dirList() to replace the one in filetools.cpp
[features.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/convert.h"
25 #include "support/environment.h"
26 #include "support/filetools.h"
27 #include "support/lstrings.h"
28 #include "support/lyxlib.h"
29 #include "support/os.h"
30 #include "support/Package.h"
31 #include "support/Path.h"
32 #include "support/Systemcall.h"
33
34 // FIXME Interface violation
35 #include "gettext.h"
36 #include "debug.h"
37
38 #include <boost/assert.hpp>
39 #include <boost/filesystem/operations.hpp>
40 #include <boost/regex.hpp>
41
42 #include <fcntl.h>
43
44 #include <cerrno>
45 #include <cstdlib>
46 #include <cstdio>
47
48 #include <utility>
49 #include <fstream>
50 #include <sstream>
51
52 using std::endl;
53 using std::getline;
54 using std::make_pair;
55 using std::string;
56 using std::ifstream;
57 using std::ostringstream;
58 using std::vector;
59 using std::pair;
60
61 namespace fs = boost::filesystem;
62
63 namespace lyx {
64 namespace support {
65
66 bool isLyXFilename(string const & filename)
67 {
68         return suffixIs(ascii_lowercase(filename), ".lyx");
69 }
70
71
72 bool isSGMLFilename(string const & filename)
73 {
74         return suffixIs(ascii_lowercase(filename), ".sgml");
75 }
76
77
78 bool isValidLaTeXFilename(string const & filename)
79 {
80         string const invalid_chars("#$%{}()[]\"^");
81         return filename.find_first_of(invalid_chars) == string::npos;
82 }
83
84
85 string const latex_path(string const & original_path,
86                 latex_path_extension extension,
87                 latex_path_dots dots)
88 {
89         // On cygwin, we may need windows or posix style paths.
90         string path = os::latex_path(original_path);
91         path = subst(path, "~", "\\string~");
92         if (path.find(' ') != string::npos) {
93                 // We can't use '"' because " is sometimes active (e.g. if
94                 // babel is loaded with the "german" option)
95                 if (extension == EXCLUDE_EXTENSION) {
96                         // ChangeExtension calls os::internal_path internally
97                         // so don't use it to remove the extension.
98                         string const ext = getExtension(path);
99                         string const base = ext.empty() ?
100                                 path :
101                                 path.substr(0, path.length() - ext.length() - 1);
102                         // ChangeExtension calls os::internal_path internally
103                         // so don't use it to re-add the extension.
104                         path = "\\string\"" + base + "\\string\"." + ext;
105                 } else {
106                         path = "\\string\"" + path + "\\string\"";
107                 }
108         }
109
110         return dots == ESCAPE_DOTS ? subst(path, ".", "\\lyxdot ") : path;
111 }
112
113
114 // Substitutes spaces with underscores in filename (and path)
115 string const makeLatexName(string const & file)
116 {
117         string name = onlyFilename(file);
118         string const path = onlyPath(file);
119
120         // ok so we scan through the string twice, but who cares.
121         string const keep = "abcdefghijklmnopqrstuvwxyz"
122                 "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
123                 "@!'()*+,-./0123456789:;<=>?[]`|";
124
125         string::size_type pos = 0;
126         while ((pos = name.find_first_not_of(keep, pos)) != string::npos)
127                 name[pos++] = '_';
128
129         return addName(path, name);
130 }
131
132
133 string const quoteName(string const & name, quote_style style)
134 {
135         switch(style) {
136         case quote_shell:
137                 // This does not work for filenames containing " (windows)
138                 // or ' (all other OSes). This can't be changed easily, since
139                 // we would need to adapt the command line parser in
140                 // Forkedcall::generateChild. Therefore we don't pass user
141                 // filenames to child processes if possible. We store them in
142                 // a python script instead, where we don't have these
143                 // limitations.
144                 return (os::shell() == os::UNIX) ?
145                         '\'' + name + '\'':
146                         '"' + name + '"';
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 == allow_unreadable ? 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 == allow_unreadable)
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 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(tempName(tempdir, mask));
330         // lyx::tempName actually creates a file to make sure that it
331         // stays unique. So we have to delete it before we can create
332         // a dir with the same name. Note also that we are not thread
333         // safe because of the gap between unlink and mkdir. (Lgb)
334         unlink(tmpfl);
335
336         if (tmpfl.empty() || mkdir(tmpfl, 0700)) {
337                 lyxerr << "LyX could not create the temporary directory '"
338                        << tmpfl << "'" << endl;
339                 return FileName();
340         }
341
342         return tmpfl;
343 }
344
345 string const createBufferTmpDir()
346 {
347         static int count;
348         // We are in our own directory.  Why bother to mangle name?
349         // In fact I wrote this code to circumvent a problematic behaviour
350         // (bug?) of EMX mkstemp().
351         string const tmpfl =
352                 package().temp_dir().absFilename() + "/lyx_tmpbuf" +
353                 convert<string>(count++);
354
355         if (mkdir(FileName(tmpfl), 0777)) {
356                 lyxerr << "LyX could not create the temporary directory '"
357                        << tmpfl << "'" << endl;
358                 return string();
359         }
360         return tmpfl;
361 }
362
363
364 FileName const createLyXTmpDir(FileName const & deflt)
365 {
366         if (deflt.empty() || deflt.absFilename() == "/tmp")
367                 return createTmpDir(FileName("/tmp"), "lyx_tmpdir");
368
369         if (!mkdir(deflt, 0777)) 
370                 return deflt;
371
372         if (deflt.isDirWritable()) {
373                 // deflt could not be created because it
374                 // did exist already, so let's create our own
375                 // dir inside deflt.
376                 return createTmpDir(deflt, "lyx_tmpdir");
377         } else {
378                 // some other error occured.
379                 return createTmpDir(FileName("/tmp"), "lyx_tmpdir");
380         }
381 }
382
383
384 // Strip filename from path name
385 string const onlyPath(string const & filename)
386 {
387         // If empty filename, return empty
388         if (filename.empty())
389                 return filename;
390
391         // Find last / or start of filename
392         size_t j = filename.rfind('/');
393         return j == string::npos ? "./" : filename.substr(0, j + 1);
394 }
395
396
397 // Convert relative path into absolute path based on a basepath.
398 // If relpath is absolute, just use that.
399 // If basepath is empty, use CWD as base.
400 FileName const makeAbsPath(string const & relPath, string const & basePath)
401 {
402         // checks for already absolute path
403         if (os::is_absolute_path(relPath))
404                 return FileName(relPath);
405
406         // Copies given paths
407         string tempRel = os::internal_path(relPath);
408         // Since TempRel is NOT absolute, we can safely replace "//" with "/"
409         tempRel = subst(tempRel, "//", "/");
410
411         string tempBase;
412
413         if (os::is_absolute_path(basePath))
414                 tempBase = basePath;
415         else
416                 tempBase = addPath(getcwd().absFilename(), basePath);
417
418         // Handle /./ at the end of the path
419         while (suffixIs(tempBase, "/./"))
420                 tempBase.erase(tempBase.length() - 2);
421
422         // processes relative path
423         string rTemp = tempRel;
424         string temp;
425
426         while (!rTemp.empty()) {
427                 // Split by next /
428                 rTemp = split(rTemp, temp, '/');
429
430                 if (temp == ".") continue;
431                 if (temp == "..") {
432                         // Remove one level of TempBase
433                         string::difference_type i = tempBase.length() - 2;
434                         if (i < 0)
435                                 i = 0;
436                         while (i > 0 && tempBase[i] != '/')
437                                 --i;
438                         if (i > 0)
439                                 tempBase.erase(i, string::npos);
440                         else
441                                 tempBase += '/';
442                 } else if (temp.empty() && !rTemp.empty()) {
443                                 tempBase = os::current_root() + rTemp;
444                                 rTemp.erase();
445                 } else {
446                         // Add this piece to TempBase
447                         if (!suffixIs(tempBase, '/'))
448                                 tempBase += '/';
449                         tempBase += temp;
450                 }
451         }
452
453         // returns absolute path
454         return FileName(os::internal_path(tempBase));
455 }
456
457
458 // Correctly append filename to the pathname.
459 // If pathname is '.', then don't use pathname.
460 // Chops any path of filename.
461 string const addName(string const & path, string const & fname)
462 {
463         string const basename = onlyFilename(fname);
464         string buf;
465
466         if (path != "." && path != "./" && !path.empty()) {
467                 buf = os::internal_path(path);
468                 if (!suffixIs(path, '/'))
469                         buf += '/';
470         }
471
472         return buf + basename;
473 }
474
475
476 // Strips path from filename
477 string const onlyFilename(string const & fname)
478 {
479         if (fname.empty())
480                 return fname;
481
482         string::size_type j = fname.rfind('/');
483         if (j == string::npos) // no '/' in fname
484                 return fname;
485
486         // Strip to basename
487         return fname.substr(j + 1);
488 }
489
490
491 /// Returns true is path is absolute
492 bool absolutePath(string const & path)
493 {
494         return os::is_absolute_path(path);
495 }
496
497
498 // Create absolute path. If impossible, don't do anything
499 // Supports ./ and ~/. Later we can add support for ~logname/. (Asger)
500 string const expandPath(string const & path)
501 {
502         // checks for already absolute path
503         string rTemp = replaceEnvironmentPath(path);
504         if (os::is_absolute_path(rTemp))
505                 return rTemp;
506
507         string temp;
508         string const copy = rTemp;
509
510         // Split by next /
511         rTemp = split(rTemp, temp, '/');
512
513         if (temp == ".")
514                 return getcwd().absFilename() + '/' + rTemp;
515
516         if (temp == "~")
517                 return package().home_dir().absFilename() + '/' + rTemp;
518
519         if (temp == "..")
520                 return makeAbsPath(copy).absFilename();
521
522         // Don't know how to handle this
523         return copy;
524 }
525
526
527 // Normalize a path. Constracts path/../path
528 // Can't handle "../../" or "/../" (Asger)
529 // Also converts paths like /foo//bar ==> /foo/bar
530 string const normalizePath(string const & path)
531 {
532         // Normalize paths like /foo//bar ==> /foo/bar
533         static boost::regex regex("/{2,}");
534         string const tmppath = boost::regex_merge(path, regex, "/");
535
536         fs::path const npath = fs::path(tmppath, fs::no_check).normalize();
537
538         if (!npath.is_complete())
539                 return "./" + npath.string() + '/';
540
541         return npath.string() + '/';
542 }
543
544
545 // Search the string for ${VAR} and $VAR and replace VAR using getenv.
546 string const replaceEnvironmentPath(string const & path)
547 {
548         // ${VAR} is defined as
549         // $\{[A-Za-z_][A-Za-z_0-9]*\}
550         static string const envvar_br = "[$]\\{([A-Za-z_][A-Za-z_0-9]*)\\}";
551
552         // $VAR is defined as:
553         // $\{[A-Za-z_][A-Za-z_0-9]*\}
554         static string const envvar = "[$]([A-Za-z_][A-Za-z_0-9]*)";
555
556         static boost::regex envvar_br_re("(.*)" + envvar_br + "(.*)");
557         static boost::regex envvar_re("(.*)" + envvar + "(.*)");
558         boost::smatch what;
559
560         string result = path;
561         while (1) {
562                 regex_match(result, what, envvar_br_re);
563                 if (!what[0].matched) {
564                         regex_match(result, what, envvar_re);
565                         if (!what[0].matched)
566                                 break;
567                 }
568                 result = what.str(1) + getEnv(what.str(2)) + what.str(3);
569         }
570         return result;
571 }
572
573
574 // Make relative path out of two absolute paths
575 docstring const makeRelPath(docstring const & abspath, docstring const & basepath)
576 // Makes relative path out of absolute path. If it is deeper than basepath,
577 // it's easy. If basepath and abspath share something (they are all deeper
578 // than some directory), it'll be rendered using ..'s. If they are completely
579 // different, then the absolute path will be used as relative path.
580 {
581         docstring::size_type const abslen = abspath.length();
582         docstring::size_type const baselen = basepath.length();
583
584         docstring::size_type i = os::common_path(abspath, basepath);
585
586         if (i == 0) {
587                 // actually no match - cannot make it relative
588                 return abspath;
589         }
590
591         // Count how many dirs there are in basepath above match
592         // and append as many '..''s into relpath
593         docstring buf;
594         docstring::size_type j = i;
595         while (j < baselen) {
596                 if (basepath[j] == '/') {
597                         if (j + 1 == baselen)
598                                 break;
599                         buf += "../";
600                 }
601                 ++j;
602         }
603
604         // Append relative stuff from common directory to abspath
605         if (abspath[i] == '/')
606                 ++i;
607         for (; i < abslen; ++i)
608                 buf += abspath[i];
609         // Remove trailing /
610         if (suffixIs(buf, '/'))
611                 buf.erase(buf.length() - 1);
612         // Substitute empty with .
613         if (buf.empty())
614                 buf = '.';
615         return buf;
616 }
617
618
619 // Append sub-directory(ies) to a path in an intelligent way
620 string const addPath(string const & path, string const & path_2)
621 {
622         string buf;
623         string const path2 = os::internal_path(path_2);
624
625         if (!path.empty() && path != "." && path != "./") {
626                 buf = os::internal_path(path);
627                 if (path[path.length() - 1] != '/')
628                         buf += '/';
629         }
630
631         if (!path2.empty()) {
632                 string::size_type const p2start = path2.find_first_not_of('/');
633                 string::size_type const p2end = path2.find_last_not_of('/');
634                 string const tmp = path2.substr(p2start, p2end - p2start + 1);
635                 buf += tmp + '/';
636         }
637         return buf;
638 }
639
640
641 string const changeExtension(string const & oldname, string const & extension)
642 {
643         string::size_type const last_slash = oldname.rfind('/');
644         string::size_type last_dot = oldname.rfind('.');
645         if (last_dot < last_slash && last_slash != string::npos)
646                 last_dot = string::npos;
647
648         string ext;
649         // Make sure the extension starts with a dot
650         if (!extension.empty() && extension[0] != '.')
651                 ext= '.' + extension;
652         else
653                 ext = extension;
654
655         return os::internal_path(oldname.substr(0, last_dot) + ext);
656 }
657
658
659 string const removeExtension(string const & name)
660 {
661         return changeExtension(name, string());
662 }
663
664
665 string const addExtension(string const & name, string const & extension)
666 {
667         if (!extension.empty() && extension[0] != '.')
668                 return name + '.' + extension;
669         return name + extension;
670 }
671
672
673 /// Return the extension of the file (not including the .)
674 string const getExtension(string const & name)
675 {
676         string::size_type const last_slash = name.rfind('/');
677         string::size_type const last_dot = name.rfind('.');
678         if (last_dot != string::npos &&
679             (last_slash == string::npos || last_dot > last_slash))
680                 return name.substr(last_dot + 1,
681                                    name.length() - (last_dot + 1));
682         else
683                 return string();
684 }
685
686
687 string const unzippedFileName(string const & zipped_file)
688 {
689         string const ext = getExtension(zipped_file);
690         if (ext == "gz" || ext == "z" || ext == "Z")
691                 return changeExtension(zipped_file, string());
692         return "unzipped_" + zipped_file;
693 }
694
695
696 FileName const unzipFile(FileName const & zipped_file, string const & unzipped_file)
697 {
698         FileName const tempfile = FileName(unzipped_file.empty() ?
699                 unzippedFileName(zipped_file.toFilesystemEncoding()) :
700                 unzipped_file);
701         // Run gunzip
702         string const command = "gunzip -c " +
703                 zipped_file.toFilesystemEncoding() + " > " +
704                 tempfile.toFilesystemEncoding();
705         Systemcall one;
706         one.startscript(Systemcall::Wait, command);
707         // test that command was executed successfully (anon)
708         // yes, please do. (Lgb)
709         return tempfile;
710 }
711
712
713 docstring const makeDisplayPath(string const & path, unsigned int threshold)
714 {
715         string str = path;
716
717         // If file is from LyXDir, display it as if it were relative.
718         string const system = package().system_support().absFilename();
719         if (prefixIs(str, system) && str != system)
720                 return from_utf8("[" + str.erase(0, system.length()) + "]");
721
722         // replace /home/blah with ~/
723         string const home = package().home_dir().absFilename();
724         if (!home.empty() && prefixIs(str, home))
725                 str = subst(str, home, "~");
726
727         if (str.length() <= threshold)
728                 return from_utf8(os::external_path(str));
729
730         string const prefix = ".../";
731         string temp;
732
733         while (str.length() > threshold)
734                 str = split(str, temp, '/');
735
736         // Did we shorten everything away?
737         if (str.empty()) {
738                 // Yes, filename itself is too long.
739                 // Pick the start and the end of the filename.
740                 str = onlyFilename(path);
741                 string const head = str.substr(0, threshold / 2 - 3);
742
743                 string::size_type len = str.length();
744                 string const tail =
745                         str.substr(len - threshold / 2 - 2, len - 1);
746                 str = head + "..." + tail;
747         }
748
749         return from_utf8(os::external_path(prefix + str));
750 }
751
752
753 bool readLink(FileName const & file, FileName & link)
754 {
755 #ifdef HAVE_READLINK
756         char linkbuffer[512];
757         // Should be PATH_MAX but that needs autconf support
758         string const encoded = file.toFilesystemEncoding();
759         int const nRead = ::readlink(encoded.c_str(),
760                                      linkbuffer, sizeof(linkbuffer) - 1);
761         if (nRead <= 0)
762                 return false;
763         linkbuffer[nRead] = '\0'; // terminator
764         link = makeAbsPath(linkbuffer, onlyPath(file.absFilename()));
765         return true;
766 #else
767         return false;
768 #endif
769 }
770
771
772 cmd_ret const runCommand(string const & cmd)
773 {
774         // FIXME: replace all calls to RunCommand with ForkedCall
775         // (if the output is not needed) or the code in ISpell.cpp
776         // (if the output is needed).
777
778         // One question is if we should use popen or
779         // create our own popen based on fork, exec, pipe
780         // of course the best would be to have a
781         // pstream (process stream), with the
782         // variants ipstream, opstream
783
784 #if defined (HAVE_POPEN)
785         FILE * inf = ::popen(cmd.c_str(), os::popen_read_mode());
786 #elif defined (HAVE__POPEN)
787         FILE * inf = ::_popen(cmd.c_str(), os::popen_read_mode());
788 #else
789 #error No popen() function.
790 #endif
791
792         // (Claus Hentschel) Check if popen was succesful ;-)
793         if (!inf) {
794                 lyxerr << "RunCommand:: could not start child process" << endl;
795                 return make_pair(-1, string());
796         }
797
798         string ret;
799         int c = fgetc(inf);
800         while (c != EOF) {
801                 ret += static_cast<char>(c);
802                 c = fgetc(inf);
803         }
804
805 #if defined (HAVE_PCLOSE)
806         int const pret = pclose(inf);
807 #elif defined (HAVE__PCLOSE)
808         int const pret = _pclose(inf);
809 #else
810 #error No pclose() function.
811 #endif
812
813         if (pret == -1)
814                 perror("RunCommand:: could not terminate child process");
815
816         return make_pair(pret, ret);
817 }
818
819
820 FileName const findtexfile(string const & fil, string const & /*format*/)
821 {
822         /* There is no problem to extend this function too use other
823            methods to look for files. It could be setup to look
824            in environment paths and also if wanted as a last resort
825            to a recursive find. One of the easier extensions would
826            perhaps be to use the LyX file lookup methods. But! I am
827            going to implement this until I see some demand for it.
828            Lgb
829         */
830
831         // If the file can be found directly, we just return a
832         // absolute path version of it.
833         FileName const absfile(makeAbsPath(fil));
834         if (absfile.exists())
835                 return absfile;
836
837         // No we try to find it using kpsewhich.
838         // It seems from the kpsewhich manual page that it is safe to use
839         // kpsewhich without --format: "When the --format option is not
840         // given, the search path used when looking for a file is inferred
841         // from the name given, by looking for a known extension. If no
842         // known extension is found, the search path for TeX source files
843         // is used."
844         // However, we want to take advantage of the format sine almost all
845         // the different formats has environment variables that can be used
846         // to controll which paths to search. f.ex. bib looks in
847         // BIBINPUTS and TEXBIB. Small list follows:
848         // bib - BIBINPUTS, TEXBIB
849         // bst - BSTINPUTS
850         // graphic/figure - TEXPICTS, TEXINPUTS
851         // ist - TEXINDEXSTYLE, INDEXSTYLE
852         // pk - PROGRAMFONTS, PKFONTS, TEXPKS, GLYPHFONTS, TEXFONTS
853         // tex - TEXINPUTS
854         // tfm - TFMFONTS, TEXFONTS
855         // This means that to use kpsewhich in the best possible way we
856         // should help it by setting additional path in the approp. envir.var.
857         string const kpsecmd = "kpsewhich " + fil;
858
859         cmd_ret const c = runCommand(kpsecmd);
860
861         LYXERR(Debug::LATEX, "kpse status = " << c.first << '\n'
862                  << "kpse result = `" << rtrim(c.second, "\n\r") << '\'');
863         if (c.first != -1)
864                 return FileName(os::internal_path(rtrim(to_utf8(from_filesystem8bit(c.second)),
865                                                         "\n\r")));
866         else
867                 return FileName();
868 }
869
870
871 void removeAutosaveFile(string const & filename)
872 {
873         string a = onlyPath(filename);
874         a += '#';
875         a += onlyFilename(filename);
876         a += '#';
877         FileName const autosave(a);
878         if (autosave.exists())
879                 unlink(autosave);
880 }
881
882
883 void readBB_lyxerrMessage(FileName const & file, bool & zipped,
884         string const & message)
885 {
886         LYXERR(Debug::GRAPHICS, "[readBB_from_PSFile] " << message);
887         // FIXME: Why is this func deleting a file? (Lgb)
888         if (zipped)
889                 unlink(file);
890 }
891
892
893 string const readBB_from_PSFile(FileName const & file)
894 {
895         // in a (e)ps-file it's an entry like %%BoundingBox:23 45 321 345
896         // It seems that every command in the header has an own line,
897         // getline() should work for all files.
898         // On the other hand some plot programs write the bb at the
899         // end of the file. Than we have in the header:
900         // %%BoundingBox: (atend)
901         // In this case we must check the end.
902         bool zipped = file.isZippedFile();
903         FileName const file_ = zipped ? unzipFile(file) : file;
904         string const format = file_.guessFormatFromContents();
905
906         if (format != "eps" && format != "ps") {
907                 readBB_lyxerrMessage(file_, zipped,"no(e)ps-format");
908                 return string();
909         }
910
911         static boost::regex bbox_re(
912                 "^%%BoundingBox:\\s*([[:digit:]]+)\\s+([[:digit:]]+)\\s+([[:digit:]]+)\\s+([[:digit:]]+)");
913         std::ifstream is(file_.toFilesystemEncoding().c_str());
914         while (is) {
915                 string s;
916                 getline(is,s);
917                 boost::smatch what;
918                 if (regex_match(s, what, bbox_re)) {
919                         // Our callers expect the tokens in the string
920                         // separated by single spaces.
921                         // FIXME: change return type from string to something
922                         // sensible
923                         ostringstream os;
924                         os << what.str(1) << ' ' << what.str(2) << ' '
925                            << what.str(3) << ' ' << what.str(4);
926                         string const bb = os.str();
927                         readBB_lyxerrMessage(file_, zipped, bb);
928                         return bb;
929                 }
930         }
931         readBB_lyxerrMessage(file_, zipped, "no bb found");
932         return string();
933 }
934
935
936 int compare_timestamps(FileName const & file1, FileName const & file2)
937 {
938         // If the original is newer than the copy, then copy the original
939         // to the new directory.
940
941         int cmp = 0;
942         if (file1.exists() && file2.exists()) {
943                 double const tmp = difftime(file1.lastModified(), file2.lastModified());
944                 if (tmp != 0)
945                         cmp = tmp > 0 ? 1 : -1;
946
947         } else if (file1.exists()) {
948                 cmp = 1;
949         } else if (file2.exists()) {
950                 cmp = -1;
951         }
952
953         return cmp;
954 }
955
956 } //namespace support
957 } // namespace lyx