Filling in functionality
3 files changed, 157 insertions(+), 9 deletions(-)

M go.mod
M go.sum
M main.go
M go.mod +1 -0
@@ 4,5 4,6 @@ go 1.16
 
 require (
 	github.com/arran4/golang-ical v0.0.0-20210601225245-48fd351b08e7
+	github.com/cosiner/flag v0.5.2 // indirect
 	github.com/jordan-wright/email v4.0.1-0.20210109023952-943e75fe5223+incompatible
 )

          
M go.sum +3 -0
@@ 1,5 1,8 @@ 
 github.com/arran4/golang-ical v0.0.0-20210601225245-48fd351b08e7 h1:oOgavmDMGCnNtwZwNoXuK3jCcpF3I96Do9/5qPeSCr8=
 github.com/arran4/golang-ical v0.0.0-20210601225245-48fd351b08e7/go.mod h1:3Fo6aCavD4wY7KNzi6z9D6e20a6FdqD/tIaHlU7GnMI=
+github.com/cosiner/argv v0.0.1/go.mod h1:p/NrK5tF6ICIly4qwEDsf6VDirFiWWz0FenfYBwJaKQ=
+github.com/cosiner/flag v0.5.2 h1:dcI3ExLwrYt/wgg1RXZBn7FFFn3Mi5Lyremoa7Tt5ts=
+github.com/cosiner/flag v0.5.2/go.mod h1:+zDQNSDNnkR7CGUlSrw2d/5S26bL91amx0FVUbnmLrU=
 github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/jordan-wright/email v4.0.1-0.20210109023952-943e75fe5223+incompatible h1:jdpOPRN1zP63Td1hDQbZW73xKmzDvZHzVdNYxhnTMDA=

          
M main.go +153 -9
@@ 2,16 2,16 @@ package main
 
 import (
 	"fmt"
+	"io"
+
 	"github.com/arran4/golang-ical"
 	"github.com/cosiner/flag"
-	"github.com/jordan-wright/email"
+
+	//"github.com/jordan-wright/email"
 	"os"
 )
 
-// TODO send one 	-- send an email for a specific event
-// TODO send all 	-- process calendar and send emails for everything not already sent
-// TODO version	 	-- print version
-// TODO stats 		-- print DB & calendar statistics
+// TODO (default) 	-- process calendar and send emails for everything not already sent
 // TODO -Z 	 	-- do not record action to DB
 // TODO -N 	 	-- NOP do nothing
 // TODO -S 	 	-- do not send emails (but record changes in DB as if they were sent)

          
@@ 22,10 22,154 @@ import (
 // TODO -c <path> 	-- calendar path
 // TODO Replace plain text DB with real embedded DB
 func main() {
-	s := os.Stdin
-	cal, err := ics.ParseCalendar(s)
-	if err != nil {
-		panic(err)
+	processFlags(os.Args...)
+}
+
+func processFlags(args ...string) {
+	var send Send
+
+	flag.NewFlagSet(flag.Flag{}).ParseStruct(&send, os.Args...)
+
+	if send.Version {
+		fmt.Println(VERSION)
+		return
+	}
+
+	if send.Query {
+		dbs := dbStats(send.DbPath)
+		cals := calStats(send.CalPath)
+		// TODO format output
+		fmt.Printf("%+v\n", dbs)
+		fmt.Printf("%+v\n", cals)
+		return
+	}
+
+	if send.One != "" {
+		var in io.Reader
+		if send.One == "-" {
+			in = os.Stdin
+		} else {
+			var err error
+			in, err = os.Open(send.One)
+			if err != nil {
+				// TODO handle panic
+				panic(err)
+			}
+		}
+		cal, err := ics.ParseCalendar(in)
+		if err != nil {
+			// TODO handle panic
+			panic(err)
+		}
+		sendInvites(cal)
+		return
 	}
+
+	// drop through to operate on calendar
+}
+
+func sendInvites(cal *ics.Calendar) error {
 	fmt.Println(cal.Serialize())
+	return nil
 }
+
+/****************************************************************************/
+/* Storage data structures                                                  */
+/****************************************************************************/
+
+// TODO fill in DbStats
+type DbStats struct {
+}
+
+// TODO implement dbStats()
+func dbStats(path string) DbStats {
+	return DbStats{}
+}
+
+// TODO fill in CalStats
+type CalStats struct {
+}
+
+// TODO implement calStats()
+func calStats(path string) CalStats {
+	return CalStats{}
+}
+
+/****************************************************************************/
+/* Parse args                                                               */
+/****************************************************************************/
+
+var VERSION string = "dev"
+
+type Send struct {
+	One         string `names:"-f" usage:"process a single event; filename, or - for stdin"`
+	Version     bool   `names:"-V" usage:"print version (& exit)"`
+	Query       bool   `names:"-Q" usage:"print DB & calendar statistics"`
+	NOP         bool   `names:"-N" usage:"do nothing"`
+	NoEmail     bool   `names:"-S" usage:"do not send emails (but maybe persist to DB)"`
+	OnlyChanged bool   `names:"-C" usage:"only send emails to changed attendees"`
+	Debug       bool   `names:"-D" usage:"be verbose"`
+	DbPath      string `names:"-d" usage:"the path to the database"`
+	CalPath     string `names:"-c" usage:"the path to the calendar"`
+	//NoPersist   bool   `names:"-Z" usage:"do not record actions to database (but maybe send emails)"`
+	//EmailInput  bool   `names:"-e" usage:"input data is an email containing an event"`
+}
+
+func (t *Send) Metadata() map[string]flag.Flag {
+	const (
+		usage = "icsmailer is an iCal attendee invitation tool."
+		desc  = `
+		icsmailer sends email invites to attendees of ICS (iCal) events, and tracks invitations
+		in a database. It can send invites for a single ICS; for all events in a calendar; to
+		only changed attendees; and for events embedded in emails.
+
+		Examples:
+
+		# See who would be sent an invite from 
+		icsmailer -f foo.ics -d ~/.icsmailer.tsv -N
+		# ditto
+		cat foo.ics | icsmailer -f - -d ~/.icsmailer.tsv -N
+		# mark every event in a calendar as having invitations already sent
+		icsmailer -S -c ~/.calendars/default/ -d ~/.icsmailer.tsv -N
+		`
+	)
+	return map[string]flag.Flag{
+		"": {
+			Usage:   usage,
+			Version: VERSION,
+			Desc:    desc,
+		},
+		"-N": {
+			Desc: "Useful for previewing what would be done.",
+		},
+		"-S": {
+			Desc: `
+			Can be used to prime a database; essentially, mark all calendar events as having
+			already had invites sent.
+			`,
+		},
+		"-C": {
+			Desc: "Send emails to only attendees who have been added or removed from an event.",
+		},
+		//"-e": {
+		//	Desc: `
+		//	Usefel only with -f; ignored otherwise. The input is expected to be an email with
+		//	either an ICS in the body, or one or more ICSes as attachments.
+		//	`,
+		//},
+		"-d": {
+			Desc: `
+			The database is a tsv file in the form of:
+			<ICS UUID><tab>[<attendee>[,<attendee>]*]?
+
+			Example: icsmailer -d ~/.icsmailer.tsv
+			`,
+		},
+		"-c": {
+			Desc: `
+			The calendar is a directory of .ics files, such as one managed by vdirsyncer. Example:
+			icsmailer -c ~/.calendars/defaultcalendar/
+			`,
+		},
+	}
+}