diff --git a/README.md b/README.md index 1a57b3eb..33efd306 100644 --- a/README.md +++ b/README.md @@ -287,6 +287,82 @@ SELECT str_split('hello,world', ',', 1) -- +----------------------------------+ ``` +#### Enry Functions + +Functions from the [`enry` project](https://github.com/go-enry/go-enry) are also available as SQL scalar functions + +##### `enry_detect_language` + +Supply a file path and some source code to detect the language. + +```sql +SELECT enry_detect_language('some/path/to/file.go', '') +``` + +##### `enry_is_binary` + +Given a blob, determine if it's a binary file or not (returns 1 or 0). + +```sql +SELECT enry_is_binary('') +``` + +##### `enry_is_configuration` + +Detect whether a file path is to a configuration file (returns 1 or 0). + +```sql +SELECT enry_is_configuration('some/path/to/file/config.json') +``` + +##### `enry_is_documentation` + +Detect whether a file path is to a documentation file (returns 1 or 0). + +```sql +SELECT enry_is_documentation('some/path/to/file/README.md') +``` + +##### `enry_is_dot_file` + +Detect whether a file path is to a dot file (returns 1 or 0). + +```sql +SELECT enry_is_dot_file('some/path/to/file/.gitignore') +``` + +##### `enry_is_generated` + +Detect whether a file path is generated (returns 1 or 0). + +```sql +SELECT enry_is_generated('some/path/to/file/generated.go', '') +``` + +##### `enry_is_image` + +Detect whether a file path is to an image (returns 1 or 0). + +```sql +SELECT enry_is_image('some/path/to/file/image.png') +``` + +##### `enry_is_test` + +Detect whether a file path is to a test file (returns 1 or 0). + +```sql +SELECT enry_is_test('some/path/to/file/image.png') +``` + +##### `enry_is_vendor` + +Detect whether a file path is to a vendored file (returns 1 or 0). + +```sql +SELECT enry_is_vendor('vendor/file.go') +``` + ### Example Queries diff --git a/go.mod b/go.mod index 014ed7ab..1e75c779 100644 --- a/go.mod +++ b/go.mod @@ -11,6 +11,7 @@ require ( github.com/augmentable-dev/vtab v0.0.0-20210717200339-0c8dcfe2033b github.com/clbanning/mxj/v2 v2.5.5 github.com/ghodss/yaml v1.0.0 + github.com/go-enry/go-enry/v2 v2.7.1 github.com/go-git/go-billy/v5 v5.3.1 github.com/go-git/go-git/v5 v5.4.2 github.com/go-openapi/errors v0.20.0 // indirect diff --git a/go.sum b/go.sum index 38fbef63..8d63abd4 100644 --- a/go.sum +++ b/go.sum @@ -101,6 +101,10 @@ github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/gliderlabs/ssh v0.2.2 h1:6zsha5zo/TWhRhwqCD3+EarCAgZ2yN28ipRnGPnwkI0= github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= +github.com/go-enry/go-enry/v2 v2.7.1 h1:WCqtfyteIz61GYk9lRVy8HblvIv4cP9GIiwm/6txCbU= +github.com/go-enry/go-enry/v2 v2.7.1/go.mod h1:GVzIiAytiS5uT/QiuakK7TF1u4xDab87Y8V5EJRpsIQ= +github.com/go-enry/go-oniguruma v1.2.1 h1:k8aAMuJfMrqm/56SG2lV9Cfti6tC4x8673aHCcBk+eo= +github.com/go-enry/go-oniguruma v1.2.1/go.mod h1:bWDhYP+S6xZQgiRL7wlTScFYBe023B6ilRZbCAD5Hf4= github.com/go-git/gcfg v1.5.0 h1:Q5ViNfGF8zFgyJWPqYwA7qGFoMTEiBmdlkcfRmpIMa4= github.com/go-git/gcfg v1.5.0/go.mod h1:5m20vg6GwYabIxaOonVkTdrILxQMpEShl1xiMF4ua+E= github.com/go-git/go-billy/v5 v5.2.0/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0= diff --git a/tables/internal/funcs/enry_detect_language.go b/tables/internal/funcs/enry_detect_language.go new file mode 100644 index 00000000..5a93fd5d --- /dev/null +++ b/tables/internal/funcs/enry_detect_language.go @@ -0,0 +1,19 @@ +package funcs + +import ( + "github.com/go-enry/go-enry/v2" + "go.riyazali.net/sqlite" +) + +type EnryDetectLanguage struct{} + +func (f *EnryDetectLanguage) Args() int { return 2 } +func (f *EnryDetectLanguage) Deterministic() bool { return true } +func (f *EnryDetectLanguage) Apply(context *sqlite.Context, value ...sqlite.Value) { + if lang := enry.GetLanguage(value[0].Text(), value[1].Blob()); lang == "" { + context.ResultNull() + return + } else { + context.ResultText(lang) + } +} diff --git a/tables/internal/funcs/enry_detect_language_test.go b/tables/internal/funcs/enry_detect_language_test.go new file mode 100644 index 00000000..f5d3f498 --- /dev/null +++ b/tables/internal/funcs/enry_detect_language_test.go @@ -0,0 +1,29 @@ +package funcs + +import ( + "io/ioutil" + "testing" + + "github.com/askgitdev/askgit/tables/internal/tools" +) + +func TestEnryDetectLanguage(t *testing.T) { + path := "./testdata/main.go" + fileContents, err := ioutil.ReadFile(path) + if err != nil { + t.Fatal(err) + } + rows, err := FixtureDatabase.Query("SELECT enry_detect_language(?,?)", path, fileContents) + if err != nil { + t.Fatal(err) + } + + rowNum, contents, err := tools.RowContent(rows) + if err != nil { + t.Fatalf("err %d at row %d", err, rowNum) + } + + if contents[0][0] != "Go" { + t.Fatalf("expected string: %s, got %s", "Go", contents[0][0]) + } +} diff --git a/tables/internal/funcs/enry_is_binary.go b/tables/internal/funcs/enry_is_binary.go new file mode 100644 index 00000000..a7bcc999 --- /dev/null +++ b/tables/internal/funcs/enry_is_binary.go @@ -0,0 +1,18 @@ +package funcs + +import ( + "github.com/go-enry/go-enry/v2" + "go.riyazali.net/sqlite" +) + +type EnryIsBinary struct{} + +func (f *EnryIsBinary) Args() int { return 1 } +func (f *EnryIsBinary) Deterministic() bool { return true } +func (f *EnryIsBinary) Apply(context *sqlite.Context, value ...sqlite.Value) { + if enry.IsBinary(value[0].Blob()) { + context.ResultInt(1) + } else { + context.ResultInt(0) + } +} diff --git a/tables/internal/funcs/enry_is_binary_test.go b/tables/internal/funcs/enry_is_binary_test.go new file mode 100644 index 00000000..1e5472ed --- /dev/null +++ b/tables/internal/funcs/enry_is_binary_test.go @@ -0,0 +1,29 @@ +package funcs + +import ( + "io/ioutil" + "testing" + + "github.com/askgitdev/askgit/tables/internal/tools" +) + +func TestEnryIsBinary(t *testing.T) { + path := "./testdata/binary" + fileContents, err := ioutil.ReadFile(path) + if err != nil { + t.Fatal(err) + } + rows, err := FixtureDatabase.Query("SELECT enry_is_binary(?)", fileContents) + if err != nil { + t.Fatal(err) + } + + rowNum, contents, err := tools.RowContent(rows) + if err != nil { + t.Fatalf("err %d at row %d", err, rowNum) + } + + if contents[0][0] != "1" { + t.Fatalf("expected string: %s, got %s", "1", contents[0][0]) + } +} diff --git a/tables/internal/funcs/enry_is_configuration.go b/tables/internal/funcs/enry_is_configuration.go new file mode 100644 index 00000000..4814b0ad --- /dev/null +++ b/tables/internal/funcs/enry_is_configuration.go @@ -0,0 +1,18 @@ +package funcs + +import ( + "github.com/go-enry/go-enry/v2" + "go.riyazali.net/sqlite" +) + +type EnryIsConfiguration struct{} + +func (f *EnryIsConfiguration) Args() int { return 1 } +func (f *EnryIsConfiguration) Deterministic() bool { return true } +func (f *EnryIsConfiguration) Apply(context *sqlite.Context, value ...sqlite.Value) { + if enry.IsConfiguration(value[0].Text()) { + context.ResultInt(1) + } else { + context.ResultInt(0) + } +} diff --git a/tables/internal/funcs/enry_is_configuration_test.go b/tables/internal/funcs/enry_is_configuration_test.go new file mode 100644 index 00000000..7c008fc8 --- /dev/null +++ b/tables/internal/funcs/enry_is_configuration_test.go @@ -0,0 +1,24 @@ +package funcs + +import ( + "testing" + + "github.com/askgitdev/askgit/tables/internal/tools" +) + +func TestEnryIsConfiguration(t *testing.T) { + path := "./testdata/configuration.json" // from here -> https://github.com/go-enry/go-enry/tree/master/_testdata + rows, err := FixtureDatabase.Query("SELECT enry_is_configuration(?)", path) + if err != nil { + t.Fatal(err) + } + + rowNum, contents, err := tools.RowContent(rows) + if err != nil { + t.Fatalf("err %d at row %d", err, rowNum) + } + + if contents[0][0] != "1" { + t.Fatalf("expected string: %s, got %s", "1", contents[0][0]) + } +} diff --git a/tables/internal/funcs/enry_is_documentation.go b/tables/internal/funcs/enry_is_documentation.go new file mode 100644 index 00000000..344b65a5 --- /dev/null +++ b/tables/internal/funcs/enry_is_documentation.go @@ -0,0 +1,18 @@ +package funcs + +import ( + "github.com/go-enry/go-enry/v2" + "go.riyazali.net/sqlite" +) + +type EnryIsDocumentation struct{} + +func (f *EnryIsDocumentation) Args() int { return 1 } +func (f *EnryIsDocumentation) Deterministic() bool { return true } +func (f *EnryIsDocumentation) Apply(context *sqlite.Context, value ...sqlite.Value) { + if enry.IsDocumentation(value[0].Text()) { + context.ResultInt(1) + } else { + context.ResultInt(0) + } +} diff --git a/tables/internal/funcs/enry_is_documentation_test.go b/tables/internal/funcs/enry_is_documentation_test.go new file mode 100644 index 00000000..373f80c9 --- /dev/null +++ b/tables/internal/funcs/enry_is_documentation_test.go @@ -0,0 +1,24 @@ +package funcs + +import ( + "testing" + + "github.com/askgitdev/askgit/tables/internal/tools" +) + +func TestEnryIsDocumentation(t *testing.T) { + path := "./README.md" + rows, err := FixtureDatabase.Query("SELECT enry_is_documentation(?)", path) + if err != nil { + t.Fatal(err) + } + + rowNum, contents, err := tools.RowContent(rows) + if err != nil { + t.Fatalf("err %d at row %d", err, rowNum) + } + + if contents[0][0] != "1" { + t.Fatalf("expected string: %s, got %s", "1", contents[0][0]) + } +} diff --git a/tables/internal/funcs/enry_is_dot_file.go b/tables/internal/funcs/enry_is_dot_file.go new file mode 100644 index 00000000..75606e8e --- /dev/null +++ b/tables/internal/funcs/enry_is_dot_file.go @@ -0,0 +1,18 @@ +package funcs + +import ( + "github.com/go-enry/go-enry/v2" + "go.riyazali.net/sqlite" +) + +type EnryIsDotFile struct{} + +func (f *EnryIsDotFile) Args() int { return 1 } +func (f *EnryIsDotFile) Deterministic() bool { return true } +func (f *EnryIsDotFile) Apply(context *sqlite.Context, value ...sqlite.Value) { + if enry.IsDotFile(value[0].Text()) { + context.ResultInt(1) + } else { + context.ResultInt(0) + } +} diff --git a/tables/internal/funcs/enry_is_dot_file_test.go b/tables/internal/funcs/enry_is_dot_file_test.go new file mode 100644 index 00000000..80bcb8e9 --- /dev/null +++ b/tables/internal/funcs/enry_is_dot_file_test.go @@ -0,0 +1,24 @@ +package funcs + +import ( + "testing" + + "github.com/askgitdev/askgit/tables/internal/tools" +) + +func TestEnryIsDotFile(t *testing.T) { + path := "./testdata/.hidden" + rows, err := FixtureDatabase.Query("SELECT enry_is_dot_file(?)", path) + if err != nil { + t.Fatal(err) + } + + rowNum, contents, err := tools.RowContent(rows) + if err != nil { + t.Fatalf("err %d at row %d", err, rowNum) + } + + if contents[0][0] != "1" { + t.Fatalf("expected string: %s, got %s", "1", contents[0][0]) + } +} diff --git a/tables/internal/funcs/enry_is_generated.go b/tables/internal/funcs/enry_is_generated.go new file mode 100644 index 00000000..b5b45795 --- /dev/null +++ b/tables/internal/funcs/enry_is_generated.go @@ -0,0 +1,18 @@ +package funcs + +import ( + "github.com/go-enry/go-enry/v2" + "go.riyazali.net/sqlite" +) + +type EnryIsGenerated struct{} + +func (f *EnryIsGenerated) Args() int { return 2 } +func (f *EnryIsGenerated) Deterministic() bool { return true } +func (f *EnryIsGenerated) Apply(context *sqlite.Context, value ...sqlite.Value) { + if enry.IsGenerated(value[0].Text(), value[1].Blob()) { + context.ResultInt(1) + } else { + context.ResultInt(0) + } +} diff --git a/tables/internal/funcs/enry_is_generated_test.go b/tables/internal/funcs/enry_is_generated_test.go new file mode 100644 index 00000000..372128f4 --- /dev/null +++ b/tables/internal/funcs/enry_is_generated_test.go @@ -0,0 +1,24 @@ +package funcs + +import ( + "testing" + + "github.com/askgitdev/askgit/tables/internal/tools" +) + +func TestEnryIsGenerated(t *testing.T) { + path := ".xcworkspacedata" + rows, err := FixtureDatabase.Query("SELECT enry_is_generated(?, ?)", path, "") + if err != nil { + t.Fatal(err) + } + + rowNum, contents, err := tools.RowContent(rows) + if err != nil { + t.Fatalf("err %d at row %d", err, rowNum) + } + + if contents[0][0] != "1" { + t.Fatalf("expected string: %s, got %s", "1", contents[0][0]) + } +} diff --git a/tables/internal/funcs/enry_is_image.go b/tables/internal/funcs/enry_is_image.go new file mode 100644 index 00000000..0d35677e --- /dev/null +++ b/tables/internal/funcs/enry_is_image.go @@ -0,0 +1,18 @@ +package funcs + +import ( + "github.com/go-enry/go-enry/v2" + "go.riyazali.net/sqlite" +) + +type EnryIsImage struct{} + +func (f *EnryIsImage) Args() int { return 1 } +func (f *EnryIsImage) Deterministic() bool { return true } +func (f *EnryIsImage) Apply(context *sqlite.Context, value ...sqlite.Value) { + if enry.IsImage(value[0].Text()) { + context.ResultInt(1) + } else { + context.ResultInt(0) + } +} diff --git a/tables/internal/funcs/enry_is_image_test.go b/tables/internal/funcs/enry_is_image_test.go new file mode 100644 index 00000000..deb42447 --- /dev/null +++ b/tables/internal/funcs/enry_is_image_test.go @@ -0,0 +1,24 @@ +package funcs + +import ( + "testing" + + "github.com/askgitdev/askgit/tables/internal/tools" +) + +func TestEnryIsImage(t *testing.T) { + path := "./testdata/logo.png" + rows, err := FixtureDatabase.Query("SELECT enry_is_image(?)", path) + if err != nil { + t.Fatal(err) + } + + rowNum, contents, err := tools.RowContent(rows) + if err != nil { + t.Fatalf("err %d at row %d", err, rowNum) + } + + if contents[0][0] != "1" { + t.Fatalf("expected string: %s, got %s", "1", contents[0][0]) + } +} diff --git a/tables/internal/funcs/enry_is_test_file.go b/tables/internal/funcs/enry_is_test_file.go new file mode 100644 index 00000000..bba250d4 --- /dev/null +++ b/tables/internal/funcs/enry_is_test_file.go @@ -0,0 +1,18 @@ +package funcs + +import ( + "github.com/go-enry/go-enry/v2" + "go.riyazali.net/sqlite" +) + +type EnryIsTest struct{} + +func (f *EnryIsTest) Args() int { return 1 } +func (f *EnryIsTest) Deterministic() bool { return true } +func (f *EnryIsTest) Apply(context *sqlite.Context, value ...sqlite.Value) { + if enry.IsTest(value[0].Text()) { + context.ResultInt(1) + } else { + context.ResultInt(0) + } +} diff --git a/tables/internal/funcs/enry_is_test_file_test.go b/tables/internal/funcs/enry_is_test_file_test.go new file mode 100644 index 00000000..fa660bb8 --- /dev/null +++ b/tables/internal/funcs/enry_is_test_file_test.go @@ -0,0 +1,24 @@ +package funcs + +import ( + "testing" + + "github.com/askgitdev/askgit/tables/internal/tools" +) + +func TestEnryIsTest(t *testing.T) { + path := "./scripts_test.go" + rows, err := FixtureDatabase.Query("SELECT enry_is_test(?)", path) + if err != nil { + t.Fatal(err) + } + + rowNum, contents, err := tools.RowContent(rows) + if err != nil { + t.Fatalf("err %d at row %d", err, rowNum) + } + + if contents[0][0] != "1" { + t.Fatalf("expected string: %s, got %s", "1", contents[0][0]) + } +} diff --git a/tables/internal/funcs/enry_is_vendor.go b/tables/internal/funcs/enry_is_vendor.go new file mode 100644 index 00000000..4b65b94b --- /dev/null +++ b/tables/internal/funcs/enry_is_vendor.go @@ -0,0 +1,18 @@ +package funcs + +import ( + "github.com/go-enry/go-enry/v2" + "go.riyazali.net/sqlite" +) + +type EnryIsVendor struct{} + +func (f *EnryIsVendor) Args() int { return 1 } +func (f *EnryIsVendor) Deterministic() bool { return true } +func (f *EnryIsVendor) Apply(context *sqlite.Context, value ...sqlite.Value) { + if enry.IsVendor(value[0].Text()) { + context.ResultInt(1) + } else { + context.ResultInt(0) + } +} diff --git a/tables/internal/funcs/enry_is_vendor_test.go b/tables/internal/funcs/enry_is_vendor_test.go new file mode 100644 index 00000000..b4e57d14 --- /dev/null +++ b/tables/internal/funcs/enry_is_vendor_test.go @@ -0,0 +1,24 @@ +package funcs + +import ( + "testing" + + "github.com/askgitdev/askgit/tables/internal/tools" +) + +func TestEnryIsVendor(t *testing.T) { + path := "./testdata/node_modules/data" + rows, err := FixtureDatabase.Query("SELECT enry_is_vendor(?)", path) + if err != nil { + t.Fatal(err) + } + + rowNum, contents, err := tools.RowContent(rows) + if err != nil { + t.Fatalf("err %d at row %d", err, rowNum) + } + + if contents[0][0] != "1" { + t.Fatalf("expected string: %s, got %s", "1", contents[0][0]) + } +} diff --git a/tables/internal/funcs/func_test.go b/tables/internal/funcs/func_test.go index b3630777..b40b83ed 100644 --- a/tables/internal/funcs/func_test.go +++ b/tables/internal/funcs/func_test.go @@ -2,12 +2,13 @@ package funcs import ( "database/sql" - _ "github.com/askgitdev/askgit/pkg/sqlite" - "github.com/pkg/errors" - "go.riyazali.net/sqlite" "log" "os" "testing" + + _ "github.com/askgitdev/askgit/pkg/sqlite" + "github.com/pkg/errors" + "go.riyazali.net/sqlite" ) // FixtureDatabase represents the database connection to run the test against @@ -17,10 +18,19 @@ func init() { // register sqlite extension when this package is loaded sqlite.Register(func(ext *sqlite.ExtensionApi) (_ sqlite.ErrorCode, err error) { var fns = map[string]sqlite.Function{ - "str_split": &StringSplit{}, - "toml_to_json": &TomlToJson{}, - "yaml_to_json": &YamlToJson{}, - "xml_to_json": &XmlToJson{}, + "str_split": &StringSplit{}, + "toml_to_json": &TomlToJson{}, + "yaml_to_json": &YamlToJson{}, + "xml_to_json": &XmlToJson{}, + "enry_detect_language": &EnryDetectLanguage{}, + "enry_is_binary": &EnryIsBinary{}, + "enry_is_configuration": &EnryIsConfiguration{}, + "enry_is_documentation": &EnryIsDocumentation{}, + "enry_is_dot_file": &EnryIsDotFile{}, + "enry_is_generated": &EnryIsGenerated{}, + "enry_is_image": &EnryIsImage{}, + "enry_is_test": &EnryIsTest{}, + "enry_is_vendor": &EnryIsVendor{}, } // alias yaml_to_json => yml_to_json diff --git a/tables/internal/funcs/testdata/binary b/tables/internal/funcs/testdata/binary new file mode 100644 index 00000000..6e343340 Binary files /dev/null and b/tables/internal/funcs/testdata/binary differ diff --git a/tables/internal/funcs/testdata/main.go b/tables/internal/funcs/testdata/main.go new file mode 100644 index 00000000..79058077 --- /dev/null +++ b/tables/internal/funcs/testdata/main.go @@ -0,0 +1,5 @@ +package main + +func main() { + +} diff --git a/tables/tables.go b/tables/tables.go index 769a59c0..cd519f13 100644 --- a/tables/tables.go +++ b/tables/tables.go @@ -58,10 +58,19 @@ func RegisterFn(fns ...OptionFn) func(ext *sqlite.ExtensionApi) (_ sqlite.ErrorC if opt.ExtraFunctions { // register sql functions var fns = map[string]sqlite.Function{ - "str_split": &funcs.StringSplit{}, - "toml_to_json": &funcs.TomlToJson{}, - "yaml_to_json": &funcs.YamlToJson{}, - "xml_to_json": &funcs.XmlToJson{}, + "str_split": &funcs.StringSplit{}, + "toml_to_json": &funcs.TomlToJson{}, + "yaml_to_json": &funcs.YamlToJson{}, + "xml_to_json": &funcs.XmlToJson{}, + "enry_detect_language": &funcs.EnryDetectLanguage{}, + "enry_is_binary": &funcs.EnryIsBinary{}, + "enry_is_configuration": &funcs.EnryIsConfiguration{}, + "enry_is_documentation": &funcs.EnryIsDocumentation{}, + "enry_is_dot_file": &funcs.EnryIsDotFile{}, + "enry_is_generated": &funcs.EnryIsGenerated{}, + "enry_is_image": &funcs.EnryIsImage{}, + "enry_is_test": &funcs.EnryIsTest{}, + "enry_is_vendor": &funcs.EnryIsVendor{}, } // alias yaml_to_json => yml_to_json