From 1d87d931720804baec6a80ff2a273da33f39a00b Mon Sep 17 00:00:00 2001 From: Joakim Date: Sat, 3 Jan 2026 16:25:23 +0100 Subject: [PATCH] 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 --- jade-depo/README.md | 79 +++++-- jade-depo/cmd/depo.go | 128 +++++++++++ jade-depo/cmd/rm.go | 46 ++-- jade-depo/cmd/root.go | 26 ++- jade-depo/cmd/search.go | 33 +-- jade-depo/internal/engine/config.go | 322 ++++++++++++++++++++++------ jade-depo/internal/engine/jade.go | 6 +- jade-depo/internal/engine/note.go | 18 +- jade-depo/internal/engine/search.go | 15 +- 9 files changed, 515 insertions(+), 158 deletions(-) create mode 100644 jade-depo/cmd/depo.go diff --git a/jade-depo/README.md b/jade-depo/README.md index 39ad41e..5046d5b 100644 --- a/jade-depo/README.md +++ b/jade-depo/README.md @@ -4,12 +4,13 @@ A simple CLI tool for managing markdown notes in a depository (vault). Inspired ## Features +- **Multi-Depository Support**: Manage multiple note repositories with short names - **Note Management**: Create, edit, list, and delete markdown notes -- **Tag Support**: Extract and search notes by tags (default `+tag` syntax, configurable) +- **Tag Support**: Extract and search notes by tags (`+tag` syntax) - **Full-Text Search**: Search note content using ripgrep - **Link Support**: Parse both `[[wiki-style]]` and `[markdown](links)` - **Trash System**: Deleted notes moved to `.jade/trash/` instead of permanent deletion -- **Flexible Configuration**: Per-depository settings for tag prefix and other options +- **Context-Aware**: Automatically detects which depository you're in ## Installation @@ -19,24 +20,53 @@ go build -o jade ## Usage +### Managing Depositories + +Before you can use jade, you need to add at least one depository: + +```bash +# Add a depository with a short name +jade depo add personal ~/my-notes + +# Add depository using current directory +cd ~/work/meeting-notes +jade depo add work + +# List all depositories +jade depo list + +# Set default depository +jade depo set-default personal + +# Remove a depository (doesn't delete files) +jade depo remove work +``` + ### Open Depository ```bash -# Open the depository directory in your $EDITOR +# Open the default depository in your $EDITOR jade -# This opens ~/jade-depository (or your configured path) in your editor -# Works great with neovim, vim, or any editor that supports directory opening +# Open a specific depository +jade --depo work ``` -### Initialize and List Notes +### List Notes ```bash -# List all notes in the default depository (~/jade-depository) +# List all notes in the default depository jade list -# Use a specific depository +# Use a specific depository by name +jade --depo work list + +# Or by path jade --depo /path/to/notes list + +# Context-aware: if you're inside a registered depository, jade uses it automatically +cd ~/my-notes +jade list # Uses the depository that contains current directory ``` ### Create a Note @@ -87,24 +117,33 @@ jade tags ## Configuration -Configuration is stored in `/.jade/config.yml` and is created automatically when you first run jade. +Jade uses a two-level configuration system: + +### Global CLI Config (`~/.config/jade/config.yml`) + +Manages your depositories and default settings: -Example config: ```yaml -depo_path: /home/user/jade-depository -tag_prefix: "+" +depositories: + personal: /home/user/my-notes + work: /home/user/work/notes +default_depository: personal ``` -You can override these with flags: -- `--depo`: Depository path -- `--config`: Config file path (if you need to override the default location) +This file is automatically created and managed by `jade depo` commands. + +### Per-Depository Structure + +Each depository contains a `.jade/` directory for metadata: + +- `.jade/trash/` - Deleted notes +- Future: `.jade/index.db` - SQLite index for fast search ## Depository Structure ``` -jade-depository/ +my-notes/ ├── .jade/ -│ ├── config.yml # Configuration file │ └── trash/ # Deleted notes go here ├── note-1.md ├── note-2.md @@ -112,7 +151,7 @@ jade-depository/ └── note-3.md ``` -The `.jade/` directory is automatically created and contains configuration and metadata. You can optionally add it to `.gitignore` if you don't want to track it in version control. +The `.jade/` directory is automatically created and contains metadata. You can optionally add it to `.gitignore` if you don't want to track it in version control, or commit it to sync trash/metadata across devices. ## Note Format @@ -139,9 +178,11 @@ Tags can appear anywhere in +content. ### v1.0 (Current) - [x] Basic note operations (add, edit, delete, list) -- [x] Tag extraction and listing +- [x] Tag extraction and listing (`+tag` syntax) - [x] Content and tag search - [x] Trash system +- [x] Multi-depository support with short names +- [x] Context-aware depository detection ### v1.1 (Future) - [ ] SQLite indexing for faster search diff --git a/jade-depo/cmd/depo.go b/jade-depo/cmd/depo.go new file mode 100644 index 0000000..f03fc62 --- /dev/null +++ b/jade-depo/cmd/depo.go @@ -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 [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 ", + Short: "Remove a depository", + Args: cobra.ExactArgs(1), + Run: depoRemove, +} + +var depoSetDefaultCmd = &cobra.Command{ + Use: "set-default ", + 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 [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) +} diff --git a/jade-depo/cmd/rm.go b/jade-depo/cmd/rm.go index fd5a2a2..6bf82bb 100644 --- a/jade-depo/cmd/rm.go +++ b/jade-depo/cmd/rm.go @@ -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) } diff --git a/jade-depo/cmd/root.go b/jade-depo/cmd/root.go index 3f392fa..882c2f0 100644 --- a/jade-depo/cmd/root.go +++ b/jade-depo/cmd/root.go @@ -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 /.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) { diff --git a/jade-depo/cmd/search.go b/jade-depo/cmd/search.go index 5749e61..092a9c6 100644 --- a/jade-depo/cmd/search.go +++ b/jade-depo/cmd/search.go @@ -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 diff --git a/jade-depo/internal/engine/config.go b/jade-depo/internal/engine/config.go index 3435c56..f44cf47 100644 --- a/jade-depo/internal/engine/config.go +++ b/jade-depo/internal/engine/config.go @@ -8,91 +8,287 @@ import ( "github.com/spf13/viper" ) -type Config struct { - // DepoPath is the path to the jade depository. Default ~/jade-depository - DepoPath string `mapstructure:"depo_path"` - // TagPrefix is the prefix used for tags in notes. Default "+" - TagPrefix string `mapstructure:"tag_prefix"` +// GlobalConfig represents the CLI-level configuration +type GlobalConfig struct { + // Depositories maps depository names to their filesystem paths + Depositories map[string]string `mapstructure:"depositories"` + // DefaultDepository is the name of the default depository to use + DefaultDepository string `mapstructure:"default_depository"` } -// getDefaultDepoPath returns the default depository path -func getDefaultDepoPath() string { +// Config represents the runtime configuration for a specific depository +type Config struct { + // DepoPath is the path to the active jade depository + DepoPath string + // DepoName is the name of the active depository (if registered) + DepoName string +} + +// getGlobalConfigPath returns the path to the global CLI config +func getGlobalConfigPath() string { home, err := os.UserHomeDir() if err != nil { - return "./jade-depository" + return "./.config/jade/config.yml" } - return filepath.Join(home, "jade-depository") + return filepath.Join(home, ".config", "jade", "config.yml") } -// getConfigPath returns the config file path within the depository -func getConfigPath(depoPath string) string { - return filepath.Join(depoPath, ".jade", "config.yml") -} +// loadGlobalConfig loads the global CLI configuration +func loadGlobalConfig() (*GlobalConfig, error) { + configPath := getGlobalConfigPath() + configDir := filepath.Dir(configPath) -// initConfig initializes the configuration using Viper -// Precedence order: flags > environment variables > config file > defaults -func initConfig(cfgPath, depoPath string) (*Config, error) { - // Set up environment variable support - viper.SetEnvPrefix("JADE") - viper.AutomaticEnv() - - // Determine depository path (from flag, env var, or default) - actualDepoPath := depoPath - if actualDepoPath == "" { - actualDepoPath = viper.GetString("depo_path") - if actualDepoPath == "" { - actualDepoPath = getDefaultDepoPath() - } + // Ensure config directory exists + if err := os.MkdirAll(configDir, 0755); err != nil { + return nil, fmt.Errorf("failed to create config directory: %w", err) } + // Create a new viper instance for global config + v := viper.New() + v.SetConfigFile(configPath) + v.SetConfigType("yaml") + // Set defaults - viper.SetDefault("depo_path", actualDepoPath) - viper.SetDefault("tag_prefix", "+") + v.SetDefault("depositories", make(map[string]string)) + v.SetDefault("default_depository", "") - // Ensure depository and .jade directory exist - jadeDir := filepath.Join(actualDepoPath, ".jade") - if err := os.MkdirAll(jadeDir, 0755); err != nil { - return nil, fmt.Errorf("failed to create .jade directory: %w", err) - } - - // Determine config file location - configFilePath := cfgPath - if configFilePath == "" { - configFilePath = getConfigPath(actualDepoPath) - } - - // Set up config file - viper.SetConfigFile(configFilePath) - - // Try to read existing config file - if err := viper.ReadInConfig(); err != nil { - // Check if file doesn't exist (handles both viper.ConfigFileNotFoundError and os.IsNotExist) + // Try to read existing config + if err := v.ReadInConfig(); err != nil { if _, ok := err.(viper.ConfigFileNotFoundError); ok || os.IsNotExist(err) { // Config file not found, create it with defaults - if err := viper.SafeWriteConfigAs(configFilePath); err != nil { - return nil, fmt.Errorf("failed to create config file: %w", err) + if err := v.SafeWriteConfigAs(configPath); err != nil { + return nil, fmt.Errorf("failed to create global config: %w", err) } } else { - // Config file was found but another error occurred - return nil, fmt.Errorf("error reading config file: %w", err) + return nil, fmt.Errorf("error reading global config: %w", err) } } - // Override with flags if provided (highest precedence) - if depoPath != "" { - viper.Set("depo_path", depoPath) + // Unmarshal into struct + cfg := &GlobalConfig{} + if err := v.Unmarshal(cfg); err != nil { + return nil, fmt.Errorf("failed to unmarshal global config: %w", err) } - // Unmarshal config into struct - cfg := &Config{} - if err := viper.Unmarshal(cfg); err != nil { - return nil, fmt.Errorf("failed to unmarshal config: %w", err) - } - - // Write config back to persist any changes (e.g., from flags or env vars) - if err := viper.WriteConfig(); err != nil { - return nil, fmt.Errorf("failed to write config: %w", err) + // Initialize map if nil + if cfg.Depositories == nil { + cfg.Depositories = make(map[string]string) } return cfg, nil } + +// saveGlobalConfig saves the global CLI configuration +func saveGlobalConfig(cfg *GlobalConfig) error { + configPath := getGlobalConfigPath() + configDir := filepath.Dir(configPath) + + // Ensure config directory exists + if err := os.MkdirAll(configDir, 0755); err != nil { + return fmt.Errorf("failed to create config directory: %w", err) + } + + // Create a new viper instance + v := viper.New() + v.SetConfigFile(configPath) + v.SetConfigType("yaml") + + // Set values + v.Set("depositories", cfg.Depositories) + v.Set("default_depository", cfg.DefaultDepository) + + // Write config + if err := v.WriteConfig(); err != nil { + // If file doesn't exist, use SafeWriteConfigAs + if os.IsNotExist(err) { + if err := v.SafeWriteConfigAs(configPath); err != nil { + return fmt.Errorf("failed to write config file: %w", err) + } + } else { + return fmt.Errorf("failed to write config file: %w", err) + } + } + + return nil +} + +// resolveDepository resolves a depository name or path to an absolute path +// Priority: 1) depoInput (name or path), 2) context (PWD), 3) global default +func resolveDepository(globalCfg *GlobalConfig, depoInput string) (path string, name string, err error) { + // 1. If depoInput is provided, check if it's a name or path + if depoInput != "" { + // Check if it's a registered name + if depoPath, exists := globalCfg.Depositories[depoInput]; exists { + return depoPath, depoInput, nil + } + // Otherwise treat it as a literal path + absPath, err := filepath.Abs(depoInput) + if err != nil { + return "", "", fmt.Errorf("invalid path: %w", err) + } + // Check if this path is registered under a name + for name, path := range globalCfg.Depositories { + if path == absPath { + return absPath, name, nil + } + } + return absPath, "", nil + } + + // 2. Check if current working directory is inside a registered depository + cwd, err := os.Getwd() + if err == nil { + for name, depoPath := range globalCfg.Depositories { + absDepoPath, err := filepath.Abs(depoPath) + if err != nil { + continue + } + // Check if cwd is inside this depository + relPath, err := filepath.Rel(absDepoPath, cwd) + if err == nil && !filepath.IsAbs(relPath) && len(relPath) > 0 && relPath[0] != '.' { + return absDepoPath, name, nil + } + // Check if cwd exactly matches the depository + if cwd == absDepoPath { + return absDepoPath, name, nil + } + } + } + + // 3. Use default depository from global config + if globalCfg.DefaultDepository != "" { + if depoPath, exists := globalCfg.Depositories[globalCfg.DefaultDepository]; exists { + return depoPath, globalCfg.DefaultDepository, nil + } + } + + // No depository found + return "", "", fmt.Errorf("no depository configured. Run 'jade depo add [path]' to add one") +} + +// initConfig initializes the configuration for a specific depository +func initConfig(depoInput string) (*Config, error) { + // Load global config + globalCfg, err := loadGlobalConfig() + if err != nil { + return nil, fmt.Errorf("failed to load global config: %w", err) + } + + // Resolve depository + depoPath, depoName, err := resolveDepository(globalCfg, depoInput) + if err != nil { + return nil, err + } + + // Ensure depository and .jade directory exist + jadeDir := filepath.Join(depoPath, ".jade") + if err := os.MkdirAll(jadeDir, 0755); err != nil { + return nil, fmt.Errorf("failed to create .jade directory: %w", err) + } + + // Create trash directory + trashDir := filepath.Join(jadeDir, "trash") + if err := os.MkdirAll(trashDir, 0755); err != nil { + return nil, fmt.Errorf("failed to create trash directory: %w", err) + } + + return &Config{ + DepoPath: depoPath, + DepoName: depoName, + }, nil +} + +// AddDepository adds a new depository to the global config +func AddDepository(name, path string) error { + globalCfg, err := loadGlobalConfig() + if err != nil { + return fmt.Errorf("failed to load global config: %w", err) + } + + // Check if name already exists + if _, exists := globalCfg.Depositories[name]; exists { + return fmt.Errorf("depository '%s' already exists", name) + } + + // Make path absolute + absPath, err := filepath.Abs(path) + if err != nil { + return fmt.Errorf("invalid path: %w", err) + } + + // Initialize depository structure + jadeDir := filepath.Join(absPath, ".jade") + if err := os.MkdirAll(jadeDir, 0755); err != nil { + return fmt.Errorf("failed to create .jade directory: %w", err) + } + + trashDir := filepath.Join(jadeDir, "trash") + if err := os.MkdirAll(trashDir, 0755); err != nil { + return fmt.Errorf("failed to create trash directory: %w", err) + } + + // Add to config + globalCfg.Depositories[name] = absPath + + // If this is the first depository, make it default + if globalCfg.DefaultDepository == "" { + globalCfg.DefaultDepository = name + } + + return saveGlobalConfig(globalCfg) +} + +// ListDepositories returns all depositories and the default one +func ListDepositories() (map[string]string, string, error) { + globalCfg, err := loadGlobalConfig() + if err != nil { + return nil, "", fmt.Errorf("failed to load global config: %w", err) + } + + return globalCfg.Depositories, globalCfg.DefaultDepository, nil +} + +// RemoveDepository removes a depository from the global config +func RemoveDepository(name string) error { + globalCfg, err := loadGlobalConfig() + if err != nil { + return fmt.Errorf("failed to load global config: %w", err) + } + + // Check if depository exists + if _, exists := globalCfg.Depositories[name]; !exists { + return fmt.Errorf("depository '%s' not found", name) + } + + // Remove from map + delete(globalCfg.Depositories, name) + + // If this was the default, clear default + if globalCfg.DefaultDepository == name { + globalCfg.DefaultDepository = "" + // Set first remaining depository as default if any exist + for depoName := range globalCfg.Depositories { + globalCfg.DefaultDepository = depoName + break + } + } + + return saveGlobalConfig(globalCfg) +} + +// SetDefaultDepository sets the default depository +func SetDefaultDepository(name string) error { + globalCfg, err := loadGlobalConfig() + if err != nil { + return fmt.Errorf("failed to load global config: %w", err) + } + + // Check if depository exists + if _, exists := globalCfg.Depositories[name]; !exists { + return fmt.Errorf("depository '%s' not found", name) + } + + globalCfg.DefaultDepository = name + + return saveGlobalConfig(globalCfg) +} diff --git a/jade-depo/internal/engine/jade.go b/jade-depo/internal/engine/jade.go index c2a851d..f4d11e1 100644 --- a/jade-depo/internal/engine/jade.go +++ b/jade-depo/internal/engine/jade.go @@ -12,8 +12,8 @@ type JadeDepo struct { Config *Config } -func Init(cfgPath, depoPath string) (*JadeDepo, error) { - cfg, err := initConfig(cfgPath, depoPath) +func Init(depoInput string) (*JadeDepo, error) { + cfg, err := initConfig(depoInput) if err != nil { return nil, fmt.Errorf("failed to initialize config: %w", err) } @@ -22,7 +22,7 @@ func Init(cfgPath, depoPath string) (*JadeDepo, error) { Config: cfg, } - // Initialize depository structure + // Initialize depository structure (already done in initConfig, but ensure it's complete) if err := jd.initDepoStructure(); err != nil { return nil, fmt.Errorf("failed to initialize depository structure: %w", err) } diff --git a/jade-depo/internal/engine/note.go b/jade-depo/internal/engine/note.go index d72abdd..3bbbf69 100644 --- a/jade-depo/internal/engine/note.go +++ b/jade-depo/internal/engine/note.go @@ -20,7 +20,7 @@ type Note struct { } // LoadNote reads a note from the filesystem and parses its content -func LoadNote(depoPath, notePath string, tagPrefix string) (*Note, error) { +func LoadNote(depoPath, notePath string) (*Note, error) { fullPath := filepath.Join(depoPath, notePath) // Get file info @@ -48,8 +48,8 @@ func LoadNote(depoPath, notePath string, tagPrefix string) (*Note, error) { note.Title = strings.TrimSuffix(filepath.Base(notePath), filepath.Ext(notePath)) } - // Parse tags - note.Tags = parseTags(string(content), tagPrefix) + // Parse tags (hardcoded to + prefix) + note.Tags = parseTags(string(content)) // Parse links note.Links = parseLinks(string(content)) @@ -69,14 +69,10 @@ func parseTitle(content string) string { return "" } -// parseTags extracts tags from markdown content based on the tag prefix -func parseTags(content string, tagPrefix string) []string { - // Escape special regex characters in tagPrefix - escapedPrefix := regexp.QuoteMeta(tagPrefix) - - // Match tagPrefix followed by word characters - pattern := escapedPrefix + `\w+` - re := regexp.MustCompile(pattern) +// parseTags extracts tags from markdown content using + prefix +func parseTags(content string) []string { + // Match + followed by word characters + re := regexp.MustCompile(`\+\w+`) matches := re.FindAllString(content, -1) diff --git a/jade-depo/internal/engine/search.go b/jade-depo/internal/engine/search.go index a8f5b2f..0ff4507 100644 --- a/jade-depo/internal/engine/search.go +++ b/jade-depo/internal/engine/search.go @@ -5,7 +5,6 @@ import ( "os" "os/exec" "path/filepath" - "regexp" "strings" ) @@ -52,18 +51,6 @@ func (jd *JadeDepo) SearchContent(query string) ([]string, error) { return results, nil } -// SearchByTag searches for notes containing a specific tag -func (jd *JadeDepo) SearchByTag(tag string) ([]string, error) { - // Ensure tag has prefix - if !strings.HasPrefix(tag, jd.Config.TagPrefix) { - tag = jd.Config.TagPrefix + tag - } - - // Use ripgrep to find the tag (escape special regex characters) - escapedTag := regexp.QuoteMeta(tag) - return jd.SearchContent(escapedTag) -} - // ListAllNotes returns all markdown notes in the depository func (jd *JadeDepo) ListAllNotes() ([]*Note, error) { var notes []*Note @@ -85,7 +72,7 @@ func (jd *JadeDepo) ListAllNotes() ([]*Note, error) { return err } - note, err := LoadNote(jd.Config.DepoPath, relPath, jd.Config.TagPrefix) + note, err := LoadNote(jd.Config.DepoPath, relPath) if err != nil { // Log error but continue fmt.Fprintf(os.Stderr, "Warning: failed to load note %s: %v\n", relPath, err)