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/convert.h"
25 #include "support/environment.h"
26 #include "support/filetools.h"
27 #include "support/lstrings.h"
28 #include "support/lyxlib.h"
29 #include "support/os.h"
30 #include "support/Package.h"
31 #include "support/Path.h"
32 #include "support/Systemcall.h"
34 // FIXME Interface violation
38 #include <boost/assert.hpp>
39 #include <boost/regex.hpp>
56 using std::ostringstream;
63 bool isLyXFilename(string const & filename)
65 return suffixIs(ascii_lowercase(filename), ".lyx");
69 bool isSGMLFilename(string const & filename)
71 return suffixIs(ascii_lowercase(filename), ".sgml");
75 bool isValidLaTeXFilename(string const & filename)
77 string const invalid_chars("#$%{}()[]\"^");
78 return filename.find_first_of(invalid_chars) == string::npos;
82 string const latex_path(string const & original_path,
83 latex_path_extension extension,
86 // On cygwin, we may need windows or posix style paths.
87 string path = os::latex_path(original_path);
88 path = subst(path, "~", "\\string~");
89 if (path.find(' ') != string::npos) {
90 // We can't use '"' because " is sometimes active (e.g. if
91 // babel is loaded with the "german" option)
92 if (extension == EXCLUDE_EXTENSION) {
93 // ChangeExtension calls os::internal_path internally
94 // so don't use it to remove the extension.
95 string const ext = getExtension(path);
96 string const base = ext.empty() ?
98 path.substr(0, path.length() - ext.length() - 1);
99 // ChangeExtension calls os::internal_path internally
100 // so don't use it to re-add the extension.
101 path = "\\string\"" + base + "\\string\"." + ext;
103 path = "\\string\"" + path + "\\string\"";
107 return dots == ESCAPE_DOTS ? subst(path, ".", "\\lyxdot ") : path;
111 // Substitutes spaces with underscores in filename (and path)
112 FileName const makeLatexName(FileName const & file)
114 string name = file.onlyFileName();
115 string const path = file.onlyPath().absFilename() + "/";
117 // ok so we scan through the string twice, but who cares.
118 // FIXME: in Unicode time this will break for sure! There is
119 // a non-latin world out there...
120 string const keep = "abcdefghijklmnopqrstuvwxyz"
121 "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
122 "@!'()*+,-./0123456789:;<=>?[]`|";
124 string::size_type pos = 0;
125 while ((pos = name.find_first_not_of(keep, pos)) != string::npos)
128 FileName latex_name(path + name);
129 latex_name.changeExtension(".tex");
134 string const quoteName(string const & name, quote_style style)
138 // This does not work for filenames containing " (windows)
139 // or ' (all other OSes). This can't be changed easily, since
140 // we would need to adapt the command line parser in
141 // Forkedcall::generateChild. Therefore we don't pass user
142 // filenames to child processes if possible. We store them in
143 // a python script instead, where we don't have these
145 return (os::shell() == os::UNIX) ?
149 return "\"" + subst(subst(name, "\\", "\\\\"), "\"", "\\\"")
152 // shut up stupid compiler
158 // Uses a string of paths separated by ";"s to find a file to open.
159 // Can't cope with pathnames with a ';' in them. Returns full path to file.
160 // If path entry begins with $$LyX/, use system_lyxdir
161 // If path entry begins with $$User/, use user_lyxdir
162 // Example: "$$User/doc;$$LyX/doc"
163 FileName const fileOpenSearch(string const & path, string const & name,
168 bool notfound = true;
169 string tmppath = split(path, path_element, ';');
171 while (notfound && !path_element.empty()) {
172 path_element = os::internal_path(path_element);
173 if (!suffixIs(path_element, '/'))
175 path_element = subst(path_element, "$$LyX",
176 package().system_support().absFilename());
177 path_element = subst(path_element, "$$User",
178 package().user_support().absFilename());
180 real_file = fileSearch(path_element, name, ext);
182 if (real_file.empty()) {
184 tmppath = split(tmppath, path_element, ';');
185 } while (!tmppath.empty() && path_element.empty());
195 // Returns the real name of file name in directory path, with optional
197 FileName const fileSearch(string const & path, string const & name,
198 string const & ext, search_mode mode)
200 // if `name' is an absolute path, we ignore the setting of `path'
201 // Expand Environmentvariables in 'name'
202 string const tmpname = replaceEnvironmentPath(name);
203 FileName fullname(makeAbsPath(tmpname, path));
204 // search first without extension, then with it.
205 if (fullname.isReadableFile())
209 return mode == allow_unreadable ? fullname : FileName();
210 // Only add the extension if it is not already the extension of
212 if (getExtension(fullname.absFilename()) != ext)
213 fullname = FileName(addExtension(fullname.absFilename(), ext));
214 if (fullname.isReadableFile() || mode == allow_unreadable)
220 // Search the file name.ext in the subdirectory dir of
222 // 2) build_lyxdir (if not empty)
224 FileName const libFileSearch(string const & dir, string const & name,
227 FileName fullname = fileSearch(addPath(package().user_support().absFilename(), dir),
229 if (!fullname.empty())
232 if (!package().build_support().empty())
233 fullname = fileSearch(addPath(package().build_support().absFilename(), dir),
235 if (!fullname.empty())
238 return fileSearch(addPath(package().system_support().absFilename(), dir), name, ext);
242 FileName const i18nLibFileSearch(string const & dir, string const & name,
245 /* The highest priority value is the `LANGUAGE' environment
246 variable. But we don't use the value if the currently
247 selected locale is the C locale. This is a GNU extension.
249 Otherwise, w use a trick to guess what gettext has done:
250 each po file is able to tell us its name. (JMarc)
253 string lang = to_ascii(_("[[Replace with the code of your language]]"));
254 string const language = getEnv("LANGUAGE");
255 if (!lang.empty() && !language.empty())
259 lang = split(lang, l, ':');
262 // First try with the full name
263 tmp = libFileSearch(addPath(dir, l), name, ext);
267 // Then the name without country code
268 string const shortl = token(l, '_', 0);
270 tmp = libFileSearch(addPath(dir, shortl), name, ext);
276 // For compatibility, to be removed later (JMarc)
277 tmp = libFileSearch(dir, token(l, '_', 0) + '_' + name,
280 lyxerr << "i18nLibFileSearch: File `" << tmp
281 << "' has been found by the old method" <<endl;
285 lang = split(lang, l, ':');
288 return libFileSearch(dir, name, ext);
292 string const libScriptSearch(string const & command_in, quote_style style)
294 static string const token_scriptpath = "$$s/";
296 string command = command_in;
297 // Find the starting position of "$$s/"
298 string::size_type const pos1 = command.find(token_scriptpath);
299 if (pos1 == string::npos)
301 // Find the end of the "$$s/some_subdir/some_script" word within
302 // command. Assumes that the script name does not contain spaces.
303 string::size_type const start_script = pos1 + 4;
304 string::size_type const pos2 = command.find(' ', start_script);
305 string::size_type const size_script = pos2 == string::npos?
306 (command.size() - start_script) : pos2 - start_script;
308 // Does this script file exist?
309 string const script =
310 libFileSearch(".", command.substr(start_script, size_script)).absFilename();
312 if (script.empty()) {
313 // Replace "$$s/" with ""
314 command.erase(pos1, 4);
316 // Replace "$$s/foo/some_script" with "<path to>/some_script".
317 string::size_type const size_replace = size_script + 4;
318 command.replace(pos1, size_replace, quoteName(script, style));
325 static FileName createTmpDir(FileName const & tempdir, string const & mask)
327 LYXERR(Debug::FILES, "createTmpDir: tempdir=`" << tempdir << "'\n"
328 << "createTmpDir: mask=`" << mask << '\'');
330 FileName const tmpfl(tempName(tempdir, mask));
331 // lyx::tempName actually creates a file to make sure that it
332 // stays unique. So we have to delete it before we can create
333 // a dir with the same name. Note also that we are not thread
334 // safe because of the gap between unlink and mkdir. (Lgb)
337 if (tmpfl.empty() || mkdir(tmpfl, 0700)) {
338 lyxerr << "LyX could not create the temporary directory '"
339 << tmpfl << "'" << endl;
346 string const createBufferTmpDir()
349 // We are in our own directory. Why bother to mangle name?
350 // In fact I wrote this code to circumvent a problematic behaviour
351 // (bug?) of EMX mkstemp().
353 package().temp_dir().absFilename() + "/lyx_tmpbuf" +
354 convert<string>(count++);
356 if (mkdir(FileName(tmpfl), 0777)) {
357 lyxerr << "LyX could not create the temporary directory '"
358 << tmpfl << "'" << endl;
365 FileName const createLyXTmpDir(FileName const & deflt)
367 if (deflt.empty() || deflt.absFilename() == "/tmp")
368 return createTmpDir(FileName("/tmp"), "lyx_tmpdir");
370 if (!mkdir(deflt, 0777))
373 if (deflt.isDirWritable()) {
374 // deflt could not be created because it
375 // did exist already, so let's create our own
377 return createTmpDir(deflt, "lyx_tmpdir");
379 // some other error occured.
380 return createTmpDir(FileName("/tmp"), "lyx_tmpdir");
385 // Strip filename from path name
386 string const onlyPath(string const & filename)
388 // If empty filename, return empty
389 if (filename.empty())
392 // Find last / or start of filename
393 size_t j = filename.rfind('/');
394 return j == string::npos ? "./" : filename.substr(0, j + 1);
398 // Convert relative path into absolute path based on a basepath.
399 // If relpath is absolute, just use that.
400 // If basepath is empty, use CWD as base.
401 FileName const makeAbsPath(string const & relPath, string const & basePath)
403 // checks for already absolute path
404 if (os::is_absolute_path(relPath))
405 return FileName(relPath);
407 // Copies given paths
408 string tempRel = os::internal_path(relPath);
409 // Since TempRel is NOT absolute, we can safely replace "//" with "/"
410 tempRel = subst(tempRel, "//", "/");
414 if (os::is_absolute_path(basePath))
417 tempBase = addPath(getcwd().absFilename(), basePath);
419 // Handle /./ at the end of the path
420 while (suffixIs(tempBase, "/./"))
421 tempBase.erase(tempBase.length() - 2);
423 // processes relative path
424 string rTemp = tempRel;
427 while (!rTemp.empty()) {
429 rTemp = split(rTemp, temp, '/');
431 if (temp == ".") continue;
433 // Remove one level of TempBase
434 string::difference_type i = tempBase.length() - 2;
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 /// Returns true is path is absolute
493 bool absolutePath(string const & path)
495 return os::is_absolute_path(path);
499 // Create absolute path. If impossible, don't do anything
500 // Supports ./ and ~/. Later we can add support for ~logname/. (Asger)
501 string const expandPath(string const & path)
503 // checks for already absolute path
504 string rTemp = replaceEnvironmentPath(path);
505 if (os::is_absolute_path(rTemp))
509 string const copy = rTemp;
512 rTemp = split(rTemp, temp, '/');
515 return getcwd().absFilename() + '/' + rTemp;
518 return package().home_dir().absFilename() + '/' + rTemp;
521 return makeAbsPath(copy).absFilename();
523 // Don't know how to handle this
528 // Search the string for ${VAR} and $VAR and replace VAR using getenv.
529 string const replaceEnvironmentPath(string const & path)
531 // ${VAR} is defined as
532 // $\{[A-Za-z_][A-Za-z_0-9]*\}
533 static string const envvar_br = "[$]\\{([A-Za-z_][A-Za-z_0-9]*)\\}";
535 // $VAR is defined as:
536 // $\{[A-Za-z_][A-Za-z_0-9]*\}
537 static string const envvar = "[$]([A-Za-z_][A-Za-z_0-9]*)";
539 static boost::regex envvar_br_re("(.*)" + envvar_br + "(.*)");
540 static boost::regex envvar_re("(.*)" + envvar + "(.*)");
543 string result = path;
545 regex_match(result, what, envvar_br_re);
546 if (!what[0].matched) {
547 regex_match(result, what, envvar_re);
548 if (!what[0].matched)
551 result = what.str(1) + getEnv(what.str(2)) + 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 "unzipped_" + 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 // No 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 removeAutosaveFile(string const & filename)
855 string a = onlyPath(filename);
857 a += onlyFilename(filename);
859 FileName const autosave(a);
860 if (autosave.exists())
861 autosave.removeFile();
865 void readBB_lyxerrMessage(FileName const & file, bool & zipped,
866 string const & message)
868 LYXERR(Debug::GRAPHICS, "[readBB_from_PSFile] " << message);
869 // FIXME: Why is this func deleting a file? (Lgb)
875 string const readBB_from_PSFile(FileName const & file)
877 // in a (e)ps-file it's an entry like %%BoundingBox:23 45 321 345
878 // It seems that every command in the header has an own line,
879 // getline() should work for all files.
880 // On the other hand some plot programs write the bb at the
881 // end of the file. Than we have in the header:
882 // %%BoundingBox: (atend)
883 // In this case we must check the end.
884 bool zipped = file.isZippedFile();
885 FileName const file_ = zipped ? unzipFile(file) : file;
886 string const format = file_.guessFormatFromContents();
888 if (format != "eps" && format != "ps") {
889 readBB_lyxerrMessage(file_, zipped,"no(e)ps-format");
893 static boost::regex bbox_re(
894 "^%%BoundingBox:\\s*([[:digit:]]+)\\s+([[:digit:]]+)\\s+([[:digit:]]+)\\s+([[:digit:]]+)");
895 std::ifstream is(file_.toFilesystemEncoding().c_str());
900 if (regex_match(s, what, bbox_re)) {
901 // Our callers expect the tokens in the string
902 // separated by single spaces.
903 // FIXME: change return type from string to something
906 os << what.str(1) << ' ' << what.str(2) << ' '
907 << what.str(3) << ' ' << what.str(4);
908 string const bb = os.str();
909 readBB_lyxerrMessage(file_, zipped, bb);
913 readBB_lyxerrMessage(file_, zipped, "no bb found");
918 int compare_timestamps(FileName const & file1, FileName const & file2)
920 // If the original is newer than the copy, then copy the original
921 // to the new directory.
924 if (file1.exists() && file2.exists()) {
925 double const tmp = difftime(file1.lastModified(), file2.lastModified());
927 cmp = tmp > 0 ? 1 : -1;
929 } else if (file1.exists()) {
931 } else if (file2.exists()) {
938 } //namespace support