diff --git a/.gitignore b/.gitignore index 9de30db..184a49f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,3 @@ .direnv result -tortu +stinkpot diff --git a/flake.nix b/flake.nix index e5b7305..e293d38 100644 --- a/flake.nix +++ b/flake.nix @@ -10,12 +10,12 @@ supportedSystems = ["x86_64-linux" "aarch64-linux" "x86_64-darwin" "aarch64-darwin"]; forAllSystems = f: nixpkgs.lib.genAttrs supportedSystems (system: f nixpkgs.legacyPackages.${system}); - tortuPackage = { + stinkpotPackage = { lib, buildGoModule, }: buildGoModule { - pname = "tortu"; + pname = "stinkpot"; version = "0.1.0"; src = lib.cleanSource ./.; vendorHash = "sha256-iDlU/176inkilehXft25KjiLt7rUtlMGqod22A3O/ko="; @@ -23,7 +23,7 @@ ldflags = ["-s" "-w"]; meta = { description = "sqlite-backed shell history"; - mainProgram = "tortu"; + mainProgram = "stinkpot"; platforms = lib.platforms.unix; }; }; @@ -31,34 +31,34 @@ formatter = forAllSystems (pkgs: pkgs.alejandra); packages = forAllSystems (pkgs: rec { - tortu = pkgs.callPackage tortuPackage {}; - default = tortu; + stinkpot = pkgs.callPackage stinkpotPackage {}; + default = stinkpot; }); overlays.default = final: _prev: { - tortu = final.callPackage tortuPackage {}; + stinkpot = final.callPackage stinkpotPackage {}; }; - homeManagerModules.tortu = { + homeManagerModules.stinkpot = { config, lib, pkgs, ... }: let - cfg = config.programs.tortu; + cfg = config.programs.stinkpot; in { - options.programs.tortu = { - enable = lib.mkEnableOption "tortu, a tiny sqlite-backed shell history"; + options.programs.stinkpot = { + enable = lib.mkEnableOption "stinkpot, a tiny sqlite-backed shell history"; package = lib.mkOption { type = lib.types.package; default = self.packages.${pkgs.stdenv.hostPlatform.system}.default; - description = "The tortu package to use."; + description = "The stinkpot package to use."; }; enableBashIntegration = lib.hm.shell.mkBashIntegrationOption { inherit config; - extraDescription = "Bind `ctrl-r` to open the tortu search and record commands into tortu."; + extraDescription = "Bind `ctrl-r` to open the stinkpot search and record commands into stinkpot."; }; }; @@ -70,7 +70,7 @@ ''; }; }; - homeManagerModules.default = self.homeManagerModules.tortu; + homeManagerModules.default = self.homeManagerModules.stinkpot; devShells = forAllSystems (pkgs: { default = pkgs.mkShell { diff --git a/go.mod b/go.mod index f7bff96..28dc72a 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module tortu +module stinkpot go 1.26.4 diff --git a/main.go b/main.go index 22a842c..2e98acc 100644 --- a/main.go +++ b/main.go @@ -37,7 +37,7 @@ func dbDir() string { dir = filepath.Join(os.Getenv("HOME"), ".local", "share") } - dir = filepath.Join(dir, "tortu") + dir = filepath.Join(dir, "stinkpot") return dir } @@ -125,7 +125,7 @@ func add(args []string) { db, err := open(dbPath()) if err != nil { - fmt.Fprintln(os.Stderr, "tortu:", err) + fmt.Fprintln(os.Stderr, "stinkpot:", err) return } defer db.Close() @@ -139,9 +139,9 @@ func add(args []string) { exit = excluded.exit, ts = excluded.ts, session = excluded.session`, - cmd, cwd, *exit, time.Now().Unix(), os.Getenv("TORTU_SESSION"), + cmd, cwd, *exit, time.Now().Unix(), os.Getenv("stinkpot_SESSION"), ); err != nil { - fmt.Fprintln(os.Stderr, "tortu:", err) + fmt.Fprintln(os.Stderr, "stinkpot:", err) } } @@ -152,13 +152,13 @@ func search(args []string) { db, err := open(dbPath()) if err != nil { - fmt.Fprintln(os.Stderr, "tortu:", err) + fmt.Fprintln(os.Stderr, "stinkpot:", err) os.Exit(1) } cands, err := loadCandidates(db) db.Close() if err != nil { - fmt.Fprintln(os.Stderr, "tortu:", err) + fmt.Fprintln(os.Stderr, "stinkpot:", err) os.Exit(1) } @@ -176,7 +176,7 @@ func search(args []string) { p := tea.NewProgram(m, tea.WithOutput(os.Stderr), tea.WithInput(os.Stdin), tea.WithAltScreen()) res, err := p.Run() if err != nil { - fmt.Fprintln(os.Stderr, "tortu:", err) + fmt.Fprintln(os.Stderr, "stinkpot:", err) os.Exit(1) } if fm, ok := res.(*model); ok && fm.selected != "" { @@ -187,20 +187,20 @@ func search(args []string) { func list(_ []string) { db, err := open(dbPath()) if err != nil { - fmt.Fprintln(os.Stderr, "tortu:", err) + fmt.Fprintln(os.Stderr, "stinkpot:", err) os.Exit(1) } defer db.Close() rows, err := db.Query(`select ts, exit, cmd from history order by id desc limit 50`) if err != nil { - fmt.Fprintln(os.Stderr, "tortu:", err) + fmt.Fprintln(os.Stderr, "stinkpot:", err) os.Exit(1) } defer rows.Close() for rows.Next() { if rows.Err() != nil { - fmt.Fprintln(os.Stderr, "tortu:", err) + fmt.Fprintln(os.Stderr, "stinkpot:", err) os.Exit(1) } var ts int64 @@ -227,21 +227,21 @@ func importBash(args []string) { f, err := os.Open(path) if err != nil { - fmt.Fprintln(os.Stderr, "tortu:", err) + fmt.Fprintln(os.Stderr, "stinkpot:", err) os.Exit(1) } defer f.Close() db, err := open(dbPath()) if err != nil { - fmt.Fprintln(os.Stderr, "tortu:", err) + fmt.Fprintln(os.Stderr, "stinkpot:", err) os.Exit(1) } defer db.Close() tx, err := db.Begin() if err != nil { - fmt.Fprintln(os.Stderr, "tortu:", err) + fmt.Fprintln(os.Stderr, "stinkpot:", err) os.Exit(1) } @@ -250,7 +250,7 @@ func importBash(args []string) { on conflict(cmd) do update set ts = max(ts, excluded.ts)`) if err != nil { _ = tx.Rollback() - fmt.Fprintln(os.Stderr, "tortu:", err) + fmt.Fprintln(os.Stderr, "stinkpot:", err) os.Exit(1) } defer stmt.Close() @@ -278,7 +278,7 @@ func importBash(args []string) { if _, err := stmt.Exec(cmd, "", 0, ts, ""); err != nil { _ = tx.Rollback() - fmt.Fprintln(os.Stderr, "tortu:", err) + fmt.Fprintln(os.Stderr, "stinkpot:", err) os.Exit(1) } @@ -288,16 +288,16 @@ func importBash(args []string) { if err := sc.Err(); err != nil { _ = tx.Rollback() - fmt.Fprintln(os.Stderr, "tortu:", err) + fmt.Fprintln(os.Stderr, "stinkpot:", err) os.Exit(1) } if err := tx.Commit(); err != nil { - fmt.Fprintln(os.Stderr, "tortu:", err) + fmt.Fprintln(os.Stderr, "stinkpot:", err) os.Exit(1) } - fmt.Fprintf(os.Stderr, "tortu: imported %d commands from %s\n", n, path) + fmt.Fprintf(os.Stderr, "stinkpot: imported %d commands from %s\n", n, path) } // ui @@ -436,32 +436,32 @@ func (m *model) View() string { } // bash side of things -const initScript = `# run eval "$(tortu init)" at startup -__tortu_record() { +const initScript = `# run eval "$(stinkpot init)" at startup +__stinkpot_record() { local exit=$? local cmd cmd=$(HISTTIMEFORMAT='' history 1 | sed '1 s/^[[:space:]]*[0-9]\{1,\}[[:space:]]*//') - [ -n "$cmd" ] && tortu add --exit "$exit" -- "$cmd" + [ -n "$cmd" ] && stinkpot add --exit "$exit" -- "$cmd" } case "$PROMPT_COMMAND" in - *__tortu_record*) ;; - *) PROMPT_COMMAND="__tortu_record${PROMPT_COMMAND:+; $PROMPT_COMMAND}" ;; + *__stinkpot_record*) ;; + *) PROMPT_COMMAND="__stinkpot_record${PROMPT_COMMAND:+; $PROMPT_COMMAND}" ;; esac -__tortu_search() { +__stinkpot_search() { local out - out=$(tortu search -- "$READLINE_LINE") || return + out=$(stinkpot search -- "$READLINE_LINE") || return if [ -n "$out" ]; then READLINE_LINE="$out" READLINE_POINT=${#READLINE_LINE} fi } -bind -x '"\C-r": __tortu_search' +bind -x '"\C-r": __stinkpot_search' ` func main() { if len(os.Args) < 2 { - fmt.Fprintln(os.Stderr, "usage: tortu ") + fmt.Fprintln(os.Stderr, "usage: stinkpot ") os.Exit(1) } @@ -477,7 +477,7 @@ func main() { case "import": importBash(os.Args[2:]) default: - fmt.Fprintf(os.Stderr, "tortu: unknown command %q\n", os.Args[1]) + fmt.Fprintf(os.Stderr, "stinkpot: unknown command %q\n", os.Args[1]) os.Exit(1) } } diff --git a/readme.txt b/readme.txt new file mode 100644 index 0000000..cd7c2c0 --- /dev/null +++ b/readme.txt @@ -0,0 +1,75 @@ +stinkpot +-------- + _ + _ / \ _ + (_\/\_/\/_) + /\/ \/\ + \/\_/\/ + \/_\/ + " " + +stinkpot is an sqlite backed shell-history searcher for +bash. + +stinkpot is a much tinier atuin (https://atuin.sh). i have +been using atuin for a while, but i don't use most of its +features: the sync server, atuin AI, dotfiles manager, +script manager or the KV store. while sync is interesting, +my machines have very different setups and i tend to run +very different commands on them. + +my only usecase for atuin was the session agnostic history +management, and the searcher TUI. stinkpot provides these +while being about 400 lines of go. + +stinkpot is a tiny turtle species apparently, hence the +name. + +develop +------- + +to build: + + go build -o stinkpot . + +usage +----- + +if you use home-manager for nixos, stinkpot has a +home-manager module: + + home-manager.users. = { + imports = [ + ./home.nix + inputs.stinkpot.homeManagerModules.default + ]; + }; + + +if not, just call eval the init script in your .bashrc: + + eval "$(stinkpot init)" + + +start by importing your existing bash history into stinkpot: + + stinkpot import + + +and then hit ctrl+r at your shell to trigger reverse search: + + > search history... + 1s vim flake.nix + 3m redis-server + 3m jj l + 35m jj show m + 36m jj git fetch + 39m cd + 40m vim readme.txt + 788 matches · ↑/↓ move · enter accept · esc cancel + + +hit tab/enter to accept the selection, hit enter again to +execute. the history database is stored in +~/.local/share/stinkpot. if upgrades break the DB, just +delete it and run the import command.