hishtory/client/cmd/query.go

144 lines
4.7 KiB
Go

package cmd
import (
"context"
"fmt"
"math/rand"
"strings"
"github.com/ddworken/hishtory/client/hctx"
"github.com/ddworken/hishtory/client/lib"
"github.com/ddworken/hishtory/client/tui"
"github.com/spf13/cobra"
)
var EXAMPLE_QUERIES string = `Example queries:
'hishtory SUBCOMMAND apt-get' # Find shell commands containing 'apt-get'
'hishtory SUBCOMMAND apt-get install' # Find shell commands containing 'apt-get' and 'install'
'hishtory SUBCOMMAND curl cwd:/tmp/' # Find shell commands containing 'curl' run in '/tmp/'
'hishtory SUBCOMMAND curl user:david' # Find shell commands containing 'curl' run by 'david'
'hishtory SUBCOMMAND curl host:x1' # Find shell commands containing 'curl' run on 'x1'
'hishtory SUBCOMMAND exit_code:1' # Find shell commands that exited with status code 1
'hishtory SUBCOMMAND before:2022-02-01' # Find shell commands run before 2022-02-01
`
var GROUP_ID_QUERYING string = "group_id:querying"
var queryCmd = &cobra.Command{
Use: "query",
Short: "Query your shell history and display the results in an ASCII art table",
GroupID: GROUP_ID_QUERYING,
Long: strings.ReplaceAll(EXAMPLE_QUERIES, "SUBCOMMAND", "query"),
DisableFlagParsing: true,
Run: func(cmd *cobra.Command, args []string) {
ctx := hctx.MakeContext()
lib.CheckFatalError(lib.ProcessDeletionRequests(ctx))
query(ctx, strings.Join(args, " "))
},
}
var tqueryCmd = &cobra.Command{
Use: "tquery",
Short: "Interactively query your shell history in a TUI interface",
GroupID: GROUP_ID_QUERYING,
Long: strings.ReplaceAll(EXAMPLE_QUERIES, "SUBCOMMAND", "tquery"),
DisableFlagParsing: true,
Run: func(cmd *cobra.Command, args []string) {
ctx := hctx.MakeContext()
lib.CheckFatalError(tui.TuiQuery(ctx, strings.Join(args, " ")))
},
}
var exportCmd = &cobra.Command{
Use: "export",
Short: "Export your shell history and display just the raw commands",
GroupID: GROUP_ID_QUERYING,
Long: strings.ReplaceAll(EXAMPLE_QUERIES, "SUBCOMMAND", "export"),
DisableFlagParsing: true,
Run: func(cmd *cobra.Command, args []string) {
ctx := hctx.MakeContext()
lib.CheckFatalError(lib.ProcessDeletionRequests(ctx))
export(ctx, strings.Join(args, " "))
},
}
var updateLocalDbFromRemoteCmd = &cobra.Command{
Use: "updateLocalDbFromRemote",
Hidden: true,
Short: "[Internal-only] Update local DB from remote",
GroupID: GROUP_ID_QUERYING,
Run: func(cmd *cobra.Command, args []string) {
// Periodically, run a query so as to ensure that the local DB stays mostly up to date and that we don't
// accumulate a large number of entries that this device doesn't know about. This ensures that queries
// are always reasonably complete and fast (even when offline).
ctx := hctx.MakeContext()
config := hctx.GetConf(ctx)
if config.IsOffline {
return
}
// TODO: Move this out of config.BetaMode only once we're confident in this change
if config.BetaMode {
// Do it a random percent of the time, which should be approximately often enough.
if rand.Intn(20) == 0 && !config.IsOffline {
lib.CheckFatalError(lib.RetrieveAdditionalEntriesFromRemote(ctx, "preload"))
lib.CheckFatalError(lib.ProcessDeletionRequests(ctx))
}
}
},
}
func export(ctx context.Context, query string) {
db := hctx.GetDb(ctx)
err := lib.RetrieveAdditionalEntriesFromRemote(ctx, "export")
if err != nil {
if lib.IsOfflineError(ctx, err) {
fmt.Println("Warning: hishtory is offline so this may be missing recent results from your other machines!")
} else {
lib.CheckFatalError(err)
}
}
data, err := lib.Search(ctx, db, query, 0)
lib.CheckFatalError(err)
for i := len(data) - 1; i >= 0; i-- {
fmt.Println(data[i].Command)
}
}
func query(ctx context.Context, query string) {
db := hctx.GetDb(ctx)
err := lib.RetrieveAdditionalEntriesFromRemote(ctx, "query")
if err != nil {
if lib.IsOfflineError(ctx, err) {
fmt.Println("Warning: hishtory is offline so this may be missing recent results from your other machines!")
} else {
lib.CheckFatalError(err)
}
}
lib.CheckFatalError(displayBannerIfSet(ctx))
numResults := 25
data, err := lib.Search(ctx, db, query, numResults*5)
lib.CheckFatalError(err)
lib.CheckFatalError(lib.DisplayResults(ctx, data, numResults))
}
func displayBannerIfSet(ctx context.Context) error {
respBody, err := lib.GetBanner(ctx)
if lib.IsOfflineError(ctx, err) {
return nil
}
if err != nil {
return err
}
if len(respBody) > 0 {
fmt.Println(string(respBody))
}
return nil
}
func init() {
rootCmd.AddCommand(queryCmd)
rootCmd.AddCommand(tqueryCmd)
rootCmd.AddCommand(exportCmd)
rootCmd.AddCommand(updateLocalDbFromRemoteCmd)
}