nogo

package module
v0.3.1 Latest Latest
Warning

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

Go to latest
Published: Jan 28, 2022 License: MIT Imports: 6 Imported by: 2

README

NoGo test CodeQL

A .gitignore parser for Go.

Features

  • parsing .gitignore files
  • loading file trees with several .gitignore files
  • fs.WalkDir WalkDirFunc implementation (and afero.Walk (see below))
  • customizable ignore filename (instead of .gitignore)
  • full compatibility with git
    As far as I could test it, it handles .gitignore files the same way as git.
    If you find an inconsistency with git, please create a new Issue.
    The goal is to provide the exact same .gitignore handling.

Stability

Note that this lib is currently beta and therefore may introduce breaking changes.

Usage

n := nogo.New(nogo.DotGitRule)
if err := n.AddFromFS(wdfs, ".gitignore"); err != nil {
    panic(err)
}

match := n.Match(toSearch, isDir)
fmt.Println(match)

There is also an alternative MatchBecause method which returns also the causing rule if you need some context.

There exists a predefined rule to ignore any .git folder automatically.

n := nogo.New(nogo.DotGitRule)
if err := n.AddFromFS(wdfs, ".gitignore"); err != nil {
    panic(err)
}

Walk

NoGo can be used with fs.WalkDir. Just see the example walk. If you need to use another Walk function, you can build your own wrapper using the NoGo.WalkFunc function.

I intentionally did not include an afero walk to avoid a new dependency just because of afero-compatibility. However, you can easily build your own.
You can find an example for afero in the documentation of NoGo.WalkFunc.

Documentation

Overview

Package nogo implements gitignore parsing in pure go. It supports the official specification. https://git-scm.com/docs/gitignore/2.34.0

