fsx

package module
v0.0.1 Latest Latest
Warning

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

Go to latest
Published: May 17, 2026 License: Apache-2.0 Imports: 4 Imported by: 0

README

fsx

main branch GitHub go.mod Go version Go Reference Go Report Card license Release

fsx is a small Go package for filesystem helpers that sit just above os and path/filepath. It is designed for applications that need compact, easy-to-review helpers for expanded user paths, containment checks, file predicates, extension matching, and atomic writes.

The package is standard-library-only, has no external module dependencies, and targets the latest Go toolchain version declared in go.mod. It intentionally keeps one short package name instead of exposing broad utils packages.

Features

  • User path expansion for leading ~ and $HOME
  • Path existence checks for files, directories, and any filesystem entry
  • Directory containment checks that normalize relative traversal
  • Case-insensitive file extension matching
  • Atomic file replacement with permissions
  • Zero third-party dependencies
  • Apache-2.0 licensed

Installation

go get github.com/slashdevops/fsx

Update to the latest available version:

go get -u github.com/slashdevops/fsx

Requirements

  • Go 1.26.3 or newer, matching the latest Go version declared by the module
  • No external Go modules or third-party dependencies

Quick Start

package main

import (
    "fmt"
    "log"
    "os"
    "path/filepath"

    "github.com/slashdevops/fsx"
)

func main() {
    configPath := fsx.ExpandPath("~/app/config.yaml")
    if !fsx.HasExtension(configPath, "yaml") {
        log.Fatal("config file must be YAML")
    }

    if err := os.MkdirAll(filepath.Dir(configPath), 0o755); err != nil {
        log.Fatal(err)
    }

    if err := fsx.WriteFileAtomic(configPath, []byte("enabled: true\n"), 0o600); err != nil {
        log.Fatal(err)
    }

    fmt.Println(filepath.Base(configPath))
}

API

ExpandPath
func ExpandPath(path string) string

ExpandPath replaces a leading ~ with the current user's home directory and replaces $HOME references with the HOME environment variable. Empty paths are returned unchanged.

configPath := fsx.ExpandPath("~/app/config.yaml")
Exists
func Exists(path string) bool

Exists reports whether a file, directory, or other filesystem entry exists. The path is expanded before it is checked.

if fsx.Exists("~/app/config.yaml") {
    // read config
}
IsDir
func IsDir(path string) bool

IsDir reports whether the expanded path exists and is a directory.

if !fsx.IsDir("~/app") {
    return errors.New("app directory does not exist")
}
IsFile
func IsFile(path string) bool

IsFile reports whether the expanded path exists and is a regular file.

if !fsx.IsFile("~/app/config.yaml") {
    return errors.New("config file does not exist")
}
IsWithin
func IsWithin(base, target string) bool

IsWithin reports whether target resolves to a path contained inside base. Both paths are expanded, cleaned, and made absolute before comparison. A target equal to base is considered within base.

This is useful before deleting or overwriting paths read from user-editable state files.

if !fsx.IsWithin(outputDir, stateFilePath) {
    return fmt.Errorf("refusing to remove file outside output directory")
}
HasExtension
func HasExtension(path string, extensions ...string) bool

HasExtension reports whether path has one of the provided extensions. Matching is case-insensitive, extensions may be passed with or without a leading dot, and the path does not need to exist.

if !fsx.HasExtension("config.YAML", "yaml", "yml") {
    return errors.New("config must be YAML")
}
Dir
func Dir(path string) string

Dir returns the directory component of path after expanding it.

dir := fsx.Dir("~/app/config.yaml")
WriteFileAtomic
func WriteFileAtomic(path string, data []byte, perm os.FileMode) error

WriteFileAtomic writes data to a temporary file in the destination directory, sets the requested permissions, and renames the temporary file over the target path.

The destination directory must already exist. If any step fails before the rename, the temporary file is removed.

if err := fsx.WriteFileAtomic("~/app/config.yaml", data, 0o600); err != nil {
    return fmt.Errorf("save config: %w", err)
}

Quality Automation

The repository follows the same GitHub quality practices used by slashdevops/e5t:

  • main.yml validates pushes to main with format checks, go vet, race-enabled tests, coverage, and go build.
  • pr.yml runs the same quality gate for pull requests targeting main.
  • codeql.yml runs CodeQL for Go and GitHub Actions on pushes, pull requests, and a weekly schedule.
  • release.yml validates tagged releases and publishes GitHub releases with generated notes.
  • dependabot.yml checks Go module and GitHub Actions updates weekly.

Project Structure

