merge with master, primarily for adrian's maximise-mixer change
[ardour.git] / libs / pbd / pathexpand.cc
1 /*
2     Copyright (C) 2013-2014 Paul Davis
3
4     This program is free software; you can redistribute it and/or modify
5     it under the terms of the GNU General Public License as published by
6     the Free Software Foundation; either version 2 of the License, or
7     (at your option) any later version.
8
9     This program is distributed in the hope that it will be useful,
10     but WITHOUT ANY WARRANTY; without even the implied warranty of
11     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12     GNU General Public License for more details.
13
14     You should have received a copy of the GNU General Public License
15     along with this program; if not, write to the Free Software
16     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
17
18 */
19
20 #include <vector>
21 #include <iostream>
22 #include <climits>
23 #include <cerrno>
24 #include <cstdlib>
25
26 #include <regex.h>
27
28 #include <glibmm/fileutils.h>
29 #include <glibmm/miscutils.h>
30
31 #include "pbd/pathexpand.h"
32 #include "pbd/strsplit.h"
33 #include "pbd/tokenizer.h"
34
35 using std::string;
36 using std::vector;
37
38 #ifdef COMPILER_MINGW
39
40 #include <stdlib.h>
41 #include <glibmm.h>
42
43 /****************************************************************
44  * Emulate POSIX realpath() using Win32 _fullpath() since realpath()
45  * is not available.
46  *
47  * Returns:
48  *    On Success: A pointer to the resolved (absolute) path
49  *    On Failure: 0 (NULL)
50  */
51
52 static char* 
53 realpath (const char *original_path, char resolved_path[_MAX_PATH+1])
54 {
55         char *rpath = 0;
56         bool bIsSymLink = false; // We'll probably need to test the incoming path
57                                  // to find out if it points to a Windows shortcut
58                                  // (or a hard link) and set this appropriately.
59
60         if (bIsSymLink) {
61                 // At the moment I'm not sure if Windows '_fullpath()' is directly
62                 // equivalent to POSIX 'realpath()' - in as much as the latter will
63                 // resolve the supplied path if it happens to point to a symbolic
64                 // link ('_fullpath()' probably DOESN'T do this but I'm not really
65                 // sure if Ardour needs such functionality anyway). Therefore we'll
66                 // possibly need to add that functionality here at a later date.
67         } else {
68                 char temp[(_MAX_PATH+1)*6]; // Allow for maximum length of a path in wchar characters
69                 
70                 // POSIX 'realpath()' requires that the buffer size is at
71                 // least PATH_MAX+1, so assume that the user knew this !!
72
73                 rpath = _fullpath (temp, Glib::locale_from_utf8 (original_path).c_str(), _MAX_PATH);
74
75                 if (0 != rpath) {
76                         snprintf (resolved_path, _MAX_PATH+1, "%s", Glib::locale_to_utf8 (temp).c_str());
77                 }
78                         
79         }
80         
81         return (rpath);
82 }
83
84 #endif  // COMPILER_MINGW
85
86 string
87 PBD::canonical_path (const std::string& path)
88 {
89         char buf[PATH_MAX+1];
90
91         if (!realpath (path.c_str(), buf)) {
92                 return path;
93         }
94
95         return string (buf);
96 }
97
98 string
99 PBD::path_expand (string path)
100 {
101         if (path.empty()) {
102                 return path;
103         }
104
105         /* tilde expansion */
106
107         if (path[0] == '~') {
108                 if (path.length() == 1) {
109                         return Glib::get_home_dir();
110                 }
111
112                 if (path[1] == '/') {
113                         path.replace (0, 1, Glib::get_home_dir());
114                 } else {
115                         /* can't handle ~roger, so just leave it */
116                 }
117         }
118
119         /* now do $VAR substitution, since wordexp isn't reliable */
120
121         regex_t compiled_pattern;
122         const int nmatches = 100;
123         regmatch_t matches[nmatches];
124         
125         if (regcomp (&compiled_pattern, "\\$([a-zA-Z_][a-zA-Z0-9_]*|\\{[a-zA-Z_][a-zA-Z0-9_]*\\})", REG_EXTENDED)) {
126                 std::cerr << "bad regcomp\n";
127                 return path;
128         }
129
130         while (true) { 
131
132                 if (regexec (&compiled_pattern, path.c_str(), nmatches, matches, 0)) {
133                         break;
134                 }
135                 
136                 /* matches[0] gives the entire match */
137                 
138                 string match = path.substr (matches[0].rm_so, matches[0].rm_eo - matches[0].rm_so);
139                 
140                 /* try to get match from the environment */
141
142                 if (match[1] == '{') {
143                         /* ${FOO} form */
144                         match = match.substr (2, match.length() - 3);
145                 }
146
147                 char* matched_value = getenv (match.c_str());
148
149                 if (matched_value) {
150                         path.replace (matches[0].rm_so, matches[0].rm_eo - matches[0].rm_so, matched_value);
151                 } else {
152                         path.replace (matches[0].rm_so, matches[0].rm_eo - matches[0].rm_so, string());
153                 }
154
155                 /* go back and do it again with whatever remains after the
156                  * substitution 
157                  */
158         }
159
160         regfree (&compiled_pattern);
161
162         /* canonicalize */
163
164         return canonical_path (path);
165 }
166
167 string
168 PBD::search_path_expand (string path)
169 {
170         if (path.empty()) {
171                 return path;
172         }
173
174         vector<string> s;
175         vector<string> n;
176
177         split (path, s, ':');
178
179         for (vector<string>::iterator i = s.begin(); i != s.end(); ++i) {
180                 string exp = path_expand (*i);
181                 if (!exp.empty()) {
182                         n.push_back (exp);
183                 }
184         }
185
186         string r;
187
188         for (vector<string>::iterator i = n.begin(); i != n.end(); ++i) {
189                 if (!r.empty()) {
190                         r += ':';
191                 }
192                 r += *i;
193         }
194
195         return r;
196 }
197
198 std::vector <std::string>
199 PBD::parse_path(std::string path, bool check_if_exists)
200 {
201         vector <std::string> pathlist;
202         vector <std::string> tmp;
203         PBD::tokenize (path, string(G_SEARCHPATH_SEPARATOR_S), std::back_inserter (tmp));
204
205         for(vector<std::string>::const_iterator i = tmp.begin(); i != tmp.end(); ++i) {
206                 if ((*i).empty()) continue;
207                 std::string dir;
208 #ifndef PLATFORM_WINDOWS
209                 if ((*i).substr(0,1) == "~") {
210                         dir = Glib::build_filename(Glib::get_home_dir(), (*i).substr(1));
211                 }
212                 else
213 #endif
214                 {
215                         dir = *i;
216                 }
217                 if (!check_if_exists || Glib::file_test (dir, Glib::FILE_TEST_IS_DIR)) {
218                         pathlist.push_back(dir);
219                 }
220         }
221   return pathlist;
222 }