Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for parent/child TypeMatchers #86

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
10 changes: 4 additions & 6 deletions filetype.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,19 +43,17 @@ func IsExtension(buf []byte, ext string) bool {

// IsType checks if a given buffer matches with the given file type
func IsType(buf []byte, kind types.Type) bool {
matcher := matchers.Matchers[kind]
if matcher == nil {
return false
if matcher, ok := matchers.Matchers[kind]; ok {
return matcher.Match(buf)
}
return matcher(buf) != types.Unknown
return false
}

// IsMIME checks if a given buffer matches with the given MIME type
func IsMIME(buf []byte, mime string) bool {
for _, kind := range types.Types {
if kind.MIME.Value == mime {
matcher := matchers.Matchers[kind]
return matcher(buf) != types.Unknown
return IsType(buf, kind)
}
}
return false
Expand Down
7 changes: 3 additions & 4 deletions match.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,12 @@ func Match(buf []byte) (types.Type, error) {
}

for _, kind := range *MatcherKeys {
checker := Matchers[kind]
match := checker(buf)
matcher := Matchers[kind]
match := matcher.Type(buf)
if match != types.Unknown && match.Extension != "" {
return match, nil
}
}

return types.Unknown, nil
}

Expand Down Expand Up @@ -64,7 +63,7 @@ func MatchReader(reader io.Reader) (types.Type, error) {
}

// AddMatcher registers a new matcher type
func AddMatcher(fileType types.Type, matcher matchers.Matcher) matchers.TypeMatcher {
func AddMatcher(fileType types.Type, matcher matchers.ByteMatcher) matchers.TypeMatcher {
return matchers.NewMatcher(fileType, matcher)
}

Expand Down
36 changes: 36 additions & 0 deletions match_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,42 @@ func TestAddMatcher(t *testing.T) {
}
}

func TestAddChild(t *testing.T) {
parentType := AddType("fooparent", "foo/parent")
parentFn := func(buf []byte) bool {
return len(buf) >= 2 && buf[0] == 0x00 && buf[1] == 0x00
}
parentMatcher := AddMatcher(parentType, parentFn)

childType := AddType("foochild", "foo/child")
childFn := func(buf []byte) bool {
return len(buf) > 2 &&
buf[0] == 0x00 && buf[1] == 0x00 && buf[2] == 0x00
}
parentMatcher.AddChild(childType, childFn)

if !Is([]byte{0x00, 0x00}, "fooparent") {
t.Fatalf("Parent cannot match")
}

if !Is([]byte{0x00, 0x00, 0x00}, "foochild") {
t.Fatalf("Child cannot match")
}

if !Is([]byte{0x00, 0x00, 0x00}, "fooparent") {
t.Fatalf("Parent does not match child")
}

if !IsSupported("foochild") {
t.Fatalf("Not supported extension")
}

if !IsMIMESupported("foo/child") {
t.Fatalf("Not supported MIME type")
}

}

func TestMatchMap(t *testing.T) {
cases := []struct {
buf []byte
Expand Down
2 changes: 1 addition & 1 deletion matchers/archive.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ var (
)

var Archive = Map{
TypeEpub: Epub,
TypeZip: Zip,
TypeEpub: ChildMatcher(TypeZip, TypeEpub, Epub),
TypeTar: Tar,
TypeRar: Rar,
TypeGz: Gz,
Expand Down
20 changes: 20 additions & 0 deletions matchers/children.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package matchers

import (
"github.com/h2non/filetype/types"
)

// ChildMatchers stores any registered children for a given type
var ChildMatchers = make(map[types.Type][]TypeMatcher)

// ChildMatcher creates a TypeMatcher as a child of the parent Type
func ChildMatcher(parent types.Type, kind types.Type, fn ByteMatcher) ByteMatcher {
matcher := NewMatcher(kind, fn)
ChildMatchers[parent] = append(ChildMatchers[parent], matcher)
return fn
}

// AddChild creates and registers a new child for a TypeMatcher
func (m TypeMatcher) AddChild(kind types.Type, fn ByteMatcher) {
_ = ChildMatcher(m.myType, kind, fn)
}
67 changes: 51 additions & 16 deletions matchers/matchers.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,32 +7,67 @@ import (
// Internal shortcut to NewType
var newType = types.NewType

// Matcher function interface as type alias
type Matcher func([]byte) bool
type Matcher interface {
Match([]byte) bool
}

// Type interface to store pairs of type with its matcher function
type Map map[types.Type]Matcher
type Typer interface {
Type([]byte) types.Type
}

// Type specific matcher function interface
type TypeMatcher func([]byte) types.Type
// ByteMatcher function interface as type alias
type ByteMatcher func([]byte) bool

// Implement Matcher interface for ByteMatcher
func (b ByteMatcher) Match(buf []byte) bool {
return b(buf)
}

// A TypeMatcher is both a Typer and Matcher
type TypeMatcher struct {
myType types.Type
matcher ByteMatcher
}

// Implement Matcher interface for TypeMatcher
func (m TypeMatcher) Match(buf []byte) bool {
// Check any matchers for child types
for _, c := range ChildMatchers[m.myType] {
if c.matcher.Match(buf) {
return true
}
}
return m.matcher.Match(buf)
}

// Implement Typer interface for TypeMatcher
func (m TypeMatcher) Type(buf []byte) types.Type {
// Return a matching child type, if any
for _, c := range ChildMatchers[m.myType] {
if c.matcher.Match(buf) {
return c.myType
}
}
if m.matcher.Match(buf) {
return m.myType
}
return types.Unknown
}

// Store registered file type matchers
var Matchers = make(map[types.Type]TypeMatcher)
var MatcherKeys []types.Type

// Create and register a new type matcher function
func NewMatcher(kind types.Type, fn Matcher) TypeMatcher {
matcher := func(buf []byte) types.Type {
if fn(buf) {
return kind
}
return types.Unknown
}
// Type interface to store pairs of type with its matcher
type Map map[types.Type]ByteMatcher

Matchers[kind] = matcher
// Create and register a new type matcher
func NewMatcher(kind types.Type, fn ByteMatcher) TypeMatcher {
m := TypeMatcher{kind, fn}
Matchers[kind] = m
// prepend here so any user defined matchers get added first
MatcherKeys = append([]types.Type{kind}, MatcherKeys...)
return matcher
return m
}

func register(matchers ...Map) {
Expand Down