.
|-- .github/         GitHub Actions, CodeQL, Dependabot, and release metadata
|-- .golangci.yaml   Optional local golangci-lint configuration
|-- doc.go           Package documentation rendered by pkg.go.dev
|-- example_test.go  Executable Go examples for documentation
|-- fsx.go           Public filesystem API
|-- fsx_test.go      Unit tests and benchmarks
|-- go.mod           Module definition with no external requirements
|-- LICENSE          Apache License 2.0
|-- README.md        Project overview and usage guide
`-- SECURITY.md      Vulnerability reporting policy

Testing

Run the test suite:

go test ./...

Run benchmarks:

go test -bench=. ./...

Check test coverage:

go test -cover ./...

Run the same local quality checks used by CI:

go fix ./...
go fmt ./...
go vet ./...
go test -race -coverprofile=/tmp/fsx-coverage.txt -covermode=atomic ./...
go build ./...

License

fsx is licensed under the Apache License 2.0.

Contributing

Issues and pull requests are welcome at github.com/slashdevops/fsx. Please keep changes small, idiomatic, tested, documented, and dependency-free unless there is a clear reason to expand the project scope.

Documentation

Overview

Package fsx provides small filesystem helpers built only on the Go standard library and targets the latest Go toolchain version declared by the module.

The package focuses on behavior that is useful across command-line tools and services:

  • expanding user-facing paths that start with ~ or contain $HOME
  • checking whether paths exist and whether they are regular files or directories
  • confirming that a path remains inside a base directory after normalization
  • matching file extensions case-insensitively
  • writing files atomically by replacing the destination with a temporary file

Path Expansion

ExpandPath expands a leading ~ with the current user's home directory and replaces $HOME references with the HOME environment variable. If the home directory cannot be determined, the original path is returned unchanged.

Atomic Writes

WriteFileAtomic writes data to a temporary file in the destination directory, sets the requested permissions, and renames the temporary file over the target. This prevents readers from observing partially written file contents on the same filesystem.

Dependencies

fsx has zero third-party dependencies and no external module requirements. It uses os, path/filepath, strings, and other standard library packages, and the module currently targets Go 1.26.3.

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func Dir

func Dir(path string) string

Dir returns the directory component of path after expanding it with ExpandPath.

func Exists

func Exists(path string) bool

Exists reports whether a file, directory, or other filesystem entry exists.

The path is expanded with ExpandPath before it is checked. Empty paths and paths that cannot be statted return false.

func ExpandPath

func ExpandPath(path string) string

ExpandPath expands a user-facing filesystem path.

It replaces a leading ~ with the current user's home directory and replaces all $HOME references with the HOME environment variable. Empty paths are returned unchanged. If the home directory cannot be determined, the original path is returned unchanged.

Example

ExampleExpandPath demonstrates expanding a user-facing path.

package main

import (
	"fmt"
	"path/filepath"

	"github.com/slashdevops/fsx"
)

func main() {
	expanded := fsx.ExpandPath("~/config.yaml")
	fmt.Println(filepath.IsAbs(expanded))

}
Output:
true

func HasExtension

func HasExtension(path string, extensions ...string) bool

HasExtension reports whether path has one of the provided extensions.

Extension matching is case-insensitive. Extensions may be passed with or without a leading dot. The path does not need to exist.

Example

ExampleHasExtension demonstrates extension matching.

package main

import (
	"fmt"

	"github.com/slashdevops/fsx"
)

func main() {
	fmt.Println(fsx.HasExtension("config.YAML", "yaml", "json"))
	fmt.Println(fsx.HasExtension("README", "md"))

}
Output:
true
false

func IsDir

func IsDir(path string) bool

IsDir reports whether path exists and is a directory.

The path is expanded with ExpandPath before it is checked.

func IsFile

func IsFile(path string) bool

IsFile reports whether path exists and is a regular file.

The path is expanded with ExpandPath before it is checked.

func IsWithin

func IsWithin(base, target string) bool

IsWithin reports whether target resolves to a path contained inside base.

Both paths are expanded, cleaned, and made absolute before comparison. The function returns false when either path is empty, either path cannot be resolved, or the relative path from base to target escapes base with "..". A target equal to base is considered within base.

Example

ExampleIsWithin demonstrates a containment check before deleting a file.

package main

import (
	"fmt"
	"os"
	"path/filepath"

	"github.com/slashdevops/fsx"
)

func main() {
	base := filepath.Join(os.TempDir(), "workspace")
	target := filepath.Join(base, "page.md")

	fmt.Println(fsx.IsWithin(base, target))
	fmt.Println(fsx.IsWithin(base, filepath.Join(base, "..", "escape.md")))

}
Output:
true
false

func WriteFileAtomic

func WriteFileAtomic(path string, data []byte, perm os.FileMode) error

WriteFileAtomic writes data to path by replacing it with a completed temporary file in the same directory.

The destination directory must already exist. The temporary file is created in that directory, written, closed, chmodded to perm, and renamed to path. If any step fails before the rename, the temporary file is removed.

Example

ExampleWriteFileAtomic demonstrates an atomic file replacement.

package main

import (
	"fmt"
	"log"
	"os"
	"path/filepath"

	"github.com/slashdevops/fsx"
)

func main() {
	dir, err := os.MkdirTemp("", "fsx-example-*")
	if err != nil {
		log.Fatal(err)
	}
	defer func() {
		if err := os.RemoveAll(dir); err != nil {
			log.Fatal(err)
		}
	}()

	path := filepath.Join(dir, "config.txt")
	if err := fsx.WriteFileAtomic(path, []byte("ready"), 0o600); err != nil {
		log.Fatal(err)
	}

	data, err := os.ReadFile(path)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(string(data))

}
Output:
ready

Types

This section is empty.

Jump to

Keyboard shortcuts

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