feat: initial commit
This commit is contained in:
commit
8a467d9004
5 changed files with 828 additions and 0 deletions
289
registry.go
Normal file
289
registry.go
Normal file
|
|
@ -0,0 +1,289 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"archive/zip"
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/adrg/xdg"
|
||||
)
|
||||
|
||||
const (
|
||||
MASON_REGISTRY_RELEASE = "https://github.com/mason-org/mason-registry/releases/latest/download/registry.json.zip"
|
||||
)
|
||||
|
||||
// LSPServer repräsentiert einen Language Server
|
||||
type LSPServer struct {
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
Homepage string `json:"homepage"`
|
||||
Languages []string `json:"languages"`
|
||||
Categories []string `json:"categories"`
|
||||
Executable string `json:"executable"`
|
||||
InstallType string `json:"install_type"` // "github", "npm", "cargo", "go"
|
||||
Source string `json:"source"` // URL oder Package-Name
|
||||
Version string `json:"version"`
|
||||
License string `json:"license"`
|
||||
}
|
||||
|
||||
// Registry verwaltet alle verfügbaren LSP-Server
|
||||
type Registry struct {
|
||||
Servers []LSPServer `json:"servers"`
|
||||
CachePath string
|
||||
LastUpdated time.Time
|
||||
}
|
||||
|
||||
// MasonPackage entspricht der Mason-Registry Struktur
|
||||
type MasonPackage struct {
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
Homepage string `json:"homepage"`
|
||||
Licenses []string `json:"licenses"`
|
||||
Languages []string `json:"languages"`
|
||||
Categories []string `json:"categories"`
|
||||
Source struct {
|
||||
ID string `json:"id"`
|
||||
} `json:"source"`
|
||||
Bin map[string]string `json:"bin,omitempty"`
|
||||
}
|
||||
|
||||
// LoadRegistry lädt die Registry aus einer JSON-Datei oder von Mason
|
||||
func LoadRegistry(path string) (*Registry, error) {
|
||||
// Prüfe ob path "mason" ist -> dann von GitHub laden
|
||||
if path == "mason" {
|
||||
return LoadRegistryFromMason()
|
||||
}
|
||||
|
||||
// Ansonsten aus lokaler Datei laden
|
||||
file, err := os.Open(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
data, err := io.ReadAll(file)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var registry Registry
|
||||
if err := json.Unmarshal(data, ®istry); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return ®istry, nil
|
||||
}
|
||||
|
||||
// LoadRegistryFromMason lädt die Registry von Mason mit Cache
|
||||
func LoadRegistryFromMason() (*Registry, error) {
|
||||
cacheDir := filepath.Join(xdg.CacheHome, "lsp-manager")
|
||||
os.MkdirAll(cacheDir, 0755)
|
||||
cachePath := filepath.Join(cacheDir, "mason-registry.json")
|
||||
|
||||
registry := &Registry{
|
||||
CachePath: cachePath,
|
||||
}
|
||||
|
||||
// Prüfe ob Cache existiert und aktuell ist (< 24h alt)
|
||||
if info, err := os.Stat(cachePath); err == nil {
|
||||
if time.Since(info.ModTime()) < 24*time.Hour {
|
||||
fmt.Println("📦 Loading registry from cache...")
|
||||
if err := registry.loadFromCache(); err == nil {
|
||||
return registry, nil
|
||||
}
|
||||
fmt.Printf("Cache load failed, downloading fresh copy...\n")
|
||||
}
|
||||
}
|
||||
|
||||
// Cache ist veraltet oder existiert nicht, lade von GitHub
|
||||
if err := registry.downloadFromMason(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return registry, nil
|
||||
}
|
||||
|
||||
// loadFromCache lädt die Registry aus dem lokalen Cache
|
||||
func (r *Registry) loadFromCache() error {
|
||||
data, err := os.ReadFile(r.CachePath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read cache: %w", err)
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(data, &r.Servers); err != nil {
|
||||
return fmt.Errorf("failed to parse cache: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// downloadFromMason lädt die Mason-Registry von GitHub
|
||||
func (r *Registry) downloadFromMason() error {
|
||||
fmt.Println("📦 Downloading Mason registry from GitHub...")
|
||||
|
||||
resp, err := http.Get(MASON_REGISTRY_RELEASE)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to download registry: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return fmt.Errorf("unexpected status code: %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
// Lese ZIP-Daten in Speicher
|
||||
zipData, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read registry data: %w", err)
|
||||
}
|
||||
|
||||
// Entpacke ZIP
|
||||
zipReader, err := zip.NewReader(bytes.NewReader(zipData), int64(len(zipData)))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to open zip: %w", err)
|
||||
}
|
||||
|
||||
// Finde registry.json in ZIP
|
||||
var registryJSON []byte
|
||||
for _, file := range zipReader.File {
|
||||
if file.Name == "registry.json" {
|
||||
f, err := file.Open()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to open registry.json: %w", err)
|
||||
}
|
||||
registryJSON, err = io.ReadAll(f)
|
||||
f.Close()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read registry.json: %w", err)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if registryJSON == nil {
|
||||
return fmt.Errorf("registry.json not found in archive")
|
||||
}
|
||||
|
||||
// Parse Mason-Format: registry.json ist ein Array
|
||||
var masonPackages []MasonPackage
|
||||
if err := json.Unmarshal(registryJSON, &masonPackages); err != nil {
|
||||
return fmt.Errorf("failed to parse registry: %w", err)
|
||||
}
|
||||
|
||||
// Konvertiere zu unserem Format
|
||||
r.Servers = convertMasonToLSPServers(masonPackages)
|
||||
|
||||
fmt.Printf("✅ Registry updated with %d packages (%d LSP servers)\n",
|
||||
len(masonPackages), len(r.Servers))
|
||||
|
||||
// Speichere konvertierte Servers im Cache
|
||||
if cacheData, err := json.MarshalIndent(r.Servers, "", " "); err == nil {
|
||||
if err := os.WriteFile(r.CachePath, cacheData, 0644); err != nil {
|
||||
fmt.Printf("⚠️ Warning: failed to cache registry: %v\n", err)
|
||||
}
|
||||
}
|
||||
|
||||
r.LastUpdated = time.Now()
|
||||
return nil
|
||||
}
|
||||
|
||||
func convertMasonToLSPServers(masonPackages []MasonPackage) []LSPServer {
|
||||
servers := make([]LSPServer, 0, len(masonPackages))
|
||||
|
||||
for _, pkg := range masonPackages {
|
||||
// Filtere nur LSP-Server
|
||||
isLSP := false
|
||||
for _, cat := range pkg.Categories {
|
||||
if cat == "LSP" {
|
||||
isLSP = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !isLSP {
|
||||
continue
|
||||
}
|
||||
|
||||
// Bestimme Executable-Namen
|
||||
executable := pkg.Name
|
||||
if len(pkg.Bin) > 0 {
|
||||
for _, binName := range pkg.Bin {
|
||||
executable = binName
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Konvertiere Source-ID zu unserem Format
|
||||
installType, source := parseMasonSourceID(pkg.Source.ID)
|
||||
|
||||
servers = append(servers, LSPServer{
|
||||
Name: pkg.Name,
|
||||
Description: pkg.Description,
|
||||
Homepage: pkg.Homepage,
|
||||
Languages: pkg.Languages,
|
||||
Categories: pkg.Categories,
|
||||
Executable: executable,
|
||||
InstallType: installType,
|
||||
Source: source,
|
||||
Version: "latest",
|
||||
License: getLicense(pkg.Licenses),
|
||||
})
|
||||
}
|
||||
|
||||
return servers
|
||||
}
|
||||
|
||||
// parseMasonSourceID extrahiert InstallType und Source aus Mason Source-ID
|
||||
// Format: "pkg:github/owner/repo", "pkg:npm/package-name", etc.
|
||||
func parseMasonSourceID(sourceID string) (installType, source string) {
|
||||
if !strings.HasPrefix(sourceID, "pkg:") {
|
||||
return "unknown", sourceID
|
||||
}
|
||||
|
||||
// Entferne "pkg:" Prefix
|
||||
parts := strings.SplitN(sourceID[4:], "/", 2)
|
||||
if len(parts) < 2 {
|
||||
return "unknown", sourceID
|
||||
}
|
||||
|
||||
installType = parts[0]
|
||||
source = parts[1]
|
||||
|
||||
return installType, source
|
||||
}
|
||||
|
||||
// getLicense gibt die erste Lizenz zurück oder "Unknown"
|
||||
func getLicense(licenses []string) string {
|
||||
if len(licenses) > 0 {
|
||||
return licenses[0]
|
||||
}
|
||||
return "Unknown"
|
||||
}
|
||||
|
||||
// LoadRegistryFromURL lädt von einer Remote-URL (Fallback)
|
||||
func LoadRegistryFromURL(url string) (*Registry, error) {
|
||||
resp, err := http.Get(url)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
data, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var registry Registry
|
||||
if err := json.Unmarshal(data, ®istry); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return ®istry, nil
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue