pathname

package module
v0.0.0-...-ef9f4dd Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 17, 2026 License: BSD-3-Clause Imports: 2 Imported by: 0

README

go-ruby-pathname/pathname

pathname — go-ruby-pathname

Docs License Go Coverage

A pure-Go (no cgo) reimplementation of the path-manipulation surface of Ruby's pathname standard library — the deterministic, interpreter-independent core of MRI 4.0.5's Pathname. It wraps a path string and exposes the lexical operations Ruby's Pathname offers (basename, dirname, extname, cleanpath, relative_path_from, join/+, split, each_filename, ascend/descend, sub_ext, comparison) — without any Ruby runtime and without touching the filesystem.

It is the path-algebra backend for go-embedded-ruby, but is a standalone, reusable module — a sibling of go-ruby-regexp (the Onigmo engine), go-ruby-erb (the ERB compiler) and go-ruby-yaml (the Psych port).

What it is — and isn't. Ruby's Pathname is two libraries in one: a pure path algebra (lexical string surgery, no I/O) and a thin set of delegations to File/Dir/IO (read, write, exist?, children, …). This package is the first half — fully deterministic, needs no interpreter, lives here as pure Go. The filesystem-touching half stays host-side in go-embedded-ruby, where it forwards to the host's File class; it is deliberately out of scope here.

Lexical, /-based, platform-independent

Ruby's Pathname hardcodes "/" as the component separator for every lexical operation on every platform — it never consults the OS path separator for join/split/cleanpath/basename. This package does the same: it uses "/" unconditionally, so the behaviour (and the test suite) is identical on Linux, macOS and Windows. The module therefore builds and passes GOOS=windows with no OS-specific path code. (Ruby's own File.basename etc. are \-aware on Windows; Pathname's lexical methods are not, and neither is this port.)

Features

Faithful port of Pathname's pure path methods, validated against the ruby binary on every supported platform:

  • New / ToS / Inspect — wrap a path, render it, #<Pathname:…>.
  • Basename / BasenameSuffix — last component, with optional suffix strip (".*" strips any extension).
  • Dirname / Parent — all but the last component (., / edge cases).
  • Extname / SubExt — MRI's File.extname rule byte-for-byte, including the fiddly trailing-dot behaviour ("foo." → ".", "a..b" → ".b") and dotfile rule (".foo" → "").
  • Cleanpath — collapse ., .. and redundant separators; a .. past the root of an absolute path is dropped, a .. escaping a relative path is kept ("a/./b/../c" → "a/c", "/a/../../b" → "/b", "a/../../b" → "../b").
  • RelativePathFrom — MRI's lexical relative_path_from algorithm, with its two ArgumentError cases (mixing absolute/relative; a .. in the base), message text matching MRI.
  • Plus / Join (+, join) — append components; an absolute component resets to the root.
  • Split / EachFilename / Filenames[dirname, basename] and the non-empty component list.
  • Ascend / Descend — the path and each parent up to the root (or the first relative component), and the reverse.
  • Absolute / Relative / Root — the predicate trio.
  • Cmp / Eql / Hash<=>, ==/eql?, a stable FNV hash.

CGO-free, dependency-free, 100% test coverage, gofmt + go vet clean, and green across the six 64-bit Go targets (amd64, arm64, riscv64, loong64, ppc64le, s390x) and the three host OSes (Linux, macOS, Windows).

Install

go get github.com/go-ruby-pathname/pathname

Usage

package main

import (
	"fmt"

	"github.com/go-ruby-pathname/pathname"
)

func main() {
	p := pathname.New("a/./b/../c")
	fmt.Println(p.Cleanpath().ToS()) // a/c

	fmt.Println(pathname.New("/usr/bin/ruby.rb").BasenameSuffix(".*").ToS()) // ruby
	fmt.Println(pathname.New("foo.tar.gz").Extname())                       // .gz

	rel, _ := pathname.New("/a/b/c").RelativePathFrom(pathname.New("/a/x/y"))
	fmt.Println(rel.ToS()) // ../../b/c

	fmt.Println(pathname.New("a").JoinStrings("b", "/c", "d").ToS()) // /c/d
}

API

*Pathname is an immutable wrapper over a path string; every transforming method returns a fresh *Pathname, string, []*Pathname or bool, mirroring the corresponding Ruby method. RelativePathFrom returns (*Pathname, error) where the error is an *ArgumentError carrying MRI's exact message. Ascend, Descend and EachFilename take a func callback (the Go analogue of Ruby's block); Filenames returns the slice form.

Differential testing against MRI

The *_oracle_test.go suite shells out to the ruby binary (gated on RUBY_VERSION >= "4.0") and checks each method against MRI's Pathname over a corpus of edge cases — the trailing-slash / . / .. / root-escape cleanpath cases, the trailing-dot extname cases, and the relative_path_from ArgumentError messages. The oracle skips itself where ruby is absent (the qemu cross-arch lanes and the Windows lane); the deterministic, ruby-free tests alone hold coverage at 100% there.

Tests & coverage

go test -race -cover ./...

100% statement coverage is enforced in CI on every supported OS.

License

BSD-3-Clause. Copyright (c) 2026, the go-ruby-pathname/pathname authors.

WebAssembly

Being pure Go (CGO=0), this library also compiles to WebAssembly — both GOOS=js GOARCH=wasm (browser / Node.js) and GOOS=wasip1 GOARCH=wasm (WASI). CI builds both targets on every push, alongside the six 64-bit native/qemu arches.

GOOS=js     GOARCH=wasm go build ./...   # browser / Node
GOOS=wasip1 GOARCH=wasm go build ./...   # WASI (wasmtime, wasmer, wasmedge, …)

Documentation

Overview

Package pathname is a pure-Go (no cgo) reimplementation of the pure path-manipulation surface of Ruby's pathname standard library, as embedded in go-embedded-ruby's prelude and matching MRI 4.0.5.

A Pathname wraps a path string and exposes the lexical operations Ruby's Pathname offers — basename, dirname, extname, cleanpath, relative_path_from, join/+, split, each_filename, ascend/descend, sub_ext, sub, comparison — none of which touch the filesystem. The filesystem-touching delegations of Ruby's Pathname (read, write, exist?, …) are deliberately out of scope: in go-embedded-ruby they remain host-side, forwarding to the File class, while this library is the standalone, interpreter-independent path algebra.

Ruby's Pathname is "/"-based for these lexical operations on every platform (it never consults the OS path separator for join/split/cleanpath), so this package uses "/" unconditionally. The behaviour is therefore identical on Windows, Linux and macOS; the package builds and runs the same on GOOS=windows.

Index

Constants

View Source
const Separator = "/"

Separator is the path component separator. Ruby's Pathname hardcodes "/" for the lexical operations on every platform, and so does this package.

Variables

This section is empty.

Functions

This section is empty.

Types

type ArgumentError

type ArgumentError struct {
	Message string
}

ArgumentError mirrors the error Ruby's relative_path_from raises for an incompatible base directory. Its message matches MRI's wording.

func (*ArgumentError) Error

func (e *ArgumentError) Error() string

type Pathname

type Pathname struct {
	// contains filtered or unexported fields
}

Pathname wraps a path string and exposes Ruby's pure path-manipulation methods. The zero value is a Pathname for the empty string. It is immutable: every transforming method returns a fresh *Pathname.

func New

func New(path string) *Pathname

New returns a Pathname for the given path string, like Ruby's Pathname.new.

func (*Pathname) Absolute

func (p *Pathname) Absolute() bool

Absolute reports whether the path is absolute (begins with "/"), like Ruby's Pathname#absolute?.

func (*Pathname) Ascend

func (p *Pathname) Ascend(yield func(*Pathname))

Ascend yields the path then each parent up to the root (or the first relative component), matching Ruby's Pathname#ascend.

func (*Pathname) Basename

func (p *Pathname) Basename() *Pathname

