Ruby 4.1.0dev (2026-08-15 revision cfb2ed7c723c5435f630fd70897273db032b0bcc)
pathname.c (cfb2ed7c723c5435f630fd70897273db032b0bcc)
1#include "ruby.h"
2#include "internal.h"
3#include "internal/file.h"
4#include "internal/string.h"
5#include "internal/vm.h"
6
7#if defined __CYGWIN__ || defined DOSISH
8# define drive_letter 1
9# define alt_separator 1
10# define isdirsep(x) ((x) == '/' || (x) == '\\')
11#else
12# define drive_letter 0
13# define alt_separator 0
14# define isdirsep(x) ((x) == '/')
15#endif
16
17static VALUE rb_cPathname;
18static ID id_at_path;
19static ID id_sub;
20
21static VALUE
22check_strpath(VALUE path)
23{
24 Check_Type(path, T_STRING);
25 rb_get_path_check_no_convert(path);
26 return path;
27}
28
29static VALUE
30get_strpath(VALUE obj)
31{
32 VALUE strpath;
33 strpath = rb_ivar_get(obj, id_at_path);
34 if (!RB_TYPE_P(strpath, T_STRING))
35 rb_raise(rb_eTypeError, "unexpected @path");
36 rb_get_path_check_no_convert(strpath);
37 return strpath;
38}
39
40/*
41 * call-seq:
42 * self <=> other -> -1, 0, 1, or nil
43 *
44 * Compares the contents of +self+ and +other+ as strings;
45 * see String#<=>.
46 *
47 * Returns:
48 *
49 * - <tt>-1</tt> if +self+'s string is smaller than +other+'s string.
50 * - <tt>0</tt> if the two are equal.
51 * - <tt>1</tt> if +self+'s string is larger than +other+'s string.
52 * - <tt>nil</tt> if +other+ is not a \Pathname.
53 *
54 * Examples:
55 *
56 * Pathname('a') <=> Pathname('b') # => -1
57 * Pathname('a') <=> Pathname('ab') # => -1
58 * Pathname('a') <=> Pathname('a') # => 0
59 * Pathname('b') <=> Pathname('a') # => 1
60 * Pathname('ab') <=> Pathname('a') # => 1
61 * Pathname('ab') <=> 'a' # => nil
62 *
63 * Two pathnames that are different may refer to the same entry in the filesystem:
64 *
65 * Pathname('lib') <=> Pathname('./lib') # => 1
66 *
67 */
68static VALUE
69path_cmp(VALUE self, VALUE other)
70{
71 VALUE s1, s2;
72 char *p1, *p2;
73 char *e1, *e2;
74 if (!rb_obj_is_kind_of(other, rb_cPathname))
75 return Qnil;
76 s1 = get_strpath(self);
77 s2 = get_strpath(other);
78 p1 = RSTRING_PTR(s1);
79 p2 = RSTRING_PTR(s2);
80 e1 = p1 + RSTRING_LEN(s1);
81 e2 = p2 + RSTRING_LEN(s2);
82 while (p1 < e1 && p2 < e2) {
83 int c1, c2;
84 c1 = (unsigned char)*p1++;
85 c2 = (unsigned char)*p2++;
86 if (c1 == '/') c1 = '\0';
87 if (c2 == '/') c2 = '\0';
88 if (c1 != c2) {
89 if (c1 < c2)
90 return INT2FIX(-1);
91 else
92 return INT2FIX(1);
93 }
94 }
95 if (p1 < e1)
96 return INT2FIX(1);
97 if (p2 < e2)
98 return INT2FIX(-1);
99 return INT2FIX(0);
100}
101
102/*
103 * :markup: markdown
104 *
105 * call-seq:
106 * sub(pattern, replacement) -> new_pathname
107 * sub(pattern) {|match| ... } -> new_pathname
108 *
109 * Returns a new pathname whose path is the path in `self`,
110 * after the specified substitutions.
111 *
112 * Argument `pattern` may be a string or a Regexp;
113 * argument `replacement` may be a string or a hash.
114 *
115 * Varying types for the argument values makes this method very versatile.
116 *
117 * Below are some simple examples;
118 * for many more related examples (using strings, not pathnames),
119 * see [Substitution Methods](rdoc-ref:String@Substitution+Methods).
120 *
121 * With arguments `pattern` and string `replacement` given,
122 * replaces the first matching substring with the given replacement string:
123 *
124 * ```ruby
125 * pn = Pathname('abracadabra.txt') # => #<Pathname:abracadabra.txt>
126 * pn.sub('bra', 'xyzzy') # => #<Pathname:axyzzycadabra.txt>
127 * pn.sub(/bra/, 'xyzzy') # => #<Pathname:axyzzycadabra.txt>
128 * pn.sub('nope', 'xyzzy') # => #<Pathname:abracadabra.txt>
129 * ```
130 *
131 * With arguments `pattern` and hash `replacement` given,
132 * replaces the first matching substring with a value from the given replacement hash,
133 * or removes it:
134 *
135 * ```ruby
136 * h = {'a' => 'A', 'b' => 'B', 'c' => 'C'}
137 * pn.sub('b', h) # => #<Pathname:aBracadabra.txt>
138 * pn.sub(/b/, h) # => #<Pathname:aBracadabra.txt>
139 * pn.sub(/d/, h) # => #<Pathname:abracaabra.txt> # 'd' removed.
140 * ```
141 *
142 * With argument `pattern` and a block given,
143 * calls the block with the first matching substring;
144 * replaces that substring with the block’s return value:
145 *
146 * ```ruby
147 * pn.sub('b') {|match| match.upcase } # => #<Pathname:aBracadabra.txt>
148 * pn.sub(/X/) {|match| match.upcase } # => #<Pathname:abracadabra.txt>
149 * ```
150 *
151 */
152static VALUE
153path_sub(int argc, VALUE *argv, VALUE self)
154{
155 VALUE str = get_strpath(self);
156
157 if (rb_block_given_p()) {
158 str = rb_block_call(str, id_sub, argc, argv, 0, 0);
159 }
160 else {
161 str = rb_funcallv(str, id_sub, argc, argv);
162 }
163 return rb_class_new_instance(1, &str, rb_obj_class(self));
164}
165
166/* :nodoc: */
167static VALUE
168same_paths(VALUE self, VALUE a, VALUE b)
169{
170 check_strpath(a);
171 check_strpath(b);
173 return RBOOL(rb_str_casecmp(a, b) == INT2FIX(0));
174 else
175 return rb_str_equal(a, b);
176}
177
178/*
179 * :markup: markdown
180 *
181 * call-seq:
182 * root? -> true or false
183 *
184 * Returns whether the path in `self` points to a root directory.
185 *
186 * On a non-Windows system, a root directory path is one whose name begins
187 * with one or more slash characters (`'/'):
188 *
189 * ```ruby
190 * Pathname('/').root? # => true
191 * Pathname('////').root? # => true
192 * Pathname('/usr').root? # => false
193 * Pathname('foo').root? # => false
194 * ```
195 *
196 * Does not resolve dot directories:
197 *
198 * ```ruby
199 * Pathname('/usr/.').root? # => false
200 * Pathname('/usr/..').root? # => false
201 * ```
202 *
203 * On a Windows system, a root directory path is one whose name begins as above,
204 * or with a device letter followed by a colon character (`':'`)
205 * and one or more slash characters (`'/'):
206 *
207 * ```ruby
208 * Pathname('/').root? # => true
209 * Pathname('////').root? # => true
210 * Pathname('C:/').root? # => true
211 * Pathname('C:////').root? # => true
212 * Pathname('c:/').root? # => true
213 * Pathname('H:/').root? # => true
214 * Pathname('C:/m').root? # => false
215 * Pathname('C:').root? # => false
216 * ```
217 *
218 */
219static VALUE
220path_root_p(VALUE self)
221{
222 VALUE path = get_strpath(self);
223 if (RSTRING_LEN(path) == 0) return Qfalse;
224 const char *ptr = RSTRING_PTR(path), *end = RSTRING_END(path);
225 rb_encoding *enc = rb_enc_get(path);
226 const char *base = rb_enc_path_skip_prefix_root(ptr, end, enc);
227 return RBOOL(base == end);
228}
229
230/*
231 * call-seq:
232 * absolute? -> true or false
233 *
234 * Returns whether +self+ contains an absolute path:
235 *
236 * Pathname('/home').absolute? # => true
237 * Pathname('lib').absolute? # => false
238 *
239 * The result is OS-dependent for some paths:
240 *
241 * Pathname('C:/').absolute? # => true # On Windows.
242 * Pathname('C:/').absolute? # => false # Elsewhere.
243 *
244 */
245static VALUE
246path_absolute_p(VALUE self)
247{
248 VALUE path = get_strpath(self);
249 const char *ptr = RSTRING_PTR(path);
250 long len = RSTRING_LEN(path);
251 if (len < 1) return Qfalse;
252 if (drive_letter) {
253 if (len >= 2 && ISALPHA(ptr[0]) && (ptr[1] == ':')) return Qtrue;
254 }
255 return RBOOL(isdirsep(ptr[0]));
256}
257
258/* :nodoc: */
259static VALUE
260has_separator_p(VALUE self, VALUE path)
261{
262 const char *ptr = RSTRING_PTR(check_strpath(path));
263 const char *end = RSTRING_END(path);
264 if (alt_separator) {
265 rb_encoding *enc = rb_enc_get(path);
266 bool mb = !rb_str_enc_fastpath(path);
267 while (ptr < end) {
268 if (isdirsep(*ptr)) return Qtrue;
269 ptr += (mb ? rb_enc_mbclen(ptr, end, enc) : 1);
270 }
271 }
272 else {
273 /* assume '/' will never be trailing bytes */
274 if (memchr(ptr, '/', end - ptr)) return Qtrue;
275 }
276 return Qfalse;
277}
278
279/*
280 * :markup: markdown
281 *
282 * call-seq:
283 * sub_ext(replacement) -> new_pathname
284 *
285 * Returns a new pathname whose path is the path in `self`,
286 * after specified changes:
287 *
288 * ```ruby
289 * Pathname('t.tmp').sub_ext('.txt') # => #<Pathname:t.txt> # Extension replaced.
290 * Pathname('temp').sub_ext('.txt') # => #<Pathname:temp.txt> # Extension added.
291 * Pathname('t.tmp').sub_ext('') # => #<Pathname:t> # Extension removed.
292 * ```
293 *
294 */
295static VALUE
296path_sub_ext(VALUE self, VALUE repl)
297{
298 VALUE path = get_strpath(self);
299 long len = RSTRING_LEN(path);
300 const char *ptr = RSTRING_PTR(path);
301 const char *ext = ruby_enc_find_extname(ptr, &len, rb_enc_get(path));
302 if (len > 0) {
303 RUBY_ASSERT(ext, "should point the last dot");
304 path = rb_str_subseq(path, 0, ext - ptr);
305 }
306 else {
307 /* no dot or dotted file */
308 path = rb_str_dup(path);
309 }
310 path = rb_str_append(path, repl);
311 return rb_class_new_instance(1, &path, rb_obj_class(self));
312}
313
314/* :nodoc: */
315/* chop_basename(path) -> [pre-basename, basename] or nil */
316static VALUE
317chop_basename(VALUE self, VALUE path)
318{
319 long baselen, alllen = RSTRING_LEN(check_strpath(path));
320 if (alllen <= 0) return Qnil;
321 rb_encoding *enc = rb_enc_get(path);
322 const char *name = RSTRING_PTR(path);
323 const char *base = ruby_enc_find_basename(name, &baselen, &alllen, enc);
324 if (baselen < 1) return Qnil;
325 if (baselen == 1 && isdirsep(*base)) return Qnil;
326 RUBY_ASSERT(base >= name);
327 RUBY_ASSERT(base <= RSTRING_END(path));
328 VALUE dir = rb_str_subseq(path, 0, base - name);
329 VALUE basename = rb_enc_str_new(base, alllen, enc);
330 RB_GC_GUARD(path);
331 return rb_assoc_new(dir, basename);
332}
333
334/* :nodoc: */
335/* split_names(path) -> prefix, [name, ...] */
336static VALUE
337split_names(VALUE self, VALUE path)
338{
339 rb_encoding *enc = rb_enc_get(check_strpath(path));
340 const char *beg = RSTRING_PTR(path), *ptr = beg;
341 const char *end = RSTRING_END(path);
342 const char *root = rb_enc_path_skip_prefix_root(ptr, end, enc);
343 VALUE pre = rb_str_subseq(path, 0, root - ptr);
344 VALUE names = rb_ary_new();
345 while (ptr < end) {
346 const char *next = rb_enc_path_next(ptr, end, enc);
347 if (next > ptr) rb_ary_push(names, rb_str_subseq(path, ptr - beg, next - ptr));
348 ptr = next;
349 while (ptr < end && isdirsep(*ptr)) ++ptr;
350 }
351 return rb_assoc_new(pre, names);
352}
353
354/* :nodoc: */
355/* has_trailing_separator?(path) -> bool */
356static VALUE
357has_trailing_separator(VALUE self, VALUE path)
358{
359 long baselen, alllen = RSTRING_LEN(check_strpath(path));
360 if (alllen <= 0) return Qfalse;
361 rb_encoding *enc = rb_enc_get(path);
362 const char *name = RSTRING_PTR(path);
363 const char *base = ruby_enc_find_basename(name, &baselen, &alllen, enc);
364 if (baselen < 1) return Qfalse;
365 if (baselen == 1 && isdirsep(*base)) return Qfalse;
366 return RBOOL(base + alllen < RSTRING_END(path));
367}
368
369/* :nodoc: */
370/* add_trailing_separator(path) -> path */
371static VALUE
372add_trailing_separator(VALUE self, VALUE path)
373{
374 if (RSTRING_LEN(check_strpath(path)) <= 0) return path;
375 rb_encoding *enc = rb_enc_get(path);
376 const char *name = RSTRING_PTR(path);
377 const char *end = RSTRING_END(path);
378 const char *top = rb_enc_path_skip_prefix(name, end, enc);
379 if (top < end && isdirsep(end[-1])) {
380 if (end[-1] == '/' || rb_enc_prev_char(top, end, end, enc) == end - 1)
381 return path;
382 }
383 return rb_str_cat_cstr(rb_str_dup(path), "/");
384}
385
386/* :nodoc: */
387static VALUE
388del_trailing_separator(VALUE self, VALUE path)
389{
390 long len = RSTRING_LEN(check_strpath(path));
391 if (len <= 0) return path;
392 rb_encoding *enc = rb_enc_get(path);
393 const char *name = RSTRING_PTR(path);
394 const char *end = name + len, *tail = end;
395 const char *top = rb_enc_path_skip_prefix(name, end, enc);
396 if (tail > top && isdirsep(tail[-1])) {
397 while (--tail > top && isdirsep(tail[-1]));
398 if (tail > top &&
399 tail[0] != '/' &&
400 !rb_str_enc_fastpath(path) &&
401 rb_enc_left_char_head(top, tail, end, enc) != tail) {
402 /* trailing byte, not a directory separator */
403 ++tail;
404 }
405 if (tail < end) {
406 if (tail == name || (drive_letter && tail == top && top[-1] == ':')) {
407 ++tail;
408 }
409 }
410 }
411 if (tail == end) return path;
412 return rb_str_subseq(path, 0, tail - name);
413}
414
415#include "pathname_builtin.rbinc"
416
417static void init_ids(void);
418
419void
420Init_pathname(void)
421{
422#ifdef HAVE_RB_EXT_RACTOR_SAFE
423 rb_ext_ractor_safe(true);
424#endif
425
426 init_ids();
427 InitVM(pathname);
428}
429
430void
431InitVM_pathname(void)
432{
433 rb_cPathname = rb_define_class("Pathname", rb_cObject);
434 rb_define_method(rb_cPathname, "<=>", path_cmp, 1);
435 rb_define_method(rb_cPathname, "sub", path_sub, -1);
436 rb_define_method(rb_cPathname, "sub_ext", path_sub_ext, 1);
437 rb_define_method(rb_cPathname, "root?", path_root_p, 0);
438 rb_define_method(rb_cPathname, "absolute?", path_absolute_p, 0);
439
440 rb_define_private_method(rb_cPathname, "same_paths?", same_paths, 2);
441 rb_define_private_method(rb_cPathname, "has_separator?", has_separator_p, 1);
442 rb_define_private_method(rb_cPathname, "chop_basename", chop_basename, 1);
443 rb_define_private_method(rb_cPathname, "split_names", split_names, 1);
444 rb_define_private_method(rb_cPathname, "has_trailing_separator?", has_trailing_separator, 1);
445 rb_define_private_method(rb_cPathname, "add_trailing_separator", add_trailing_separator, 1);
446 rb_define_private_method(rb_cPathname, "del_trailing_separator", del_trailing_separator, 1);
447
448 rb_provide("pathname.so");
449}
450
451void
452init_ids(void)
453{
454#undef rb_intern
455 id_at_path = rb_intern("@path");
456 id_sub = rb_intern("sub");
457}
#define RUBY_ASSERT(...)
Asserts that the given expression is truthy if and only if RUBY_DEBUG is truthy.
Definition assert.h:219
#define rb_define_method(klass, mid, func, arity)
Defines klass#mid.
#define rb_define_private_method(klass, mid, func, arity)
Defines klass#mid and makes it private.
#define CASEFOLD_FILESYSTEM
Stone age assumption was that an operating system supports only one file system at a moment.
Definition dosish.h:85
int rb_block_given_p(void)
Determines if the current method is given a block.
Definition eval.c:1029
#define T_STRING
Old name of RUBY_T_STRING.
Definition value_type.h:78
#define INT2FIX
Old name of RB_INT2FIX.
Definition long.h:48
#define ISALPHA
Old name of rb_isalpha.
Definition ctype.h:92
#define Qtrue
Old name of RUBY_Qtrue.
#define Qnil
Old name of RUBY_Qnil.
#define Qfalse
Old name of RUBY_Qfalse.
VALUE rb_eTypeError
TypeError exception.
Definition error.c:1431
VALUE rb_cObject
Object class.
Definition object.c:58
VALUE rb_class_new_instance(int argc, const VALUE *argv, VALUE klass)
Allocates, then initialises an instance of the given class.
Definition object.c:2280
VALUE rb_obj_class(VALUE obj)
Queries the class of an object.
Definition object.c:232
VALUE rb_obj_is_kind_of(VALUE obj, VALUE klass)
Queries if the given object is an instance (of possibly descendants) of the given class.
Definition object.c:894
static char * rb_enc_left_char_head(const char *s, const char *p, const char *e, rb_encoding *enc)
Queries the left boundary of a character.
Definition encoding.h:683
VALUE rb_funcallv(VALUE recv, ID mid, int argc, const VALUE *argv)
Identical to rb_funcall(), except it takes the method arguments as a C array.
Definition vm_eval.c:1081
VALUE rb_ary_new(void)
Allocates a new, empty array.
VALUE rb_ary_push(VALUE ary, VALUE elem)
Special case of rb_ary_cat() that it adds only one element.
VALUE rb_assoc_new(VALUE car, VALUE cdr)
Identical to rb_ary_new_from_values(), except it expects exactly two parameters.
void rb_provide(const char *feature)
Declares that the given feature is already provided by someone else.
Definition load.c:710
void rb_ext_ractor_safe(bool flag)
Asserts that the extension library that calls this function is aware of Ractor.
Definition load.c:1295
VALUE rb_str_append(VALUE dst, VALUE src)
Identical to rb_str_buf_append(), except it converts the right hand side before concatenating.
Definition string.c:3880
VALUE rb_str_subseq(VALUE str, long beg, long len)
Identical to rb_str_substr(), except the numbers are interpreted as byte offsets instead of character...
Definition string.c:3233
VALUE rb_str_dup(VALUE str)
Duplicates a string.
Definition string.c:2005
VALUE rb_str_equal(VALUE str1, VALUE str2)
Equality of two strings.
Definition string.c:4350
#define rb_str_cat_cstr(buf, str)
Identical to rb_str_cat(), except it assumes the passed pointer is a pointer to a C string.
Definition string.h:1657
VALUE rb_ivar_get(VALUE obj, ID name)
Identical to rb_iv_get(), except it accepts the name as an ID instead of a C string.
Definition variable.c:1578
int len
Length of the buffer.
Definition io.h:8
#define RB_GC_GUARD(v)
Prevents premature destruction of local objects.
Definition memory.h:167
VALUE rb_block_call(VALUE q, ID w, int e, const VALUE *r, type *t, VALUE y)
Call a method with a block.
static char * RSTRING_END(VALUE str)
Queries the end of the contents pointer of the string.
Definition rstring.h:409
#define InitVM(ext)
This macro is for internal use.
Definition ruby.h:231
uintptr_t ID
Type that represents a Ruby identifier such as a variable name.
Definition value.h:52
uintptr_t VALUE
Type that represents a Ruby object.
Definition value.h:40
static void Check_Type(VALUE v, enum ruby_value_type t)
Identical to RB_TYPE_P(), except it raises exceptions on predication failure.
Definition value_type.h:425
static bool RB_TYPE_P(VALUE obj, enum ruby_value_type t)
Queries if the given object is of given type.
Definition value_type.h:376