mirror of
https://github.com/navidrome/navidrome.git
synced 2026-08-31 07:30:32 +00:00
feat(plugins): generate Rust lib.rs file to expose host function wrappers
Signed-off-by: Deluan <deluan@navidrome.org>
This commit is contained in:
parent
de45079ff6
commit
7afe906d11
@ -266,9 +266,19 @@ type ServiceB interface {
|
||||
rustDir := filepath.Join(outputDir, "rust")
|
||||
rsClientEntries, err := os.ReadDir(rustDir)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(rsClientEntries).To(HaveLen(1), "Expected exactly one Rust client file")
|
||||
Expect(rsClientEntries).To(HaveLen(2), "Expected Rust client file and lib.rs")
|
||||
|
||||
rsClientActual, err := os.ReadFile(filepath.Join(rustDir, rsClientEntries[0].Name()))
|
||||
// Find the client file (not lib.rs)
|
||||
var rsClientName string
|
||||
for _, entry := range rsClientEntries {
|
||||
if entry.Name() != "lib.rs" {
|
||||
rsClientName = entry.Name()
|
||||
break
|
||||
}
|
||||
}
|
||||
Expect(rsClientName).ToNot(BeEmpty(), "Expected to find Rust client file")
|
||||
|
||||
rsClientActual, err := os.ReadFile(filepath.Join(rustDir, rsClientName))
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
Expect(string(rsClientActual)).To(Equal(rsClientExpected), "Rust client code mismatch")
|
||||
|
||||
@ -190,3 +190,45 @@ func GenerateClientRust(svc Service) ([]byte, error) {
|
||||
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
// firstLine returns the first line of a multi-line string, with the first word removed.
|
||||
func firstLine(s string) string {
|
||||
line := s
|
||||
if idx := strings.Index(s, "\n"); idx >= 0 {
|
||||
line = s[:idx]
|
||||
}
|
||||
// Remove the first word (service name like "ArtworkService")
|
||||
if idx := strings.Index(line, " "); idx >= 0 {
|
||||
line = line[idx+1:]
|
||||
}
|
||||
return line
|
||||
}
|
||||
|
||||
// GenerateRustLib generates the lib.rs file that exposes all service modules.
|
||||
func GenerateRustLib(services []Service) ([]byte, error) {
|
||||
tmplContent, err := templatesFS.ReadFile("templates/lib_rs.rs.tmpl")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("reading Rust lib template: %w", err)
|
||||
}
|
||||
|
||||
tmpl, err := template.New("lib_rs").Funcs(template.FuncMap{
|
||||
"lower": strings.ToLower,
|
||||
"firstLine": firstLine,
|
||||
}).Parse(string(tmplContent))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parsing template: %w", err)
|
||||
}
|
||||
|
||||
data := struct {
|
||||
Services []Service
|
||||
}{
|
||||
Services: services,
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
if err := tmpl.Execute(&buf, data); err != nil {
|
||||
return nil, fmt.Errorf("executing template: %w", err)
|
||||
}
|
||||
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
43
plugins/cmd/hostgen/internal/templates/lib_rs.rs.tmpl
Normal file
43
plugins/cmd/hostgen/internal/templates/lib_rs.rs.tmpl
Normal file
@ -0,0 +1,43 @@
|
||||
// Code generated by hostgen. DO NOT EDIT.
|
||||
//
|
||||
//! Navidrome Host Function Wrappers for Rust Plugins
|
||||
//!
|
||||
//! This crate provides idiomatic Rust wrappers for all Navidrome host services.
|
||||
//! It is auto-generated by the hostgen tool and should not be edited manually.
|
||||
//!
|
||||
//! # Usage
|
||||
//!
|
||||
//! Add this crate as a dependency in your plugin's Cargo.toml:
|
||||
//!
|
||||
//! ```toml
|
||||
//! [dependencies]
|
||||
//! nd-host = { path = "../../host/rust" }
|
||||
//! ```
|
||||
//!
|
||||
//! Then import the services you need:
|
||||
//!
|
||||
//! ```ignore
|
||||
//! use nd_host::{cache, scheduler};
|
||||
//!
|
||||
//! fn my_plugin_function() -> Result<(), extism_pdk::Error> {
|
||||
//! // Use the cache service
|
||||
//! cache::set_string("my_key", "my_value", 3600)?;
|
||||
//!
|
||||
//! // Schedule a recurring task
|
||||
//! scheduler::schedule_recurring("@every 5m", "payload", "task_id")?;
|
||||
//!
|
||||
//! Ok(())
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! # Available Services
|
||||
//!
|
||||
{{- range .Services}}
|
||||
//! - [`{{.Name | lower}}`] - {{if .Doc}}{{.Doc | firstLine}}{{else}}{{.Name}} service{{end}}
|
||||
{{- end}}
|
||||
{{range .Services}}
|
||||
#[path = "nd_host_{{.Name | lower}}.rs"]
|
||||
pub mod {{.Name | lower}};
|
||||
{{end}}
|
||||
// Re-export commonly used types from extism-pdk for convenience
|
||||
pub use extism_pdk::Error;
|
||||
@ -147,6 +147,14 @@ func main() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Generate Rust lib.rs to expose all modules
|
||||
if generateRsClient && len(services) > 0 {
|
||||
if err := generateRustLibFile(services, absOutput, *dryRun, *verbose); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error generating Rust lib.rs: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// generateHostCode generates host-side code for a service.
|
||||
@ -275,3 +283,33 @@ func generateRustClientCode(svc internal.Service, outputDir string, dryRun, verb
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// generateRustLibFile generates the lib.rs file that exposes all Rust modules.
|
||||
func generateRustLibFile(services []internal.Service, outputDir string, dryRun, verbose bool) error {
|
||||
code, err := internal.GenerateRustLib(services)
|
||||
if err != nil {
|
||||
return fmt.Errorf("generating lib.rs: %w", err)
|
||||
}
|
||||
|
||||
clientDir := filepath.Join(outputDir, "rust")
|
||||
libFile := filepath.Join(clientDir, "lib.rs")
|
||||
|
||||
if dryRun {
|
||||
fmt.Printf("=== %s ===\n%s\n", libFile, code)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Create rust/ subdirectory if needed
|
||||
if err := os.MkdirAll(clientDir, 0755); err != nil {
|
||||
return fmt.Errorf("creating rust client directory: %w", err)
|
||||
}
|
||||
|
||||
if err := os.WriteFile(libFile, code, 0600); err != nil {
|
||||
return fmt.Errorf("writing file: %w", err)
|
||||
}
|
||||
|
||||
if verbose {
|
||||
fmt.Printf("Generated Rust lib.rs: %s\n", libFile)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
// Code generated by hostgen. DO NOT EDIT.
|
||||
//
|
||||
//! Navidrome Host Function Wrappers for Rust Plugins
|
||||
//!
|
||||
//! This crate provides idiomatic Rust wrappers for all Navidrome host services.
|
||||
@ -15,14 +17,14 @@
|
||||
//! Then import the services you need:
|
||||
//!
|
||||
//! ```ignore
|
||||
//! use nd_host::{cache, scheduler, kvstore};
|
||||
//! use nd_host::{cache, scheduler};
|
||||
//!
|
||||
//! fn my_plugin_function() -> Result<(), extism_pdk::Error> {
|
||||
//! // Use the cache service
|
||||
//! cache::cache_set("my_key", b"my_value", 3600)?;
|
||||
//! cache::set_string("my_key", "my_value", 3600)?;
|
||||
//!
|
||||
//! // Schedule a recurring task
|
||||
//! scheduler::scheduler_schedule_recurring("@every 5m", "payload", "task_id")?;
|
||||
//! scheduler::schedule_recurring("@every 5m", "payload", "task_id")?;
|
||||
//!
|
||||
//! Ok(())
|
||||
//! }
|
||||
@ -30,13 +32,13 @@
|
||||
//!
|
||||
//! # Available Services
|
||||
//!
|
||||
//! - [`artwork`] - Access album and artist artwork
|
||||
//! - [`cache`] - Temporary key-value storage with TTL
|
||||
//! - [`kvstore`] - Persistent key-value storage
|
||||
//! - [`library`] - Access the music library
|
||||
//! - [`scheduler`] - Schedule one-time and recurring tasks
|
||||
//! - [`subsonicapi`] - Make Subsonic API calls
|
||||
//! - [`websocket`] - Send real-time messages to clients
|
||||
//! - [`artwork`] - provides artwork URL generation capabilities for plugins.
|
||||
//! - [`cache`] - provides in-memory TTL-based caching capabilities for plugins.
|
||||
//! - [`kvstore`] - provides persistent key-value storage for plugins.
|
||||
//! - [`library`] - provides access to music library metadata for plugins.
|
||||
//! - [`scheduler`] - provides task scheduling capabilities for plugins.
|
||||
//! - [`subsonicapi`] - provides access to Navidrome's Subsonic API from plugins.
|
||||
//! - [`websocket`] - provides WebSocket communication capabilities for plugins.
|
||||
|
||||
#[path = "nd_host_artwork.rs"]
|
||||
pub mod artwork;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user