Add multi-depository support with global config
- Implement global CLI config at ~/.config/jade/config.yml - Add jade depo commands (add, list, remove, set-default) - Support depository short names and context-aware detection - Remove tag_prefix config, hardcode + syntax for consistency - Update depository resolution: flag -> context -> default - Auto-initialize .jade/ directory structure when adding depos - Update documentation with new multi-depository workflow
This commit is contained in:
@@ -0,0 +1,128 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"git.jnss.me/joakim/jadedepo/internal/engine"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var depoCmd = &cobra.Command{
|
||||
Use: "depo",
|
||||
Short: "Manage depositories",
|
||||
}
|
||||
|
||||
var depoAddCmd = &cobra.Command{
|
||||
Use: "add <name> [path]",
|
||||
Short: "Add a depository",
|
||||
Long: "Register a new depository with a short name. If path is not provided, uses current working directory.",
|
||||
Args: cobra.RangeArgs(1, 2),
|
||||
Run: depoAdd,
|
||||
}
|
||||
|
||||
var depoListCmd = &cobra.Command{
|
||||
Use: "list",
|
||||
Short: "List all depositories",
|
||||
Run: depoList,
|
||||
}
|
||||
|
||||
var depoRemoveCmd = &cobra.Command{
|
||||
Use: "remove <name>",
|
||||
Short: "Remove a depository",
|
||||
Args: cobra.ExactArgs(1),
|
||||
Run: depoRemove,
|
||||
}
|
||||
|
||||
var depoSetDefaultCmd = &cobra.Command{
|
||||
Use: "set-default <name>",
|
||||
Short: "Set the default depository",
|
||||
Args: cobra.ExactArgs(1),
|
||||
Run: depoSetDefault,
|
||||
}
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(depoCmd)
|
||||
depoCmd.AddCommand(depoAddCmd)
|
||||
depoCmd.AddCommand(depoListCmd)
|
||||
depoCmd.AddCommand(depoRemoveCmd)
|
||||
depoCmd.AddCommand(depoSetDefaultCmd)
|
||||
}
|
||||
|
||||
func depoAdd(cmd *cobra.Command, args []string) {
|
||||
name := args[0]
|
||||
var path string
|
||||
|
||||
if len(args) == 2 {
|
||||
path = args[1]
|
||||
} else {
|
||||
// Use current working directory
|
||||
cwd, err := os.Getwd()
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error getting current directory: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
path = cwd
|
||||
}
|
||||
|
||||
// Make path absolute
|
||||
absPath, err := filepath.Abs(path)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error resolving path: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Add depository
|
||||
if err := engine.AddDepository(name, absPath); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error adding depository: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
fmt.Printf("Added depository '%s' at %s\n", name, absPath)
|
||||
}
|
||||
|
||||
func depoList(cmd *cobra.Command, args []string) {
|
||||
depos, defaultDepo, err := engine.ListDepositories()
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error listing depositories: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if len(depos) == 0 {
|
||||
fmt.Println("No depositories configured")
|
||||
fmt.Println("Add one with: jade depo add <name> [path]")
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Println("Depositories:")
|
||||
for name, path := range depos {
|
||||
if name == defaultDepo {
|
||||
fmt.Printf(" * %s -> %s (default)\n", name, path)
|
||||
} else {
|
||||
fmt.Printf(" %s -> %s\n", name, path)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func depoRemove(cmd *cobra.Command, args []string) {
|
||||
name := args[0]
|
||||
|
||||
if err := engine.RemoveDepository(name); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error removing depository: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
fmt.Printf("Removed depository '%s'\n", name)
|
||||
}
|
||||
|
||||
func depoSetDefault(cmd *cobra.Command, args []string) {
|
||||
name := args[0]
|
||||
|
||||
if err := engine.SetDefaultDepository(name); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error setting default depository: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
fmt.Printf("Set '%s' as default depository\n", name)
|
||||
}
|
||||
+31
-15
@@ -12,7 +12,8 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
forceDelete bool
|
||||
forceDelete bool
|
||||
permanentDelete bool
|
||||
|
||||
rmCmd = &cobra.Command{
|
||||
Use: "rm [title]",
|
||||
@@ -24,6 +25,7 @@ var (
|
||||
|
||||
func init() {
|
||||
rmCmd.Flags().BoolVarP(&forceDelete, "force", "f", false, "Skip confirmation prompt")
|
||||
rmCmd.Flags().BoolVarP(&permanentDelete, "permanent", "p", false, "Delete permanently")
|
||||
rootCmd.AddCommand(rmCmd)
|
||||
}
|
||||
|
||||
@@ -68,9 +70,15 @@ func removeNote(cmd *cobra.Command, args []string) {
|
||||
noteToDelete = matches[selection-1]
|
||||
}
|
||||
|
||||
notePath := filepath.Join(jd.Config.DepoPath, noteToDelete.Path)
|
||||
|
||||
// Confirm deletion
|
||||
if !forceDelete {
|
||||
fmt.Printf("Are you sure you want to delete '%s'? (y/N): ", noteToDelete.Title)
|
||||
if permanentDelete {
|
||||
fmt.Printf("Are you sure you want to PERMANENTLY delete '%s'? This cannot be undone. (y/N): ", noteToDelete.Title)
|
||||
} else {
|
||||
fmt.Printf("Are you sure you want to move '%s' to trash? (y/N): ", noteToDelete.Title)
|
||||
}
|
||||
var confirm string
|
||||
fmt.Scanln(&confirm)
|
||||
if strings.ToLower(confirm) != "y" && strings.ToLower(confirm) != "yes" {
|
||||
@@ -79,20 +87,28 @@ func removeNote(cmd *cobra.Command, args []string) {
|
||||
}
|
||||
}
|
||||
|
||||
// Move to trash
|
||||
notePath := filepath.Join(jd.Config.DepoPath, noteToDelete.Path)
|
||||
trashPath := jd.GetTrashPath()
|
||||
if permanentDelete {
|
||||
// Permanently delete the file
|
||||
if err := os.Remove(notePath); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error deleting note: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Printf("Permanently deleted '%s'\n", noteToDelete.Title)
|
||||
} else {
|
||||
// Move to trash
|
||||
trashPath := jd.GetTrashPath()
|
||||
|
||||
// Create unique filename in trash (add timestamp to avoid conflicts)
|
||||
timestamp := time.Now().Format("20060102-150405")
|
||||
trashFilename := fmt.Sprintf("%s-%s", timestamp, filepath.Base(noteToDelete.Path))
|
||||
trashDestination := filepath.Join(trashPath, trashFilename)
|
||||
// Create unique filename in trash (add timestamp to avoid conflicts)
|
||||
timestamp := time.Now().Format("20060102-150405")
|
||||
trashFilename := fmt.Sprintf("%s-%s", timestamp, filepath.Base(noteToDelete.Path))
|
||||
trashDestination := filepath.Join(trashPath, trashFilename)
|
||||
|
||||
if err := os.Rename(notePath, trashDestination); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error moving note to trash: %v\n", err)
|
||||
os.Exit(1)
|
||||
if err := os.Rename(notePath, trashDestination); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error moving note to trash: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
fmt.Printf("Moved '%s' to trash\n", noteToDelete.Title)
|
||||
fmt.Printf("Trash location: %s\n", trashDestination)
|
||||
}
|
||||
|
||||
fmt.Printf("Moved '%s' to trash\n", noteToDelete.Title)
|
||||
fmt.Printf("Trash location: %s\n", trashDestination)
|
||||
}
|
||||
|
||||
+16
-10
@@ -17,21 +17,27 @@ var (
|
||||
)
|
||||
|
||||
var (
|
||||
cfgPath string
|
||||
depoPath string
|
||||
depoInput string
|
||||
)
|
||||
|
||||
func init() {
|
||||
cobra.OnInitialize(func() {
|
||||
if _, err := engine.Init(cfgPath, depoPath); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error initializing configuration: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
})
|
||||
cobra.OnInitialize(initializeIfNeeded)
|
||||
rootCmd.PersistentFlags().StringVar(&depoInput, "depo", "", "Depository name or path")
|
||||
}
|
||||
|
||||
rootCmd.PersistentFlags().StringVar(&cfgPath, "config", "", "Configuration file path. Default <depo>/.jade/config.yml")
|
||||
rootCmd.PersistentFlags().StringVar(&depoPath, "depo", "", "Depository path. Default $HOME/jade-depository")
|
||||
// initializeIfNeeded initializes the depository only for commands that need it
|
||||
func initializeIfNeeded() {
|
||||
// Skip initialization for depo management commands
|
||||
cmd := os.Args
|
||||
if len(cmd) > 1 && cmd[1] == "depo" {
|
||||
return
|
||||
}
|
||||
|
||||
// Initialize for all other commands
|
||||
if _, err := engine.Init(depoInput); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error initializing configuration: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func openDepository(cmd *cobra.Command, args []string) {
|
||||
|
||||
+10
-23
@@ -10,8 +10,6 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
searchTag string
|
||||
|
||||
searchCmd = &cobra.Command{
|
||||
Use: "search [query]",
|
||||
Short: "Search notes by content or tags",
|
||||
@@ -20,7 +18,6 @@ var (
|
||||
)
|
||||
|
||||
func init() {
|
||||
searchCmd.Flags().StringVarP(&searchTag, "tag", "t", "", "Search by tag")
|
||||
rootCmd.AddCommand(searchCmd)
|
||||
}
|
||||
|
||||
@@ -33,25 +30,15 @@ func searchNotes(cmd *cobra.Command, args []string) {
|
||||
|
||||
var results []string
|
||||
|
||||
// Search by tag if flag is provided
|
||||
if searchTag != "" {
|
||||
results, err = jd.SearchByTag(searchTag)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error searching by tag: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
} else {
|
||||
// Search by content
|
||||
if len(args) == 0 {
|
||||
fmt.Fprintf(os.Stderr, "Error: query required when not searching by tag\n")
|
||||
os.Exit(1)
|
||||
}
|
||||
query := strings.Join(args, " ")
|
||||
results, err = jd.SearchContent(query)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error searching: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
if len(args) == 0 {
|
||||
fmt.Fprintf(os.Stderr, "Error: query required when not searching by tag\n")
|
||||
os.Exit(1)
|
||||
}
|
||||
query := strings.Join(args, " ")
|
||||
results, err = jd.SearchContent(query)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error searching: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if len(results) == 0 {
|
||||
@@ -62,7 +49,7 @@ func searchNotes(cmd *cobra.Command, args []string) {
|
||||
fmt.Printf("Found %d match(es):\n\n", len(results))
|
||||
for _, path := range results {
|
||||
// Load note to get title
|
||||
note, err := engine.LoadNote(jd.Config.DepoPath, path, jd.Config.TagPrefix)
|
||||
note, err := engine.LoadNote(jd.Config.DepoPath, path)
|
||||
if err != nil {
|
||||
fmt.Printf(" %s\n", path)
|
||||
continue
|
||||
|
||||
Reference in New Issue
Block a user