about summary refs log tree commit diff stats
path: root/internal
diff options
context:
space:
mode:
authorAlan Pearce2025-03-22 16:51:43 +0100
committerAlan Pearce2025-03-22 16:51:43 +0100
commit1328df9c5ca44e903a052c9bfc9a67dd34e25704 (patch)
treea18ca6edf71d019a0895367c0bca53786d2f892e /internal
parentd39204eadf673b6c7ae940203fd75c0805245a96 (diff)
downloadsearchix-1328df9c5ca44e903a052c9bfc9a67dd34e25704.tar.lz
searchix-1328df9c5ca44e903a052c9bfc9a67dd34e25704.tar.zst
searchix-1328df9c5ca44e903a052c9bfc9a67dd34e25704.zip
feat: promote prefix/exact name/attr matches
Fixes: https://todo.sr.ht/~alanpearce/searchix/18
Diffstat (limited to 'internal')
-rw-r--r--internal/index/indexer.go15
-rw-r--r--internal/index/search.go27
-rw-r--r--internal/index/search_test.go150
3 files changed, 181 insertions, 11 deletions
diff --git a/internal/index/indexer.go b/internal/index/indexer.go
index 8cbc8e2..c4032e8 100644
--- a/internal/index/indexer.go
+++ b/internal/index/indexer.go
@@ -8,6 +8,7 @@ import (
 	"math"
 	"os"
 	"path"
+	"path/filepath"
 	"slices"
 
 	"go.alanpearce.eu/searchix/internal/file"
@@ -93,13 +94,6 @@ func createIndexMapping() (mapping.IndexMapping, errors.E) {
 	if err != nil {
 		return nil, errors.WithMessage(err, "could not add custom analyser")
 	}
-	err = indexMapping.AddCustomAnalyzer("keyword_single", map[string]any{
-		"type":      keyword.Name,
-		"tokenizer": letter.Name,
-	})
-	if err != nil {
-		return nil, errors.WithMessage(err, "could not add custom analyser")
-	}
 
 	identityFieldMapping := bleve.NewKeywordFieldMapping()
 
@@ -222,6 +216,13 @@ func OpenOrCreate(
 ) (*ReadIndex, *WriteIndex, bool, errors.E) {
 	var err errors.E
 	bleve.SetLog(zap.NewStdLog(options.Logger.Named("bleve").GetLogger()))
+	if !filepath.IsAbs(dataRoot) {
+		wd, err := os.Getwd()
+		if err != nil {
+			return nil, nil, false, errors.WithMessagef(err, "could not get working directory")
+		}
+		dataRoot = filepath.Join(wd, dataRoot)
+	}
 
 	indexPath := path.Join(dataRoot, indexBaseName)
 	metaPath := path.Join(dataRoot, metaBaseName)
diff --git a/internal/index/search.go b/internal/index/search.go
index 292661e..a8124c7 100644
--- a/internal/index/search.go
+++ b/internal/index/search.go
@@ -128,8 +128,8 @@ func (index *ReadIndex) Search(
 
 	// match the user's query in any field ...
 	query.AddMust(bleve.NewDisjunctionQuery(
-		bleve.NewTermQuery(keyword),
-		bleve.NewPrefixQuery(keyword),
+		setBoost(bleve.NewTermQuery(keyword), 50),
+		setBoost(bleve.NewPrefixQuery(keyword), 25),
 		bleve.NewMatchPhraseQuery(keyword),
 		bleve.NewMatchQuery(keyword),
 	))
@@ -140,13 +140,23 @@ func (index *ReadIndex) Search(
 		)
 	} else {
 		q := bleve.NewDisjunctionQuery(
-			setBoost(setField(bleve.NewTermQuery("nixpkgs"), "Source"), -150),
-			setBoost(setField(bleve.NewTermQuery("nur"), "Source"), -200),
+			setBoost(setField(bleve.NewTermQuery("nixpkgs"), "Source"), -1000),
+			setBoost(setField(bleve.NewTermQuery("nur"), "Source"), -5000),
 		)
 
 		query.AddShould(q)
 	}
 
+	mainProgramQuery := bleve.NewMatchQuery(keyword)
+	mainProgramQuery.SetField("MainProgram")
+	mainProgramQuery.SetBoost(50)
+	query.AddShould(mainProgramQuery)
+
+	mainProgramLiteralQuery := bleve.NewTermQuery(keyword)
+	mainProgramLiteralQuery.SetField("MainProgram")
+	mainProgramLiteralQuery.SetBoost(100)
+	query.AddShould(mainProgramLiteralQuery)
+
 	programsQuery := bleve.NewMatchQuery(keyword)
 	programsQuery.SetField("Programs")
 	programsQuery.SetBoost(2)
@@ -215,3 +225,12 @@ func (index *ReadIndex) GetDocument(
 
 	return nil, err
 }
+
+func (index *ReadIndex) Close() error {
+	err := index.index.Close()
+	if err != nil {
+		return errors.WithStack(err)
+	}
+
+	return nil
+}
diff --git a/internal/index/search_test.go b/internal/index/search_test.go
new file mode 100644
index 0000000..339a0de
--- /dev/null
+++ b/internal/index/search_test.go
@@ -0,0 +1,150 @@
+package index_test
+
+import (
+	"context"
+	"maps"
+	"math"
+	"slices"
+	"testing"
+	"time"
+
+	"go.alanpearce.eu/searchix/internal/config"
+	"go.alanpearce.eu/searchix/internal/index"
+	"go.alanpearce.eu/searchix/internal/nix"
+	"go.alanpearce.eu/x/log"
+)
+
+const dataRoot = "../../data"
+
+func TestSearchGitPackagesFirst(t *testing.T) {
+	log := log.Configure(false)
+	cfg := config.DefaultConfig
+
+	read, _, exists, err := index.OpenOrCreate(dataRoot, false, &index.Options{
+		Logger:    log.Named("index"),
+		LowMemory: false,
+	})
+	defer read.Close()
+	if err != nil {
+		t.Fatal(err)
+	}
+	if !exists {
+		t.Fatal("expected index to exist")
+	}
+
+	ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
+	defer cancel()
+
+	source := cfg.Importer.Sources["nixpkgs"]
+	if source == nil || !source.Enable {
+		t.Fatal("expected source to exist and be enabled")
+	}
+
+	result, err := read.Search(
+		ctx,
+		source,
+		"git",
+		0,
+		100,
+	)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	if result.Total < 4 {
+		t.Errorf("Expected at least 4 results, got %d", result.Total)
+	}
+	important := map[string]int{
+		"git":        0,
+		"git-doc":    0,
+		"gitFull":    0,
+		"gitMinimal": 0,
+		"gitSVN":     0,
+	}
+	var i int
+	for hit := range result.Hits {
+		data := hit.Data.(nix.Package)
+		if _, found := important[data.Attribute]; found {
+			important[data.Attribute] = i
+		}
+		i++
+	}
+	if slices.Max(slices.Collect(maps.Values(important))) > len(important) {
+		t.Errorf(
+			"Expected all of %s to be the first %d matches, got %v",
+			slices.Collect(maps.Keys(important)),
+			len(important),
+			important,
+		)
+	}
+}
+
+func TestSearchJujutsuPackagesFirst(t *testing.T) {
+	log := log.Configure(false)
+	cfg := config.DefaultConfig
+
+	read, _, exists, err := index.OpenOrCreate(dataRoot, false, &index.Options{
+		Logger:    log.Named("index"),
+		LowMemory: false,
+	})
+	defer read.Close()
+	if err != nil {
+		t.Fatal(err)
+	}
+	if !exists {
+		t.Fatal("expected index to exist")
+	}
+
+	ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
+	defer cancel()
+
+	source := cfg.Importer.Sources["nixpkgs"]
+	if source == nil || !source.Enable {
+		t.Fatal("expected source to exist and be enabled")
+	}
+
+	result, err := read.Search(
+		ctx,
+		source,
+		"jj",
+		0,
+		100,
+	)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	if result.Total < 4 {
+		t.Errorf("Expected at least 4 results, got %d", result.Total)
+	}
+	important := map[string]int{
+		"jj":      0,
+		"jujutsu": 0,
+		"lazyjj":  0,
+		"jjui":    0,
+		"jj-fzf":  0,
+	}
+	matches := []string{}
+	unwanted := "javacc"
+	unwantedIndex := math.MaxInt
+	var i int
+	for hit := range result.Hits {
+		data := hit.Data.(nix.Package)
+		if _, found := important[data.Attribute]; found {
+			matches = append(matches, data.Attribute)
+		} else if data.Attribute == unwanted {
+			unwantedIndex = i
+			matches = append(matches, data.Attribute)
+		}
+		i++
+	}
+	if slices.Max(slices.Collect(maps.Values(important))) > unwantedIndex {
+		t.Errorf(
+			"Expected all of %s to be above unwanted result %s at index %d. Results: %v",
+			slices.Collect(maps.Keys(important)),
+			unwanted,
+			unwantedIndex,
+			matches,
+		)
+	}
+}