feat(marketplace): Git-based module index parser and search
Module/Index types, ParseIndex from JSON, Search (fuzzy across code/name/ category), ByCategory filter, Find by code. Foundation for git-based plugin marketplace. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
3399994977
commit
d13f57de69
2 changed files with 132 additions and 0 deletions
67
pkg/marketplace/marketplace.go
Normal file
67
pkg/marketplace/marketplace.go
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
package marketplace
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Module is a marketplace entry pointing to a module's Git repo.
|
||||
type Module struct {
|
||||
Code string `json:"code"`
|
||||
Name string `json:"name"`
|
||||
Repo string `json:"repo"`
|
||||
SignKey string `json:"sign_key"`
|
||||
Category string `json:"category"`
|
||||
}
|
||||
|
||||
// Index is the root marketplace catalog.
|
||||
type Index struct {
|
||||
Version int `json:"version"`
|
||||
Modules []Module `json:"modules"`
|
||||
Categories []string `json:"categories"`
|
||||
}
|
||||
|
||||
// ParseIndex decodes a marketplace index.json.
|
||||
func ParseIndex(data []byte) (*Index, error) {
|
||||
var idx Index
|
||||
if err := json.Unmarshal(data, &idx); err != nil {
|
||||
return nil, fmt.Errorf("marketplace.ParseIndex: %w", err)
|
||||
}
|
||||
return &idx, nil
|
||||
}
|
||||
|
||||
// Search returns modules matching the query in code, name, or category.
|
||||
func (idx *Index) Search(query string) []Module {
|
||||
q := strings.ToLower(query)
|
||||
var results []Module
|
||||
for _, m := range idx.Modules {
|
||||
if strings.Contains(strings.ToLower(m.Code), q) ||
|
||||
strings.Contains(strings.ToLower(m.Name), q) ||
|
||||
strings.Contains(strings.ToLower(m.Category), q) {
|
||||
results = append(results, m)
|
||||
}
|
||||
}
|
||||
return results
|
||||
}
|
||||
|
||||
// ByCategory returns all modules in the given category.
|
||||
func (idx *Index) ByCategory(category string) []Module {
|
||||
var results []Module
|
||||
for _, m := range idx.Modules {
|
||||
if m.Category == category {
|
||||
results = append(results, m)
|
||||
}
|
||||
}
|
||||
return results
|
||||
}
|
||||
|
||||
// Find returns the module with the given code, or false if not found.
|
||||
func (idx *Index) Find(code string) (Module, bool) {
|
||||
for _, m := range idx.Modules {
|
||||
if m.Code == code {
|
||||
return m, true
|
||||
}
|
||||
}
|
||||
return Module{}, false
|
||||
}
|
||||
65
pkg/marketplace/marketplace_test.go
Normal file
65
pkg/marketplace/marketplace_test.go
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
package marketplace
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestParseIndex_Good(t *testing.T) {
|
||||
raw := `{
|
||||
"version": 1,
|
||||
"modules": [
|
||||
{"code": "mining-xmrig", "name": "XMRig Miner", "repo": "https://forge.lthn.io/host-uk/mod-xmrig.git", "sign_key": "abc123", "category": "miner"},
|
||||
{"code": "utils-cyberchef", "name": "CyberChef", "repo": "https://forge.lthn.io/host-uk/mod-cyberchef.git", "sign_key": "def456", "category": "utils"}
|
||||
],
|
||||
"categories": ["miner", "utils"]
|
||||
}`
|
||||
idx, err := ParseIndex([]byte(raw))
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, 1, idx.Version)
|
||||
assert.Len(t, idx.Modules, 2)
|
||||
assert.Equal(t, "mining-xmrig", idx.Modules[0].Code)
|
||||
}
|
||||
|
||||
func TestSearch_Good(t *testing.T) {
|
||||
idx := &Index{
|
||||
Modules: []Module{
|
||||
{Code: "mining-xmrig", Name: "XMRig Miner", Category: "miner"},
|
||||
{Code: "utils-cyberchef", Name: "CyberChef", Category: "utils"},
|
||||
},
|
||||
}
|
||||
results := idx.Search("miner")
|
||||
assert.Len(t, results, 1)
|
||||
assert.Equal(t, "mining-xmrig", results[0].Code)
|
||||
}
|
||||
|
||||
func TestByCategory_Good(t *testing.T) {
|
||||
idx := &Index{
|
||||
Modules: []Module{
|
||||
{Code: "a", Category: "miner"},
|
||||
{Code: "b", Category: "utils"},
|
||||
{Code: "c", Category: "miner"},
|
||||
},
|
||||
}
|
||||
miners := idx.ByCategory("miner")
|
||||
assert.Len(t, miners, 2)
|
||||
}
|
||||
|
||||
func TestFind_Good(t *testing.T) {
|
||||
idx := &Index{
|
||||
Modules: []Module{
|
||||
{Code: "mining-xmrig", Name: "XMRig"},
|
||||
},
|
||||
}
|
||||
m, ok := idx.Find("mining-xmrig")
|
||||
assert.True(t, ok)
|
||||
assert.Equal(t, "XMRig", m.Name)
|
||||
}
|
||||
|
||||
func TestFind_Bad_NotFound(t *testing.T) {
|
||||
idx := &Index{}
|
||||
_, ok := idx.Find("nope")
|
||||
assert.False(t, ok)
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue