159 lines
4.2 KiB
Go
159 lines
4.2 KiB
Go
package cmd
|
|
|
|
import (
|
|
"fmt"
|
|
"log"
|
|
"slices"
|
|
"time"
|
|
|
|
"github.com/Jaculabilis/intake/core"
|
|
"github.com/spf13/cobra"
|
|
)
|
|
|
|
var actionExecuteCmd = &cobra.Command{
|
|
Use: "execute",
|
|
Aliases: []string{"exec"},
|
|
Short: "Run a source action for an item",
|
|
Long: fmt.Sprintf(`Execute a source action for an item.
|
|
|
|
The item must declare support for the action by having the action's name
|
|
in its "action" field. Use --force to execute the action anyway.
|
|
|
|
The "fetch" action is special and does not execute for any specific item.
|
|
Use "intake source fetch" to run the fetch action.
|
|
|
|
In a dry run, the item will be printed in the chosen format and not updated.
|
|
|
|
%s`, makeFormatHelpText()),
|
|
Run: func(cmd *cobra.Command, args []string) {
|
|
actionExecute(
|
|
stringArg(cmd, "source"),
|
|
stringArg(cmd, "action"),
|
|
stringArg(cmd, "item"),
|
|
stringArg(cmd, "format"),
|
|
stringArg(cmd, "timeout"),
|
|
boolArg(cmd, "dry-run"),
|
|
boolArg(cmd, "diff"),
|
|
boolArg(cmd, "force"),
|
|
)
|
|
},
|
|
}
|
|
|
|
func init() {
|
|
actionCmd.AddCommand(actionExecuteCmd)
|
|
|
|
actionExecuteCmd.PersistentFlags().StringP("source", "s", "", "Source of the item")
|
|
actionExecuteCmd.MarkFlagRequired("source")
|
|
|
|
actionExecuteCmd.PersistentFlags().StringP("item", "i", "", "Item to run action on")
|
|
actionExecuteCmd.MarkFlagRequired("item")
|
|
|
|
actionExecuteCmd.PersistentFlags().StringP("action", "a", "", "Action to run")
|
|
actionExecuteCmd.MarkFlagRequired("action")
|
|
|
|
actionExecuteCmd.Flags().StringP("format", "f", "headlines", "Feed format for returned items")
|
|
|
|
actionExecuteCmd.Flags().StringP("timeout", "t", core.DefaultTimeout.String(),
|
|
fmt.Sprintf("Timeout duration (default: %s)", core.DefaultTimeout.String()))
|
|
|
|
actionExecuteCmd.Flags().Bool("dry-run", false, "Instead of updating the item, print it")
|
|
|
|
actionExecuteCmd.Flags().Bool("diff", false, "Show which fields of the item changed")
|
|
|
|
actionExecuteCmd.Flags().Bool("force", false, "Execute the action even if the item does not support it")
|
|
}
|
|
|
|
func actionExecute(
|
|
source string,
|
|
action string,
|
|
itemId string,
|
|
format string,
|
|
timeout string,
|
|
dryRun bool,
|
|
diff bool,
|
|
force bool,
|
|
) {
|
|
formatter := formatAs(format)
|
|
|
|
if source == "" {
|
|
log.Fatal("error: --source is empty")
|
|
}
|
|
if action == "" {
|
|
log.Fatal("error: --action is empty")
|
|
}
|
|
if itemId == "" {
|
|
log.Fatal("error: --item is empty")
|
|
}
|
|
duration, err := time.ParseDuration(timeout)
|
|
if err != nil {
|
|
log.Fatalf("error: invalid duration: %v", err)
|
|
}
|
|
|
|
db := openAndMigrateDb()
|
|
|
|
state, envs, argv, postProcess, err := core.GetSourceActionInputs(db, source, action)
|
|
if err != nil {
|
|
log.Fatalf("error: failed to load data for %s: %v", source, err)
|
|
}
|
|
|
|
item, err := core.GetItem(db, source, itemId)
|
|
if err != nil {
|
|
log.Fatalf("error: failed to get item: %v", err)
|
|
}
|
|
|
|
if item.Action[action] == nil {
|
|
if force {
|
|
log.Printf("warning: force-executing %s on %s/%s", action, source, itemId)
|
|
} else {
|
|
log.Fatalf("error: %s/%s does not support %s", source, itemId, action)
|
|
}
|
|
}
|
|
|
|
newItem, newState, errItem, err := core.ExecuteItemAction(item, argv, envs, state, duration, postProcess)
|
|
if err != nil {
|
|
core.AddErrorItem(db, errItem)
|
|
log.Fatalf("error executing %s: %v", action, err)
|
|
}
|
|
|
|
if diff {
|
|
if item.Title != newItem.Title {
|
|
log.Printf("title: %s => %s", item.Title, newItem.Title)
|
|
}
|
|
if item.Author != newItem.Author {
|
|
log.Printf("author: %s => %s", item.Author, newItem.Author)
|
|
}
|
|
if item.Body != newItem.Body {
|
|
log.Printf("body: %s => %s", item.Body, newItem.Body)
|
|
}
|
|
if item.Link != newItem.Link {
|
|
log.Printf("link: %s => %s", item.Link, newItem.Link)
|
|
}
|
|
if item.Time != newItem.Time {
|
|
log.Printf("time: %d => %d", item.Time, newItem.Time)
|
|
}
|
|
if core.ItemsAreEqual(item, newItem) {
|
|
log.Printf("no changes\n")
|
|
}
|
|
if !slices.Equal(state, newState) {
|
|
log.Printf("state changed (%d => %d bytes)", len(state), len(newState))
|
|
}
|
|
}
|
|
|
|
if dryRun {
|
|
fmt.Println(formatter(newItem))
|
|
return
|
|
}
|
|
|
|
if err = db.Transact(func(tx core.DB) error {
|
|
if _err := core.UpdateItems(tx, []core.Item{newItem}); err != nil {
|
|
return fmt.Errorf("failed to update item: %v", _err)
|
|
}
|
|
if _err := core.SetState(tx, source, newState); err != nil {
|
|
return fmt.Errorf("failed to set state for %s: %v", source, _err)
|
|
}
|
|
return nil
|
|
}); err != nil {
|
|
log.Fatalf("error: %v", err)
|
|
}
|
|
}
|