diff --git a/filetype.go b/filetype.go index 933058c..57803f6 100644 --- a/filetype.go +++ b/filetype.go @@ -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 diff --git a/match.go b/match.go index 82cf804..f446fba 100644 --- a/match.go +++ b/match.go @@ -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 } @@ -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) } diff --git a/match_test.go b/match_test.go index fb99563..745e316 100644 --- a/match_test.go +++ b/match_test.go @@ -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 diff --git a/matchers/archive.go b/matchers/archive.go index d801005..43ffe05 100644 --- a/matchers/archive.go +++ b/matchers/archive.go @@ -30,8 +30,8 @@ var ( ) var Archive = Map{ - TypeEpub: Epub, TypeZip: Zip, + TypeEpub: ChildMatcher(TypeZip, TypeEpub, Epub), TypeTar: Tar, TypeRar: Rar, TypeGz: Gz, diff --git a/matchers/children.go b/matchers/children.go new file mode 100644 index 0000000..6729d28 --- /dev/null +++ b/matchers/children.go @@ -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) +} diff --git a/matchers/matchers.go b/matchers/matchers.go index 20d74d0..1827fe3 100644 --- a/matchers/matchers.go +++ b/matchers/matchers.go @@ -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) {