diff --git a/Justfile b/Justfile index 52d9c30..977ea88 100644 --- a/Justfile +++ b/Justfile @@ -9,33 +9,47 @@ default: @just --list # start nerfs artifact builder in demo mode +[group('build')] run mode: compile $GOBIN/nerfs {{mode}} # compile the nerfs executable +[group('build')] compile: tidy cd cmds/nerfs && go install # tidy up Go modules +[group('build')] tidy: go mod tidy # vet the nerfs source tree +[group('lint')] vet: go vet ./... # run go test on the source tree +[group('testing')] tests: go test -race ./... +# run specific unit test +[group('testing')] +[no-cd] +test unit: + go test -v -count=1 -race -run {{unit}} 2>/dev/null + # lint the nerfs source tree +[group('lint')] lint: vet $GOBIN/golangci-lint run --config $scripts/golangci.yaml # show host system information +[group('build')] @sysinfo: echo "{{os()/arch()}} {{num_cpus()}}c" # locally install build dependencies +[group('build')] init: go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.11.4 diff --git a/cmds/nerfs/commands/commands.go b/cmds/nerfs/commands/commands.go index 203f67c..c696ae5 100644 --- a/cmds/nerfs/commands/commands.go +++ b/cmds/nerfs/commands/commands.go @@ -1,10 +1,10 @@ package commands import ( - "fmt" "os" "cattlecloud.net/go/babycli" + "cattlecloud.net/go/ulog" "cattlecloud.net/nerfs/cmds/nerfs/domains" "cattlecloud.net/nerfs/cmds/nerfs/wordlist" ) @@ -40,17 +40,19 @@ func invoke(args []string) babycli.Code { }, }, Function: func(c *babycli.Component) babycli.Code { + log := ulog.New("nerfs") + output := c.GetString("output") buildDomains := domains.NewBuilder() if err := buildDomains.Build(output); err != nil { - fmt.Println("build failure:", err) + log.E.Fmt("unable to build domains list: %v", err) return babycli.Failure } buildWords := wordlist.NewBuilder() if err := buildWords.Build(output); err != nil { - fmt.Println("build failure:", err) + log.E.Fmt("unable to build words list: %v", err) return babycli.Failure } diff --git a/nerfs.go b/nerfs.go index 86559c6..201f3cc 100644 --- a/nerfs.go +++ b/nerfs.go @@ -14,17 +14,15 @@ import ( const ( DomainsFile = "domains.txt" - WordsFile = "wordlist.txt" + WordsFile = "wordlist.json" ) // An Artifact contains the in-memory optimized form of the domain and word // block-list files created by the nerfs tool. // -// Create an Artifact by calling Load() with the directory of the compiled -// artifacts. -type Artifact struct { - domains *set.Set[string] - words []*regexp.Regexp +// Call Synopsis on some text to analyze it. +type Artifact interface { + Synopsis(r io.Reader) *Synopsis } // A Synopsis contains the result of analyzing some text. @@ -36,6 +34,11 @@ type Synopsis struct { Words int } +type artifact struct { + domains *set.Set[string] + words []*regexp.Regexp +} + // Any returns whether any domains or words appear on the block lists. func (s *Synopsis) Any() bool { return s.Domains > 0 || s.Words > 0 @@ -47,7 +50,7 @@ func (s *Synopsis) Any() bool { // Note that this should be used carefully - scanning large text consumes lots // of CPU due to regular expression matching against every token. Many use cases // need only scan against blocked domains which is must faster. -func (a *Artifact) Synopsis(r io.Reader) *Synopsis { +func (a *artifact) Synopsis(r io.Reader) *Synopsis { syn := new(Synopsis) scanner := bufio.NewScanner(r) scanner.Split(bufio.ScanWords) @@ -63,7 +66,7 @@ func (a *Artifact) Synopsis(r io.Reader) *Synopsis { return syn } -func (a *Artifact) matchDomain(s string) bool { +func (a *artifact) matchDomain(s string) bool { s, _ = strings.CutPrefix(s, "https://") s, _ = strings.CutPrefix(s, "http://") if i := strings.IndexAny(s, "/?#"); i >= 0 { @@ -72,7 +75,7 @@ func (a *Artifact) matchDomain(s string) bool { return a.domains.Contains(s) } -func (a *Artifact) matchWord(s string) bool { +func (a *artifact) matchWord(s string) bool { for _, reg := range a.words { if reg.MatchString(s) { return true @@ -81,7 +84,7 @@ func (a *Artifact) matchWord(s string) bool { return false } -func Load(directory string) (*Artifact, error) { +func Load(directory string) (Artifact, error) { domainsFile := filepath.Join(directory, DomainsFile) wordsFile := filepath.Join(directory, WordsFile) @@ -95,7 +98,7 @@ func Load(directory string) (*Artifact, error) { return nil, werr } - return &Artifact{ + return &artifact{ domains: domains, words: words, }, nil diff --git a/nerfs_test.go b/nerfs_test.go index 265164a..7dd790e 100644 --- a/nerfs_test.go +++ b/nerfs_test.go @@ -62,14 +62,14 @@ func TestArtifact_Load(t *testing.T) { art, lerr := Load(d) must.NoError(t, lerr) - must.Size(t, 3, art.domains) - must.Len(t, 2, art.words) + must.Size(t, 3, art.(*artifact).domains) + must.Len(t, 2, art.(*artifact).words) } func TestSynopsis_matching(t *testing.T) { t.Parallel() - a := &Artifact{ + a := &artifact{ domains: set.From([]string{"example.com", "example.org", "example.xyz"}), words: []*regexp.Regexp{ regexp.MustCompile(`p[oO0]+p`), @@ -116,7 +116,7 @@ visit example.org we have pOop and f4rts! example.xyz and example.com too! func TestArtifact_matchDomain(t *testing.T) { t.Parallel() - a := &Artifact{ + a := &artifact{ domains: set.From([]string{"example.com", "example.xyz"}), }