Files
doks/cmd/root.go
2026-01-22 23:03:49 -06:00

186 lines
4.1 KiB
Go

package cmd
import (
"fmt"
"os"
"os/exec"
"github.com/spf13/cobra"
"github.com/yeho/doks/internal/config"
"github.com/yeho/doks/internal/registry"
"github.com/yeho/doks/internal/search"
"github.com/yeho/doks/internal/tui"
)
var (
Version = "dev" // Set at build time via ldflags
keyFlag string
searchFlag string
versionFlag bool
cfg *config.Config
)
var rootCmd = &cobra.Command{
Use: "doks",
Short: "A terminal app for quick access to personal notes",
Long: `doks is a terminal application for quickly accessing short notes
and manuals. Access documents by key or search through content.`,
Run: func(cmd *cobra.Command, args []string) {
if versionFlag {
fmt.Println(Version)
return
}
if keyFlag != "" {
retrieveByKey(keyFlag)
return
}
if searchFlag != "" {
searchDocuments(searchFlag)
return
}
cmd.Help()
},
}
func Execute() {
if err := rootCmd.Execute(); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}
func init() {
cobra.OnInitialize(initConfig)
rootCmd.Flags().StringVarP(&keyFlag, "key", "k", "", "Retrieve document by key")
rootCmd.Flags().StringVarP(&searchFlag, "search", "s", "", "Search documents by text")
rootCmd.Flags().BoolVarP(&versionFlag, "version", "v", false, "Print version")
// Register key completion
rootCmd.RegisterFlagCompletionFunc("key", completeKeys)
}
func completeKeys(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
// Need to initialize config for completion
if cfg == nil {
var err error
cfg, err = config.Load()
if err != nil {
return nil, cobra.ShellCompDirectiveError
}
}
reg, err := registry.Load(cfg.RegistryPath())
if err != nil {
return nil, cobra.ShellCompDirectiveError
}
keys := []string{}
for _, entry := range reg.List() {
keys = append(keys, entry.Key)
}
return keys, cobra.ShellCompDirectiveNoFileComp
}
func initConfig() {
var err error
cfg, err = config.Load()
if err != nil {
fmt.Fprintf(os.Stderr, "Error loading config: %v\n", err)
os.Exit(1)
}
}
func retrieveByKey(key string) {
reg, err := registry.Load(cfg.RegistryPath())
if err != nil {
fmt.Fprintf(os.Stderr, "Error loading registry: %v\n", err)
os.Exit(1)
}
entry, err := reg.Get(key)
if err != nil {
fmt.Fprintf(os.Stderr, "Document not found: %s\n", key)
os.Exit(1)
}
filePath := cfg.FilePath(entry.Filename)
displayFile(filePath, 0)
}
func searchDocuments(query string) {
reg, err := registry.Load(cfg.RegistryPath())
if err != nil {
fmt.Fprintf(os.Stderr, "Error loading registry: %v\n", err)
os.Exit(1)
}
searcher := search.NewTextSearcher(cfg.FilesDir(), reg)
opts := search.Options{
MaxResults: cfg.Display.MaxResults,
ContextLines: cfg.Display.ContextLines,
}
results, err := searcher.Search(query, opts)
if err != nil {
fmt.Fprintf(os.Stderr, "Error searching: %v\n", err)
os.Exit(1)
}
if len(results) == 0 {
fmt.Printf("No results found for '%s'\n", query)
return
}
// If only one result, show it directly
if len(results) == 1 {
showResult(&results[0])
return
}
// Multiple results - show TUI
selected, err := tui.Run(results, query)
if err != nil {
fmt.Fprintf(os.Stderr, "Error running TUI: %v\n", err)
os.Exit(1)
}
if selected != nil {
showResult(selected)
}
}
func showResult(result *search.Result) {
fmt.Printf("--- %s (line %d) ---\n", result.Key, result.LineNumber)
displayFile(result.FilePath, result.LineNumber)
}
func displayFile(filePath string, highlightLine int) {
// Try bat first for syntax highlighting
if batPath, err := exec.LookPath("bat"); err == nil {
args := []string{
"--paging=never",
"--color=always",
}
if highlightLine > 0 {
args = append(args, fmt.Sprintf("--highlight-line=%d", highlightLine))
}
args = append(args, filePath)
cmd := exec.Command(batPath, args...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run(); err == nil {
return
}
}
// Fallback to plain output
content, err := os.ReadFile(filePath)
if err != nil {
fmt.Fprintf(os.Stderr, "Error reading file: %v\n", err)
os.Exit(1)
}
fmt.Print(string(content))
}