Skip to content

Commit

Permalink
feat: add file content regex matching expression, refactor shoulds co…
Browse files Browse the repository at this point in the history
…de and tests
  • Loading branch information
omissis committed Aug 10, 2022
1 parent f4d7a9c commit c3e0c1d
Show file tree
Hide file tree
Showing 21 changed files with 386 additions and 115 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ if a file:
- [x] does not exist
- [x] name matches a regex
- [x] name does not match a regex
- [ ] content matches value
- [ ] content matches regex
- [x] content matches value
- [x] content matches regex
- [ ] content matches template
- [ ] is gitignored
- [ ] is gitcrypted
Expand Down
10 changes: 10 additions & 0 deletions internal/arch/file/except/except.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,16 @@ import (
"goarkitect/internal/arch/rule"
)

type evaluateFunc func(filePath string) bool

func NewExpression(
evaluate evaluateFunc,
) *Expression {
return &Expression{
evaluate: evaluate,
}
}

type Expression struct {
evaluate func(filePath string) bool
}
Expand Down
6 changes: 3 additions & 3 deletions internal/arch/file/except/this.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ package except
import "path/filepath"

func This(value string) *Expression {
return &Expression{
evaluate: func(filePath string) bool {
return NewExpression(
func(filePath string) bool {
return filepath.Base(filePath) != value
},
}
)
}
8 changes: 4 additions & 4 deletions internal/arch/file/rule_all_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -161,16 +161,16 @@ func Test_It_Checks_All_Files_Paths_In_A_Folder_Match_A_Glob_Pattern(t *testing.
{
desc: "check that all files' names in a folder match a glob pattern",
folder: filepath.Join(basePath, "test/project3"),
glob: "*/*/*.go",
glob: "*/*/*.txt",
wantViolations: nil,
},
{
desc: "check that all files' names in a folder do not match a glob pattern",
folder: filepath.Join(basePath, "test/project3"),
glob: "*/*/*.ts",
glob: "*/*/*.doc",
wantViolations: []rule.Violation{
rule.NewViolation("file's path 'baz.go' does not match glob pattern '*/*/*.ts'"),
rule.NewViolation("file's path 'quux.go' does not match glob pattern '*/*/*.ts'"),
rule.NewViolation("file's path 'baz.txt' does not match glob pattern '*/*/*.doc'"),
rule.NewViolation("file's path 'quux.txt' does not match glob pattern '*/*/*.doc'"),
},
},
}
Expand Down
32 changes: 16 additions & 16 deletions internal/arch/file/rule_set_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -198,43 +198,43 @@ func Test_It_Checks_A_Set_Of_Files_Names_Matches_A_Glob_Pattern(t *testing.T) {
{
desc: "check that a set of files' names match a glob pattern when it's actually there",
filenames: []string{
filepath.Join(basePath, "test/project3/baz.go"),
filepath.Join(basePath, "test/project3/quux.go"),
filepath.Join(basePath, "test/project3/baz.txt"),
filepath.Join(basePath, "test/project3/quux.txt"),
},
glob: "*/*/*.go",
glob: "*/*/*.txt",
wantViolations: nil,
},
{
desc: "check that a set of files' names match a glob pattern when it's not actually there",
filenames: []string{
filepath.Join(basePath, "test/project3/baz.ts"),
filepath.Join(basePath, "test/project3/quux.ts"),
filepath.Join(basePath, "test/project3/baz.doc"),
filepath.Join(basePath, "test/project3/quux.doc"),
},
glob: "*/*/*.ts",
glob: "*/*/*.doc",
wantViolations: nil,
},
{
desc: "check that a set of files' names do not match a glob pattern when it's actually there",
filenames: []string{
filepath.Join(basePath, "test/project3/baz.go"),
filepath.Join(basePath, "test/project3/quux.go"),
filepath.Join(basePath, "test/project3/baz.txt"),
filepath.Join(basePath, "test/project3/quux.txt"),
},
glob: "*/*/*.ts",
glob: "*/*/*.doc",
wantViolations: []rule.Violation{
rule.NewViolation("file's path 'baz.go' does not match glob pattern '*/*/*.ts'"),
rule.NewViolation("file's path 'quux.go' does not match glob pattern '*/*/*.ts'"),
rule.NewViolation("file's path 'baz.txt' does not match glob pattern '*/*/*.doc'"),
rule.NewViolation("file's path 'quux.txt' does not match glob pattern '*/*/*.doc'"),
},
},
{
desc: "check that a set of files' names do not match a glob pattern when it's not actually there",
filenames: []string{
filepath.Join(basePath, "test/project3/baz.ts"),
filepath.Join(basePath, "test/project3/quux.ts"),
filepath.Join(basePath, "test/project3/baz.doc"),
filepath.Join(basePath, "test/project3/quux.doc"),
},
glob: "*/*/*.go",
glob: "*/*/*.txt",
wantViolations: []rule.Violation{
rule.NewViolation("file's path 'baz.ts' does not match glob pattern '*/*/*.go'"),
rule.NewViolation("file's path 'quux.ts' does not match glob pattern '*/*/*.go'"),
rule.NewViolation("file's path 'baz.doc' does not match glob pattern '*/*/*.txt'"),
rule.NewViolation("file's path 'quux.doc' does not match glob pattern '*/*/*.txt'"),
},
},
}
Expand Down
13 changes: 7 additions & 6 deletions internal/arch/file/should/end_with.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,26 +6,27 @@ import (
"path/filepath"
)

func EndWith(suffix string) *Expression {
func EndWith(suffix string, opts ...Option) *Expression {
ls := len(suffix)

return &Expression{
evaluate: func(_ rule.Builder, filePath string) bool {
return NewExpression(
func(_ rule.Builder, filePath string) bool {
fileName := filepath.Base(filePath)

lf := len(fileName)

return ls <= lf && fileName[lf-ls:] != suffix
},
getViolation: func(filePath string, negated bool) rule.Violation {
func(filePath string, options options) rule.Violation {
format := "file's name '%s' does not end with '%s'"
if negated {
if options.negated {
format = "file's name '%s' does end with '%s'"
}

return rule.NewViolation(
fmt.Sprintf(format, filepath.Base(filePath), suffix),
)
},
}
opts...,
)
}
13 changes: 7 additions & 6 deletions internal/arch/file/should/exist.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ import (
"path/filepath"
)

func Exist() *Expression {
return &Expression{
evaluate: func(rb rule.Builder, filePath string) bool {
func Exist(opts ...Option) *Expression {
return NewExpression(
func(rb rule.Builder, filePath string) bool {
if _, err := os.Stat(filePath); err != nil {
if !os.IsNotExist(err) {
rb.AddError(err)
Expand All @@ -20,15 +20,16 @@ func Exist() *Expression {

return false
},
getViolation: func(filePath string, negated bool) rule.Violation {
func(filePath string, options options) rule.Violation {
format := "file '%s' does not exist"
if negated {
if options.negated {
format = "file '%s' does exist"
}

return rule.NewViolation(
fmt.Sprintf(format, filepath.Base(filePath)),
)
},
}
opts...,
)
}
67 changes: 41 additions & 26 deletions internal/arch/file/should/have_content_matching.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,50 +10,65 @@ import (
"golang.org/x/exp/slices"
)

type ContentMatchOption interface {
apply(data []byte) []byte
}

type IgnoreNewLinesAtTheEndOfFile struct{}

func (opt IgnoreNewLinesAtTheEndOfFile) apply(data []byte) []byte {
return bytes.TrimRight(data, "\n")
}

type IgnoreCase struct{}

func (opt IgnoreCase) apply(data []byte) []byte {
return bytes.ToLower(data)
}

func HaveContentMatching(want []byte, opts ...ContentMatchOption) *Expression {
return &Expression{
evaluate: func(rb rule.Builder, filePath string) bool {
func HaveContentMatching(want []byte, opts ...Option) *Expression {
return NewExpression(
func(rb rule.Builder, filePath string) bool {
data, err := os.ReadFile(filePath)
if err != nil {
rb.AddError(err)
return true
}

match := "SINGLE"
separator := []byte("\n")
for _, opt := range opts {
data = opt.apply(data)
switch opt.(type) {
case IgnoreNewLinesAtTheEndOfFile:
data = bytes.TrimRight(data, "\n")
want = bytes.TrimRight(want, "\n")
case IgnoreCase:
data = bytes.ToLower(data)
want = bytes.ToLower(want)
case MatchSingleLines:
match = "MULTIPLE"
if sep := opt.(MatchSingleLines).Separator; sep != "" {
separator = []byte(sep)
}
}
}

for _, opt := range opts {
want = opt.apply(want)
if match == "SINGLE" {
return slices.Compare(data, want) != 0
}

linesData := bytes.Split(data, separator)
for _, ld := range linesData {
if slices.Compare(ld, want) != 0 {
return true
}
}

return slices.Compare(data, want) != 0
return false
},
getViolation: func(filePath string, negated bool) rule.Violation {
func(filePath string, options options) rule.Violation {
format := "file '%s' does not have content matching '%s'"
if negated {

if options.matchSingleLines {
format = "file '%s' does not have all lines matching '%s'"
}

if options.negated {
format = "file '%s' does have content matching '%s'"
}

if options.negated && options.matchSingleLines {
format = "file '%s' does have all lines matching '%s'"
}

return rule.NewViolation(
fmt.Sprintf(format, filepath.Base(filePath), want),
)
},
}
opts...,
)
}
74 changes: 74 additions & 0 deletions internal/arch/file/should/have_content_matching_regex.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package should

import (
"bytes"
"fmt"
"goarkitect/internal/arch/rule"
"os"
"path/filepath"
"regexp"
)

func HaveContentMatchingRegex(res string, opts ...Option) *Expression {
rx := regexp.MustCompile(res)

return NewExpression(
func(rb rule.Builder, filePath string) bool {
data, err := os.ReadFile(filePath)
if err != nil {
rb.AddError(err)

return true
}

match := "SINGLE"
separator := []byte("\n")
for _, opt := range opts {
switch opt.(type) {
case IgnoreNewLinesAtTheEndOfFile:
data = bytes.TrimRight(data, "\n")
case IgnoreCase:
data = bytes.ToLower(data)
case MatchSingleLines:
match = "MULTIPLE"
if sep := opt.(MatchSingleLines).Separator; sep != "" {
separator = []byte(sep)
}
}
}

if match == "SINGLE" {
return !rx.Match(data)
}

linesData := bytes.Split(data, separator)
for _, ld := range linesData {
if !rx.Match(ld) {
return true
}
}

return false
},
func(filePath string, options options) rule.Violation {
format := "file '%s' does not have content matching regex '%s'"

if options.matchSingleLines {
format = "file '%s' does not have all lines matching regex '%s'"
}

if options.negated {
format = "file '%s' does have content matching regex '%s'"
}

if options.negated && options.matchSingleLines {
format = "file '%s' does have all lines matching regex '%s'"
}

return rule.NewViolation(
fmt.Sprintf(format, filepath.Base(filePath), res),
)
},
opts...,
)
}
Loading

0 comments on commit c3e0c1d

Please sign in to comment.