Skip to content

Instantly share code, notes, and snippets.

@Snarp
Created January 25, 2026 19:10
Show Gist options
  • Select an option

  • Save Snarp/fb4c14e31d5324bade8a6af25370bfce to your computer and use it in GitHub Desktop.

Select an option

Save Snarp/fb4c14e31d5324bade8a6af25370bfce to your computer and use it in GitHub Desktop.
Monkeypatch / core extension adding some functionality to Pathname
# SOURCES:
# - https://docs.ruby-lang.org/en/master/Pathname.html
# - https://github.com/ruby/pathname
class Pathname
# Returns the basename with the extension removed: if #basename is "foo.bar", returns "foo".
#
# @return [String]
def stemname
basename.chomp(extname)
end
def sub_parts(dir: nil, base: nil, stem: nil, ext: nil)
dir ||= dirname
if !base
if stem || ext
stem ||= stemname_full
ext ||= extname_full
base = stem + ext
else
base = basename_full
end
end
self.class.new File.join(dir, base)
end
# Return a pathname with `repl` replacing the dirname. If self has no dirname part, `repl` is prepended.
#
# @param [String] repl new dirname
# @return [Pathname]
def sub_dir(repl)
self.class.new File.join(repl, basename_full)
end
# Return a pathname with `repl` replacing the basename. If self has no basename part, `repl` is appended.
#
# @param [String] repl new basename
# @return [Pathname]
def sub_base(repl)
self.class.new(@path.chomp(basename_full.to_s) + repl)
end
# Return a pathname with `repl` replacing the stemname portion of basename. If self has no basename part, `repl` is appended.
#
# @param [String] repl new stemname
# @return [Pathname]
def sub_stem(repl)
self.class.new(@path.chomp(basename_full.to_s) + repl + extname_full)
end
# Based on source for #sub_ext; corrects for potential issues with File.extname's handling of constructions like `foo.bar:stream` on NTFS.
#
# @return [String]
def extname_full
ext = File.extname(@path)
unless @path.end_with?(ext)
ext = @path[@path.rindex(ext)..]
end
return ext
end
# Based on source for #sub_ext; corrects for potential issues with File.extname's handling of constructions like `foo.bar:stream` on NTFS.
#
# @return [String]
def basename_full
base = File.basename(@path)
unless @path.end_with?(base)
base = @path[@path.rindex(base)..]
end
return base
end
# Based on source for #sub_ext; corrects for potential issues with File.extname's handling of constructions like `foo.bar:stream` on NTFS.
#
# @return [String]
def stemname_full
return basename_full.chomp(extname_full)
end
# # File pathname_builtin.rb, line 292
# def sub_ext(repl)
# ext = File.extname(@path)
#
# # File.extname("foo.bar:stream") returns ".bar" on NTFS and not ".bar:stream"
# # (see ruby_enc_find_extname()).
# # The behavior of Pathname#sub_ext is to replace everything
# # from the start of the extname until the end of the path with repl.
# unless @path.end_with?(ext)
# ext = @path[@path.rindex(ext)..]
# end
#
# self.class.new(@path.chomp(ext) + repl)
# end
def chomp(*args)
self.class.new to_s.chomp(*args)
end
def slice(*args)
self.class.new to_s.slice(*args)
end
def length
to_s.length
end
def to_h
{
dirname: dirname,
stemname: stemname,
extname: extname,
stat: stat,
}
end
def to_a
dirname.split + [stemname, extname]
end
class << self
def build(dir: nil, base: nil, stem: nil, ext: nil)
if !base && stem
base = stem + ext.to_s
end
if dir.is_a?(Array)
dir = File.join *dir.flatten
end
if dir && base
return self.new(File.join dir, base)
elsif dir || base
return self.new(dir || base)
else
raise ArgumentError.new("Invalid args: #{{dir: dir, base: base, stem: stem, ext: ext}}")
end
end
end # class << self
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment