- Add core CLI functionality (save, run, update, delete, list, show, search)
- Implement JSON-based storage in ~/.tolo/tolo.db.json
- Add beautiful terminal UI with colors and icons
- Support command shortcuts (s, r, u, d, ls, l, sh, se, h, v)
- Add Bash and Zsh shell completion
- Include comprehensive documentation (README, CONTRIBUTING, SECURITY)
- Set up CI/CD workflows with GitHub Actions
- Add installation script and Makefile for build automation
- MIT License
Made with ❤️ at Zemenawi Lab
177 lines
3 KiB
Go
177 lines
3 KiB
Go
package storage
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
const configDir = ".tolo"
|
|
const dbFile = "tolo.db.json"
|
|
|
|
type Alias struct {
|
|
Name string `json:"name"`
|
|
Command string `json:"command"`
|
|
CreatedAt string `json:"created_at"`
|
|
}
|
|
|
|
type Data struct {
|
|
Aliases []Alias `json:"aliases"`
|
|
mu sync.RWMutex
|
|
}
|
|
|
|
var data *Data
|
|
var dataFile string
|
|
|
|
func init() {
|
|
homeDir, err := os.UserHomeDir()
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
dataFile = filepath.Join(homeDir, configDir, dbFile)
|
|
data = &Data{}
|
|
load()
|
|
}
|
|
|
|
func load() error {
|
|
data.mu.Lock()
|
|
defer data.mu.Unlock()
|
|
|
|
if err := os.MkdirAll(filepath.Dir(dataFile), 0755); err != nil {
|
|
return err
|
|
}
|
|
|
|
content, err := os.ReadFile(dataFile)
|
|
if err != nil {
|
|
if os.IsNotExist(err) {
|
|
return nil
|
|
}
|
|
return err
|
|
}
|
|
|
|
if len(content) == 0 {
|
|
return nil
|
|
}
|
|
|
|
return json.Unmarshal(content, data)
|
|
}
|
|
|
|
func saveLocked() error {
|
|
content, err := json.MarshalIndent(data, "", " ")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return os.WriteFile(dataFile, content, 0644)
|
|
}
|
|
|
|
func save() error {
|
|
data.mu.Lock()
|
|
defer data.mu.Unlock()
|
|
return saveLocked()
|
|
}
|
|
|
|
func AddAlias(name, command string) error {
|
|
data.mu.Lock()
|
|
defer data.mu.Unlock()
|
|
|
|
for _, a := range data.Aliases {
|
|
if a.Name == name {
|
|
return fmt.Errorf("alias '%s' already exists", name)
|
|
}
|
|
}
|
|
|
|
data.Aliases = append(data.Aliases, Alias{
|
|
Name: name,
|
|
Command: command,
|
|
CreatedAt: time.Now().Format(time.RFC3339),
|
|
})
|
|
|
|
return saveLocked()
|
|
}
|
|
|
|
func GetAlias(name string) (Alias, bool) {
|
|
data.mu.RLock()
|
|
defer data.mu.RUnlock()
|
|
|
|
for _, a := range data.Aliases {
|
|
if a.Name == name {
|
|
return a, true
|
|
}
|
|
}
|
|
return Alias{}, false
|
|
}
|
|
|
|
func UpdateAlias(name, command string) error {
|
|
data.mu.Lock()
|
|
defer data.mu.Unlock()
|
|
|
|
for i, a := range data.Aliases {
|
|
if a.Name == name {
|
|
data.Aliases[i].Command = command
|
|
return saveLocked()
|
|
}
|
|
}
|
|
return fmt.Errorf("alias '%s' not found", name)
|
|
}
|
|
|
|
func DeleteAlias(name string) error {
|
|
data.mu.Lock()
|
|
defer data.mu.Unlock()
|
|
|
|
for i, a := range data.Aliases {
|
|
if a.Name == name {
|
|
data.Aliases = append(data.Aliases[:i], data.Aliases[i+1:]...)
|
|
return saveLocked()
|
|
}
|
|
}
|
|
return fmt.Errorf("alias '%s' not found", name)
|
|
}
|
|
|
|
func ListAliases() []Alias {
|
|
data.mu.RLock()
|
|
defer data.mu.RUnlock()
|
|
|
|
return data.Aliases
|
|
}
|
|
|
|
func ShowAlias(name string) (Alias, bool) {
|
|
data.mu.RLock()
|
|
defer data.mu.RUnlock()
|
|
|
|
for _, a := range data.Aliases {
|
|
if a.Name == name {
|
|
return a, true
|
|
}
|
|
}
|
|
return Alias{}, false
|
|
}
|
|
|
|
func SearchAliases(query string) []Alias {
|
|
data.mu.RLock()
|
|
defer data.mu.RUnlock()
|
|
|
|
var results []Alias
|
|
for _, a := range data.Aliases {
|
|
if contains(a.Name, query) || contains(a.Command, query) {
|
|
results = append(results, a)
|
|
}
|
|
}
|
|
return results
|
|
}
|
|
|
|
func contains(s, substr string) bool {
|
|
return len(s) >= len(substr) && (s == substr || findSubstring(s, substr))
|
|
}
|
|
|
|
func findSubstring(s, substr string) bool {
|
|
for i := 0; i <= len(s)-len(substr); i++ {
|
|
if s[i:i+len(substr)] == substr {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|