diff --git a/detect/detect.go b/detect/detect.go index 0cd163b..bbf990d 100644 --- a/detect/detect.go +++ b/detect/detect.go @@ -597,7 +597,7 @@ func (e *Engine) recursiveGlob(pattern string) bool { suffix := parts[1] // e.g. "*.py" // Use the cached extension set for simple "**/*.ext" patterns - if strings.HasPrefix(suffix, "*.") { + if strings.HasPrefix(suffix, "*.") && strings.Count(suffix, ".") == 1 { ext := suffix[1:] // ".py" e.loadFileExts() return e.fileExts[ext] > 0 diff --git a/detect/research_tools_test.go b/detect/research_tools_test.go new file mode 100644 index 0000000..603666e --- /dev/null +++ b/detect/research_tools_test.go @@ -0,0 +1,149 @@ +package detect + +import "testing" + +func TestResearchToolsProject(t *testing.T) { + report := runOn(t, "../testdata/research-tools-project") + + assertToolDetected(t, report, "test", "nf-test") + assertToolDetected(t, report, "build", "nf-core") + assertToolDetected(t, report, "docs", "MultiQC") + assertToolDetected(t, report, "infrastructure", "Dockstore") + assertToolDetected(t, report, "infrastructure", "DVC") + assertToolDetected(t, report, "build", "cibuildwheel") + assertToolDetected(t, report, "docs", "MyST-Parser") + assertToolDetected(t, report, "test", "BenchmarkTools.jl") + assertToolDetected(t, report, "lint", "lintr") + assertToolDetected(t, report, "format", "styler") + assertToolDetected(t, report, "build", "targets") + assertToolDetected(t, report, "test", "vdiffr") + assertToolDetected(t, report, "test", "tinytest") +} + +func TestResearchToolSignals(t *testing.T) { + tests := []struct { + name string + files map[string]string + category string + tool string + }{ + { + name: "nf-test test file", + files: map[string]string{ + "nextflow.config": "nextflow.enable.dsl=2\n", + "tests/modules/fastqc/main.nf.test": "nextflow_process { script \"main.nf\" }\n", + }, + category: "test", + tool: "nf-test", + }, + { + name: "nf-core metadata", + files: map[string]string{ + ".nf-core.yml": "repository_type: pipeline\n", + }, + category: "build", + tool: "nf-core", + }, + { + name: "nested MultiQC config", + files: map[string]string{ + "assets/multiqc_config.yml": "title: Example analysis\n", + }, + category: "docs", + tool: "MultiQC", + }, + { + name: "Dockstore GitHub descriptor", + files: map[string]string{ + ".github/.dockstore.yml": "version: 1.2\nworkflows:\n - subclass: NFL\n primaryDescriptorPath: /nextflow.config\n", + }, + category: "infrastructure", + tool: "Dockstore", + }, + { + name: "DVC project config", + files: map[string]string{ + ".dvc/config": "[core]\n remote = storage\n", + }, + category: "infrastructure", + tool: "DVC", + }, + { + name: "cibuildwheel pyproject config", + files: map[string]string{ + "pyproject.toml": "[project]\nname = \"native-example\"\nversion = \"1.0.0\"\n\n[tool.cibuildwheel]\ntest-command = \"pytest {project}/tests\"\n", + }, + category: "build", + tool: "cibuildwheel", + }, + { + name: "MyST-Parser dependency", + files: map[string]string{ + "pyproject.toml": "[project]\nname = \"research-docs\"\nversion = \"1.0.0\"\ndependencies = [\"myst-parser>=4\"]\n", + }, + category: "docs", + tool: "MyST-Parser", + }, + { + name: "BenchmarkTools Julia dependency", + files: map[string]string{ + "Project.toml": "name = \"NumericalModel\"\nuuid = \"12345678-1234-1234-1234-123456789abc\"\nversion = \"0.1.0\"\n\n[deps]\nBenchmarkTools = \"6e4b80f9-dd63-53aa-95a3-0cdb28fa8baf\"\n", + }, + category: "test", + tool: "BenchmarkTools.jl", + }, + { + name: "lintr R dependency", + files: map[string]string{ + "DESCRIPTION": "Package: modeltools\nVersion: 1.0.0\nSuggests: lintr\n", + }, + category: "lint", + tool: "lintr", + }, + { + name: "styler R dependency", + files: map[string]string{ + "DESCRIPTION": "Package: modeltools\nVersion: 1.0.0\nSuggests: styler\n", + }, + category: "format", + tool: "styler", + }, + { + name: "targets pipeline", + files: map[string]string{ + "_targets.R": "library(targets)\nlist(tar_target(result, run_model()))\n", + }, + category: "build", + tool: "targets", + }, + { + name: "vdiffr R dependency", + files: map[string]string{ + "DESCRIPTION": "Package: plottools\nVersion: 1.0.0\nSuggests: vdiffr\n", + }, + category: "test", + tool: "vdiffr", + }, + { + name: "tinytest package runner", + files: map[string]string{ + "DESCRIPTION": "Package: modeltools\nVersion: 1.0.0\nSuggests: tinytest\n", + "tests/tinytest.R": "if (requireNamespace(\"tinytest\")) tinytest::test_package(\"modeltools\")\n", + }, + category: "test", + tool: "tinytest", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + dir := t.TempDir() + for path, content := range tt.files { + writeProjectFile(t, dir, path, content) + } + + report := runOn(t, dir) + assertToolDetected(t, report, tt.category, tt.tool) + }) + } +} diff --git a/knowledge/_shared/dockstore.toml b/knowledge/_shared/dockstore.toml new file mode 100644 index 0000000..ce82fa1 --- /dev/null +++ b/knowledge/_shared/dockstore.toml @@ -0,0 +1,20 @@ +[tool] +name = "Dockstore" +category = "infrastructure" +homepage = "https://dockstore.org" +docs = "https://docs.dockstore.org" +repo = "https://github.com/dockstore/dockstore" +description = "Registry for scientific tools and workflows" + +[detect] +files = [".dockstore.yml", ".dockstore.yaml", ".github/.dockstore.yml", ".github/.dockstore.yaml"] + +[config] +files = [".dockstore.yml", ".dockstore.yaml", ".github/.dockstore.yml", ".github/.dockstore.yaml"] + +[taxonomy] +role = ["cli-tool"] +layer = ["infrastructure"] +function = ["deployment"] +domain = ["scientific-computing"] +audience = ["researcher"] diff --git a/knowledge/_shared/dvc.toml b/knowledge/_shared/dvc.toml new file mode 100644 index 0000000..3fd78c5 --- /dev/null +++ b/knowledge/_shared/dvc.toml @@ -0,0 +1,25 @@ +[tool] +name = "DVC" +category = "infrastructure" +homepage = "https://dvc.org" +docs = "https://dvc.org/doc" +repo = "https://github.com/iterative/dvc" +description = "Version control for data and machine learning projects" + +[detect] +files = ["dvc.yaml", "dvc.lock", ".dvc/config", ".dvcignore", "*.dvc", "**/*.dvc"] +dependencies = ["dvc"] + +[commands] +run = "dvc pull" +alternatives = ["dvc repro", "dvc status"] + +[config] +files = ["dvc.yaml", "dvc.lock", ".dvc/config", ".dvcignore"] +lockfile = "dvc.lock" + +[taxonomy] +role = ["cli-tool"] +function = ["file-management"] +domain = ["data-science"] +audience = ["data-scientist", "researcher"] diff --git a/knowledge/_shared/multiqc.toml b/knowledge/_shared/multiqc.toml new file mode 100644 index 0000000..75e631b --- /dev/null +++ b/knowledge/_shared/multiqc.toml @@ -0,0 +1,23 @@ +[tool] +name = "MultiQC" +category = "docs" +homepage = "https://multiqc.info" +docs = "https://docs.seqera.io/multiqc" +repo = "https://github.com/MultiQC/MultiQC" +description = "Aggregate reports for bioinformatics analyses" + +[detect] +files = ["multiqc_config.yml", "multiqc_config.yaml", ".multiqc_config.yml", ".multiqc_config.yaml", "**/multiqc_config.yml", "**/multiqc_config.yaml"] +dependencies = ["multiqc"] + +[commands] +run = "multiqc ." + +[config] +files = ["multiqc_config.yml", "multiqc_config.yaml", ".multiqc_config.yml", ".multiqc_config.yaml", "**/multiqc_config.yml", "**/multiqc_config.yaml"] + +[taxonomy] +role = ["cli-tool"] +function = ["visualization"] +domain = ["scientific-computing"] +audience = ["researcher"] diff --git a/knowledge/_shared/nf-core.toml b/knowledge/_shared/nf-core.toml new file mode 100644 index 0000000..cbbccfb --- /dev/null +++ b/knowledge/_shared/nf-core.toml @@ -0,0 +1,24 @@ +[tool] +name = "nf-core" +category = "build" +homepage = "https://nf-co.re" +docs = "https://nf-co.re/docs/nf-core-tools" +repo = "https://github.com/nf-core/tools" +description = "Tools for creating and maintaining nf-core Nextflow pipelines" + +[detect] +files = [".nf-core.yml", ".nf-core.yaml"] +dependencies = ["nf-core"] + +[commands] +run = "nf-core pipelines lint" +alternatives = ["nf-core pipelines schema lint", "nf-core pipelines create"] + +[config] +files = [".nf-core.yml", ".nf-core.yaml"] + +[taxonomy] +role = ["build-tool"] +function = ["automation"] +domain = ["scientific-computing"] +audience = ["researcher"] diff --git a/knowledge/_shared/nf-test.toml b/knowledge/_shared/nf-test.toml new file mode 100644 index 0000000..fb4bc5e --- /dev/null +++ b/knowledge/_shared/nf-test.toml @@ -0,0 +1,23 @@ +[tool] +name = "nf-test" +category = "test" +homepage = "https://www.nf-test.com" +docs = "https://www.nf-test.com/docs/getting-started/" +repo = "https://github.com/askimed/nf-test" +description = "Testing tool for Nextflow pipelines" + +[detect] +files = ["nf-test.config", "**/*.nf.test", "**/*.nf.test.snap"] + +[commands] +run = "nf-test test" +alternatives = ["nf-test list", "nf-test status"] + +[config] +files = ["nf-test.config"] + +[taxonomy] +role = ["testing-framework"] +function = ["testing"] +domain = ["scientific-computing"] +audience = ["researcher"] diff --git a/knowledge/julia/benchmarktools.toml b/knowledge/julia/benchmarktools.toml new file mode 100644 index 0000000..6d955f0 --- /dev/null +++ b/knowledge/julia/benchmarktools.toml @@ -0,0 +1,21 @@ +[tool] +name = "BenchmarkTools.jl" +category = "test" +homepage = "https://juliaci.github.io/BenchmarkTools.jl" +docs = "https://juliaci.github.io/BenchmarkTools.jl/stable/manual/" +repo = "https://github.com/JuliaCI/BenchmarkTools.jl" +description = "Benchmarking and performance tracking for Julia" + +[detect] +dependencies = ["BenchmarkTools"] +dev_dependencies = ["BenchmarkTools"] +ecosystems = ["julia"] + +[detect.key_exists] +"Project.toml" = ["deps.BenchmarkTools", "extras.BenchmarkTools", "weakdeps.BenchmarkTools"] + +[taxonomy] +role = ["testing-framework"] +function = ["benchmarking"] +domain = ["scientific-computing"] +audience = ["researcher"] diff --git a/knowledge/python/cibuildwheel.toml b/knowledge/python/cibuildwheel.toml new file mode 100644 index 0000000..85b3c23 --- /dev/null +++ b/knowledge/python/cibuildwheel.toml @@ -0,0 +1,25 @@ +[tool] +name = "cibuildwheel" +category = "build" +homepage = "https://cibuildwheel.pypa.io" +docs = "https://cibuildwheel.pypa.io/en/stable/" +repo = "https://github.com/pypa/cibuildwheel" +description = "Build and test Python wheels across supported platforms" + +[detect] +dependencies = ["cibuildwheel"] +dev_dependencies = ["cibuildwheel"] +ecosystems = ["python"] + +[detect.key_exists] +"pyproject.toml" = ["tool.cibuildwheel"] + +[commands] +run = "cibuildwheel" +alternatives = ["python -m cibuildwheel"] + +[config] +files = ["pyproject.toml"] + +[taxonomy] +role = ["build-tool"] diff --git a/knowledge/python/myst-parser.toml b/knowledge/python/myst-parser.toml new file mode 100644 index 0000000..80aea3c --- /dev/null +++ b/knowledge/python/myst-parser.toml @@ -0,0 +1,20 @@ +[tool] +name = "MyST-Parser" +category = "docs" +homepage = "https://myst-parser.readthedocs.io" +docs = "https://myst-parser.readthedocs.io/en/latest/" +repo = "https://github.com/executablebooks/MyST-Parser" +description = "MyST Markdown parser and Sphinx extension" + +[detect] +dependencies = ["myst-parser"] +dev_dependencies = ["myst-parser"] +ecosystems = ["python"] + +[detect.file_contains] +"conf.py" = ["myst_parser"] +"**/conf.py" = ["myst_parser"] + +[taxonomy] +role = ["library"] +function = ["documentation"] diff --git a/knowledge/r/lintr.toml b/knowledge/r/lintr.toml new file mode 100644 index 0000000..aa170b3 --- /dev/null +++ b/knowledge/r/lintr.toml @@ -0,0 +1,23 @@ +[tool] +name = "lintr" +category = "lint" +homepage = "https://lintr.r-lib.org" +docs = "https://lintr.r-lib.org/reference/" +repo = "https://github.com/r-lib/lintr" +description = "Static analysis and style checks for R" + +[detect] +files = [".lintr", ".lintr.R"] +dependencies = ["lintr"] +dev_dependencies = ["lintr"] +ecosystems = ["r"] + +[commands] +run = "Rscript -e 'lintr::lint_dir()'" +alternatives = ["Rscript -e 'lintr::lint_package()'"] + +[config] +files = [".lintr", ".lintr.R"] + +[taxonomy] +role = ["linter"] diff --git a/knowledge/r/styler.toml b/knowledge/r/styler.toml new file mode 100644 index 0000000..7a9646d --- /dev/null +++ b/knowledge/r/styler.toml @@ -0,0 +1,19 @@ +[tool] +name = "styler" +category = "format" +homepage = "https://styler.r-lib.org" +docs = "https://styler.r-lib.org/reference/" +repo = "https://github.com/r-lib/styler" +description = "Source code formatter for R" + +[detect] +dependencies = ["styler"] +dev_dependencies = ["styler"] +ecosystems = ["r"] + +[commands] +run = "Rscript -e 'styler::style_pkg()'" +alternatives = ["Rscript -e 'styler::style_dir()'"] + +[taxonomy] +role = ["formatter"] diff --git a/knowledge/r/targets.toml b/knowledge/r/targets.toml new file mode 100644 index 0000000..32c11df --- /dev/null +++ b/knowledge/r/targets.toml @@ -0,0 +1,26 @@ +[tool] +name = "targets" +category = "build" +homepage = "https://docs.ropensci.org/targets/" +docs = "https://books.ropensci.org/targets/" +repo = "https://github.com/ropensci/targets" +description = "Pipeline tool for reproducible R analyses" + +[detect] +files = ["_targets.R"] +dependencies = ["targets"] +dev_dependencies = ["targets"] +ecosystems = ["r"] + +[commands] +run = "Rscript -e 'targets::tar_make()'" +alternatives = ["Rscript -e 'targets::tar_visnetwork()'"] + +[config] +files = ["_targets.R"] + +[taxonomy] +role = ["build-tool", "orchestrator"] +function = ["automation"] +domain = ["data-science", "scientific-computing"] +audience = ["data-scientist", "researcher"] diff --git a/knowledge/r/tinytest.toml b/knowledge/r/tinytest.toml new file mode 100644 index 0000000..41d89ba --- /dev/null +++ b/knowledge/r/tinytest.toml @@ -0,0 +1,21 @@ +[tool] +name = "tinytest" +category = "test" +homepage = "https://github.com/markvanderloo/tinytest" +docs = "https://cran.r-project.org/package=tinytest" +repo = "https://github.com/markvanderloo/tinytest" +description = "Lightweight unit testing for R packages" + +[detect] +files = ["tests/tinytest.R", "inst/tinytest/"] +dependencies = ["tinytest"] +dev_dependencies = ["tinytest"] +ecosystems = ["r"] + +[commands] +run = "Rscript -e 'tinytest::test_all()'" +alternatives = ["Rscript -e 'tinytest::run_test_dir(\"inst/tinytest\")'"] + +[taxonomy] +role = ["testing-framework"] +function = ["testing"] diff --git a/knowledge/r/vdiffr.toml b/knowledge/r/vdiffr.toml new file mode 100644 index 0000000..fca9f56 --- /dev/null +++ b/knowledge/r/vdiffr.toml @@ -0,0 +1,16 @@ +[tool] +name = "vdiffr" +category = "test" +homepage = "https://vdiffr.r-lib.org" +docs = "https://vdiffr.r-lib.org/reference/" +repo = "https://github.com/r-lib/vdiffr" +description = "Visual regression testing for R plots" + +[detect] +dependencies = ["vdiffr"] +dev_dependencies = ["vdiffr"] +ecosystems = ["r"] + +[taxonomy] +role = ["testing-framework"] +function = ["testing", "visualization"] diff --git a/testdata/research-tools-project/.dvc/config b/testdata/research-tools-project/.dvc/config new file mode 100644 index 0000000..b1881cf --- /dev/null +++ b/testdata/research-tools-project/.dvc/config @@ -0,0 +1,2 @@ +[core] + remote = storage diff --git a/testdata/research-tools-project/.github/.dockstore.yml b/testdata/research-tools-project/.github/.dockstore.yml new file mode 100644 index 0000000..8909380 --- /dev/null +++ b/testdata/research-tools-project/.github/.dockstore.yml @@ -0,0 +1,4 @@ +version: 1.2 +workflows: + - subclass: NFL + primaryDescriptorPath: /nextflow.config diff --git a/testdata/research-tools-project/.nf-core.yml b/testdata/research-tools-project/.nf-core.yml new file mode 100644 index 0000000..3805dc8 --- /dev/null +++ b/testdata/research-tools-project/.nf-core.yml @@ -0,0 +1 @@ +repository_type: pipeline diff --git a/testdata/research-tools-project/DESCRIPTION b/testdata/research-tools-project/DESCRIPTION new file mode 100644 index 0000000..7d88396 --- /dev/null +++ b/testdata/research-tools-project/DESCRIPTION @@ -0,0 +1,8 @@ +Package: researchtools +Version: 1.0.0 +Suggests: + lintr, + styler, + targets, + tinytest, + vdiffr diff --git a/testdata/research-tools-project/Project.toml b/testdata/research-tools-project/Project.toml new file mode 100644 index 0000000..209caf3 --- /dev/null +++ b/testdata/research-tools-project/Project.toml @@ -0,0 +1,6 @@ +name = "NumericalModel" +uuid = "12345678-1234-1234-1234-123456789abc" +version = "0.1.0" + +[deps] +BenchmarkTools = "6e4b80f9-dd63-53aa-95a3-0cdb28fa8baf" diff --git a/testdata/research-tools-project/_targets.R b/testdata/research-tools-project/_targets.R new file mode 100644 index 0000000..b132f60 --- /dev/null +++ b/testdata/research-tools-project/_targets.R @@ -0,0 +1,3 @@ +library(targets) + +list(tar_target(result, run_model())) diff --git a/testdata/research-tools-project/assets/multiqc_config.yml b/testdata/research-tools-project/assets/multiqc_config.yml new file mode 100644 index 0000000..eb93f31 --- /dev/null +++ b/testdata/research-tools-project/assets/multiqc_config.yml @@ -0,0 +1 @@ +title: Example analysis diff --git a/testdata/research-tools-project/nf-test.config b/testdata/research-tools-project/nf-test.config new file mode 100644 index 0000000..0565036 --- /dev/null +++ b/testdata/research-tools-project/nf-test.config @@ -0,0 +1,3 @@ +config { + testsDir "tests" +} diff --git a/testdata/research-tools-project/pyproject.toml b/testdata/research-tools-project/pyproject.toml new file mode 100644 index 0000000..037534c --- /dev/null +++ b/testdata/research-tools-project/pyproject.toml @@ -0,0 +1,7 @@ +[project] +name = "research-tools" +version = "1.0.0" +dependencies = ["myst-parser>=4"] + +[tool.cibuildwheel] +test-command = "pytest {project}/tests" diff --git a/testdata/research-tools-project/tests/pipeline/main.nf.test b/testdata/research-tools-project/tests/pipeline/main.nf.test new file mode 100644 index 0000000..d96294c --- /dev/null +++ b/testdata/research-tools-project/tests/pipeline/main.nf.test @@ -0,0 +1,3 @@ +nextflow_pipeline { + script "main.nf" +} diff --git a/testdata/research-tools-project/tests/tinytest.R b/testdata/research-tools-project/tests/tinytest.R new file mode 100644 index 0000000..d3748cb --- /dev/null +++ b/testdata/research-tools-project/tests/tinytest.R @@ -0,0 +1,3 @@ +if (requireNamespace("tinytest")) { + tinytest::test_package("researchtools") +}