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