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