3 * This file is part of LyX, the document processor.
4 * Licence details can be found in the file COPYING.
6 * parts Copyright 1985, 1990, 1993 Free Software Foundation, Inc.
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
17 * Full author contact details are available in file CREDITS.
19 * General path-mangling functions
24 #include "support/filetools.h"
26 #include "support/debug.h"
27 #include "support/environment.h"
28 #include "support/gettext.h"
29 #include "support/lstrings.h"
30 #include "support/os.h"
31 #include "support/Package.h"
32 #include "support/Path.h"
33 #include "support/Systemcall.h"
34 #include "support/qstring_helpers.h"
38 #include "support/lassert.h"
39 #include <boost/regex.hpp>
58 bool isLyXFilename(string const & filename)
60 return suffixIs(ascii_lowercase(filename), ".lyx");
64 bool isSGMLFilename(string const & filename)
66 return suffixIs(ascii_lowercase(filename), ".sgml");
70 bool isValidLaTeXFilename(string const & filename)
72 string const invalid_chars("#$%{}()[]\"^");
73 return filename.find_first_of(invalid_chars) == string::npos;
77 string const latex_path(string const & original_path,
78 latex_path_extension extension,
81 // On cygwin, we may need windows or posix style paths.
82 string path = os::latex_path(original_path);
83 path = subst(path, "~", "\\string~");
84 if (path.find(' ') != string::npos) {
85 // We can't use '"' because " is sometimes active (e.g. if
86 // babel is loaded with the "german" option)
87 if (extension == EXCLUDE_EXTENSION) {
88 // ChangeExtension calls os::internal_path internally
89 // so don't use it to remove the extension.
90 string const ext = getExtension(path);
91 string const base = ext.empty() ?
93 path.substr(0, path.length() - ext.length() - 1);
94 // ChangeExtension calls os::internal_path internally
95 // so don't use it to re-add the extension.
96 path = "\\string\"" + base + "\\string\"." + ext;
98 path = "\\string\"" + path + "\\string\"";
102 return dots == ESCAPE_DOTS ? subst(path, ".", "\\lyxdot ") : path;
106 // Substitutes spaces with underscores in filename (and path)
107 FileName const makeLatexName(FileName const & file)
109 string name = file.onlyFileName();
110 string const path = file.onlyPath().absFilename() + "/";
112 // ok so we scan through the string twice, but who cares.
113 // FIXME: in Unicode time this will break for sure! There is
114 // a non-latin world out there...
115 string const keep = "abcdefghijklmnopqrstuvwxyz"
116 "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
117 "@!'()*+,-./0123456789:;<=>?[]`|";
119 string::size_type pos = 0;
120 while ((pos = name.find_first_not_of(keep, pos)) != string::npos)
123 FileName latex_name(path + name);
124 latex_name.changeExtension(".tex");
129 string const quoteName(string const & name, quote_style style)
133 // This does not work for filenames containing " (windows)
134 // or ' (all other OSes). This can't be changed easily, since
135 // we would need to adapt the command line parser in
136 // Forkedcall::generateChild. Therefore we don't pass user
137 // filenames to child processes if possible. We store them in
138 // a python script instead, where we don't have these
141 return (os::shell() == os::UNIX) ?
145 // According to the QProcess parser, a single double
146 // quote is represented by three consecutive ones.
147 // Here we simply escape the double quote and let our
148 // simple parser in Systemcall.cpp do the substitution.
149 return '"' + subst(name, "\"", "\\\"") + '"';
152 return "\"" + subst(subst(name, "\\", "\\\\"), "\"", "\\\"")
155 // shut up stupid compiler
161 // Uses a string of paths separated by ";"s to find a file to open.
162 // Can't cope with pathnames with a ';' in them. Returns full path to file.
163 // If path entry begins with $$LyX/, use system_lyxdir
164 // If path entry begins with $$User/, use user_lyxdir
165 // Example: "$$User/doc;$$LyX/doc"
166 FileName const fileOpenSearch(string const & path, string const & name,
171 bool notfound = true;
172 string tmppath = split(path, path_element, ';');
174 while (notfound && !path_element.empty()) {
175 path_element = os::internal_path(path_element);
176 if (!suffixIs(path_element, '/'))
178 path_element = subst(path_element, "$$LyX",
179 package().system_support().absFilename());
180 path_element = subst(path_element, "$$User",
181 package().user_support().absFilename());
183 real_file = fileSearch(path_element, name, ext);
185 if (real_file.empty()) {
187 tmppath = split(tmppath, path_element, ';');
188 } while (!tmppath.empty() && path_element.empty());
198 // Returns the real name of file name in directory path, with optional
200 FileName const fileSearch(string const & path, string const & name,
201 string const & ext, search_mode mode)
203 // if `name' is an absolute path, we ignore the setting of `path'
204 // Expand Environmentvariables in 'name'
205 string const tmpname = replaceEnvironmentPath(name);
206 FileName fullname(makeAbsPath(tmpname, path));
207 // search first without extension, then with it.
208 if (fullname.isReadableFile())
212 return mode == may_not_exist ? fullname : FileName();
213 // Only add the extension if it is not already the extension of
215 if (getExtension(fullname.absFilename()) != ext)
216 fullname = FileName(addExtension(fullname.absFilename(), ext));
217 if (fullname.isReadableFile() || mode == may_not_exist)
223 // Search the file name.ext in the subdirectory dir of
225 // 2) build_lyxdir (if not empty)
227 FileName const libFileSearch(string const & dir, string const & name,
230 FileName fullname = fileSearch(addPath(package().user_support().absFilename(), dir),
232 if (!fullname.empty())
235 if (!package().build_support().empty())
236 fullname = fileSearch(addPath(package().build_support().absFilename(), dir),
238 if (!fullname.empty())
241 return fileSearch(addPath(package().system_support().absFilename(), dir), name, ext);
245 FileName const i18nLibFileSearch(string const & dir, string const & name,
248 /* The highest priority value is the `LANGUAGE' environment
249 variable. But we don't use the value if the currently
250 selected locale is the C locale. This is a GNU extension.
252 Otherwise, w use a trick to guess what support/gettext.has done:
253 each po file is able to tell us its name. (JMarc)
256 string lang = to_ascii(_("[[Replace with the code of your language]]"));
257 string const language = getEnv("LANGUAGE");
258 if (!lang.empty() && !language.empty())
262 lang = split(lang, l, ':');
265 // First try with the full name
266 tmp = libFileSearch(addPath(dir, l), name, ext);
270 // Then the name without country code
271 string const shortl = token(l, '_', 0);
273 tmp = libFileSearch(addPath(dir, shortl), name, ext);
279 // For compatibility, to be removed later (JMarc)
280 tmp = libFileSearch(dir, token(l, '_', 0) + '_' + name,
283 lyxerr << "i18nLibFileSearch: File `" << tmp
284 << "' has been found by the old method" <<endl;
288 lang = split(lang, l, ':');
291 return libFileSearch(dir, name, ext);
295 string const libScriptSearch(string const & command_in, quote_style style)
297 static string const token_scriptpath = "$$s/";
299 string command = command_in;
300 // Find the starting position of "$$s/"
301 string::size_type const pos1 = command.find(token_scriptpath);
302 if (pos1 == string::npos)
304 // Find the end of the "$$s/some_subdir/some_script" word within
305 // command. Assumes that the script name does not contain spaces.
306 string::size_type const start_script = pos1 + 4;
307 string::size_type const pos2 = command.find(' ', start_script);
308 string::size_type const size_script = pos2 == string::npos?
309 (command.size() - start_script) : pos2 - start_script;
311 // Does this script file exist?
312 string const script =
313 libFileSearch(".", command.substr(start_script, size_script)).absFilename();
315 if (script.empty()) {
316 // Replace "$$s/" with ""
317 command.erase(pos1, 4);
319 // Replace "$$s/foo/some_script" with "<path to>/some_script".
320 string::size_type const size_replace = size_script + 4;
321 command.replace(pos1, size_replace, quoteName(script, style));
328 static FileName createTmpDir(FileName const & tempdir, string const & mask)
330 LYXERR(Debug::FILES, "createTmpDir: tempdir=`" << tempdir << "'\n"
331 << "createTmpDir: mask=`" << mask << '\'');
333 FileName const tmpfl = FileName::tempName(tempdir, mask);
335 if (tmpfl.empty() || !tmpfl.createDirectory(0700)) {
336 LYXERR0("LyX could not create temporary directory in " << tempdir
345 FileName const createLyXTmpDir(FileName const & deflt)
347 if (deflt.empty() || deflt == package().system_temp_dir())
348 return createTmpDir(package().system_temp_dir(), "lyx_tmpdir");
350 if (deflt.createDirectory(0777))
353 if (deflt.isDirWritable()) {
354 // deflt could not be created because it
355 // did exist already, so let's create our own
357 return createTmpDir(deflt, "lyx_tmpdir");
359 // some other error occured.
360 return createTmpDir(package().system_temp_dir(), "lyx_tmpdir");
365 // Strip filename from path name
366 string const onlyPath(string const & filename)
368 // If empty filename, return empty
369 if (filename.empty())
372 // Find last / or start of filename
373 size_t j = filename.rfind('/');
374 return j == string::npos ? "./" : filename.substr(0, j + 1);
378 // Convert relative path into absolute path based on a basepath.
379 // If relpath is absolute, just use that.
380 // If basepath is empty, use CWD as base.
381 // Note that basePath can be a relative path, in the sense that it may
382 // not begin with "/" (e.g.), but it should NOT contain such constructs
384 // FIXME It might be nice if the code didn't simply assume that.
385 FileName const makeAbsPath(string const & relPath, string const & basePath)
387 // checks for already absolute path
388 if (FileName::isAbsolute(relPath))
389 return FileName(relPath);
391 // Copies given paths
392 string tempRel = os::internal_path(relPath);
393 // Since TempRel is NOT absolute, we can safely replace "//" with "/"
394 tempRel = subst(tempRel, "//", "/");
398 if (FileName::isAbsolute(basePath))
401 tempBase = addPath(FileName::getcwd().absFilename(), basePath);
403 // Handle /./ at the end of the path
404 while (suffixIs(tempBase, "/./"))
405 tempBase.erase(tempBase.length() - 2);
407 // processes relative path
408 string rTemp = tempRel;
411 // Check for a leading "~"
413 rTemp = split(rTemp, temp, '/');
415 tempBase = package().home_dir().absFilename();
420 while (!rTemp.empty()) {
422 rTemp = split(rTemp, temp, '/');
424 if (temp == ".") continue;
426 // Remove one level of TempBase
427 if (tempBase.length() <= 1) {
428 //this is supposed to be an absolute path, so...
432 //erase a trailing slash if there is one
433 if (suffixIs(tempBase, "/"))
434 tempBase.erase(tempBase.length() - 1, string::npos);
436 string::size_type i = tempBase.length() - 1;
437 while (i > 0 && tempBase[i] != '/')
440 tempBase.erase(i, string::npos);
443 } else if (temp.empty() && !rTemp.empty()) {
444 tempBase = os::current_root() + rTemp;
447 // Add this piece to TempBase
448 if (!suffixIs(tempBase, '/'))
454 // returns absolute path
455 return FileName(tempBase);
459 // Correctly append filename to the pathname.
460 // If pathname is '.', then don't use pathname.
461 // Chops any path of filename.
462 string const addName(string const & path, string const & fname)
464 string const basename = onlyFilename(fname);
467 if (path != "." && path != "./" && !path.empty()) {
468 buf = os::internal_path(path);
469 if (!suffixIs(path, '/'))
473 return buf + basename;
477 // Strips path from filename
478 string const onlyFilename(string const & fname)
483 string::size_type j = fname.rfind('/');
484 if (j == string::npos) // no '/' in fname
488 return fname.substr(j + 1);
492 // Create absolute path. If impossible, don't do anything
493 // Supports ./ and ~/. Later we can add support for ~logname/. (Asger)
494 string const expandPath(string const & path)
496 // checks for already absolute path
497 string rTemp = replaceEnvironmentPath(path);
498 if (FileName::isAbsolute(rTemp))
502 string const copy = rTemp;
505 rTemp = split(rTemp, temp, '/');
508 return FileName::getcwd().absFilename() + '/' + rTemp;
511 return package().home_dir().absFilename() + '/' + rTemp;
514 return makeAbsPath(copy).absFilename();
516 // Don't know how to handle this
521 // Search the string for ${VAR} and $VAR and replace VAR using getenv.
522 string const replaceEnvironmentPath(string const & path)
524 // ${VAR} is defined as
525 // $\{[A-Za-z_][A-Za-z_0-9]*\}
526 static string const envvar_br = "[$]\\{([A-Za-z_][A-Za-z_0-9]*)\\}";
528 // $VAR is defined as:
529 // $\{[A-Za-z_][A-Za-z_0-9]*\}
530 static string const envvar = "[$]([A-Za-z_][A-Za-z_0-9]*)";
532 static boost::regex envvar_br_re("(.*)" + envvar_br + "(.*)");
533 static boost::regex envvar_re("(.*)" + envvar + "(.*)");
536 string remaining = path;
538 regex_match(remaining, what, envvar_br_re);
539 if (!what[0].matched) {
540 regex_match(remaining, what, envvar_re);
541 if (!what[0].matched) {
546 string env_var = getEnv(what.str(2));
547 if (!env_var.empty())
548 result += what.str(1) + env_var;
550 result += what.str(1) + "$" + what.str(2);
551 remaining = what.str(3);
557 // Make relative path out of two absolute paths
558 docstring const makeRelPath(docstring const & abspath, docstring const & basepath)
559 // Makes relative path out of absolute path. If it is deeper than basepath,
560 // it's easy. If basepath and abspath share something (they are all deeper
561 // than some directory), it'll be rendered using ..'s. If they are completely
562 // different, then the absolute path will be used as relative path.
564 docstring::size_type const abslen = abspath.length();
565 docstring::size_type const baselen = basepath.length();
567 docstring::size_type i = os::common_path(abspath, basepath);
570 // actually no match - cannot make it relative
574 // Count how many dirs there are in basepath above match
575 // and append as many '..''s into relpath
577 docstring::size_type j = i;
578 while (j < baselen) {
579 if (basepath[j] == '/') {
580 if (j + 1 == baselen)
587 // Append relative stuff from common directory to abspath
588 if (abspath[i] == '/')
590 for (; i < abslen; ++i)
593 if (suffixIs(buf, '/'))
594 buf.erase(buf.length() - 1);
595 // Substitute empty with .
602 // Append sub-directory(ies) to a path in an intelligent way
603 string const addPath(string const & path, string const & path_2)
606 string const path2 = os::internal_path(path_2);
608 if (!path.empty() && path != "." && path != "./") {
609 buf = os::internal_path(path);
610 if (path[path.length() - 1] != '/')
614 if (!path2.empty()) {
615 string::size_type const p2start = path2.find_first_not_of('/');
616 string::size_type const p2end = path2.find_last_not_of('/');
617 string const tmp = path2.substr(p2start, p2end - p2start + 1);
624 string const changeExtension(string const & oldname, string const & extension)
626 string::size_type const last_slash = oldname.rfind('/');
627 string::size_type last_dot = oldname.rfind('.');
628 if (last_dot < last_slash && last_slash != string::npos)
629 last_dot = string::npos;
632 // Make sure the extension starts with a dot
633 if (!extension.empty() && extension[0] != '.')
634 ext= '.' + extension;
638 return os::internal_path(oldname.substr(0, last_dot) + ext);
642 string const removeExtension(string const & name)
644 return changeExtension(name, string());
648 string const addExtension(string const & name, string const & extension)
650 if (!extension.empty() && extension[0] != '.')
651 return name + '.' + extension;
652 return name + extension;
656 /// Return the extension of the file (not including the .)
657 string const getExtension(string const & name)
659 string::size_type const last_slash = name.rfind('/');
660 string::size_type const last_dot = name.rfind('.');
661 if (last_dot != string::npos &&
662 (last_slash == string::npos || last_dot > last_slash))
663 return name.substr(last_dot + 1,
664 name.length() - (last_dot + 1));
670 string const unzippedFileName(string const & zipped_file)
672 string const ext = getExtension(zipped_file);
673 if (ext == "gz" || ext == "z" || ext == "Z")
674 return changeExtension(zipped_file, string());
675 return onlyPath(zipped_file) + "unzipped_" + onlyFilename(zipped_file);
679 FileName const unzipFile(FileName const & zipped_file, string const & unzipped_file)
681 FileName const tempfile = FileName(unzipped_file.empty() ?
682 unzippedFileName(zipped_file.toFilesystemEncoding()) :
685 string const command = "gunzip -c " +
686 zipped_file.toFilesystemEncoding() + " > " +
687 tempfile.toFilesystemEncoding();
689 one.startscript(Systemcall::Wait, command);
690 // test that command was executed successfully (anon)
691 // yes, please do. (Lgb)
696 docstring const makeDisplayPath(string const & path, unsigned int threshold)
700 // If file is from LyXDir, display it as if it were relative.
701 string const system = package().system_support().absFilename();
702 if (prefixIs(str, system) && str != system)
703 return from_utf8("[" + str.erase(0, system.length()) + "]");
705 // replace /home/blah with ~/
706 string const home = package().home_dir().absFilename();
707 if (!home.empty() && prefixIs(str, home))
708 str = subst(str, home, "~");
710 if (str.length() <= threshold)
711 return from_utf8(os::external_path(str));
713 string const prefix = ".../";
716 while (str.length() > threshold)
717 str = split(str, temp, '/');
719 // Did we shorten everything away?
721 // Yes, filename itself is too long.
722 // Pick the start and the end of the filename.
723 str = onlyFilename(path);
724 string const head = str.substr(0, threshold / 2 - 3);
726 string::size_type len = str.length();
728 str.substr(len - threshold / 2 - 2, len - 1);
729 str = head + "..." + tail;
732 return from_utf8(os::external_path(prefix + str));
736 bool readLink(FileName const & file, FileName & link)
739 char linkbuffer[512];
740 // Should be PATH_MAX but that needs autconf support
741 string const encoded = file.toFilesystemEncoding();
742 int const nRead = ::readlink(encoded.c_str(),
743 linkbuffer, sizeof(linkbuffer) - 1);
746 linkbuffer[nRead] = '\0'; // terminator
747 link = makeAbsPath(linkbuffer, onlyPath(file.absFilename()));
755 cmd_ret const runCommand(string const & cmd)
757 // FIXME: replace all calls to RunCommand with ForkedCall
758 // (if the output is not needed) or the code in ISpell.cpp
759 // (if the output is needed).
761 // One question is if we should use popen or
762 // create our own popen based on fork, exec, pipe
763 // of course the best would be to have a
764 // pstream (process stream), with the
765 // variants ipstream, opstream
767 #if defined (HAVE_POPEN)
768 FILE * inf = ::popen(cmd.c_str(), os::popen_read_mode());
769 #elif defined (HAVE__POPEN)
770 FILE * inf = ::_popen(cmd.c_str(), os::popen_read_mode());
772 #error No popen() function.
775 // (Claus Hentschel) Check if popen was succesful ;-)
777 lyxerr << "RunCommand:: could not start child process" << endl;
778 return make_pair(-1, string());
784 ret += static_cast<char>(c);
788 #if defined (HAVE_PCLOSE)
789 int const pret = pclose(inf);
790 #elif defined (HAVE__PCLOSE)
791 int const pret = _pclose(inf);
793 #error No pclose() function.
797 perror("RunCommand:: could not terminate child process");
799 return make_pair(pret, ret);
803 FileName const findtexfile(string const & fil, string const & /*format*/)
805 /* There is no problem to extend this function too use other
806 methods to look for files. It could be setup to look
807 in environment paths and also if wanted as a last resort
808 to a recursive find. One of the easier extensions would
809 perhaps be to use the LyX file lookup methods. But! I am
810 going to implement this until I see some demand for it.
814 // If the file can be found directly, we just return a
815 // absolute path version of it.
816 FileName const absfile(makeAbsPath(fil));
817 if (absfile.exists())
820 // Now we try to find it using kpsewhich.
821 // It seems from the kpsewhich manual page that it is safe to use
822 // kpsewhich without --format: "When the --format option is not
823 // given, the search path used when looking for a file is inferred
824 // from the name given, by looking for a known extension. If no
825 // known extension is found, the search path for TeX source files
827 // However, we want to take advantage of the format sine almost all
828 // the different formats has environment variables that can be used
829 // to controll which paths to search. f.ex. bib looks in
830 // BIBINPUTS and TEXBIB. Small list follows:
831 // bib - BIBINPUTS, TEXBIB
833 // graphic/figure - TEXPICTS, TEXINPUTS
834 // ist - TEXINDEXSTYLE, INDEXSTYLE
835 // pk - PROGRAMFONTS, PKFONTS, TEXPKS, GLYPHFONTS, TEXFONTS
837 // tfm - TFMFONTS, TEXFONTS
838 // This means that to use kpsewhich in the best possible way we
839 // should help it by setting additional path in the approp. envir.var.
840 string const kpsecmd = "kpsewhich " + fil;
842 cmd_ret const c = runCommand(kpsecmd);
844 LYXERR(Debug::LATEX, "kpse status = " << c.first << '\n'
845 << "kpse result = `" << rtrim(c.second, "\n\r") << '\'');
847 return FileName(rtrim(to_utf8(from_filesystem8bit(c.second)), "\n\r"));
853 void readBB_lyxerrMessage(FileName const & file, bool & zipped,
854 string const & message)
856 LYXERR(Debug::GRAPHICS, "[readBB_from_PSFile] " << message);
857 // FIXME: Why is this func deleting a file? (Lgb)
863 string const readBB_from_PSFile(FileName const & file)
865 // in a (e)ps-file it's an entry like %%BoundingBox:23 45 321 345
866 // It seems that every command in the header has an own line,
867 // getline() should work for all files.
868 // On the other hand some plot programs write the bb at the
869 // end of the file. Than we have in the header:
870 // %%BoundingBox: (atend)
871 // In this case we must check the end.
872 bool zipped = file.isZippedFile();
873 FileName const file_ = zipped ? unzipFile(file) : file;
874 string const format = file_.guessFormatFromContents();
876 if (format != "eps" && format != "ps") {
877 readBB_lyxerrMessage(file_, zipped,"no(e)ps-format");
881 static boost::regex bbox_re(
882 "^%%BoundingBox:\\s*([[:digit:]]+)\\s+([[:digit:]]+)\\s+([[:digit:]]+)\\s+([[:digit:]]+)");
883 ifstream is(file_.toFilesystemEncoding().c_str());
888 if (regex_match(s, what, bbox_re)) {
889 // Our callers expect the tokens in the string
890 // separated by single spaces.
891 // FIXME: change return type from string to something
894 os << what.str(1) << ' ' << what.str(2) << ' '
895 << what.str(3) << ' ' << what.str(4);
896 string const bb = os.str();
897 readBB_lyxerrMessage(file_, zipped, bb);
901 readBB_lyxerrMessage(file_, zipped, "no bb found");
906 int compare_timestamps(FileName const & file1, FileName const & file2)
908 // If the original is newer than the copy, then copy the original
909 // to the new directory.
912 if (file1.exists() && file2.exists()) {
913 double const tmp = difftime(file1.lastModified(), file2.lastModified());
915 cmp = tmp > 0 ? 1 : -1;
917 } else if (file1.exists()) {
919 } else if (file2.exists()) {
927 } //namespace support