win32-xpath 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 9b7fbc4af66e5ca073a64411ff6c48dcf4ab60ae
4
+ data.tar.gz: 2a1a3f3871c8bc63e70d133875e52760a98f6690
5
+ SHA512:
6
+ metadata.gz: e1353c201cd8ef54a55600f3203aa06e6b20e55bc8682a07e3822bbe7f938ea7154d67a04b00260b7ffe77b480f568d9e159952dc23c4115821706b41508f591
7
+ data.tar.gz: 2b357454c4bad3722db57d7fbbf25ea2697cae1af585d332200d5950daeb633688a70582697f0ad23237472a09dbc55c52c04898fca9b4a8915edf6d41ccb283
data/CHANGES ADDED
@@ -0,0 +1,2 @@
1
+ = 1.0.0 - 26-Feb-2015
2
+ * Initial release
data/MANIFEST ADDED
@@ -0,0 +1,9 @@
1
+ bench/bench_win32_xpath.rb
2
+ CHANGES
3
+ ext/extconf.rb
4
+ ext/win32/xpath.c
5
+ futzing/futz.rb
6
+ Rakefile
7
+ README
8
+ test/test_win32_xpath.rb
9
+ win32-xpath.gemspec
data/README ADDED
@@ -0,0 +1,54 @@
1
+ = Description
2
+ A custom File.expand_path method for Ruby on Windows that's much
3
+ faster and works better.
4
+
5
+ = Installation
6
+ gem install win32-xpath
7
+
8
+ = Synopsis
9
+ require 'win32/xpath'
10
+
11
+ # That's it, you are now using this library when you call File.expand_path
12
+
13
+ = Features
14
+ * A 5x average performance boost over MRI's current method.
15
+ * Support for ~user expansion.
16
+
17
+ = Known Issues
18
+ * This library does not support drive-current paths for the 2nd argument.
19
+ * This library does not support alt-stream name autocorrection.
20
+
21
+ It is very unlikely you will ever be affected by either of these things.
22
+ Drive-current paths are a relic of DOS 1.0, but even so this library
23
+ will handle them in the first argument.
24
+
25
+ I don't support alt-stream mangling because I don't believe it's the
26
+ job of this method to peform autocorrection. Even in MRI it only works
27
+ for the default $DATA stream, and then only for a certain type of
28
+ syntax error. I do not know when or why it was added.
29
+
30
+ If you discover any other issues, please report them on the project
31
+ page at https://github.com/djberg96/win32-xpath.
32
+
33
+ = Acknowledgements
34
+ Park Heesob for encoding advice and help.
35
+
36
+ == License
37
+ Artistic 2.0
38
+
39
+ == Contributions
40
+ Although this library is free, please consider having your company
41
+ setup a gittip if used by your company professionally.
42
+
43
+ http://www.gittip.com/djberg96/
44
+
45
+ == Copyright
46
+ (C) 2003-2015 Daniel J. Berger, All Rights Reserved
47
+
48
+ == Warranty
49
+ This package is provided "as is" and without any express or
50
+ implied warranties, including, without limitation, the implied
51
+ warranties of merchantability and fitness for a particular purpose.
52
+
53
+ = Author
54
+ Daniel Berger
data/Rakefile ADDED
@@ -0,0 +1,60 @@
1
+ require 'rake'
2
+ require 'rake/clean'
3
+ require 'rake/testtask'
4
+ require 'rbconfig'
5
+ include RbConfig
6
+
7
+ CLEAN.include(
8
+ '**/*.gem', # Gem files
9
+ '**/*.rbc', # Rubinius
10
+ '**/*.o', # C object file
11
+ '**/*.log', # Ruby extension build log
12
+ '**/Makefile', # C Makefile
13
+ '**/*.def', # Definition files
14
+ '**/*.exp',
15
+ '**/*.lib',
16
+ '**/*.pdb',
17
+ '**/*.obj',
18
+ '**/*.stackdump', # Junk that can happen on Windows
19
+ "**/*.#{CONFIG['DLEXT']}" # C shared object
20
+ )
21
+
22
+ make = CONFIG['host_os'] =~ /mingw|cygwin/i ? 'make' : 'nmake'
23
+
24
+ desc "Build the win32-xpath library"
25
+ task :build => [:clean] do
26
+ require 'devkit' if CONFIG['host_os'] =~ /mingw|cygwin/i
27
+ Dir.chdir('ext') do
28
+ ruby "extconf.rb"
29
+ sh make
30
+ cp 'xpath.so', 'win32' # For testing
31
+ end
32
+ end
33
+
34
+ namespace :gem do
35
+ desc "Build the win32-xpath gem"
36
+ task :create => [:clean] do
37
+ require 'rubygems/package'
38
+ spec = eval(IO.read('win32-xpath.gemspec'))
39
+ Gem::Package.build(spec)
40
+ end
41
+
42
+ task "Install the win32-xpath gem"
43
+ task :install => [:create] do
44
+ file = Dir["*.gem"].first
45
+ sh "gem install -l #{file}"
46
+ end
47
+ end
48
+
49
+ Rake::TestTask.new do |t|
50
+ task :test => [:build]
51
+ t.test_files = FileList['test/*']
52
+ t.libs << 'ext'
53
+ end
54
+
55
+ desc "Run benchmarks"
56
+ task :bench => [:build] do
57
+ ruby "-Iext bench/bench_win32_xpath.rb"
58
+ end
59
+
60
+ task :default => :test
@@ -0,0 +1,79 @@
1
+ require 'benchmark'
2
+
3
+ MAX = 100000
4
+ puts "\nOld File.expand_path"
5
+
6
+ Benchmark.bm(30) do |x|
7
+ x.report("expand_path('foo/bar')") do
8
+ str = "foo/bar"
9
+ MAX.times{ File.expand_path(str) }
10
+ end
11
+
12
+ x.report("expand_path('C:/foo/bar')") do
13
+ str = "C:/foo/bar"
14
+ MAX.times{ File.expand_path(str) }
15
+ end
16
+
17
+ x.report("expand_path('//foo/bar')") do
18
+ str = "//foo/bar"
19
+ MAX.times{ File.expand_path(str) }
20
+ end
21
+
22
+ x.report("expand_path('foo//bar///')") do
23
+ str = "foo//bar///"
24
+ MAX.times{ File.expand_path(str) }
25
+ end
26
+
27
+ x.report("expand_path('~')") do
28
+ str = "~"
29
+ MAX.times{ File.expand_path(str) }
30
+ end
31
+
32
+ x.report("expand_path('')") do
33
+ str = ""
34
+ MAX.times{ File.expand_path(str) }
35
+ end
36
+
37
+ x.report("expand_path('', '~')") do
38
+ MAX.times{ File.expand_path('', '~') }
39
+ end
40
+ end
41
+
42
+ require 'win32/xpath'
43
+ puts "\nNew File.expand_path\n"
44
+
45
+ Benchmark.bm(30) do |x|
46
+ x.report("expand_path('foo/bar')") do
47
+ str = "foo/bar"
48
+ MAX.times{ File.expand_path(str) }
49
+ end
50
+
51
+ x.report("expand_path('C:/foo/bar')") do
52
+ str = "C:/foo/bar"
53
+ MAX.times{ File.expand_path(str) }
54
+ end
55
+
56
+ x.report("expand_path('//foo/bar')") do
57
+ str = "//foo/bar"
58
+ MAX.times{ File.expand_path(str) }
59
+ end
60
+
61
+ x.report("expand_path('foo//bar///')") do
62
+ str = "foo//bar///"
63
+ MAX.times{ File.expand_path(str) }
64
+ end
65
+
66
+ x.report("expand_path('~')") do
67
+ str = "~"
68
+ MAX.times{ File.expand_path(str) }
69
+ end
70
+
71
+ x.report("expand_path('')") do
72
+ str = ""
73
+ MAX.times{ File.expand_path(str) }
74
+ end
75
+
76
+ x.report("expand_path('', '~')") do
77
+ MAX.times{ File.expand_path('', '~') }
78
+ end
79
+ end
data/ext/extconf.rb ADDED
@@ -0,0 +1,4 @@
1
+ require 'mkmf'
2
+ have_library('shlwapi')
3
+ have_library('advapi32')
4
+ create_makefile('win32/xpath', 'win32')
data/ext/win32/xpath.c ADDED
@@ -0,0 +1,360 @@
1
+ #include <ruby.h>
2
+ #include <ruby/encoding.h>
3
+ #include <windows.h>
4
+ #include <shlwapi.h>
5
+ #include <sddl.h>
6
+
7
+ #define MAX_WPATH MAX_PATH * sizeof(wchar_t)
8
+
9
+ // Helper function to find user's home directory
10
+ wchar_t* find_user(wchar_t* str){
11
+ SID* sid;
12
+ DWORD cbSid, cbDom, cbData, lpType;
13
+ SID_NAME_USE peUse;
14
+ LPWSTR str_sid;
15
+ LONG rv;
16
+ HKEY phkResult;
17
+ wchar_t subkey[MAX_PATH];
18
+ wchar_t* lpData;
19
+ wchar_t* dom;
20
+ wchar_t* ptr;
21
+ const wchar_t* key_base = L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\ProfileList\\";
22
+
23
+ // Read up until first backslash, and preserve the rest for later
24
+ if (ptr = wcschr(str, L'\\')){
25
+ ptr++;
26
+ str[wcscspn(str, L"\\")] = 0;
27
+ }
28
+
29
+ sid = (SID*)ruby_xmalloc(MAX_WPATH);
30
+ dom = (wchar_t*)ruby_xmalloc(MAX_WPATH);
31
+
32
+ cbSid = MAX_PATH;
33
+ cbDom = MAX_PATH;
34
+
35
+ // Get the user's SID
36
+ if (!LookupAccountNameW(NULL, str, sid, &cbSid, dom, &cbDom, &peUse)){
37
+ ruby_xfree(sid);
38
+ ruby_xfree(dom);
39
+ rb_raise(rb_eArgError, "can't find user %ls", str);
40
+ }
41
+
42
+ ruby_xfree(dom);
43
+
44
+ // Get the stringy version of the SID
45
+ if (!ConvertSidToStringSidW(sid, &str_sid)){
46
+ ruby_xfree(sid);
47
+ rb_sys_fail("ConvertSidToStringSid");
48
+ }
49
+
50
+ ruby_xfree(sid);
51
+
52
+ // Mash the stringified SID onto our base key
53
+ if(swprintf(subkey, MAX_PATH, L"%s%s", key_base, str_sid) < 0)
54
+ rb_sys_fail("swprintf");
55
+
56
+ // Get the key handle we need
57
+ rv = RegOpenKeyExW(HKEY_LOCAL_MACHINE, subkey, 0, KEY_QUERY_VALUE, &phkResult);
58
+
59
+ if (rv != ERROR_SUCCESS)
60
+ rb_sys_fail("RegOpenKeyEx");
61
+
62
+ lpData = (wchar_t*)malloc(MAX_WPATH);
63
+ cbData = MAX_WPATH;
64
+ lpType = REG_EXPAND_SZ;
65
+
66
+ // Finally, get the user's home directory
67
+ rv = RegQueryValueExW(phkResult, L"ProfileImagePath", NULL, &lpType, (LPBYTE)lpData, &cbData);
68
+
69
+ if (rv != ERROR_SUCCESS){
70
+ ruby_xfree(lpData);
71
+ rb_raise(rb_eArgError, "can't find user %ls", str);
72
+ }
73
+
74
+ // Append any remaining path data that was originally present
75
+ if (ptr){
76
+ if (swprintf(lpData, MAX_WPATH, L"%s/%s", lpData, ptr) < 0)
77
+ rb_sys_fail("swprintf");
78
+ }
79
+
80
+ return lpData;
81
+ }
82
+
83
+ // Helper function to expand tilde into full path
84
+ wchar_t* expand_tilde(){
85
+ DWORD size = 0;
86
+ wchar_t* home = NULL;
87
+ const wchar_t* env = L"HOME";
88
+
89
+ // First, try to get HOME environment variable
90
+ size = GetEnvironmentVariableW(env, NULL, 0);
91
+
92
+ // If that isn't found then try USERPROFILE
93
+ if(!size){
94
+ env = L"USERPROFILE";
95
+ size = GetEnvironmentVariableW(env, home, size);
96
+ }
97
+
98
+ // If that isn't found the try HOMEDRIVE + HOMEPATH
99
+ if(!size){
100
+ DWORD size2;
101
+ wchar_t* temp;
102
+ wchar_t* env2 = L"HOMEPATH";
103
+ env = L"HOMEDRIVE";
104
+
105
+ // If neither are found then raise an error
106
+ size = GetEnvironmentVariableW(env, NULL, 0);
107
+ size2 = GetEnvironmentVariableW(env2, NULL, 0);
108
+
109
+ if(!size || !size2){
110
+ if (GetLastError() != ERROR_ENVVAR_NOT_FOUND)
111
+ rb_sys_fail("GetEnvironmentVariable");
112
+ else
113
+ rb_raise(rb_eArgError, "couldn't find HOME environment -- expanding '~'");
114
+ }
115
+
116
+ home = (wchar_t*)ruby_xmalloc(MAX_WPATH);
117
+ temp = (wchar_t*)ruby_xmalloc(MAX_WPATH);
118
+
119
+ if(!GetEnvironmentVariableW(env, home, size) || !GetEnvironmentVariableW(env2, temp, size2)){
120
+ ruby_xfree(home);
121
+ ruby_xfree(temp);
122
+ rb_sys_fail("GetEnvironmentVariable");
123
+ }
124
+
125
+ if(!PathAppendW(home, temp))
126
+ rb_sys_fail("PathAppend");
127
+ }
128
+ else{
129
+ home = (wchar_t*)ruby_xmalloc(MAX_WPATH);
130
+ size = GetEnvironmentVariableW(env, home, size);
131
+
132
+ if(!size){
133
+ ruby_xfree(home);
134
+ rb_sys_fail("GetEnvironmentVariable");
135
+ }
136
+ }
137
+
138
+ while(wcsstr(home, L"/"))
139
+ home[wcscspn(home, L"/")] = L'\\';
140
+
141
+ if (PathIsRelativeW(home))
142
+ rb_raise(rb_eArgError, "non-absolute home");
143
+
144
+ return home;
145
+ }
146
+
147
+ // My version of File.expand_path
148
+ static VALUE rb_xpath(int argc, VALUE* argv, VALUE self){
149
+ VALUE v_path, v_path_orig, v_dir_orig;
150
+ wchar_t* buffer = NULL;
151
+ wchar_t* ptr = NULL;
152
+ wchar_t* path = NULL;
153
+ char* final_path;
154
+ int length;
155
+ rb_encoding* path_encoding;
156
+ rb_econv_t* ec;
157
+ const int replaceflags = ECONV_UNDEF_REPLACE|ECONV_INVALID_REPLACE;
158
+
159
+ rb_scan_args(argc, argv, "11", &v_path_orig, &v_dir_orig);
160
+
161
+ if (rb_respond_to(v_path_orig, rb_intern("to_path")))
162
+ v_path_orig = rb_funcall(v_path_orig, rb_intern("to_path"), 0, NULL);
163
+
164
+ SafeStringValue(v_path_orig);
165
+
166
+ if (!NIL_P(v_dir_orig))
167
+ SafeStringValue(v_dir_orig);
168
+
169
+ // Dup and prep string for modification
170
+ path_encoding = rb_enc_get(v_path_orig);
171
+
172
+ if (rb_enc_to_index(path_encoding) == rb_utf8_encindex()){
173
+ v_path = rb_str_dup(v_path_orig);
174
+ }
175
+ else{
176
+ ec = rb_econv_open(rb_enc_name(path_encoding), "UTF-8", replaceflags);
177
+ v_path = rb_econv_str_convert(ec, v_path_orig, ECONV_PARTIAL_INPUT);
178
+ rb_econv_close(ec);
179
+ }
180
+
181
+ rb_str_modify_expand(v_path, MAX_PATH);
182
+
183
+ // Make our path a wide string for later functions
184
+ length = MultiByteToWideChar(CP_UTF8, 0, RSTRING_PTR(v_path), -1, NULL, 0);
185
+ path = (wchar_t*)ruby_xmalloc(MAX_PATH * sizeof(wchar_t));
186
+
187
+ if(!MultiByteToWideChar(CP_UTF8, 0, RSTRING_PTR(v_path), -1, path, length)){
188
+ ruby_xfree(path);
189
+ rb_sys_fail("MultiByteToWideChar");
190
+ }
191
+
192
+ // Convert all forward slashes to backslashes to Windows API functions work properly
193
+ while(wcsstr(path, L"/"))
194
+ path[wcscspn(path, L"/")] = L'\\';
195
+
196
+ // Handle ~ expansion.
197
+ if (ptr = wcschr(path, L'~')){
198
+ wchar_t* home;
199
+
200
+ // Handle both ~/user and ~user syntax
201
+ if (ptr[1] && ptr[1] != L'\\'){
202
+ home = find_user(++ptr);
203
+ }
204
+ else{
205
+ home = expand_tilde(path);
206
+
207
+ if (!PathAppendW(home, ++ptr)){
208
+ ruby_xfree(home);
209
+ rb_sys_fail("PathAppend");
210
+ }
211
+ }
212
+
213
+ path = home;
214
+ }
215
+
216
+ // Directory argument is present
217
+ if (!NIL_P(v_dir_orig)){
218
+ wchar_t* dir;
219
+ VALUE v_dir;
220
+ rb_encoding* dir_encoding;
221
+
222
+ dir_encoding = rb_enc_get(v_dir_orig);
223
+
224
+ // Hooray for encodings
225
+ if (rb_enc_to_index(dir_encoding) == rb_utf8_encindex()){
226
+ v_dir = rb_str_dup(v_dir_orig);
227
+ }
228
+ else{
229
+ ec = rb_econv_open(rb_enc_name(dir_encoding), "UTF-8", replaceflags);
230
+ v_dir = rb_econv_str_convert(ec, v_dir_orig, ECONV_PARTIAL_INPUT);
231
+ rb_econv_close(ec);
232
+ }
233
+
234
+ // Prep string for modification
235
+ rb_str_modify_expand(v_dir, MAX_PATH);
236
+
237
+ length = MultiByteToWideChar(CP_UTF8, 0, RSTRING_PTR(v_dir), -1, NULL, 0);
238
+ dir = (wchar_t*)ruby_xmalloc(MAX_PATH * sizeof(wchar_t));
239
+
240
+ if (!MultiByteToWideChar(CP_UTF8, 0, RSTRING_PTR(v_dir), -1, dir, length)){
241
+ ruby_xfree(dir);
242
+ rb_sys_fail("MultiByteToWideChar");
243
+ }
244
+
245
+ while (wcsstr(dir, L"/"))
246
+ dir[wcscspn(dir, L"/")] = L'\\';
247
+
248
+ if (ptr = wcschr(dir, L'~')){
249
+ if (ptr[1] && ptr[1] != L'\\'){
250
+ dir = find_user(++ptr);
251
+ }
252
+ else{
253
+ dir = expand_tilde();
254
+
255
+ if (!PathAppendW(dir, ++ptr)){
256
+ ruby_xfree(dir);
257
+ rb_sys_fail("PathAppend");
258
+ }
259
+ }
260
+ }
261
+
262
+ if (!wcslen(path))
263
+ path = dir;
264
+
265
+ if (PathIsRelativeW(path)){
266
+ if(!PathAppendW(dir, path)){
267
+ ruby_xfree(dir);
268
+ rb_sys_fail("PathAppend");
269
+ }
270
+
271
+ // Remove leading slashes from relative paths
272
+ if (dir[0] == L'\\')
273
+ ++dir;
274
+
275
+ path = dir;
276
+ }
277
+ }
278
+ else{
279
+ if (!wcslen(path)){
280
+ char* pwd;
281
+ wchar_t* wpwd;
282
+
283
+ // First call, get the length
284
+ length = GetCurrentDirectoryW(0, NULL);
285
+ wpwd = (wchar_t*)ruby_xmalloc(length * sizeof(wchar_t));
286
+
287
+ length = GetCurrentDirectoryW(length, wpwd);
288
+
289
+ if(!length){
290
+ ruby_xfree(wpwd);
291
+ rb_sys_fail("GetCurrentDirectory");
292
+ }
293
+
294
+ // Convert backslashes into forward slashes
295
+ while(wcsstr(wpwd, L"\\"))
296
+ wpwd[wcscspn(wpwd, L"\\")] = L'/';
297
+
298
+ // Convert string back to multibyte string before returning Ruby object
299
+ length = WideCharToMultiByte(CP_UTF8, 0, wpwd, -1, NULL, 0, NULL, NULL);
300
+ pwd = (char*)ruby_xmalloc(length);
301
+ length = WideCharToMultiByte(CP_UTF8, 0, wpwd, -1, pwd, length, NULL, NULL);
302
+
303
+ if (!length){
304
+ ruby_xfree(pwd);
305
+ rb_sys_fail("WideCharToMultiByte");
306
+ }
307
+
308
+ return rb_str_new2(pwd);
309
+ }
310
+ }
311
+
312
+ // Strip all trailing backslashes
313
+ while (!*PathRemoveBackslashW(path));
314
+
315
+ // First call, get the length
316
+ length = GetFullPathNameW(path, 0, buffer, NULL);
317
+ buffer = (wchar_t*)ruby_xmalloc(length * sizeof(wchar_t));
318
+
319
+ // Now get the path
320
+ length = GetFullPathNameW(path, length, buffer, NULL);
321
+
322
+ if (!length){
323
+ ruby_xfree(buffer);
324
+ rb_sys_fail("GetFullPathName");
325
+ }
326
+
327
+ // Convert backslashes into forward slashes
328
+ while(wcsstr(buffer, L"\\"))
329
+ buffer[wcscspn(buffer, L"\\")] = L'/';
330
+
331
+ length = WideCharToMultiByte(CP_UTF8, 0, buffer, -1, NULL, 0, NULL, NULL);
332
+ final_path = (char*)ruby_xmalloc(length);
333
+ length = WideCharToMultiByte(CP_UTF8, 0, buffer, -1, final_path, length, NULL, NULL);
334
+
335
+ ruby_xfree(buffer);
336
+
337
+ if (!length){
338
+ ruby_xfree(final_path);
339
+ rb_sys_fail("WideCharToMultiByte");
340
+ }
341
+
342
+ v_path = rb_str_new(final_path, length - 1); // Don't count null terminator
343
+
344
+ if (rb_enc_to_index(path_encoding) != rb_utf8_encindex()){
345
+ ec = rb_econv_open("UTF-8", rb_enc_name(path_encoding), replaceflags);
346
+ v_path = rb_econv_str_convert(ec, v_path, ECONV_PARTIAL_INPUT);
347
+ rb_econv_close(ec);
348
+ }
349
+
350
+ rb_enc_associate(v_path, path_encoding);
351
+
352
+ if (OBJ_TAINTED(v_path_orig) || rb_equal(v_path, v_path_orig) == Qfalse)
353
+ OBJ_TAINT(v_path);
354
+
355
+ return v_path;
356
+ }
357
+
358
+ void Init_xpath(){
359
+ rb_define_singleton_method(rb_cFile, "expand_path", rb_xpath, -1);
360
+ }
data/futzing/futz.rb ADDED
@@ -0,0 +1,71 @@
1
+ require 'win32/xpath'
2
+ require 'tmpdir'
3
+
4
+ #p File.expand_path("~djberge", "foo")
5
+ #p File.expand_path("~/foo")
6
+ #p File.expand_path("", "~djberge")
7
+ #p File.expand_path("~djberge")
8
+ #p File.expand_path("~djberge/foo/bar")
9
+ #p File.expand_path("~bogus")
10
+
11
+ ENV['HOME'] = nil
12
+ ENV['USERPROFILE'] = nil
13
+ ENV['HOMEDRIVE'] = "D:/"
14
+ #ENV['USERPROFILE'] = "C:/bogus"
15
+ p File.expand_path("~")
16
+
17
+ #p File.expand_path("foo", "~")
18
+ #p File.expand_path("foo", "~")
19
+ #p File.expand_path("", "~")
20
+ #p File.expand_path("", "~")
21
+ #p File.expand_path("foo", "~/bar")
22
+ #p File.expand_path("foo", "~/bar")
23
+ #p File.expand_path("")
24
+ #p File.expand_path("C:/foo/bar")
25
+ #p File.expand_path("C:/foo/bar///")
26
+ #p File.expand_path('foo', Dir.tmpdir)
27
+ #p File.expand_path("C:/foo/bar", "D:/foo")
28
+ #p File.expand_path("foo")
29
+ #p File.expand_path("foo", "bar")
30
+
31
+ #ENV['HOME'] = nil
32
+ #ENV['USERPROFILE'] = nil
33
+ #p File.expand_path("~")
34
+
35
+ #path = "../a"
36
+ #tmp = Dir.tmpdir
37
+
38
+ #p path
39
+ #p tmp
40
+
41
+ #100.times{
42
+ # File.expand_path(path, tmp)
43
+ # File.expand_path('foo', tmp)
44
+ #}
45
+
46
+ #p path
47
+ #p tmp
48
+
49
+ #p File.expand_path('../a', tmp)
50
+
51
+ #p File.expand_path('../a', 'foo')
52
+ #p File.expand_path('../a', 'foo')
53
+ #p File.expand_path('../a', 'C:/foo')
54
+ #p File.expand_path('../a', 'C:/foo')
55
+
56
+
57
+ =begin
58
+ p File.expand_path("/foo").tainted?
59
+ p File.expand_path("foo").tainted? # True
60
+ p File.expand_path("foo".taint).tainted? # True
61
+ p File.expand_path("C:/foo").tainted? # False
62
+ p File.expand_path("C:/foo".taint).tainted? # True
63
+ =end
64
+
65
+ =begin
66
+ p File.expand_path("~")
67
+ p File.expand_path("~/foo")
68
+ p File.expand_path("//foo/bar")
69
+ p File.expand_path("//foo/bar//")
70
+ p File.expand_path("//foo/bar//")
71
+ =end
data/futzing/test.rb ADDED
@@ -0,0 +1,90 @@
1
+ require 'ffi'
2
+
3
+ class Windows
4
+ extend FFI::Library
5
+ typedef :ulong, :dword
6
+
7
+ ffi_lib :shlwapi
8
+
9
+ attach_function :PathCanonicalizeA, [:buffer_out, :string], :bool
10
+ attach_function :PathStripToRootA, [:pointer], :bool
11
+ attach_function :PathIsRelativeA, [:string], :bool
12
+ attach_function :PathAppendA, [:buffer_out, :string], :bool
13
+ attach_function :PathSkipRootA, [:string], :string
14
+ attach_function :PathIsNetworkPathA, [:string], :bool
15
+
16
+ ffi_lib :kernel32
17
+ attach_function :GetFullPathNameA, [:string, :dword, :buffer_out, :pointer], :dword
18
+
19
+ def xpath(path, dir=nil)
20
+ path = path.tr("/", "\\")
21
+
22
+ buf = 0.chr * 512
23
+
24
+ p path
25
+ p PathIsRelativeA(path)
26
+
27
+ if !PathCanonicalizeA(buf, path)
28
+ raise
29
+ end
30
+
31
+ p buf.strip
32
+
33
+ #p dir
34
+ #p path
35
+
36
+ #regex = /\A(\w):([^\\]+)(.*)/i
37
+ =begin
38
+ if m = regex.match(path)
39
+ drive = m.captures[0]
40
+ path = m.captures[1..-1].join
41
+ p drive
42
+ p path
43
+ end
44
+ =end
45
+
46
+ #p PathIsNetworkPathA(path)
47
+ #p PathSkipRootA(path)
48
+ #p PathIsRelativeA(path)
49
+
50
+ =begin
51
+ buf = 0.chr * 1024
52
+
53
+ if GetFullPathNameA(path, buf.size, buf, nil) == 0
54
+ raise SystemCallError.new('GetFullPathName', FFI.errno)
55
+ end
56
+
57
+ p buf.strip
58
+ =end
59
+
60
+ =begin
61
+ ptr = FFI::MemoryPointer.from_string(path)
62
+
63
+ p PathIsRelativeA(path)
64
+
65
+ unless PathStripToRootA(ptr)
66
+ raise SystemCallError.new('PathStripToRoot', FFI.errno)
67
+ end
68
+
69
+ p ptr.read_string
70
+ =end
71
+
72
+ =begin
73
+ unless PathCanonicalizeA(buf, path)
74
+ raise SystemCallError.new('PathCanonicalize', FFI.errno)
75
+ end
76
+
77
+ buf.strip
78
+ =end
79
+ end
80
+ end
81
+
82
+ if $0 == __FILE__
83
+ Windows.new.xpath("/../../a", "foo")
84
+ #p Windows.new.xpath("C:/foo/../bar")
85
+ #p Windows.new.xpath("C:foo")
86
+ #Windows.new.xpath("C:foo")
87
+ #Windows.new.xpath("C:foo/bar")
88
+ #Windows.new.xpath("C:/foo")
89
+ #Windows.new.xpath("foo")
90
+ end
@@ -0,0 +1,185 @@
1
+ require 'test-unit'
2
+ require 'tmpdir'
3
+ require 'win32/xpath'
4
+ require 'etc'
5
+
6
+ class Test_XPath < Test::Unit::TestCase
7
+ def self.startup
8
+ ENV['HOME'] ||= ENV['USERPROFILE']
9
+ @@login = Etc.getlogin
10
+ end
11
+
12
+ def setup
13
+ @pwd = Dir.pwd
14
+ @tmp = Dir.tmpdir
15
+ @root = 'C:/'
16
+ @drive = ENV['HOMEDRIVE']
17
+ @home = ENV['HOME'].tr('\\', '/')
18
+ @unc = "//foo/bar"
19
+ end
20
+
21
+ test "converts an empty pathname into absolute current pathname" do
22
+ assert_equal(@pwd, File.expand_path(''))
23
+ end
24
+
25
+ test "converts '.' into absolute pathname using current directory" do
26
+ assert_equal(@pwd, File.expand_path('.'))
27
+ end
28
+
29
+ test "converts 'foo' into absolute pathname using current directory" do
30
+ assert_equal(File.join(@pwd, 'foo'), File.expand_path('foo'))
31
+ end
32
+
33
+ test "converts 'foo' into absolute pathname ignoring nil dir" do
34
+ assert_equal(File.join(@pwd, 'foo'), File.expand_path('foo', nil))
35
+ end
36
+
37
+ test "converts 'foo' and 'bar' into absolute pathname" do
38
+ assert_equal(File.join(@pwd, "bar", "foo"), File.expand_path('foo', 'bar'))
39
+ end
40
+
41
+ test "converts a pathname into absolute pathname" do
42
+ assert_equal(File.join(@pwd, 'a'), File.expand_path('a.'))
43
+ assert_equal(File.join(@pwd, '.a'), File.expand_path('.a'))
44
+ assert_equal(File.join(@pwd, '..a'), File.expand_path('..a'))
45
+ assert_equal(File.join(@pwd, 'a../b'), File.expand_path('a../b'))
46
+ end
47
+
48
+ test "converts a pathname and make it valid" do
49
+ assert_equal(File.join(@pwd, 'a'), File.expand_path('a..'))
50
+ end
51
+
52
+ test "converts a pathname to an absolute pathname using a complete path" do
53
+ assert_equal(@tmp, File.expand_path('', @tmp))
54
+ assert_equal(File.join(@tmp, 'a'), File.expand_path('a', @tmp))
55
+ assert_equal(File.join(@tmp, 'a'), File.expand_path('../a', "#{@tmp}/xxx"))
56
+ assert_equal(@root, File.expand_path('.', @root))
57
+ end
58
+
59
+ test "ignores supplied dir if path contains a drive letter" do
60
+ assert_equal(@root, File.expand_path(@root, "D:/"))
61
+ end
62
+
63
+ test "removes trailing slashes from absolute path" do
64
+ assert_equal(File.join(@root, 'foo'), File.expand_path("#{@root}foo/"))
65
+ assert_equal(File.join(@root, 'foo.rb'), File.expand_path("#{@root}foo.rb/"))
66
+ end
67
+
68
+ test "removes trailing spaces from absolute path" do
69
+ assert_equal(File.join(@root, 'foo'), File.expand_path("#{@root}foo "))
70
+ end
71
+
72
+ test "removes trailing dots from absolute path" do
73
+ assert_equal(File.join(@root, 'a'), File.expand_path("#{@root}a."))
74
+ end
75
+
76
+ test "converts a pathname with a drive letter but no slash" do
77
+ assert_match(/\Ac:\//i, File.expand_path("c:"))
78
+ end
79
+
80
+ test "converts a pathname with a drive letter ignoring different drive dir" do
81
+ assert_match(/\Ac:\//i, File.expand_path("c:foo", "d:/bar"))
82
+ end
83
+
84
+ test "converts a pathname which starts with a slash using current drive" do
85
+ assert_match(/\A#{@drive}\/foo\z/i, File.expand_path('/foo'))
86
+ end
87
+
88
+ test "returns tainted strings or not" do
89
+ assert_true(File.expand_path('foo').tainted?)
90
+ assert_true(File.expand_path('foo'.taint).tainted?)
91
+ assert_true(File.expand_path('/foo').tainted?)
92
+ assert_true(File.expand_path('/foo'.taint).tainted?)
93
+ assert_true(File.expand_path('C:/foo'.taint).tainted?)
94
+ assert_false(File.expand_path('C:/foo').tainted?)
95
+ assert_false(File.expand_path('//foo').tainted?)
96
+ end
97
+
98
+ test "converts a pathname to an absolute pathname using tilde as base" do
99
+ assert_equal(@home, File.expand_path('~'))
100
+ assert_equal("#{@home}/foo", File.expand_path('~/foo'))
101
+ end
102
+
103
+ test "converts a pathname to an absolute pathname using tilde for UNC path" do
104
+ ENV['HOME'] = @unc
105
+ assert_equal(@unc, File.expand_path('~'))
106
+ end
107
+
108
+ test "converts a tilde to path if used for dir argument" do
109
+ assert_equal(@home, File.expand_path('', '~'))
110
+ assert_equal("#{@home}/foo", File.expand_path('', '~/foo'))
111
+ assert_equal("#{@home}/foo", File.expand_path('foo', '~'))
112
+ end
113
+
114
+ test "does not modify a HOME string argument" do
115
+ str = "~/a"
116
+ assert_equal("#{@home}/a", File.expand_path(str))
117
+ assert_equal("~/a", str)
118
+ end
119
+
120
+ test "raises ArgumentError when HOME is nil" do
121
+ ENV['HOME'] = nil
122
+ ENV['USERPROFILE'] = nil
123
+ ENV['HOMEDRIVE'] = nil
124
+ assert_raise(ArgumentError){ File.expand_path('~') }
125
+ end
126
+
127
+ test "raises ArgumentError if HOME is relative" do
128
+ ENV['HOME'] = '.'
129
+ assert_raise(ArgumentError){ File.expand_path('~') }
130
+ end
131
+
132
+ test "raises ArgumentError if relative user is provided" do
133
+ ENV['HOME'] = '.'
134
+ assert_raise(ArgumentError){ File.expand_path('~anything') }
135
+ end
136
+
137
+ test "raises an ArgumentError if a bogus username is supplied" do
138
+ assert_raise(ArgumentError){ File.expand_path('~anything') }
139
+ end
140
+
141
+ test "converts a tilde plus username as expected" do
142
+ assert_equal("C:/Users/#{@@login}", File.expand_path("~#{@@login}"))
143
+ assert_equal("C:/Users/#{@@login}/foo", File.expand_path("~#{@@login}/foo"))
144
+ end
145
+
146
+ test "raises a TypeError if not passed a string" do
147
+ assert_raise(TypeError){ File.expand_path(1) }
148
+ assert_raise(TypeError){ File.expand_path(nil) }
149
+ assert_raise(TypeError){ File.expand_path(true) }
150
+ end
151
+
152
+ test "canonicalizes absolute path" do
153
+ assert_equal("C:/dir", File.expand_path("C:/./dir"))
154
+ end
155
+
156
+ test "does not modify its argument" do
157
+ str = "./a/b/../c"
158
+ assert_equal("#{@home}/a/c", File.expand_path(str, @home))
159
+ assert_equal("./a/b/../c", str)
160
+ end
161
+
162
+ test "accepts objects that have a to_path method" do
163
+ klass = Class.new{ def to_path; "a/b/c"; end }
164
+ obj = klass.new
165
+ assert_equal("#{@pwd}/a/b/c", File.expand_path(obj))
166
+ end
167
+
168
+ test "works with unicode characters" do
169
+ assert_equal("#{@pwd}/Ελλάσ", File.expand_path("Ελλάσ"))
170
+ end
171
+
172
+ def teardown
173
+ @pwd = nil
174
+ @unc = nil
175
+ @dir = nil
176
+ @root = nil
177
+ @drive = nil
178
+
179
+ ENV['HOME'] = @home
180
+ end
181
+
182
+ def self.shutdown
183
+ @@login = nil
184
+ end
185
+ end
@@ -0,0 +1,25 @@
1
+ require 'rubygems'
2
+
3
+ Gem::Specification.new do |spec|
4
+ spec.name = 'win32-xpath'
5
+ spec.version = '1.0.0'
6
+ spec.author = 'Daniel J. Berger'
7
+ spec.license = 'Artistic 2.0'
8
+ spec.email = 'djberg96@gmail.com'
9
+ spec.homepage = 'http://github.com/djberg96/win32-xpath'
10
+ spec.summary = 'Revamped File.expand_path for Windows'
11
+ spec.test_file = 'test/test_win32_xpath.rb'
12
+ spec.files = Dir['**/*'].reject{ |f| f.include?('git') }
13
+
14
+ spec.extra_rdoc_files = ['README', 'CHANGES', 'MANIFEST']
15
+
16
+ spec.extensions = ['ext/extconf.rb']
17
+ spec.add_development_dependency('rake')
18
+ spec.add_development_dependency('test-unit', '>= 3.0.0')
19
+
20
+ spec.description = <<-EOF
21
+ The win32-xpath library provides a revamped File.expand_path method
22
+ for MS Windows. It is significantly faster, and supports ~user
23
+ expansion as well.
24
+ EOF
25
+ end
metadata ADDED
@@ -0,0 +1,90 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: win32-xpath
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Daniel J. Berger
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-02-26 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rake
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: test-unit
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: 3.0.0
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: 3.0.0
41
+ description: |2
42
+ The win32-xpath library provides a revamped File.expand_path method
43
+ for MS Windows. It is significantly faster, and supports ~user
44
+ expansion as well.
45
+ email: djberg96@gmail.com
46
+ executables: []
47
+ extensions:
48
+ - ext/extconf.rb
49
+ extra_rdoc_files:
50
+ - README
51
+ - CHANGES
52
+ - MANIFEST
53
+ files:
54
+ - CHANGES
55
+ - MANIFEST
56
+ - README
57
+ - Rakefile
58
+ - bench/bench_win32_xpath.rb
59
+ - ext/extconf.rb
60
+ - ext/win32/xpath.c
61
+ - futzing/futz.rb
62
+ - futzing/test.rb
63
+ - test/test_win32_xpath.rb
64
+ - win32-xpath.gemspec
65
+ homepage: http://github.com/djberg96/win32-xpath
66
+ licenses:
67
+ - Artistic 2.0
68
+ metadata: {}
69
+ post_install_message:
70
+ rdoc_options: []
71
+ require_paths:
72
+ - lib
73
+ required_ruby_version: !ruby/object:Gem::Requirement
74
+ requirements:
75
+ - - ">="
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
78
+ required_rubygems_version: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ requirements: []
84
+ rubyforge_project:
85
+ rubygems_version: 2.4.5
86
+ signing_key:
87
+ specification_version: 4
88
+ summary: Revamped File.expand_path for Windows
89
+ test_files:
90
+ - test/test_win32_xpath.rb