diff --git a/cmd/feed.go b/cmd/feed.go new file mode 100644 index 0000000..71e2601 --- /dev/null +++ b/cmd/feed.go @@ -0,0 +1,101 @@ +package cmd + +import ( + "database/sql" + "encoding/json" + "fmt" + "log" + + "github.com/Jaculabilis/intake/core" + "github.com/spf13/cobra" +) + +var feedCmd = &cobra.Command{ + Use: "feed", + Short: "Print item feeds", + Long: `Display the intake item feed in various formats. +The default format is "headlines". + +Available formats: + headlines Only item titles + json Full item JSON + short Item source and id +`, + Run: func(cmd *cobra.Command, args []string) { + feed() + }, +} + +var feedFormat string +var feedSource string +var feedChannel string + +func init() { + rootCmd.AddCommand(feedCmd) + + feedCmd.Flags().StringVarP(&feedFormat, "format", "f", "headlines", "Feed format") + feedCmd.Flags().StringVarP(&feedSource, "source", "s", "", "Limit to items from source") + feedCmd.Flags().StringVarP(&feedChannel, "channel", "c", "", "Limit to items from channel") + feedCmd.MarkFlagsMutuallyExclusive("source", "channel") +} + +func feed() { + var formatter func(core.Item) + switch feedFormat { + case "headlines": + formatter = formatHeadline + case "json": + formatter = formatJson + case "short": + formatter = formatShort + default: + log.Fatalf("error: invalid format %s", feedFormat) + } + + db, err := sql.Open("sqlite3", getDbPath()) + if err != nil { + log.Fatalf("error: failed to open %s", dbPath) + } + + core.InitDatabase(db) + core.MigrateDatabase(db) + + var items []core.Item + if feedSource != "" { + items, err = core.GetActiveItemsForSource(db, feedSource) + if err != nil { + log.Fatalf("error: failed to fetch items from %s", feedSource) + } + } else if feedChannel != "" { + log.Fatal("error: unimplemented") + } else { + items, err = core.GetAllActiveItems(db) + if err != nil { + log.Fatal("error: failed to fetch items") + } + } + + for _, item := range items { + formatter(item) + } +} + +func formatHeadline(item core.Item) { + title := item.Title + if title == "" { + title = item.Id + } + fmt.Println(title) +} + +func formatJson(item core.Item) { + data, err := json.Marshal(item) + if err != nil { + log.Fatalf("error: failed to serialize %s/%s: %v", item.Source, item.Id, err) + } + fmt.Println(string(data)) +} + +func formatShort(item core.Item) { + fmt.Printf("%s/%s\n", item.Source, item.Id) +} diff --git a/cmd/root.go b/cmd/root.go index e424680..86089f5 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -27,7 +27,7 @@ func init() { rootCmd.SetHelpCommand(&cobra.Command{Hidden: true}) // All commands need to operate on a database - rootCmd.PersistentFlags().StringVarP(&dbPath, "db", "d", "", "Path to the intake sqlite database") + rootCmd.PersistentFlags().StringVarP(&dbPath, "db", "d", "", "Path to the intake sqlite database (default: INTAKE_DB)") } func getDbPath() string { diff --git a/core/db.go b/core/db.go index 3e344e9..2c87d67 100644 --- a/core/db.go +++ b/core/db.go @@ -11,15 +11,15 @@ import ( ) type Item struct { - source string - id string - created int - active bool - title string - author string - body string - link string - time int + Source string `json:"source"` + Id string `json:"id"` + Created int `json:"created"` + Active bool `json:"active"` + Title string `json:"title"` + Author string `json:"author"` + Body string `json:"body"` + Link string `json:"link"` + Time int `json:"time"` } //go:embed sql/*.sql @@ -169,7 +169,34 @@ func DeactivateItem(db *sql.DB, source string, id string) (bool, error) { return active, nil } -func GetActiveItems(db *sql.DB, source string) ([]Item, error) { +func GetAllActiveItems(db *sql.DB) ([]Item, error) { + rows, err := db.Query(` + select + source, + id, + created, + active, + title, + author, + body, + link, + time + from items + where active <> 0 + `) + if err != nil { + return nil, err + } + var items []Item + for rows.Next() { + var item Item + rows.Scan(&item.Source, &item.Id, &item.Created, &item.Active, &item.Title, &item.Author, &item.Body, &item.Link, &item.Time) + items = append(items, item) + } + return items, nil +} + +func GetActiveItemsForSource(db *sql.DB, source string) ([]Item, error) { rows, err := db.Query(` select source, @@ -192,7 +219,7 @@ func GetActiveItems(db *sql.DB, source string) ([]Item, error) { var items []Item for rows.Next() { var item Item - rows.Scan(&item.source, &item.id, &item.created, &item.active, &item.title, &item.author, &item.body, &item.link, &item.time) + rows.Scan(&item.Source, &item.Id, &item.Created, &item.Active, &item.Title, &item.Author, &item.Body, &item.Link, &item.Time) items = append(items, item) } return items, nil diff --git a/core/db_test.go b/core/db_test.go index 5410ba7..0bfc90c 100644 --- a/core/db_test.go +++ b/core/db_test.go @@ -98,14 +98,14 @@ func TestCreateSource(t *testing.T) { func AssertItemIs(t *testing.T, item Item, expected string) { actual := fmt.Sprintf( "%s/%s/%t/%s/%s/%s/%s/%d", - item.source, - item.id, - item.active, - item.title, - item.author, - item.body, - item.link, - item.time, + item.Source, + item.Id, + item.Active, + item.Title, + item.Author, + item.Body, + item.Link, + item.Time, ) if actual != expected { t.Fatalf("expected %s, got %s", expected, actual) @@ -125,7 +125,7 @@ func TestAddItem(t *testing.T) { if err := AddItem(db, "test", "two", "title", "author", "body", "link", 123456); err != nil { t.Fatal(err) } - items, err := GetActiveItems(db, "test") + items, err := GetActiveItemsForSource(db, "test") if err != nil { t.Fatal(err) } @@ -138,7 +138,7 @@ func TestAddItem(t *testing.T) { if _, err = DeactivateItem(db, "test", "one"); err != nil { t.Fatal(err) } - items, err = GetActiveItems(db, "test") + items, err = GetActiveItemsForSource(db, "test") if err != nil { t.Fatal(err) }