PATTERN FORMAT

  * A blank line matches no files, so it can serve as a separator for readability.

  * A line starting with # serves as a comment. Put a backslash ("\") in front of the first hash for patterns that begin with a hash.

  * Trailing spaces are ignored unless they are quoted with backslash ("\").

  * An optional prefix "!" which negates the pattern; any matching file excluded by a previous pattern will become included again. It is not possible to re-include a file if a parent directory of that file is excluded. Git doesn’t list excluded directories for performance reasons, so any patterns on contained files have no effect, no matter where they are defined. Put a backslash ("\") in front of the first "!" for patterns that begin with a literal "!", for example, "\!important!.txt".

  * The slash / is used as the directory separator. Separators may occur at the beginning, middle or end of the .gitignore search pattern.

  * If there is a separator at the beginning or middle (or both) of the pattern, then the pattern is relative to the directory level of the particular .gitignore file itself. Otherwise the pattern may also matches at any level below the .gitignore level.

  * If there is a separator at the end of the pattern then the pattern will only matches directories, otherwise the pattern can matches both files and directories.
    For example, a pattern doc/frotz/ matches doc/frotz directory, but not a/doc/frotz directory; however frotz/ matches frotz and a/frotz that is a directory (all paths are relative from the .gitignore file).

  * An asterisk "*" matches anything except a slash. The character "?" matches any one character except "/". The range notation, e.g. [a-zA-Z], can be used to matches one of the characters in a range. See fnmatch(3) and the FNM_PATHNAME flag for a more detailed description.

Two consecutive asterisks ("**") in patterns matched against full pathname may have special meaning:

  * A leading "**" followed by a slash means matches in all directories. For example, "**/foo" matches file or directory "foo" anywhere, the same as pattern "foo". "**/foo/bar" matches file or directory "bar" anywhere that is directly under directory "foo".

  * A trailing "/**" matches everything inside. For example, "abc/**" matches all files inside directory "abc", relative to the location of the .gitignore file, with infinite depth.

  * A slash followed by two consecutive asterisks then a slash matches zero or more directories. For example, "a/**/b" matches "a/b", "a/x/b", "a/x/y/b" and so on.

  * Other consecutive asterisks are considered regular asterisks and will matches according to the previous rules.

Index

Constants

This section is empty.

Variables

View Source
var (
	DotGitRule = MustCompileAll("", []byte(".git"))[0]
)

Functions

This section is empty.

Types

type NoGo

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

func New

func New(rules ...Rule) *NoGo

New creates a NoGo instance which works for the given ignoreFileNames. You can pass additional options if needed.

func (*NoGo) AddFile

func (n *NoGo) AddFile(fsys fs.FS, path string) error

AddFile reads the given file and tries to load the content as an ignore file. It does not check the filename. So you can add any file, independently of the configured ignoreFileNames.

The folder of the give filepath is used as Prefix for the rules.

Note that the order in which rules are added is very important. You should always first add the rules of parent folders and then of the children folders. TODO: in the future the rules could be re-sorted based on the prefix names.

func (*NoGo) AddFromFS added in v0.2.0

func (n *NoGo) AddFromFS(fsys fs.FS, ignoreFilename string) error

AddFromFS ignore files which can be found in the given fsys. It only loads ignore files which are not ignored itself by another ignore-file.

func (*NoGo) AddRules

func (n *NoGo) AddRules(rules ...Rule)

AddRules to NoGo which are already compiled.

func (*NoGo) ForWalkDir added in v0.2.0

func (n *NoGo) ForWalkDir(fsys fs.FS, root string, fn fs.WalkDirFunc) (fs.FS, string, fs.WalkDirFunc)

ForWalkDir can be used to set all parameters of fs.WalkDir. It only calls the passed WalkDirFunc for files and directories which are not ignored.

You have to call AddFromFS with the same fs before running the walk!

If you need something similar for any other Walk function (e.g. afero.Walk) You can use WalkFunc for that.

Example:

 if err := n.AddFromFS(walkFS, ".gitignore"); err != nil {
		panic(err)
	}

 n := nogo.New(nogo.DotGitRule)
 err = fs.WalkDir(n.ForWalkDir(walkFS, ".", func(path string, d fs.DirEntry, err error) error {
		if err != nil {
			return err
		}
		fmt.Println(path, d.Name())
		return nil
	}))

func (*NoGo) Match added in v0.2.0

func (n *NoGo) Match(path string, isDir bool) bool

Match calculates if the path matches any rule. It does the same as MatchBecause but only returns the boolean for more easy in-if usage.

func (*NoGo) MatchBecause added in v0.2.0

func (n *NoGo) MatchBecause(path string, isDir bool) (match bool, because Result)

MatchBecause calculates if the path matches any rule. It returns the match but also a result, where the match was calculated from. Use Match if you do not need the cause.

You have to pass if the path is a directory or not using isDir.

func (*NoGo) MatchWithoutParents added in v0.2.0

func (n *NoGo) MatchWithoutParents(path string, isDir bool) (match bool, because Result)

MatchWithoutParents does the same as MatchBecause and Match but it disables a time-consuming check of all parent folder rules. This is faster, but it results in wrong results if the check of the parents is not done in another way.

DO NOT USE THIS IF YOU DON'T UNDERSTAND HOW IT WORKS. Use MatchBecause or Match instead.

You can use this if you know that no file gets checked without also all parents being checked before.

As the parent-check is time-consuming it is for example better to disable that check when using Walk function. (NoGo.WalkDirFunc and NoGo.WalkAferoFunc use it for example).

Example:

Folder1
 - File1
.gitignore -> Rule: "/Folder1"

If the gitignore contains the rule "/Folder1" and you check the file `/Folder1/File1`, you will get a correct match.

But if you check the file WITH "WithoutMatchParents", the file will not match as it itself is not in any ignore-list and the parent folder does not get checked.

When doing file traversal with a Walk method, this doesn't matter as the Folder1 won't be read and therefore /Folder1/File1 won't be read either.

But when checking only the file /Folder1/File1 directly, you will NOT want "WithoutMatchParents".

func (*NoGo) WalkFunc added in v0.2.0

func (n *NoGo) WalkFunc(fsys fs.FS, path string, isDir bool, err error) (bool, error)

WalkFunc can be used in any Walk function to automatically ignore ignored files. It is similar to ForWalkDir but with it you can write a WalkFunc for any other (than fs.WalkDir) Walk function. It returns true if everything is ok and false if the path is ignored and should be skipped.

You have to call AddFromFS with the same fs before running the walk!

The Walk function you use must support the fs.SkipDir error (or you have to skip that manually)

Example for afero:

 if err := n.AddFromFS(walkFS, ".gitignore"); err != nil {
		panic(err)
	}

 err = afero.Walk(baseFS, ".", func(path string, info fs.FileInfo, err error) error {
		if ok, err := n.WalkFunc(afero.NewIOFS(baseFS), path, info.IsDir(), err); !ok {
			return err
		}

		fmt.Println(path, info.Name())
		return nil
	})

type Result

type Result struct {
	Rule

	// Found is true if any matching rule was found.
	// Do not use it to check if the file is actually to be ignored!
	// For this use Resolve as it takes into account some special cases.
	Found bool

	// ParentMatch saves if the actual rule matched for a parent or not.
	// In case of a parent match the check for OnlyFolder has to be different.
	ParentMatch bool
}

func (Result) Resolve

func (r Result) Resolve(isDir bool) bool

Resolve the Result by taking into account OnlyFolder and if the matched path is a directory.

type Rule

type Rule struct {
	// Regexp defines all regexp-rules which have to pass in order
	// to pass the rule.
	Regexp     []*regexp.Regexp
	Prefix     string
	Pattern    string
	Negate     bool
	OnlyFolder bool
}

func Compile

func Compile(prefix string, pattern string) (skip bool, rule Rule, err error)

Compile the pattern into a single regexp. skip means that this pattern doesn't contain any rule (e.g. just a comment or empty line).

func CompileAll

func CompileAll(prefix string, data []byte) ([]Rule, error)

CompileAll rules in the given data line by line. The prefix is added to all rules.

func MustCompileAll

func MustCompileAll(prefix string, data []byte) []Rule

MustCompileAll does the same as CompileAll but panics on error.

func (Rule) MatchPath

func (r Rule) MatchPath(path string) Result

Directories

Path Synopsis
example

Jump to

Keyboard shortcuts

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