]> git.lyx.org Git - lyx.git/blob - src/support/filetools.cpp
cleanup and reorder initialisation code of GuiView and GuiToolbars. Move some things...
[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/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 a vector of all files in directory dir having extension ext.
195 vector<FileName> const dirList(FileName const & dir, string const & ext)
196 {
197         // EXCEPTIONS FIXME. Rewrite needed when we turn on exceptions. (Lgb)
198         vector<FileName> dirlist;
199
200         if (!(dir.exists() && dir.isDirectory())) {
201                 LYXERR(Debug::FILES, "Directory \"" << dir
202                         << "\" does not exist to DirList.");
203                 return dirlist;
204         }
205
206         string extension;
207         if (!ext.empty() && ext[0] != '.')
208                 extension += '.';
209         extension += ext;
210
211         string const encoded_dir = dir.toFilesystemEncoding();
212         fs::directory_iterator dit(encoded_dir);
213         fs::directory_iterator end;
214         for (; dit != end; ++dit) {
215                 string const & fil = dit->leaf();
216                 if (suffixIs(fil, extension))
217                         dirlist.push_back(FileName::fromFilesystemEncoding(
218                                         encoded_dir + '/' + fil));
219         }
220         return dirlist;
221 }
222
223
224 // Returns the real name of file name in directory path, with optional
225 // extension ext.
226 FileName const fileSearch(string const & path, string const & name,
227                           string const & ext, search_mode mode)
228 {
229         // if `name' is an absolute path, we ignore the setting of `path'
230         // Expand Environmentvariables in 'name'
231         string const tmpname = replaceEnvironmentPath(name);
232         FileName fullname(makeAbsPath(tmpname, path));
233         // search first without extension, then with it.
234         if (fullname.isReadable())
235                 return fullname;
236         if (ext.empty())
237                 // We are done.
238                 return mode == allow_unreadable ? fullname : FileName();
239         // Only add the extension if it is not already the extension of
240         // fullname.
241         if (getExtension(fullname.absFilename()) != ext)
242                 fullname = FileName(addExtension(fullname.absFilename(), ext));
243         if (fullname.isReadable() || mode == allow_unreadable)
244                 return fullname;
245         return FileName();
246 }
247
248
249 // Search the file name.ext in the subdirectory dir of
250 //   1) user_lyxdir
251 //   2) build_lyxdir (if not empty)
252 //   3) system_lyxdir
253 FileName const libFileSearch(string const & dir, string const & name,
254                            string const & ext)
255 {
256         FileName fullname = fileSearch(addPath(package().user_support().absFilename(), dir),
257                                      name, ext);
258         if (!fullname.empty())
259                 return fullname;
260
261         if (!package().build_support().empty())
262                 fullname = fileSearch(addPath(package().build_support().absFilename(), dir),
263                                       name, ext);
264         if (!fullname.empty())
265                 return fullname;
266
267         return fileSearch(addPath(package().system_support().absFilename(), dir), name, ext);
268 }
269
270
271 FileName const i18nLibFileSearch(string const & dir, string const & name,
272                   string const & ext)
273 {
274         /* The highest priority value is the `LANGUAGE' environment
275            variable. But we don't use the value if the currently
276            selected locale is the C locale. This is a GNU extension.
277
278            Otherwise, w use a trick to guess what gettext has done:
279            each po file is able to tell us its name. (JMarc)
280         */
281
282         string lang = to_ascii(_("[[Replace with the code of your language]]"));
283         string const language = getEnv("LANGUAGE");
284         if (!lang.empty() && !language.empty())
285                 lang = language;
286
287         string l;
288         lang = split(lang, l, ':');
289         while (!l.empty()) {
290                 FileName tmp;
291                 // First try with the full name
292                 tmp = libFileSearch(addPath(dir, l), name, ext);
293                 if (!tmp.empty())
294                         return tmp;
295
296                 // Then the name without country code
297                 string const shortl = token(l, '_', 0);
298                 if (shortl != l) {
299                         tmp = libFileSearch(addPath(dir, shortl), name, ext);
300                         if (!tmp.empty())
301                                 return tmp;
302                 }
303
304 #if 1
305                 // For compatibility, to be removed later (JMarc)
306                 tmp = libFileSearch(dir, token(l, '_', 0) + '_' + name,
307                                     ext);
308                 if (!tmp.empty()) {
309                         lyxerr << "i18nLibFileSearch: File `" << tmp
310                                << "' has been found by the old method" <<endl;
311                         return tmp;
312                 }
313 #endif
314                 lang = split(lang, l, ':');
315         }
316
317         return libFileSearch(dir, name, ext);
318 }
319
320
321 string const libScriptSearch(string const & command_in, quote_style style)
322 {
323         static string const token_scriptpath = "$$s/";
324
325         string command = command_in;
326         // Find the starting position of "$$s/"
327         string::size_type const pos1 = command.find(token_scriptpath);
328         if (pos1 == string::npos)
329                 return command;
330         // Find the end of the "$$s/some_subdir/some_script" word within
331         // command. Assumes that the script name does not contain spaces.
332         string::size_type const start_script = pos1 + 4;
333         string::size_type const pos2 = command.find(' ', start_script);
334         string::size_type const size_script = pos2 == string::npos?
335                 (command.size() - start_script) : pos2 - start_script;
336
337         // Does this script file exist?
338         string const script =
339                 libFileSearch(".", command.substr(start_script, size_script)).absFilename();
340
341         if (script.empty()) {
342                 // Replace "$$s/" with ""
343                 command.erase(pos1, 4);
344         } else {
345                 // Replace "$$s/foo/some_script" with "<path to>/some_script".
346                 string::size_type const size_replace = size_script + 4;
347                 command.replace(pos1, size_replace, quoteName(script, style));
348         }
349
350         return command;
351 }
352
353
354 static FileName createTmpDir(FileName const & tempdir, string const & mask)
355 {
356         LYXERR(Debug::FILES, "createTmpDir: tempdir=`" << tempdir << "'\n"
357                 << "createTmpDir:    mask=`" << mask << '\'');
358
359         FileName const tmpfl(tempName(tempdir, mask));
360         // lyx::tempName actually creates a file to make sure that it
361         // stays unique. So we have to delete it before we can create
362         // a dir with the same name. Note also that we are not thread
363         // safe because of the gap between unlink and mkdir. (Lgb)
364         unlink(tmpfl);
365
366         if (tmpfl.empty() || mkdir(tmpfl, 0700)) {
367                 lyxerr << "LyX could not create the temporary directory '"
368                        << tmpfl << "'" << endl;
369                 return FileName();
370         }
371
372         return tmpfl;
373 }
374
375 string const createBufferTmpDir()
376 {
377         static int count;
378         // We are in our own directory.  Why bother to mangle name?
379         // In fact I wrote this code to circumvent a problematic behaviour
380         // (bug?) of EMX mkstemp().
381         string const tmpfl =
382                 package().temp_dir().absFilename() + "/lyx_tmpbuf" +
383                 convert<string>(count++);
384
385         if (mkdir(FileName(tmpfl), 0777)) {
386                 lyxerr << "LyX could not create the temporary directory '"
387                        << tmpfl << "'" << endl;
388                 return string();
389         }
390         return tmpfl;
391 }
392
393
394 FileName const createLyXTmpDir(FileName const & deflt)
395 {
396         if (deflt.empty() || deflt.absFilename() == "/tmp")
397                 return createTmpDir(FileName("/tmp"), "lyx_tmpdir");
398
399         if (!mkdir(deflt, 0777)) 
400                 return deflt;
401
402         if (deflt.isDirWritable()) {
403                 // deflt could not be created because it
404                 // did exist already, so let's create our own
405                 // dir inside deflt.
406                 return createTmpDir(deflt, "lyx_tmpdir");
407         } else {
408                 // some other error occured.
409                 return createTmpDir(FileName("/tmp"), "lyx_tmpdir");
410         }
411 }
412
413
414 // Strip filename from path name
415 string const onlyPath(string const & filename)
416 {
417         // If empty filename, return empty
418         if (filename.empty())
419                 return filename;
420
421         // Find last / or start of filename
422         size_t j = filename.rfind('/');
423         return j == string::npos ? "./" : filename.substr(0, j + 1);
424 }
425
426
427 // Convert relative path into absolute path based on a basepath.
428 // If relpath is absolute, just use that.
429 // If basepath is empty, use CWD as base.
430 FileName const makeAbsPath(string const & relPath, string const & basePath)
431 {
432         // checks for already absolute path
433         if (os::is_absolute_path(relPath))
434                 return FileName(relPath);
435
436         // Copies given paths
437         string tempRel = os::internal_path(relPath);
438         // Since TempRel is NOT absolute, we can safely replace "//" with "/"
439         tempRel = subst(tempRel, "//", "/");
440
441         string tempBase;
442
443         if (os::is_absolute_path(basePath))
444                 tempBase = basePath;
445         else
446                 tempBase = addPath(getcwd().absFilename(), basePath);
447
448         // Handle /./ at the end of the path
449         while (suffixIs(tempBase, "/./"))
450                 tempBase.erase(tempBase.length() - 2);
451
452         // processes relative path
453         string rTemp = tempRel;
454         string temp;
455
456         while (!rTemp.empty()) {
457                 // Split by next /
458                 rTemp = split(rTemp, temp, '/');
459
460                 if (temp == ".") continue;
461                 if (temp == "..") {
462                         // Remove one level of TempBase
463                         string::difference_type i = tempBase.length() - 2;
464                         if (i < 0)
465                                 i = 0;
466                         while (i > 0 && tempBase[i] != '/')
467                                 --i;
468                         if (i > 0)
469                                 tempBase.erase(i, string::npos);
470                         else
471                                 tempBase += '/';
472                 } else if (temp.empty() && !rTemp.empty()) {
473                                 tempBase = os::current_root() + rTemp;
474                                 rTemp.erase();
475                 } else {
476                         // Add this piece to TempBase
477                         if (!suffixIs(tempBase, '/'))
478                                 tempBase += '/';
479                         tempBase += temp;
480                 }
481         }
482
483         // returns absolute path
484         return FileName(os::internal_path(tempBase));
485 }
486
487
488 // Correctly append filename to the pathname.
489 // If pathname is '.', then don't use pathname.
490 // Chops any path of filename.
491 string const addName(string const & path, string const & fname)
492 {
493         string const basename = onlyFilename(fname);
494         string buf;
495
496         if (path != "." && path != "./" && !path.empty()) {
497                 buf = os::internal_path(path);
498                 if (!suffixIs(path, '/'))
499                         buf += '/';
500         }
501
502         return buf + basename;
503 }
504
505
506 // Strips path from filename
507 string const onlyFilename(string const & fname)
508 {
509         if (fname.empty())
510                 return fname;
511
512         string::size_type j = fname.rfind('/');
513         if (j == string::npos) // no '/' in fname
514                 return fname;
515
516         // Strip to basename
517         return fname.substr(j + 1);
518 }
519
520
521 /// Returns true is path is absolute
522 bool absolutePath(string const & path)
523 {
524         return os::is_absolute_path(path);
525 }
526
527
528 // Create absolute path. If impossible, don't do anything
529 // Supports ./ and ~/. Later we can add support for ~logname/. (Asger)
530 string const expandPath(string const & path)
531 {
532         // checks for already absolute path
533         string rTemp = replaceEnvironmentPath(path);
534         if (os::is_absolute_path(rTemp))
535                 return rTemp;
536
537         string temp;
538         string const copy = rTemp;
539
540         // Split by next /
541         rTemp = split(rTemp, temp, '/');
542
543         if (temp == ".")
544                 return getcwd().absFilename() + '/' + rTemp;
545
546         if (temp == "~")
547                 return package().home_dir().absFilename() + '/' + rTemp;
548
549         if (temp == "..")
550                 return makeAbsPath(copy).absFilename();
551
552         // Don't know how to handle this
553         return copy;
554 }
555
556
557 // Normalize a path. Constracts path/../path
558 // Can't handle "../../" or "/../" (Asger)
559 // Also converts paths like /foo//bar ==> /foo/bar
560 string const normalizePath(string const & path)
561 {
562         // Normalize paths like /foo//bar ==> /foo/bar
563         static boost::regex regex("/{2,}");
564         string const tmppath = boost::regex_merge(path, regex, "/");
565
566         fs::path const npath = fs::path(tmppath, fs::no_check).normalize();
567
568         if (!npath.is_complete())
569                 return "./" + npath.string() + '/';
570
571         return npath.string() + '/';
572 }
573
574
575 // Search the string for ${VAR} and $VAR and replace VAR using getenv.
576 string const replaceEnvironmentPath(string const & path)
577 {
578         // ${VAR} is defined as
579         // $\{[A-Za-z_][A-Za-z_0-9]*\}
580         static string const envvar_br = "[$]\\{([A-Za-z_][A-Za-z_0-9]*)\\}";
581
582         // $VAR is defined as:
583         // $\{[A-Za-z_][A-Za-z_0-9]*\}
584         static string const envvar = "[$]([A-Za-z_][A-Za-z_0-9]*)";
585
586         static boost::regex envvar_br_re("(.*)" + envvar_br + "(.*)");
587         static boost::regex envvar_re("(.*)" + envvar + "(.*)");
588         boost::smatch what;
589
590         string result = path;
591         while (1) {
592                 regex_match(result, what, envvar_br_re);
593                 if (!what[0].matched) {
594                         regex_match(result, what, envvar_re);
595                         if (!what[0].matched)
596                                 break;
597                 }
598                 result = what.str(1) + getEnv(what.str(2)) + what.str(3);
599         }
600         return result;
601 }
602
603
604 // Make relative path out of two absolute paths
605 docstring const makeRelPath(docstring const & abspath, docstring const & basepath)
606 // Makes relative path out of absolute path. If it is deeper than basepath,
607 // it's easy. If basepath and abspath share something (they are all deeper
608 // than some directory), it'll be rendered using ..'s. If they are completely
609 // different, then the absolute path will be used as relative path.
610 {
611         docstring::size_type const abslen = abspath.length();
612         docstring::size_type const baselen = basepath.length();
613
614         docstring::size_type i = os::common_path(abspath, basepath);
615
616         if (i == 0) {
617                 // actually no match - cannot make it relative
618                 return abspath;
619         }
620
621         // Count how many dirs there are in basepath above match
622         // and append as many '..''s into relpath
623         docstring buf;
624         docstring::size_type j = i;
625         while (j < baselen) {
626                 if (basepath[j] == '/') {
627                         if (j + 1 == baselen)
628                                 break;
629                         buf += "../";
630                 }
631                 ++j;
632         }
633
634         // Append relative stuff from common directory to abspath
635         if (abspath[i] == '/')
636                 ++i;
637         for (; i < abslen; ++i)
638                 buf += abspath[i];
639         // Remove trailing /
640         if (suffixIs(buf, '/'))
641                 buf.erase(buf.length() - 1);
642         // Substitute empty with .
643         if (buf.empty())
644                 buf = '.';
645         return buf;
646 }
647
648
649 // Append sub-directory(ies) to a path in an intelligent way
650 string const addPath(string const & path, string const & path_2)
651 {
652         string buf;
653         string const path2 = os::internal_path(path_2);
654
655         if (!path.empty() && path != "." && path != "./") {
656                 buf = os::internal_path(path);
657                 if (path[path.length() - 1] != '/')
658                         buf += '/';
659         }
660
661         if (!path2.empty()) {
662                 string::size_type const p2start = path2.find_first_not_of('/');
663                 string::size_type const p2end = path2.find_last_not_of('/');
664                 string const tmp = path2.substr(p2start, p2end - p2start + 1);
665                 buf += tmp + '/';
666         }
667         return buf;
668 }
669
670
671 string const changeExtension(string const & oldname, string const & extension)
672 {
673         string::size_type const last_slash = oldname.rfind('/');
674         string::size_type last_dot = oldname.rfind('.');
675         if (last_dot < last_slash && last_slash != string::npos)
676                 last_dot = string::npos;
677
678         string ext;
679         // Make sure the extension starts with a dot
680         if (!extension.empty() && extension[0] != '.')
681                 ext= '.' + extension;
682         else
683                 ext = extension;
684
685         return os::internal_path(oldname.substr(0, last_dot) + ext);
686 }
687
688
689 string const removeExtension(string const & name)
690 {
691         return changeExtension(name, string());
692 }
693
694
695 string const addExtension(string const & name, string const & extension)
696 {
697         if (!extension.empty() && extension[0] != '.')
698                 return name + '.' + extension;
699         return name + extension;
700 }
701
702
703 /// Return the extension of the file (not including the .)
704 string const getExtension(string const & name)
705 {
706         string::size_type const last_slash = name.rfind('/');
707         string::size_type const last_dot = name.rfind('.');
708         if (last_dot != string::npos &&
709             (last_slash == string::npos || last_dot > last_slash))
710                 return name.substr(last_dot + 1,
711                                    name.length() - (last_dot + 1));
712         else
713                 return string();
714 }
715
716
717 string const unzippedFileName(string const & zipped_file)
718 {
719         string const ext = getExtension(zipped_file);
720         if (ext == "gz" || ext == "z" || ext == "Z")
721                 return changeExtension(zipped_file, string());
722         return "unzipped_" + zipped_file;
723 }
724
725
726 FileName const unzipFile(FileName const & zipped_file, string const & unzipped_file)
727 {
728         FileName const tempfile = FileName(unzipped_file.empty() ?
729                 unzippedFileName(zipped_file.toFilesystemEncoding()) :
730                 unzipped_file);
731         // Run gunzip
732         string const command = "gunzip -c " +
733                 zipped_file.toFilesystemEncoding() + " > " +
734                 tempfile.toFilesystemEncoding();
735         Systemcall one;
736         one.startscript(Systemcall::Wait, command);
737         // test that command was executed successfully (anon)
738         // yes, please do. (Lgb)
739         return tempfile;
740 }
741
742
743 docstring const makeDisplayPath(string const & path, unsigned int threshold)
744 {
745         string str = path;
746
747         // If file is from LyXDir, display it as if it were relative.
748         string const system = package().system_support().absFilename();
749         if (prefixIs(str, system) && str != system)
750                 return from_utf8("[" + str.erase(0, system.length()) + "]");
751
752         // replace /home/blah with ~/
753         string const home = package().home_dir().absFilename();
754         if (!home.empty() && prefixIs(str, home))
755                 str = subst(str, home, "~");
756
757         if (str.length() <= threshold)
758                 return from_utf8(os::external_path(str));
759
760         string const prefix = ".../";
761         string temp;
762
763         while (str.length() > threshold)
764                 str = split(str, temp, '/');
765
766         // Did we shorten everything away?
767         if (str.empty()) {
768                 // Yes, filename itself is too long.
769                 // Pick the start and the end of the filename.
770                 str = onlyFilename(path);
771                 string const head = str.substr(0, threshold / 2 - 3);
772
773                 string::size_type len = str.length();
774                 string const tail =
775                         str.substr(len - threshold / 2 - 2, len - 1);
776                 str = head + "..." + tail;
777         }
778
779         return from_utf8(os::external_path(prefix + str));
780 }
781
782
783 bool readLink(FileName const & file, FileName & link)
784 {
785 #ifdef HAVE_READLINK
786         char linkbuffer[512];
787         // Should be PATH_MAX but that needs autconf support
788         string const encoded = file.toFilesystemEncoding();
789         int const nRead = ::readlink(encoded.c_str(),
790                                      linkbuffer, sizeof(linkbuffer) - 1);
791         if (nRead <= 0)
792                 return false;
793         linkbuffer[nRead] = '\0'; // terminator
794         link = makeAbsPath(linkbuffer, onlyPath(file.absFilename()));
795         return true;
796 #else
797         return false;
798 #endif
799 }
800
801
802 cmd_ret const runCommand(string const & cmd)
803 {
804         // FIXME: replace all calls to RunCommand with ForkedCall
805         // (if the output is not needed) or the code in ISpell.cpp
806         // (if the output is needed).
807
808         // One question is if we should use popen or
809         // create our own popen based on fork, exec, pipe
810         // of course the best would be to have a
811         // pstream (process stream), with the
812         // variants ipstream, opstream
813
814 #if defined (HAVE_POPEN)
815         FILE * inf = ::popen(cmd.c_str(), os::popen_read_mode());
816 #elif defined (HAVE__POPEN)
817         FILE * inf = ::_popen(cmd.c_str(), os::popen_read_mode());
818 #else
819 #error No popen() function.
820 #endif
821
822         // (Claus Hentschel) Check if popen was succesful ;-)
823         if (!inf) {
824                 lyxerr << "RunCommand:: could not start child process" << endl;
825                 return make_pair(-1, string());
826         }
827
828         string ret;
829         int c = fgetc(inf);
830         while (c != EOF) {
831                 ret += static_cast<char>(c);
832                 c = fgetc(inf);
833         }
834
835 #if defined (HAVE_PCLOSE)
836         int const pret = pclose(inf);
837 #elif defined (HAVE__PCLOSE)
838         int const pret = _pclose(inf);
839 #else
840 #error No pclose() function.
841 #endif
842
843         if (pret == -1)
844                 perror("RunCommand:: could not terminate child process");
845
846         return make_pair(pret, ret);
847 }
848
849
850 FileName const findtexfile(string const & fil, string const & /*format*/)
851 {
852         /* There is no problem to extend this function too use other
853            methods to look for files. It could be setup to look
854            in environment paths and also if wanted as a last resort
855            to a recursive find. One of the easier extensions would
856            perhaps be to use the LyX file lookup methods. But! I am
857            going to implement this until I see some demand for it.
858            Lgb
859         */
860
861         // If the file can be found directly, we just return a
862         // absolute path version of it.
863         FileName const absfile(makeAbsPath(fil));
864         if (absfile.exists())
865                 return absfile;
866
867         // No we try to find it using kpsewhich.
868         // It seems from the kpsewhich manual page that it is safe to use
869         // kpsewhich without --format: "When the --format option is not
870         // given, the search path used when looking for a file is inferred
871         // from the name given, by looking for a known extension. If no
872         // known extension is found, the search path for TeX source files
873         // is used."
874         // However, we want to take advantage of the format sine almost all
875         // the different formats has environment variables that can be used
876         // to controll which paths to search. f.ex. bib looks in
877         // BIBINPUTS and TEXBIB. Small list follows:
878         // bib - BIBINPUTS, TEXBIB
879         // bst - BSTINPUTS
880         // graphic/figure - TEXPICTS, TEXINPUTS
881         // ist - TEXINDEXSTYLE, INDEXSTYLE
882         // pk - PROGRAMFONTS, PKFONTS, TEXPKS, GLYPHFONTS, TEXFONTS
883         // tex - TEXINPUTS
884         // tfm - TFMFONTS, TEXFONTS
885         // This means that to use kpsewhich in the best possible way we
886         // should help it by setting additional path in the approp. envir.var.
887         string const kpsecmd = "kpsewhich " + fil;
888
889         cmd_ret const c = runCommand(kpsecmd);
890
891         LYXERR(Debug::LATEX, "kpse status = " << c.first << '\n'
892                  << "kpse result = `" << rtrim(c.second, "\n\r") << '\'');
893         if (c.first != -1)
894                 return FileName(os::internal_path(rtrim(to_utf8(from_filesystem8bit(c.second)),
895                                                         "\n\r")));
896         else
897                 return FileName();
898 }
899
900
901 void removeAutosaveFile(string const & filename)
902 {
903         string a = onlyPath(filename);
904         a += '#';
905         a += onlyFilename(filename);
906         a += '#';
907         FileName const autosave(a);
908         if (autosave.exists())
909                 unlink(autosave);
910 }
911
912
913 void readBB_lyxerrMessage(FileName const & file, bool & zipped,
914         string const & message)
915 {
916         LYXERR(Debug::GRAPHICS, "[readBB_from_PSFile] " << message);
917         // FIXME: Why is this func deleting a file? (Lgb)
918         if (zipped)
919                 unlink(file);
920 }
921
922
923 string const readBB_from_PSFile(FileName const & file)
924 {
925         // in a (e)ps-file it's an entry like %%BoundingBox:23 45 321 345
926         // It seems that every command in the header has an own line,
927         // getline() should work for all files.
928         // On the other hand some plot programs write the bb at the
929         // end of the file. Than we have in the header:
930         // %%BoundingBox: (atend)
931         // In this case we must check the end.
932         bool zipped = file.isZippedFile();
933         FileName const file_ = zipped ? unzipFile(file) : file;
934         string const format = file_.guessFormatFromContents();
935
936         if (format != "eps" && format != "ps") {
937                 readBB_lyxerrMessage(file_, zipped,"no(e)ps-format");
938                 return string();
939         }
940
941         static boost::regex bbox_re(
942                 "^%%BoundingBox:\\s*([[:digit:]]+)\\s+([[:digit:]]+)\\s+([[:digit:]]+)\\s+([[:digit:]]+)");
943         std::ifstream is(file_.toFilesystemEncoding().c_str());
944         while (is) {
945                 string s;
946                 getline(is,s);
947                 boost::smatch what;
948                 if (regex_match(s, what, bbox_re)) {
949                         // Our callers expect the tokens in the string
950                         // separated by single spaces.
951                         // FIXME: change return type from string to something
952                         // sensible
953                         ostringstream os;
954                         os << what.str(1) << ' ' << what.str(2) << ' '
955                            << what.str(3) << ' ' << what.str(4);
956                         string const bb = os.str();
957                         readBB_lyxerrMessage(file_, zipped, bb);
958                         return bb;
959                 }
960         }
961         readBB_lyxerrMessage(file_, zipped, "no bb found");
962         return string();
963 }
964
965
966 int compare_timestamps(FileName const & file1, FileName const & file2)
967 {
968         // If the original is newer than the copy, then copy the original
969         // to the new directory.
970
971         int cmp = 0;
972         if (file1.exists() && file2.exists()) {
973                 double const tmp = difftime(file1.lastModified(), file2.lastModified());
974                 if (tmp != 0)
975                         cmp = tmp > 0 ? 1 : -1;
976
977         } else if (file1.exists()) {
978                 cmp = 1;
979         } else if (file2.exists()) {
980                 cmp = -1;
981         }
982
983         return cmp;
984 }
985
986 } //namespace support
987 } // namespace lyx