GNU Octave 11.1.0
A high-level interpreted language, primarily intended for numerical computations, mostly compatible with Matlab
 
Loading...
Searching...
No Matches
kpse.cc
Go to the documentation of this file.
1// This file is not compiled to a separate object file.
2// It is included in pathsearch.cc.
3
4//////////////////////////////////////////////////////////////////////////
5//
6// Copyright (C) 1991-2026 The Octave Project Developers
7//
8// See the file COPYRIGHT.md in the top-level directory of this
9// distribution or <https://octave.org/copyright/>.
10//
11// This file is part of Octave.
12//
13// Octave is free software: you can redistribute it and/or modify it
14// under the terms of the GNU General Public License as published by
15// the Free Software Foundation, either version 3 of the License, or
16// (at your option) any later version.
17//
18// Octave is distributed in the hope that it will be useful, but
19// WITHOUT ANY WARRANTY; without even the implied warranty of
20// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21// GNU General Public License for more details.
22//
23// You should have received a copy of the GNU General Public License
24// along with Octave; see the file COPYING. If not, see
25// <https://www.gnu.org/licenses/>.
26//
27////////////////////////////////////////////////////////////////////////
28
29// Look up a filename in a path.
30
31#if defined (HAVE_CONFIG_H)
32# include "config.h"
33#endif
34
35#include <cctype>
36#include <cerrno>
37#include <cstdlib>
38
39#include <map>
40#include <fstream>
41#include <iostream>
42#include <string>
43
44#include "dir-ops.h"
45#include "file-ops.h"
46#include "file-stat.h"
47#include "kpse.h"
48#include "oct-env.h"
49#include "oct-password.h"
50#include "oct-sysdep.h"
51#include "oct-time.h"
52#include "pathsearch.h"
53#include "unistd-wrappers.h"
54
55#if defined (OCTAVE_USE_WINDOWS_API)
56# define WIN32_LEAN_AND_MEAN 1
57# include <windows.h>
58#endif
59
60// Define the characters which separate components of filenames and
61// environment variable paths.
62#define IS_DEVICE_SEP(ch) octave::sys::file_ops::is_dev_sep (ch)
63#define NAME_BEGINS_WITH_DEVICE(name) \
64 (name.length () > 0 && IS_DEVICE_SEP ((name)[1]))
65
66#define DIR_SEP_STRING octave::sys::file_ops::dir_sep_str ()
67#define IS_DIR_SEP(ch) octave::sys::file_ops::is_dir_sep (ch)
68
69#define ENV_SEP octave::directory_path::path_sep_char ()
70#define ENV_SEP_STRING octave::directory_path::path_sep_str ()
71#define IS_ENV_SEP(ch) octave::directory_path::is_path_sep (ch)
72
73// Define to 1 to enable debugging
74#define OCTAVE_KPSE_DEBUG 0
75
76
77void
78kpse_path_iterator::set_end ()
79{
80 m_e = m_b + 1;
81
82 if (m_e == m_len)
83 ; // OK, we have found the last element.
84 else if (m_e > m_len)
85 m_b = m_e = std::string::npos;
86 else
87 {
88 // Find the next colon not enclosed by braces (or the end of the path).
89 while (m_e < m_len && ! octave::directory_path::is_path_sep (m_path[m_e]))
90 m_e++;
91 }
92}
93
94void
95kpse_path_iterator::next ()
96{
97 m_b = m_e + 1;
98
99 // Skip any consecutive colons.
100 while (m_b < m_len && octave::directory_path::is_path_sep (m_path[m_b]))
101 m_b++;
102
103 if (m_b >= m_len)
104 m_b = m_e = std::string::npos;
105 else
106 set_end ();
107}
108
109/* Truncate any too-long components in NAME, returning the result.
110 It's too bad this is necessary. See comments in readable.c for why. */
111static std::string
112kpse_truncate_filename (const std::string& name)
113{
114 unsigned c_len = 0; // Length of current component.
115 unsigned ret_len = 0; // Length of constructed result.
116
117 std::string ret = name;
118
119 std::size_t m_len = name.length ();
120
121 for (std::size_t i = 0; i < m_len; i++)
122 {
123 if (IS_DIR_SEP (name[i]) || IS_DEVICE_SEP (name[i]))
124 {
125 // At a directory delimiter, reset component length.
126 c_len = 0;
127 }
128 else if (c_len > octave::sys::dir_entry::max_name_length ())
129 {
130 // If past the max for a component, ignore this character.
131 continue;
132 }
133
134 // Copy this character.
135 ret[ret_len++] = name[i];
136 c_len++;
137 }
138
139 ret.resize (ret_len);
140
141 return ret;
142}
143
144/* If access can read FN, run stat (assigning to stat buffer ST) and check that
145 fn is not a directory. Don't check for just being a regular file, as it is
146 potentially useful to read FIFO's or some kinds of devices. */
147
148static inline bool
149READABLE (const std::string& fn)
150{
151#if defined (OCTAVE_USE_WINDOWS_API)
152
153 std::wstring w_fn = octave::sys::u8_to_wstring (fn);
154
155 DWORD f_attr = GetFileAttributesW (w_fn.c_str ());
156
157 return (f_attr != 0xFFFFFFFF && ! (f_attr & FILE_ATTRIBUTE_DIRECTORY));
158
159#else
160
161 bool retval = false;
162
163 const char *t = fn.c_str ();
164
166 {
167 octave::sys::file_stat fs (fn);
168
169 retval = fs && ! fs.is_dir ();
170 }
171
172 return retval;
173
174#endif
175}
176
177/* POSIX invented the brain-damage of not necessarily truncating filename
178 components; the system's behavior is defined by the value of the symbol
179 _POSIX_NO_TRUNC, but you can't change it dynamically!
180
181 Generic const return warning. See extend-fname.c. */
182
183static std::string
184kpse_readable_file (const std::string& name)
185{
186 std::string ret;
187
188 if (READABLE (name))
189 {
190 ret = name;
191
192#if defined (ENAMETOOLONG)
193 }
194 else if (errno == ENAMETOOLONG)
195 {
196 ret = kpse_truncate_filename (name);
197
198 /* Perhaps some other error will occur with the truncated name,
199 so let's call access again. */
200
201 if (! READABLE (ret))
202 {
203 // Failed.
204 ret = "";
205 }
206#endif
207
208 }
209 else
210 {
211 // Some other error.
212 if (errno == EACCES)
213 {
214 // Maybe warn them if permissions are bad.
215 perror (name.c_str ());
216 }
217
218 ret = "";
219 }
220
221 return ret;
222}
223
224static bool
225kpse_absolute_p (const std::string& filename, int relative_ok)
226{
227 return (octave::sys::env::absolute_pathname (filename)
228 || (relative_ok
229 && octave::sys::env::rooted_relative_pathname (filename)));
230}
231
232/* The very first search is for texmf.cnf, called when someone tries to
233 initialize the TFM path or whatever. init_path calls kpse_cnf_get which
234 calls kpse_all_path_search to find all the texmf.cnf's. We need to do
235 various special things in this case, since we obviously don't yet have the
236 configuration files when we're searching for the configuration files. */
237static bool first_search = true;
238
239#if OCTAVE_KPSE_DEBUG
240// This function is called after every search.
241static void
242log_search (const std::list<std::string>& filenames)
243{
244 for (const auto& filename : filenames)
245 {
246 octave::sys::time now;
247 std::cerr << now.unix_time () << ' ' << filename << std::endl;
248 }
249}
250#endif
251
252/* Concatenate each element in DIRS with NAME (assume each ends with a /, to
253 save time). If SEARCH_ALL is false, return the first readable regular file.
254 Else continue to search for more. In any case, if none, return a list
255 containing just NULL.
256
257 We keep a single buffer for the potential filenames and reallocate only when
258 necessary. I'm not sure it's noticeably faster, but it does seem cleaner.
259 (We do waste a bit of space in the return value, though, since we don't
260 shrink it to the final size returned.) */
261
262static std::list<std::string>
263dir_search (const std::string& dir, const std::string& name,
264 bool search_all)
265{
266 std::list<std::string> ret;
267
268 std::string potential = dir + name;
269
270 std::string tmp = kpse_readable_file (potential);
271
272 if (! tmp.empty ())
273 {
274 ret.push_back (potential);
275
276 if (! search_all)
277 return ret;
278 }
279
280 return ret;
281}
282
283/* This is called when NAME is absolute or explicitly relative; if it's
284 readable, return (a list containing) it; otherwise, return NULL. */
285
286static std::list<std::string>
287absolute_search (const std::string& name)
288{
289 std::list<std::string> ret_list;
290 std::string found = kpse_readable_file (name);
291
292 /* Add 'found' to the return list even if it's null; that tells
293 the caller we didn't find anything. */
294 ret_list.push_back (found);
295
296 return ret_list;
297}
298
299/* This is the hard case -- look for NAME in PATH. If ALL is false,
300 return the first file found. Otherwise, search all elements of PATH. */
301
302static std::list<std::string>
303path_search (const std::string& path, const std::string& name, bool all)
304{
305 std::list<std::string> ret_list;
306 bool done = false;
307
308 for (kpse_path_iterator pi (path); ! done && pi != std::string::npos; pi++)
309 {
310 std::string elt = *pi;
311
312 std::list<std::string> found;
313
314 // Do not touch the device if present
315 if (NAME_BEGINS_WITH_DEVICE (elt))
316 {
317 while (elt.length () > 3
318 && IS_DIR_SEP (elt[2]) && IS_DIR_SEP (elt[3]))
319 {
320 elt[2] = elt[1];
321 elt[1] = elt[0];
322 elt = elt.substr (1);
323 }
324 }
325#if (! defined (OCTAVE_HAVE_WINDOWS_FILESYSTEM) \
326 && ! defined (DOUBLE_SLASH_IS_DISTINCT_ROOT))
327 else
328 {
329 // We never want to search the whole disk.
330 while (elt.length () > 1
331 && IS_DIR_SEP (elt[0]) && IS_DIR_SEP (elt[1]))
332 elt = elt.substr (1);
333 }
334#endif
335
336 // Our caller (search), also tests first_search, and does the resetting.
337 if (first_search)
338 found = std::list<std::string> ();
339
340 // Search the filesystem.
341 if (found.empty ())
342 {
343 std::string dir = kpse_element_dir (elt);
344
345 if (! dir.empty ())
346 found = dir_search (dir, name, all);
347 }
348
349 // Did we find anything anywhere?
350 if (! found.empty ())
351 {
352 if (all)
353 ret_list.splice (ret_list.end (), found);
354 else
355 {
356 ret_list.push_back (found.front ());
357 done = true;
358 }
359 }
360 }
361
362 return ret_list;
363}
364
365/* If NAME has a leading ~ or ~user, Unix-style, expand it to the user's home
366 directory, and return a new malloced string. If no ~, or no <pwd.h>, just
367 return NAME. */
368
369static std::string
370kpse_tilde_expand (const std::string& name)
371{
372 std::string expansion;
373
374 // If no leading tilde, do nothing.
375 if (name.empty () || name[0] != '~')
376 {
377 expansion = name;
378
379 /* If a bare tilde, return the home directory or '.'. (Very unlikely
380 that the directory name will do anyone any good, but ... */
381 }
382 else if (name.length () == 1)
383 {
384 expansion = octave::sys::env::get_home_directory ();
385
386 if (expansion.empty ())
387 expansion = ".";
388
389 /* If '~/', remove any trailing / or replace leading // in $HOME.
390 Should really check for doubled intermediate slashes, too. */
391 }
392 else if (IS_DIR_SEP (name[1]))
393 {
394 unsigned c = 1;
395 std::string home = octave::sys::env::get_home_directory ();
396
397 if (home.empty ())
398 home = ".";
399
400 std::size_t home_len = home.length ();
401
402#if (! defined (OCTAVE_HAVE_WINDOWS_FILESYSTEM) \
403 && ! defined (DOUBLE_SLASH_IS_DISTINCT_ROOT))
404 // handle leading "//"
405 if (home_len > 1 && IS_DIR_SEP (home[0]) && IS_DIR_SEP (home[1]))
406 home = home.substr (1);
407#endif
408
409 // omit '/' after '~'
410 if (IS_DIR_SEP (home[home_len - 1]))
411 c++;
412
413 expansion = home + name.substr (c);
414 }
415 else
416 /* If "~user" or "~user/", look up user in the passwd database
417 (but OS/2 doesn't have this concept). */
418#if defined (HAVE_PWD_H)
419 {
420 unsigned c = 2;
421
422 // find user name
423 while (name.length () > c && ! IS_DIR_SEP (name[c]))
424 c++;
425
426 std::string user = name.substr (1, c-1);
427
428 /* We only need the cast here for (deficient) systems
429 which do not declare 'getpwnam' in <pwd.h>. */
430 octave::sys::password p = octave::sys::password::getpwnam (user);
431
432 // If no such user, just use '.'.
433 std::string home = (p ? p.dir () : ".");
434
435 if (home.empty ())
436 home = ".";
437
438# if (! defined (OCTAVE_HAVE_WINDOWS_FILESYSTEM) \
439 && ! defined (DOUBLE_SLASH_IS_DISTINCT_ROOT))
440 // handle leading "//"
441 if (home.length () > 1 && IS_DIR_SEP (home[0]) && IS_DIR_SEP (home[1]))
442 home = home.substr (1);
443# endif
444
445 // If HOME ends in '/', omit the '/' after ~user.
446 if (name.length () > c && IS_DIR_SEP (home.back ()))
447 c++;
448
449 expansion = (name.length () > c ? home : home + name.substr (c));
450 }
451#else // not HAVE_PWD_H
452 expansion = name;
453#endif // HAVE_PWD_H
454
455 return expansion;
456}
457
458/* Search PATH for ORIGINAL_NAME. If ALL is false, or ORIGINAL_NAME is
459 absolute_p, check ORIGINAL_NAME itself. Otherwise, look at each element of
460 PATH for the first readable ORIGINAL_NAME.
461
462 Always return a list; if no files are found, the list will contain just
463 NULL. If ALL is true, the list will be terminated with NULL. */
464
465static std::list<std::string>
466search (const std::string& path, const std::string& original_name, bool all)
467{
468 std::list<std::string> ret_list;
469 bool absolute_p;
470
471 // Make a leading ~ count as an absolute filename.
472 std::string name = kpse_tilde_expand (original_name);
473
474 /* If the first name is absolute or explicitly relative,
475 no need to consider PATH at all. */
476 absolute_p = kpse_absolute_p (name, true);
477
478#if OCTAVE_KPSE_DEBUG
479 std::cerr << "kpse: start search (file=" << name
480 << ", find_all=" << all << ", path=" << path << ")." << std::endl;
481#endif
482
483 // Find the file(s).
484 ret_list = (absolute_p ? absolute_search (name)
485 : path_search (path, name, all));
486
487 /* The very first search is for texmf.cnf. We can't log that, since
488 we want to allow setting TEXMFLOG in texmf.cnf. */
489 if (first_search)
490 {
491 first_search = false;
492 }
493#if OCTAVE_KPSE_DEBUG
494 else
495 {
496 // Record the filenames we found.
497 std::cerr << "kpse: search (" << original_name << ") =>";
498 log_search (ret_list);
499 std::cerr << std::endl;
500 }
501#endif
502
503 return ret_list;
504}
505
506/* Search PATH for the first NAME. */
507
508/* Perform tilde expansion on NAME. If the result is an absolute or explicitly
509 relative filename, check whether it is a readable (regular) file.
510
511 Otherwise, look in each of the directories specified in PATH (also do tilde
512 and variable expansion on elements in PATH).
513
514 The caller must expand PATH. This is because it makes more sense to do this
515 once, in advance, instead of for every search using it.
516
517 In any case, return the complete filename if found, otherwise NULL. */
518
519std::string
520kpse_path_search (const std::string& path, const std::string& name)
521{
522 std::list<std::string> ret_list = search (path, name, false);
523
524 return ret_list.empty () ? "" : ret_list.front ();
525}
526
527/* Like 'kpse_path_search' with MUST_EXIST true, but return a list of
528 all the filenames (or NULL if none), instead of taking the first. */
529
530std::list<std::string>
531kpse_all_path_search (const std::string& path, const std::string& name)
532{
533 return search (path, name, true);
534}
535
536/* This is the hard case -- look in each element of PATH for each element of
537 NAMES. If ALL is false, return the first file found. Otherwise, search all
538 elements of PATH. */
539
540std::list<std::string>
541path_find_first_of (const std::string& path,
542 const std::list<std::string>& names, bool all)
543{
544 std::list<std::string> ret_list;
545 bool done = false;
546
547 for (kpse_path_iterator pi (path); ! done && pi != std::string::npos; pi++)
548 {
549 std::string elt = *pi;
550
551 std::string dir;
552 std::list<std::string> found;
553
554 // Do not touch the device if present
555 if (NAME_BEGINS_WITH_DEVICE (elt))
556 {
557 while (elt.length () > 3
558 && IS_DIR_SEP (elt[2]) && IS_DIR_SEP (elt[3]))
559 {
560 elt[2] = elt[1];
561 elt[1] = elt[0];
562 elt = elt.substr (1);
563 }
564 }
565#if (! defined (OCTAVE_HAVE_WINDOWS_FILESYSTEM) \
566 && ! defined (DOUBLE_SLASH_IS_DISTINCT_ROOT))
567 else
568 {
569 // We never want to search the whole disk.
570 while (elt.length () > 1
571 && IS_DIR_SEP (elt[0]) && IS_DIR_SEP (elt[1]))
572 elt = elt.substr (1);
573 }
574#endif
575
576 // We have to search one directory at a time.
577 dir = kpse_element_dir (elt);
578
579 if (! dir.empty ())
580 {
581 for (auto it = names.cbegin (); it != names.cend () && ! done; it++)
582 {
583 std::string name = *it;
584
585 /* Our caller (find_first_of), also tests first_search,
586 and does the resetting. */
587 if (first_search)
588 found = std::list<std::string> ();
589
590 // Search the filesystem.
591 if (found.empty ())
592 found = dir_search (dir, name, all);
593
594 // Did we find anything anywhere?
595 if (! found.empty ())
596 {
597 if (all)
598 ret_list.splice (ret_list.end (), found);
599 else
600 {
601 ret_list.push_back (found.front ());
602 done = true;
603 }
604 }
605 }
606 }
607 }
608
609 return ret_list;
610}
611
612static std::list<std::string>
613find_first_of (const std::string& path, const std::list<std::string>& names,
614 bool all)
615{
616 std::list<std::string> ret_list;
617
618#if OCTAVE_KPSE_DEBUG
619 std::cerr << "kpse: start find_first_of (";
620
621 for (auto p = names.cbegin (); p != names.cend (); p++)
622 {
623 if (p == names.cbegin ())
624 std::cerr << *p;
625 else
626 std::cerr << ", " << *p;
627 }
628
629 std::cerr << "), path=" << path << '.' << std::endl;
630#endif
631
632 for (const auto& name : names)
633 {
634 if (kpse_absolute_p (name, true))
635 {
636 /* If the name is absolute or explicitly relative, no need to
637 consider PATH at all. If we find something, then we are done. */
638 ret_list = absolute_search (name);
639
640 if (! ret_list.empty ())
641 return ret_list;
642 }
643 }
644
645 // Find the file.
646 ret_list = path_find_first_of (path, names, all);
647
648 /* The very first search is for texmf.cnf. We can't log that, since
649 we want to allow setting TEXMFLOG in texmf.cnf. */
650 if (first_search)
651 {
652 first_search = false;
653 }
654#if OCTAVE_KPSE_DEBUG
655 else
656 {
657 /* Record the filenames we found, if desired. And wrap them in a
658 debugging line if we're doing that. */
659 std::cerr << "kpse: find_first_of (";
660
661 for (auto p = names.cbegin (); p != names.cend (); p++)
662 {
663 if (p == names.cbegin ())
664 std::cerr << *p;
665 else
666 std::cerr << ", " << *p;
667 }
668
669 std::cerr << ") =>";
670
671 log_search (ret_list);
672
673 std::cerr << std::endl;
674 }
675#endif
676
677 return ret_list;
678}
679
680/* Search each element of PATH for each element in the list of NAMES.
681 Return the first one found. */
682
683std::string
684kpse_path_find_first_of (const std::string& path,
685 const std::list<std::string>& names)
686{
687 std::list<std::string> ret_list = find_first_of (path, names, false);
688
689 return ret_list.empty () ? "" : ret_list.front ();
690}
691
692/* Search each element of PATH for each element of NAMES and return a
693 list containing everything found, in the order found. */
694
695/* Like 'kpse_path_find_first_of' with MUST_EXIST true, but return a
696 list of all the filenames (or NULL if none), instead of taking the
697 first. */
698
699std::list<std::string>
700kpse_all_path_find_first_of (const std::string& path,
701 const std::list<std::string>& names)
702{
703 return find_first_of (path, names, true);
704}
705
706/* Perform tilde expansion on each element of the path, and include
707 canonical directory names for only the the actually existing
708 directories in the result. */
709
710std::string
711kpse_path_expand (const std::string& path)
712{
713 std::string ret;
714 unsigned len = 0;
715
716 // Now expand each of the path elements, printing the results
717 for (kpse_path_iterator pi (path); pi != std::string::npos; pi++)
718 {
719 std::string elt = kpse_tilde_expand (*pi);
720
721 std::string dir;
722
723 // Do not touch the device if present
724 if (NAME_BEGINS_WITH_DEVICE (elt))
725 {
726 while (elt.length () > 3
727 && IS_DIR_SEP (elt[2]) && IS_DIR_SEP (elt[3]))
728 {
729 elt[2] = elt[1];
730 elt[1] = elt[0];
731 elt = elt.substr (1);
732 }
733 }
734#if (! defined (OCTAVE_HAVE_WINDOWS_FILESYSTEM) \
735 && ! defined (DOUBLE_SLASH_IS_DISTINCT_ROOT))
736 else
737 {
738 // We never want to search the whole disk.
739 while (elt.length () > 1
740 && IS_DIR_SEP (elt[0]) && IS_DIR_SEP (elt[1]))
741 elt = elt.substr (1);
742 }
743#endif
744
745 /* Search the disk for all dirs in the component specified.
746 Be faster to check the database, but this is more reliable. */
747 dir = kpse_element_dir (elt);
748
749 std::size_t dirlen = dir.length ();
750
751 if (dirlen > 0)
752 {
753 ret += dir;
754 len += dirlen;
755
756 // Retain trailing slash if that's the root directory.
757 if (dirlen == 1
758 || (dirlen == 3 && NAME_BEGINS_WITH_DEVICE (dir)
759 && IS_DIR_SEP (dir[2])))
760 {
761 ret += ENV_SEP_STRING;
762 len++;
763 }
764
765 ret[len-1] = ENV_SEP;
766 }
767 }
768
769 if (! ret.empty ())
770 ret.pop_back ();
771
772 return ret;
773}
774
775/* braces.c -- code for doing word expansion in curly braces.
776 Taken from bash 1.14.5. [And subsequently modified for kpatshea.]
777
778 Copyright (C) 1987,1991 Free Software Foundation, Inc. */
779
780#define brace_whitespace(c) (! (c) || (c) == ' ' || (c) == '\t' || (c) == '\n')
781
782/* Basic idea:
783
784 Segregate the text into 3 sections: preamble (stuff before an open brace),
785 postamble (stuff after the matching close brace) and amble (stuff after
786 preamble, and before postamble). Expand amble, and then tack on the
787 expansions to preamble. Expand postamble, and tack on the expansions to the
788 result so far. */
789
790/* Return a new array of strings which is the result of appending each string
791 in ARR2 to each string in ARR1. The resultant array is len (arr1) * len
792 (arr2) long. For convenience, ARR1 (and its contents) are free ()'ed. ARR1
793 can be NULL, in that case, a new version of ARR2 is returned. */
794
795static std::list<std::string>
796array_concat (const std::list<std::string>& arr1,
797 const std::list<std::string>& arr2)
798{
799 std::list<std::string> result;
800
801 if (arr1.empty ())
802 result = arr2;
803 else if (arr2.empty ())
804 result = arr1;
805 else
806 {
807 for (const auto& elt_2 : arr2)
808 for (const auto& elt_1 : arr1)
809 result.push_back (elt_1 + elt_2);
810 }
811
812 return result;
813}
814
815static int brace_gobbler (const std::string&, int&, int);
816static std::list<std::string> expand_amble (const std::string&);
817
818// Return an array of strings; the brace expansion of TEXT.
819static std::list<std::string>
820brace_expand (const std::string& text)
821{
822 // Find the text of the preamble.
823 int i = 0;
824 int c = brace_gobbler (text, i, '{');
825
826 std::string preamble = text.substr (0, i);
827
828 std::list<std::string> result (1, preamble);
829
830 if (c == '{')
831 {
832 // Find the amble. This is the stuff inside this set of braces.
833 int start = ++i;
834 c = brace_gobbler (text, i, '}');
835
836 // What if there isn't a matching close brace?
837 if (! c)
838 {
839 (*current_liboctave_warning_with_id_handler)
840 ("Octave:pathsearch-syntax",
841 "%s: Unmatched {", text.c_str ());
842
843 result = std::list<std::string> (1, text);
844 }
845 else
846 {
847 std::string amble = text.substr (start, i-start);
848 result = array_concat (result, expand_amble (amble));
849
850 std::string postamble = text.substr (i+1);
851 result = array_concat (result, brace_expand (postamble));
852 }
853 }
854
855 return result;
856}
857
858// The character which is used to separate arguments.
859static int brace_arg_separator = ',';
860
861/* Expand the text found inside of braces. We simply try to split the text at
862 BRACE_ARG_SEPARATORs into separate strings. We then brace expand each slot
863 which needs it, until there are no more slots which need it. */
864static std::list<std::string>
865expand_amble (const std::string& text)
866{
867 std::list<std::string> result;
868
869 std::size_t text_len = text.length ();
870 std::size_t start;
871 int i, c;
872
873 for (start = 0, i = 0, c = 1; c && start < text_len; start = ++i)
874 {
875 int i0 = i;
876 int c0 = brace_gobbler (text, i0, brace_arg_separator);
877 int i1 = i;
878 int c1 = brace_gobbler (text, i1, ENV_SEP);
879 c = c0 | c1;
880 i = (i0 < i1 ? i0 : i1);
881
882 std::string tem = text.substr (start, i-start);
883
884 std::list<std::string> partial = brace_expand (tem);
885
886 if (result.empty ())
887 result = partial;
888 else
889 result.splice (result.end (), partial);
890 }
891
892 return result;
893}
894
895/* Start at INDEX, and skip characters in TEXT. Set INDEX to the
896 index of the character matching SATISFY. This understands about
897 quoting. Return the character that caused us to stop searching;
898 this is either the same as SATISFY, or 0. */
899static int
900brace_gobbler (const std::string& text, int& indx, int satisfy)
901{
902 int c = 0;
903 int level = 0;
904 int quoted = 0;
905 int pass_next = 0;
906
907 std::size_t text_len = text.length ();
908
909 std::size_t i = indx;
910
911 for (; i < text_len; i++)
912 {
913 c = text[i];
914
915 if (pass_next)
916 {
917 pass_next = 0;
918 continue;
919 }
920
921 /* A backslash escapes the next character. This allows backslash to
922 escape the quote character in a double-quoted string. */
923 if (c == '\\' && (quoted == 0 || quoted == '"' || quoted == '`'))
924 {
925 pass_next = 1;
926 continue;
927 }
928
929 if (quoted)
930 {
931 if (c == quoted)
932 quoted = 0;
933 continue;
934 }
935
936 if (c == '"' || c == '\'' || c == '`')
937 {
938 quoted = c;
939 continue;
940 }
941
942 if (c == satisfy && ! level && ! quoted)
943 {
944 /* We ignore an open brace surrounded by whitespace, and also an open
945 brace followed immediately by a close brace, that was preceded
946 with whitespace. */
947 if (c == '{'
948 && ((i == 0 || brace_whitespace (text[i-1]))
949 && (i+1 < text_len
950 && (brace_whitespace (text[i+1]) || text[i+1] == '}'))))
951 continue;
952 /* If this is being compiled as part of bash, ignore the '{'
953 in a '${ }' construct. */
954 if ((c != '{') || i == 0 || (text[i-1] != '$'))
955 break;
956 }
957
958 if (c == '{')
959 level++;
960 else if (c == '}' && level)
961 level--;
962 }
963
964 indx = i;
965 c = (c == satisfy) ? c : 0;
966 return c;
967}
968
969/* Given a path element ELT, return a the element with a trailing slash or an
970 empty string if the element is not a directory.
971
972 It's up to the caller to expand ELT. This is because this routine is most
973 likely only useful to be called from 'kpse_path_search', which has already
974 assumed expansion has been done. */
975
976std::string
977kpse_element_dir (const std::string& elt)
978{
979 std::string ret;
980
981 // If given nothing, return nothing.
982 if (elt.empty ())
983 return ret;
984
985 if (octave::sys::dir_exists (elt))
986 {
987 ret = elt;
988
989 char last_char = ret.back ();
990
991 if (! (IS_DIR_SEP (last_char) || IS_DEVICE_SEP (last_char)))
992 ret += DIR_SEP_STRING;
993 }
994
995 return ret;
996}
std::string kpse_path_find_first_of(const std::string &path, const std::list< std::string > &names)
Definition kpse.cc:684
#define DIR_SEP_STRING
Definition kpse.cc:66
#define NAME_BEGINS_WITH_DEVICE(name)
Definition kpse.cc:63
std::string kpse_element_dir(const std::string &elt)
Definition kpse.cc:977
#define ENV_SEP_STRING
Definition kpse.cc:70
std::string kpse_path_expand(const std::string &path)
Definition kpse.cc:711
std::list< std::string > kpse_all_path_find_first_of(const std::string &path, const std::list< std::string > &names)
Definition kpse.cc:700
std::list< std::string > kpse_all_path_search(const std::string &path, const std::string &name)
Definition kpse.cc:531
#define IS_DIR_SEP(ch)
Definition kpse.cc:67
#define brace_whitespace(c)
Definition kpse.cc:780
std::list< std::string > path_find_first_of(const std::string &path, const std::list< std::string > &names, bool all)
Definition kpse.cc:541
#define IS_DEVICE_SEP(ch)
Definition kpse.cc:62
#define ENV_SEP
Definition kpse.cc:69
std::string kpse_path_search(const std::string &path, const std::string &name)
Definition kpse.cc:520
int octave_access_r_ok(void)
int octave_access_wrapper(const char *nm, int mode)
F77_RET_T len
Definition xerbla.cc:61