diff --git a/cmd/action.go b/cmd/action.go index 14db3a7..27b31a0 100644 --- a/cmd/action.go +++ b/cmd/action.go @@ -7,8 +7,19 @@ import ( var actionCmd = &cobra.Command{ Use: "action", Short: "Manage and run source actions", - Long: ` -`, + Long: `Add, edit, delete, and run source actions on items. + +A feed source is updated by the "fetch" action, which receives no input and +returns one JSON item per line on stdout. Other source actions are run on a +specific item, receiving that item on stdin and expecting that item, with any +modifications made by the action, on stdout. + +Items declare support for an action by having an "action" key containing an +object with a key for every supported action. The value of that key may be +any arbitrary JSON value. Use --force to execute an unsupported action anyway, +though the action may fail if it operated on the item's action data. + +To execute the "fetch" action, use "intake source fetch".`, } func init() { diff --git a/cmd/actionAdd.go b/cmd/actionAdd.go index ec27c96..1c8fa20 100644 --- a/cmd/actionAdd.go +++ b/cmd/actionAdd.go @@ -3,19 +3,50 @@ package cmd import ( "log" + "github.com/Jaculabilis/intake/core" "github.com/spf13/cobra" ) var actionAddCmd = &cobra.Command{ - Use: "add", + Use: "add [flags] -- argv...", Short: "Add an action to a source", - Long: ` + Long: `Add an action to a source. `, Run: func(cmd *cobra.Command, args []string) { - log.Fatal("not implemented") + actionAdd(getArgv(cmd, args)) }, } +var actionAddSource string +var actionAddAction string + func init() { actionCmd.AddCommand(actionAddCmd) + + actionAddCmd.Flags().StringVarP(&actionAddSource, "source", "s", "", "Source to add action") + actionAddCmd.MarkFlagRequired("source") + + actionAddCmd.Flags().StringVarP(&actionAddAction, "action", "a", "", "Action name") + actionAddCmd.MarkFlagRequired("action") +} + +func actionAdd(argv []string) { + if actionAddSource == "" { + log.Fatal("error: --source is empty") + } + if actionAddAction == "" { + log.Fatal("error: --action is empty") + } + if len(argv) == 0 { + log.Fatal("error: no argv provided") + } + + db := openAndMigrateDb() + + err := core.AddAction(db, actionAddSource, actionAddAction, argv) + if err != nil { + log.Fatalf("error: %v", err) + } + + log.Printf("Added action %s to source %s", actionAddAction, actionAddSource) } diff --git a/cmd/actionDelete.go b/cmd/actionDelete.go index d033a3b..ccc7dbb 100644 --- a/cmd/actionDelete.go +++ b/cmd/actionDelete.go @@ -3,19 +3,48 @@ package cmd import ( "log" + "github.com/Jaculabilis/intake/core" "github.com/spf13/cobra" ) var actionDeleteCmd = &cobra.Command{ - Use: "delete", - Short: "Delete an action from a source", - Long: ` + Use: "delete", + Aliases: []string{"rm"}, + Short: "Delete an action from a source", + Long: `Delete an action from a source. `, Run: func(cmd *cobra.Command, args []string) { - log.Fatal("not implemented") + actionDelete() }, } +var actionDeleteSource string +var actionDeleteAction string + func init() { actionCmd.AddCommand(actionDeleteCmd) + + actionDeleteCmd.Flags().StringVarP(&actionDeleteSource, "source", "s", "", "Source to add action") + actionDeleteCmd.MarkFlagRequired("source") + + actionDeleteCmd.Flags().StringVarP(&actionDeleteAction, "action", "a", "", "Action name") + actionDeleteCmd.MarkFlagRequired("action") +} + +func actionDelete() { + if actionDeleteSource == "" { + log.Fatal("error: --source is empty") + } + if actionDeleteAction == "" { + log.Fatal("error: --action is empty") + } + + db := openAndMigrateDb() + + err := core.DeleteAction(db, actionDeleteSource, actionDeleteAction) + if err != nil { + log.Fatalf("error: %v", err) + } + + log.Printf("Deleted action %s from source %s", actionDeleteAction, actionDeleteSource) } diff --git a/cmd/actionEdit.go b/cmd/actionEdit.go index 03ed1f5..9d5a2aa 100644 --- a/cmd/actionEdit.go +++ b/cmd/actionEdit.go @@ -3,19 +3,50 @@ package cmd import ( "log" + "github.com/Jaculabilis/intake/core" "github.com/spf13/cobra" ) var actionEditCmd = &cobra.Command{ Use: "edit", - Short: "Edit an action", - Long: ` + Short: "Edit an action on a source", + Long: `Edit an action on a source. `, Run: func(cmd *cobra.Command, args []string) { - log.Fatal("not implemented") + actionEdit(getArgv(cmd, args)) }, } +var actionEditSource string +var actionEditAction string + func init() { actionCmd.AddCommand(actionEditCmd) + + actionEditCmd.Flags().StringVarP(&actionEditSource, "source", "s", "", "Source to edit action") + actionEditCmd.MarkFlagRequired("source") + + actionEditCmd.Flags().StringVarP(&actionEditAction, "action", "a", "", "Action name") + actionEditCmd.MarkFlagRequired("action") +} + +func actionEdit(argv []string) { + if actionEditSource == "" { + log.Fatal("error: --source is empty") + } + if actionEditAction == "" { + log.Fatal("error: --action is empty") + } + if len(argv) == 0 { + log.Fatal("error: no argv provided") + } + + db := openAndMigrateDb() + + err := core.UpdateAction(db, actionEditSource, actionEditAction, argv) + if err != nil { + log.Fatalf("error: %v", err) + } + + log.Printf("Updated action %s on source %s", actionEditAction, actionEditSource) } diff --git a/cmd/actionList.go b/cmd/actionList.go new file mode 100644 index 0000000..7307538 --- /dev/null +++ b/cmd/actionList.go @@ -0,0 +1,66 @@ +package cmd + +import ( + "fmt" + "log" + "slices" + + "github.com/Jaculabilis/intake/core" + "github.com/spf13/cobra" +) + +var actionListCmd = &cobra.Command{ + Use: "list", + Aliases: []string{"ls"}, + Short: "List actions on a source", + Long: `List actions on a source. +`, + Run: func(cmd *cobra.Command, args []string) { + actionList() + }, +} + +var actionListSource string +var actionListArgv bool + +func init() { + actionCmd.AddCommand(actionListCmd) + + actionListCmd.Flags().StringVarP(&actionListSource, "source", "s", "", "Source to list actions") + actionListCmd.MarkFlagRequired("source") + + actionListCmd.Flags().BoolVarP(&actionListArgv, "argv", "a", false, "Include action command") +} + +func actionList() { + if actionListSource == "" { + log.Fatal("error: --source is empty") + } + + db := openAndMigrateDb() + + actions, err := core.GetActionsForSource(db, actionListSource) + if err != nil { + log.Fatal(err) + } + slices.SortFunc(actions, actionSort) + + if actionListArgv { + actionArgv := make(map[string][]string) + for _, name := range actions { + argv, err := core.GetArgvForAction(db, actionListSource, name) + if err != nil { + log.Fatalf("error: could not get argv for source %s action %s: %v", actionListSource, name, err) + } + actionArgv[name] = argv + } + for _, name := range actions { + fmt.Printf("%s %v\n", name, actionArgv[name]) + } + + } else { + for _, action := range actions { + fmt.Println(action) + } + } +} diff --git a/cmd/root.go b/cmd/root.go index 4c981f9..b0c06f0 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -4,6 +4,7 @@ import ( "fmt" "log" "os" + "strings" "github.com/Jaculabilis/intake/core" "github.com/spf13/cobra" @@ -65,3 +66,23 @@ func openAndMigrateDb() *core.DB { } return db } + +func getArgv(cmd *cobra.Command, args []string) []string { + lenAtDash := cmd.Flags().ArgsLenAtDash() + if lenAtDash == -1 { + return nil + } else { + return args[lenAtDash:] + } +} + +// Sort "fetch" action ahead of other actions +func actionSort(a string, b string) int { + if a == "fetch" { + return -1 + } + if b == "fetch" { + return 1 + } + return strings.Compare(a, b) +} diff --git a/cmd/sourceList.go b/cmd/sourceList.go index d6a4e89..cd4e14a 100644 --- a/cmd/sourceList.go +++ b/cmd/sourceList.go @@ -20,8 +20,12 @@ var sourceListCmd = &cobra.Command{ }, } +var sourceListShowActions bool + func init() { sourceCmd.AddCommand(sourceListCmd) + + sourceListCmd.Flags().BoolVarP(&sourceListShowActions, "actions", "a", false, "Include source actions") } func sourceList() { @@ -31,10 +35,24 @@ func sourceList() { if err != nil { log.Fatalf("error: %v", err) } - slices.Sort(names) - for _, name := range names { - fmt.Println(name) + if sourceListShowActions { + sourceActions := make(map[string][]string) + for _, name := range names { + actions, err := core.GetActionsForSource(db, name) + if err != nil { + log.Fatalf("error: could not get actions for source %s: %v", name, err) + } + slices.SortFunc(actions, actionSort) + sourceActions[name] = actions + } + for _, name := range names { + fmt.Printf("%s %v\n", name, sourceActions[name]) + } + } else { + for _, name := range names { + fmt.Println(name) + } } } diff --git a/core/action.go b/core/action.go index ec2de34..f204724 100644 --- a/core/action.go +++ b/core/action.go @@ -81,7 +81,7 @@ func DeleteAction(db *DB, source string, name string) error { _, err := db.Exec(` delete from actions where source = ? and name = ? - `) + `, source, name) return err } diff --git a/core/action_test.go b/core/action_test.go index 021e0b6..fb8672c 100644 --- a/core/action_test.go +++ b/core/action_test.go @@ -48,6 +48,11 @@ func TestActionCreate(t *testing.T) { if len(argv) != 2 || argv[0] != "echo" || argv[1] != "goodbye" { t.Fatalf("expected [echo goodbye], got: %v", argv) } + + err = DeleteAction(db, "test", "hello") + if err != nil { + t.Fatal(err) + } } func TestExecute(t *testing.T) {