win32-xpath 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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