root/trunk/tango/scrapple/io/Tilde.d

Revision 26, 8.1 kB (checked in by flithm, 1 year ago)

Add Tilde.e

Line 
1 /*******************************************************************************
2
3         copyright:      Copyright (c) 2006 Lars Ivar Igesund, Thomas KÃŒhne,
4                                             Grzegorz Adam Hankiewicz
5
6         license:        BSD style: $(LICENSE)
7
8         version:        Initial release: December 2006
9
10         author:         Lars Ivar Igesund, Thomas KÃŒhne,
11                         Grzegorz Adam Hankiewicz
12
13 *******************************************************************************/
14
15 module tango.scrapple.io.Tilde;
16
17 private import tango.io.FileConst;
18
19 version (Posix)
20 {
21     import tango.stdc.stdlib;
22     import tango.text.Util;
23     import tango.stdc.posix.pwd;
24     import tango.stdc.posix.stdlib;
25     import tango.stdc.errno;
26     import tango.core.Exception;
27
28     private extern (C) int strlen (char *);
29 }
30
31
32 /******************************************************************************
33
34     Performs tilde expansion in paths.
35
36     There are two ways of using tilde expansion in a path. One
37     involves using the tilde alone or followed by a path separator. In
38     this case, the tilde will be expanded with the value of the
39     environment variable <i>HOME</i>.  The second way is putting
40     a username after the tilde (i.e. <tt>~john/Mail</tt>). Here,
41     the username will be searched for in the user database
42     (i.e. <tt>/etc/passwd</tt> on Unix systems) and will expand to
43     whatever path is stored there.  The username is considered the
44     string after the tilde ending at the first instance of a path
45     separator.
46
47     Note that using the <i>~user</i> syntax may give different
48     values from just <i>~</i> if the environment variable doesn't
49     match the value stored in the user database.
50
51     When the environment variable version is used, the path won't
52     be modified if the environment variable doesn't exist or it
53     is empty. When the database version is used, the path won't be
54     modified if the user doesn't exist in the database or there is
55     not enough memory to perform the query.
56
57     Returns: inputPath with the tilde expanded, or just inputPath
58     if it could not be expanded.
59     For Windows, expandTilde() merely returns its argument inputPath.
60
61     Throws: OutOfMemoryException if there is not enough
62     memory to perform
63     the database lookup for the <i>~user</i> syntax.
64
65     Examples:
66     -----
67     import tango.text.Path;
68
69     void process_file(char[] filename)
70     {
71          char[] path = FilePath.expandTilde(filename);
72         ...
73     }
74     -----
75
76     -----
77     import tango.text.Path;
78
79     const char[] RESOURCE_DIR_TEMPLATE = "~/.applicationrc";
80     char[] RESOURCE_DIR;    // This gets expanded in main().
81
82     int main(char[][] args)
83     {
84         RESOURCE_DIR = FilePath.expandTilde(RESOURCE_DIR_TEMPLATE);
85         ...
86     }
87     -----
88 ******************************************************************************/
89
90 char[] expandTilde(char[] inputPath)
91 {
92     version(Posix)
93     {
94         // Return early if there is no tilde in path.
95         if (inputPath.length < 1 || inputPath[0] != '~')
96             return inputPath;
97
98         if (inputPath.length == 1 || inputPath[1] == FileConst.PathSeparatorChar)
99             return expandFromEnvironment(inputPath);
100         else
101             return expandFromDatabase(inputPath);
102     }
103     else version(Win32)
104     {
105         // TODO: Put here real windows implementation.
106         return inputPath;
107     }
108     else
109     {
110         static assert(0); // Guard. Implement on other platforms.
111     }
112 }
113
114 debug(UnitTest) {
115
116 unittest
117 {
118     version (Posix)
119     {
120     // Retrieve the current home variable.
121     char* c_home = getenv("HOME");
122
123     // Testing when there is no environment variable.
124     unsetenv("HOME");
125     assert(expandTilde("~/") == "~/");
126     assert(expandTilde("~") == "~");
127
128     // Testing when an environment variable is set.
129     int ret = setenv("HOME", "tango/test\0", 1);
130     assert(ret == 0);
131     assert(expandTilde("~/") == "tango/test/");
132     assert(expandTilde("~") == "tango/test");
133
134     // The same, but with a variable ending in a slash.
135     ret = setenv("HOME", "tango/test/\0", 1);
136     assert(ret == 0);
137     assert(expandTilde("~/") == "tango/test/");
138     assert(expandTilde("~") == "tango/test");
139
140     // Recover original HOME variable before continuing.
141     if (c_home)
142         setenv("HOME", c_home, 1);
143     else
144         unsetenv("HOME");
145
146     // Test user expansion for root. Are there unices without /root?
147     assert(expandTilde("~root") == "/root");
148     assert(expandTilde("~root/") == "/root/");
149     assert(expandTilde("~Idontexist/hey") == "~Idontexist/hey");
150     }
151 }
152 }
153
154 /*******************************************************************************
155
156         Replaces the tilde from path with the environment variable
157         HOME.
158
159 ******************************************************************************/
160
161 private char[] expandFromEnvironment(char[] path)
162 {
163     assert(path.length >= 1);
164     assert(path[0] == '~');
165
166     // Get HOME and use that to replace the tilde.
167     char* home = getenv("HOME");
168     if (home == null)
169         return path;
170
171     return combineCPathWithDPath(home, path, 1);
172 }
173
174
175 /*******************************************************************************
176
177         Joins a path from a C string to the remainder of path.
178
179         The last path separator from c_path is discarded. The result
180         is joined to path[char_pos .. length] if char_pos is smaller
181         than length, otherwise path is not appended to c_path.
182         
183 ******************************************************************************/
184
185 private char[] combineCPathWithDPath(char* cPath, char[] path, int charPos)
186 {
187     assert(cPath != null);
188     assert(path.length > 0);
189     assert(charPos >= 0);
190
191     // Search end of C string
192     size_t end = strlen(cPath);
193
194     // Remove trailing path separator, if any
195     if (end && cPath[end - 1] == FileConst.PathSeparatorChar)
196     end--;
197
198     // Create our own copy, as lifetime of cPath is undocumented
199     char[] cp = cPath[0 .. end].dup;
200
201     // Do we append something from path?
202     if (charPos < path.length)
203         cp ~= path[charPos..$];
204
205     return cp;
206 }
207
208
209 /*******************************************************************************
210
211         Replaces the tilde from path with the path from the user
212         database.
213
214 ******************************************************************************/
215
216 private char[] expandFromDatabase(char[] path)
217 {
218     assert(path.length > 2 || (path.length == 2 && path[1] != FileConst.PathSeparatorChar));
219     assert(path[0] == '~');
220
221     // Extract username, searching for path separator.
222     char[] username;
223     int last_char = locate(path, FileConst.PathSeparatorChar);
224
225     if (last_char == -1)
226         {
227         username = path[1 .. length] ~ '\0';
228         last_char = username.length + 1;
229         }
230     else
231         {
232         username = path[1 .. last_char] ~ '\0';
233         }
234
235     assert(last_char > 1);
236
237     // Reserve C memory for the getpwnam_r() function.
238     passwd result;
239     int extra_memory_size = 5 * 1024;
240     void* extra_memory;
241
242     while (1)
243         {
244         extra_memory = tango.stdc.stdlib.malloc(extra_memory_size);
245         if (extra_memory == null)
246             goto Lerror;
247
248         // Obtain info from database.
249         passwd *verify;
250         tango.stdc.errno.errno(0);
251         if (getpwnam_r(username.ptr, &result, cast(char*)extra_memory, extra_memory_size,
252         &verify) == 0)
253             {
254             // Failure if verify doesn't point at result.
255             if (verify != &result)
256             // username is not found, so return path[]
257                 goto Lnotfound;
258             break;
259             }
260
261         if (tango.stdc.errno.errno() != ERANGE)
262             goto Lerror;
263
264         // extra_memory isn't large enough
265         tango.stdc.stdlib.free(extra_memory);
266         extra_memory_size *= 2;
267         }
268
269     path = combineCPathWithDPath(result.pw_dir, path, last_char);
270
271     Lnotfound:
272         tango.stdc.stdlib.free(extra_memory);
273         return path;
274
275     Lerror:
276         // Errors are going to be caused by running out of memory
277         if (extra_memory)
278             tango.stdc.stdlib.free(extra_memory);
279         throw new OutOfMemoryException("Not enough memory for user lookup in tilde expansion.", __LINE__);
280     return null;
281 }
Note: See TracBrowser for help on using the browser.