Skip to content

Commit

Permalink
Enable usage as a normal go module.
Browse files Browse the repository at this point in the history
These changes enable to import codenotify as a go module and to use the
now exported Subscribers function to compute subscribers from Go code
rather than having to call the binary.

Other than a slight code reorganization to allow that, behaviour remains
unchanged.
  • Loading branch information
jhchabran committed Apr 26, 2023
1 parent 278d7a9 commit 458518e
Show file tree
Hide file tree
Showing 6 changed files with 109 additions and 89 deletions.
4 changes: 2 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ FROM golang:1.15-alpine as builder
#ENV CGO_ENABLED=0
WORKDIR /build
COPY go.mod go.sum *.go ./
COPY cmd/ ./cmd

RUN go build -o codenotify

RUN go build -o codenotify ./cmd/codenotify

FROM alpine:3.12

Expand Down
2 changes: 0 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -131,8 +131,6 @@ dir/**" @all-dir
**/* @all
```



## Why use Codenotify?

GitHub projects can create a [CODEOWNERS](https://docs.github.com/en/github/creating-cloning-and-archiving-repositories/about-code-owners) file (inspired by [Chromium's use of OWNERS files](https://chromium.googlesource.com/chromium/src/+/master/docs/code_reviews.md#OWNERS-files)) and GitHub will automatically add reviewers. There are a few downsides to this approach:
Expand Down
84 changes: 9 additions & 75 deletions main.go → cmd/codenotify/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,11 @@ import (
"net/http/httputil"
"os"
"os/exec"
"path/filepath"
"regexp"
"sort"
"strconv"
"strings"

"github.com/sourcegraph/codenotify"
)

var verbose io.Writer = os.Stderr
Expand Down Expand Up @@ -62,7 +62,7 @@ func testableMain(stdout io.Writer, args []string) error {
return fmt.Errorf("error scanning lines from diff: %s\n%s", err, string(diff))
}

notifs, err := notifications(&gitfs{cwd: opts.cwd, rev: opts.baseRef}, paths, opts.filename)
notifs, err := notifications(codenotify.NewGitFS(opts.cwd, opts.baseRef), paths, opts.filename)
if err != nil {
return err
}
Expand Down Expand Up @@ -457,7 +457,12 @@ func readLines(b []byte) ([]string, error) {
return lines, scanner.Err()
}

func notifications(fs FS, paths []string, notifyFilename string) (map[string][]string, error) {
func subscribers(fs codenotify.FS, path string, notifyFilename string) ([]string, error) {
fmt.Fprintf(verbose, "analyzing subscribers in %s files\n", notifyFilename)
return codenotify.Subscribers(fs, path, notifyFilename)
}

func notifications(fs codenotify.FS, paths []string, notifyFilename string) (map[string][]string, error) {
notifications := map[string][]string{}
for _, path := range paths {
subs, err := subscribers(fs, path, notifyFilename)
Expand All @@ -472,74 +477,3 @@ func notifications(fs FS, paths []string, notifyFilename string) (map[string][]s

return notifications, nil
}

func subscribers(fs FS, path string, notifyFilename string) ([]string, error) {
fmt.Fprintf(verbose, "analyzing subscribers in %s files\n", notifyFilename)
subscribers := []string{}

parts := strings.Split(path, string(os.PathSeparator))
for i := range parts {
base := filepath.Join(parts[:i]...)
rulefilepath := filepath.Join(base, notifyFilename)

rulefile, err := fs.Open(rulefilepath)
if err != nil {
if err == os.ErrNotExist {
continue
}
return nil, err
}

scanner := bufio.NewScanner(rulefile)
for scanner.Scan() {
rule := scanner.Text()
if rule != "" && rule[0] == '#' {
// skip comment
continue
}

fields := strings.Fields(rule)
switch len(fields) {
case 0:
// skip blank line
continue
case 1:
return nil, fmt.Errorf("expected at least two fields for rule in %s: %s", rulefilepath, rule)
}

rel, err := filepath.Rel(base, path)
if err != nil {
return nil, err
}

re, err := patternToRegexp(fields[0])
if err != nil {
return nil, fmt.Errorf("invalid pattern in %s: %s: %w", rulefilepath, rule, err)
}

if re.MatchString(rel) {
subscribers = append(subscribers, fields[1:]...)
}
}

if err := scanner.Err(); err != nil {
return nil, err
}
}

return subscribers, nil
}

func patternToRegexp(pattern string) (*regexp.Regexp, error) {
if pattern[len(pattern)-1:] == "/" {
pattern += "**"
}
pattern = regexp.QuoteMeta(pattern)
pattern = strings.ReplaceAll(pattern, `/\*\*/`, "/([^/]*/)*")
pattern = strings.ReplaceAll(pattern, `\*\*/`, "([^/]+/)*")
pattern = strings.ReplaceAll(pattern, `/\*\*`, ".*")
pattern = strings.ReplaceAll(pattern, `\*\*`, ".*")
pattern = strings.ReplaceAll(pattern, `\*`, "[^/]*")
pattern = "^" + pattern + "$"
return regexp.Compile(pattern)
}
6 changes: 4 additions & 2 deletions main_test.go → cmd/codenotify/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import (
"sort"
"strings"
"testing"

"github.com/sourcegraph/codenotify"
)

func TestMain(t *testing.T) {
Expand Down Expand Up @@ -682,13 +684,13 @@ func (m memfs) paths() []string {
return paths
}

func (m memfs) Open(name string) (File, error) {
func (m memfs) Open(name string) (codenotify.File, error) {
content, ok := m[name]
if !ok {
return nil, os.ErrNotExist
}

mf := memfile{
mf := codenotify.Memfile{
Buffer: bytes.NewBufferString(content),
}

Expand Down
20 changes: 12 additions & 8 deletions fs.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package main
package codenotify

import (
"bytes"
Expand All @@ -18,32 +18,36 @@ type File interface {
}

// memfile is an in-memory file
type memfile struct {
type Memfile struct {
*bytes.Buffer
}

func (m memfile) Close() error {
func (m Memfile) Close() error {
m.Buffer = nil
return nil
}

func (m memfile) Stat() (os.FileInfo, error) {
func (m Memfile) Stat() (os.FileInfo, error) {
return nil, errors.New("memfile does not support stat")
}

// gitfs implements the FS interface for files at a specific git revision.
type gitfs struct {
// Gitfs implements the FS interface for files at a specific git revision.
type Gitfs struct {
cwd string
rev string
}

func (g *gitfs) Open(name string) (File, error) {
func NewGitFS(cwd string, rev string) *Gitfs {
return &Gitfs{cwd: cwd, rev: rev}
}

func (g *Gitfs) Open(name string) (File, error) {
cmd := exec.Command("git", "-C", g.cwd, "show", g.rev+":"+name)
buf, err := cmd.Output()
if err != nil {
return nil, os.ErrNotExist
}
return memfile{
return Memfile{
Buffer: bytes.NewBuffer(buf),
}, nil
}
82 changes: 82 additions & 0 deletions subcribers.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package codenotify

import (
"bufio"
"fmt"
"os"
"path/filepath"
"regexp"
"strings"
)

// Subscribers returns the list of subscribers defined in the topmost notifyFilename
// found in the path.
func Subscribers(fs FS, path string, notifyFilename string) ([]string, error) {
subscribers := []string{}

parts := strings.Split(path, string(os.PathSeparator))
for i := range parts {
base := filepath.Join(parts[:i]...)
rulefilepath := filepath.Join(base, notifyFilename)

rulefile, err := fs.Open(rulefilepath)
if err != nil {
if err == os.ErrNotExist {
continue
}
return nil, err
}

scanner := bufio.NewScanner(rulefile)
for scanner.Scan() {
rule := scanner.Text()
if rule != "" && rule[0] == '#' {
// skip comment
continue
}

fields := strings.Fields(rule)
switch len(fields) {
case 0:
// skip blank line
continue
case 1:
return nil, fmt.Errorf("expected at least two fields for rule in %s: %s", rulefilepath, rule)
}

rel, err := filepath.Rel(base, path)
if err != nil {
return nil, err
}

re, err := patternToRegexp(fields[0])
if err != nil {
return nil, fmt.Errorf("invalid pattern in %s: %s: %w", rulefilepath, rule, err)
}

if re.MatchString(rel) {
subscribers = append(subscribers, fields[1:]...)
}
}

if err := scanner.Err(); err != nil {
return nil, err
}
}

return subscribers, nil
}

func patternToRegexp(pattern string) (*regexp.Regexp, error) {
if pattern[len(pattern)-1:] == "/" {
pattern += "**"
}
pattern = regexp.QuoteMeta(pattern)
pattern = strings.ReplaceAll(pattern, `/\*\*/`, "/([^/]*/)*")
pattern = strings.ReplaceAll(pattern, `\*\*/`, "([^/]+/)*")
pattern = strings.ReplaceAll(pattern, `/\*\*`, ".*")
pattern = strings.ReplaceAll(pattern, `\*\*`, ".*")
pattern = strings.ReplaceAll(pattern, `\*`, "[^/]*")
pattern = "^" + pattern + "$"
return regexp.Compile(pattern)
}

0 comments on commit 458518e

Please sign in to comment.