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 glow first for Markdown rendering if glowPath, err := exec.LookPath("glow"); err == nil { cmd := exec.Command(glowPath, filePath) cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr if err := cmd.Run(); err == nil { return } } // Fallback to bat 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 cat if catPath, err := exec.LookPath("cat"); err == nil { cmd := exec.Command(catPath, filePath) cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr if err := cmd.Run(); err == nil { return } } // Final fallback to plain Go file read 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)) }