pathrules

package module
v0.1.0 Latest Latest
Warning

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

Go to latest
Published: Feb 17, 2026 License: MIT Imports: 11 Imported by: 2

README

pathrules

Reusable gitignore-like path rule engine for include/exclude workflows.

Features

  • gitignore-like patterns: *, ?, **, **/, [char-class]
  • leading / anchored rules
  • trailing / directory-only rules
  • ! negation support
  • deterministic last match wins
  • two policy modes:
    • ignore mode (DefaultAction: ActionInclude)
    • allow-list mode (DefaultAction: ActionExclude)

Quick Start

rules, _ := pathrules.ParseRulesString(`
*.tmp
!keep.tmp
`)

m, _ := pathrules.NewMatcher(rules, pathrules.MatcherOptions{
    DefaultAction: pathrules.ActionInclude,
})

_ = m.Included("keep.tmp", false) // true
_ = m.Included("a.tmp", false)    // false

Recursive Provider

p, _ := pathrules.NewProvider("/project", pathrules.ProviderOptions{
    RulesFileName: ".pboignore",
    BaseRules: []pathrules.Rule{
        {Action: pathrules.ActionInclude, Pattern: "*.c"},
    },
    MatcherOptions: pathrules.MatcherOptions{
        DefaultAction: pathrules.ActionExclude,
    },
})

ok, _ := p.Included("scripts/main.c", false)
_ = ok

Provider loads rules files from root to target directory, caches compiled matchers, and applies deterministic last-match-wins.

[!IMPORTANT]
for performance, reuse one Provider for the whole directory walk. Creating a new Provider per file forces cold path behavior on every check.

Provider hardening:

  • rejects invalid RulesFileName values (path separators, absolute paths, ..)
  • optional symlink/junction escape check via EnableSymlinkEscapeCheck (disabled by default)

For one-directory batch checks, use DecideInDir / IncludedInDir and pass entry names (DirEntry) instead of calling Decide per file.

Documentation

Overview

Package pathrules implements gitignore-like path matching with reusable include/exclude policies.

The package is intentionally generic and can be used for ignore workflows, allow-list workflows, compression selection, conversion selection, and other path-based pipelines.

Basic flow:

  • parse rules from text (`ParseRules`)
  • optionally load rules from file (`LoadRulesFile`)
  • compile matcher (`NewMatcher`)
  • ask for decision (`Decide` / `Included` / `Excluded`)

For hierarchical rule files, use `Provider`:

  • create provider with root directory and rules file name
  • evaluate paths relative to that root
  • provider caches compiled directory matchers
  • for one-directory batches use `DecideInDir` / `IncludedInDir`
  • optional symlink/junction escape hardening: `EnableSymlinkEscapeCheck` (disabled by default)

Index

Constants

This section is empty.

Variables

View Source
var (
	// ErrInvalidRule indicates malformed or unsupported rule input.
	ErrInvalidRule = errors.New("invalid rule")
	// ErrInvalidPattern indicates malformed or unsupported rule pattern.
	ErrInvalidPattern = errors.New("invalid pattern")
	// ErrInvalidRulesFileName indicates invalid provider rules file name.
	ErrInvalidRulesFileName = errors.New("invalid rules file name")
	// ErrInvalidEntryName indicates invalid directory entry input for batch APIs.
	ErrInvalidEntryName = errors.New("invalid entry name")
	// ErrNilProvider indicates a nil Provider receiver.
	ErrNilProvider = errors.New("provider is nil")
	// ErrPathOutsideRoot indicates path traversal or non-relative input path.
	ErrPathOutsideRoot = errors.New("path is outside provider root")
	// ErrRulesPathOutsideRoot indicates resolved rules file path escaped provider root.
	ErrRulesPathOutsideRoot = errors.New("rules file path is outside provider root")
)

Sentinel errors for pathrules operations.

Functions

This section is empty.

Types

type Action

type Action uint8

Action represents a decision action of one rule.

const (
	// ActionUnknown is unset/invalid action placeholder.
	ActionUnknown Action = iota
	// ActionExclude means matching path should be excluded.
	ActionExclude
	// ActionInclude means matching path should be included.
	ActionInclude
)

type DirEntry

type DirEntry struct {
	// Name is one entry name relative to target directory (without path separators).
	Name string `json:"name" yaml:"name"`
	// IsDir reports whether entry path is a directory.
	IsDir bool `json:"is_dir,omitempty" yaml:"is_dir,omitempty"`
}

DirEntry is one directory entry input for Provider batch APIs.

type MatchResult

type MatchResult struct {
	// Included reports final include decision.
	Included bool `json:"included" yaml:"included"`
	// Matched reports whether at least one rule matched.
	Matched bool `json:"matched" yaml:"matched"`
	// RuleIndex is the matched rule index in matcher input order, -1 when no match.
	RuleIndex int `json:"rule_index" yaml:"rule_index"`
}