Basename returns the last path component (Ruby's Pathname#basename with an empty suffix). For an all-separator or empty path it returns "/" as MRI does for a path whose only non-empty component is the root.

func (*Pathname) BasenameSuffix

func (p *Pathname) BasenameSuffix(suffix string) *Pathname

BasenameSuffix returns the last path component with an optional trailing suffix stripped, matching Ruby's Pathname#basename(suffix). A suffix of ".*" strips any file extension; any other non-empty suffix is removed only when the component ends with it.

func (*Pathname) Cleanpath

func (p *Pathname) Cleanpath() *Pathname

Cleanpath collapses ".", ".." and redundant separators, matching Ruby's Pathname#cleanpath (the default, aggressive form). A ".." that would escape the root of an absolute path is dropped (as MRI does); a ".." that escapes a relative path is preserved.

func (*Pathname) Cmp

func (p *Pathname) Cmp(other *Pathname) int

Cmp compares two paths lexically by their wrapped strings, matching Ruby's Pathname#<=>: -1, 0 or 1.

func (*Pathname) Descend

func (p *Pathname) Descend(yield func(*Pathname))

Descend yields the shortest prefix first down to the full path — the ascend sequence reversed — matching Ruby's Pathname#descend.

func (*Pathname) Dirname

func (p *Pathname) Dirname() *Pathname

Dirname returns all but the last path component, matching Ruby's Pathname#dirname (and its alias #parent): "." when there is no separator and "/" when the only separator is the leading one.

func (*Pathname) EachFilename

func (p *Pathname) EachFilename(yield func(string))

EachFilename calls yield once per non-empty path component (leading, trailing and doubled separators contribute no component), matching Ruby's Pathname#each_filename.

func (*Pathname) Eql

func (p *Pathname) Eql(other *Pathname) bool

Eql reports whether two paths are equal, matching Ruby's Pathname#== / #eql? (string equality of the wrapped paths).

func (*Pathname) Extname

func (p *Pathname) Extname() string

Extname returns the file extension of the last path component (".txt", or "" when there is none), matching Ruby's Pathname#extname.

func (*Pathname) Filenames

func (p *Pathname) Filenames() []string

Filenames returns the non-empty path components as a slice, matching the result of Ruby's each_filename.to_a.

func (*Pathname) Hash

func (p *Pathname) Hash() uint64

Hash returns the FNV-1a hash of the wrapped path. Ruby's Pathname#hash delegates to String#hash; this is a stable Go-side hash for use as a map key, equal exactly when the paths are equal.

func (*Pathname) Inspect

func (p *Pathname) Inspect() string

Inspect mirrors Ruby's Pathname#inspect: "#<Pathname:PATH>".

func (*Pathname) Join

func (p *Pathname) Join(args ...*Pathname) *Pathname

Join appends one or more components left to right, matching Ruby's Pathname#join: each component follows the +/join append rule, so an absolute component resets the accumulated path to the root.

func (*Pathname) JoinStrings

func (p *Pathname) JoinStrings(args ...string) *Pathname

JoinStrings is Join accepting raw string components.

func (*Pathname) Parent

func (p *Pathname) Parent() *Pathname

Parent is an alias for Dirname, matching Ruby's Pathname#parent.

func (*Pathname) Plus

func (p *Pathname) Plus(other *Pathname) *Pathname

Plus appends a path component, matching Ruby's Pathname#+ (and #/): an absolute argument resets to the root, otherwise a single "/" separates them.

func (*Pathname) PlusString

func (p *Pathname) PlusString(other string) *Pathname

PlusString is Plus accepting a raw string component, matching Ruby's Pathname#+ when handed a String (it wraps it in a Pathname first).

func (*Pathname) Relative

func (p *Pathname) Relative() bool

Relative reports whether the path is relative, like Ruby's Pathname#relative?.

func (*Pathname) RelativePathFrom

func (p *Pathname) RelativePathFrom(baseDirectory *Pathname) (*Pathname, error)

RelativePathFrom returns this path expressed relative to baseDirectory, using only the lexical components (no filesystem access), matching Ruby's Pathname#relative_path_from. Both paths are cleaned and split, a shared prefix is dropped, each remaining base component contributes a ".." and the remaining self components follow. Mixing an absolute path with a relative one — or a ".." that escapes a relative base — returns an *ArgumentError, as MRI raises.

func (*Pathname) Root

func (p *Pathname) Root() bool

Root reports whether the path is a root, i.e. one or more "/" and nothing else, like Ruby's Pathname#root?.

func (*Pathname) Split

func (p *Pathname) Split() []*Pathname

Split returns [dirname, basename], matching Ruby's Pathname#split.

func (*Pathname) String

func (p *Pathname) String() string

String implements fmt.Stringer, returning the same value as ToS so a *Pathname formats as its path. (Ruby's #to_s.)

func (*Pathname) Sub

func (p *Pathname) Sub(pattern, repl string) *Pathname

Sub returns a Pathname whose path has the first occurrence of pattern replaced by repl, matching the lexical use of Ruby's Pathname#sub (a plain String#sub on the wrapped path, no filesystem access).

func (*Pathname) SubExt

func (p *Pathname) SubExt(repl string) *Pathname

SubExt replaces the file extension of the whole path with repl, matching Ruby's Pathname#sub_ext. When there is no extension, repl is appended.

func (*Pathname) ToS

func (p *Pathname) ToS() string

ToS returns the wrapped path string (Ruby's #to_s / #to_path / #to_str).

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL