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 +7 -0
- data/CHANGES +2 -0
- data/MANIFEST +9 -0
- data/README +54 -0
- data/Rakefile +60 -0
- data/bench/bench_win32_xpath.rb +79 -0
- data/ext/extconf.rb +4 -0
- data/ext/win32/xpath.c +360 -0
- data/futzing/futz.rb +71 -0
- data/futzing/test.rb +90 -0
- data/test/test_win32_xpath.rb +185 -0
- data/win32-xpath.gemspec +25 -0
- metadata +90 -0
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
data/MANIFEST
ADDED
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
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
|
data/win32-xpath.gemspec
ADDED
@@ -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
|