MatchResult is a deterministic decision produced by matcher.

type Matcher

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

Matcher evaluates path decisions against compiled ordered rules.

func NewMatcher

func NewMatcher(rules []Rule, opts MatcherOptions) (*Matcher, error)

NewMatcher compiles ordered rules into matcher.

func (*Matcher) Decide

func (m *Matcher) Decide(path string, isDir bool) MatchResult

Decide returns deterministic include/exclude decision for one path.

Decision policy: - last matched rule wins - if no rule matched, default action is used

func (*Matcher) Excluded

func (m *Matcher) Excluded(path string, isDir bool) bool

Excluded reports whether path is excluded by decision policy.

func (*Matcher) Included

func (m *Matcher) Included(path string, isDir bool) bool

Included reports whether path is included by decision policy.

type MatcherOptions

type MatcherOptions struct {
	// CaseInsensitive enables ASCII case-insensitive matching.
	CaseInsensitive bool `json:"case_insensitive,omitempty" yaml:"case_insensitive,omitempty"`
	// DefaultAction is applied when no rule matched.
	DefaultAction Action `json:"default_action,omitempty" yaml:"default_action,omitempty"`
}

MatcherOptions controls matcher behavior.

type Provider

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

Provider loads rules files along path hierarchy and evaluates final decisions.

func NewProvider

func NewProvider(rootDir string, opts ProviderOptions) (*Provider, error)

NewProvider creates a recursive rules provider rooted at rootDir.

func (*Provider) Decide

func (p *Provider) Decide(relPath string, isDir bool) (MatchResult, error)

Decide returns final include/exclude decision for a path relative to provider root.

Decision order: 1. BaseRules matcher. 2. Rules files from root to deepest containing directory. Last matched rule wins.

func (*Provider) DecideInDir

func (p *Provider) DecideInDir(relDir string, entries []DirEntry) ([]MatchResult, error)

DecideInDir returns decisions for multiple entries from one directory.

The same directory matcher chain is loaded once and reused for every entry.

func (*Provider) Excluded

func (p *Provider) Excluded(relPath string, isDir bool) (bool, error)

Excluded reports whether path is excluded by provider decision.

func (*Provider) ExcludedInDir

func (p *Provider) ExcludedInDir(relDir string, entries []DirEntry) ([]bool, error)

ExcludedInDir reports exclude decisions for multiple entries from one directory.

func (*Provider) Included

func (p *Provider) Included(relPath string, isDir bool) (bool, error)

Included reports whether path is included by provider decision.

func (*Provider) IncludedInDir

func (p *Provider) IncludedInDir(relDir string, entries []DirEntry) ([]bool, error)

IncludedInDir reports include decisions for multiple entries from one directory.

type ProviderOptions

type ProviderOptions struct {
	// RulesFileName is the rules file loaded in each directory in the path chain.
	// Empty value defaults to ".pathrules".
	RulesFileName string `json:"rules_file_name,omitempty" yaml:"rules_file_name,omitempty"`
	// BaseRules are in-memory rules evaluated before directory-loaded rules.
	BaseRules []Rule `json:"base_rules,omitempty" yaml:"base_rules,omitempty"`
	// MatcherOptions controls rule matching behavior for all compiled matchers.
	MatcherOptions MatcherOptions `json:"matcher_options" yaml:"matcher_options"`
	// EnableSymlinkEscapeCheck enables resolved-path validation to block
	// symlink/junction escapes outside provider root.
	// Default is false for lower cold-path overhead.
	EnableSymlinkEscapeCheck bool `json:"enable_symlink_escape_check,omitempty" yaml:"enable_symlink_escape_check,omitempty"`
}

ProviderOptions configures recursive rules provider behavior.

type Rule

type Rule struct {
	// Pattern is a gitignore-like pattern.
	Pattern string `json:"pattern" yaml:"pattern"`
	// Action is a decision action applied when the rule matches.
	Action Action `json:"action" yaml:"action"`
}

Rule is one user-visible path rule.

func LoadRulesFile

func LoadRulesFile(path string) ([]Rule, error)

LoadRulesFile reads and parses rules from a file.

func LoadRulesFiles

func LoadRulesFiles(paths ...string) ([]Rule, error)

LoadRulesFiles reads and merges rules from files in the given order.

Returned rules preserve file order and rule order inside each file.

func MergeRules

func MergeRules(ruleSets ...[]Rule) []Rule

MergeRules merges rule slices preserving input order.

func ParseRules

func ParseRules(r io.Reader) ([]Rule, error)

ParseRules parses gitignore-like rules from reader.

Semantics: - blank lines and comments are ignored - "!" creates include rule - plain lines create exclude rule - "\#" and "\!" escape leading comment/negation tokens

func ParseRulesString

func ParseRulesString(src string) ([]Rule, error)

ParseRulesString parses rules from string input.

Jump to

Keyboard shortcuts

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