Skip to content

Commit

Permalink
feat: Migrating to CLI adapters to make the package
Browse files Browse the repository at this point in the history
more usable
  • Loading branch information
dploeger committed Jan 24, 2024
1 parent d70a94a commit 3002e7b
Show file tree
Hide file tree
Showing 42 changed files with 1,369 additions and 454 deletions.
150 changes: 2 additions & 148 deletions cmd/icarus.go
Original file line number Diff line number Diff line change
@@ -1,156 +1,10 @@
package main

import (
"fmt"
"github.com/akamensky/argparse"
"github.com/dploeger/icarus/v2/pkg/outputTypes"
"github.com/dploeger/icarus/v2/pkg/processors"
"github.com/emersion/go-ical"
"github.com/sirupsen/logrus"
"github.com/thoas/go-funk"
"github.com/dploeger/icarus/v2/internal"
"os"
"regexp"
"time"
)

type processorCommand struct {
processor processors.BaseProcessor
command *argparse.Command
}

func main() {
availableOutputTypes := outputTypes.GetOutputTypes()
parser := argparse.NewParser("icarus", "iCal file processor")
inputFile := parser.File("f", "file", os.O_RDONLY, 0444, &argparse.Options{
Help: "File to read ics data from. Defaults to stdin",
})
outputFile := parser.File("o", "output", os.O_RDWR, 0644, &argparse.Options{
Help: "File to write ics data to. Defaults to stdout",
})
selector := parser.String("s", "selector", &argparse.Options{
Default: ".*",
Help: "Regular Expression pattern to select events by their summary, description, etc.",
})
selectorProps := parser.StringList("p", "selector-props", &argparse.Options{
Help: "Event properties that are searched using the text selector pattern",
Default: []string{ical.PropSummary, ical.PropDescription},
})
dateSelectorStart := parser.String("b", "timestamp-start", &argparse.Options{
Help: "An RFC3339-formatted (2006-01-02T15:04:05+07:00) timestamp that selects only events starting at or after that time",
Validate: func(args []string) error {
_, err := time.Parse(time.RFC3339, args[0])
if err != nil {
return fmt.Errorf("can not parse start timestamp: %w", err)
}
return nil
},
})
dateSelectorEnd := parser.String("e", "timestamp-end", &argparse.Options{
Help: "An RFC3339-formatted (2006-01-02T15:04:05+07:00) timestamp that selects only events ending at or before that time",
Validate: func(args []string) error {
_, err := time.Parse(time.RFC3339, args[0])
if err != nil {
return fmt.Errorf("can not parse end timestamp: %w", err)
}
return nil
},
})
outputType := parser.Selector("t", "output-type", funk.Keys(availableOutputTypes).([]string), &argparse.Options{
Help: fmt.Sprintf("Type of output. Valid types:\n%s\n\t\t\t", outputTypes.GetOutputHelp()),
Default: "ics",
})
logLevel := parser.String("l", "loglevel", &argparse.Options{
Help: "Loglevel to use",
Default: "error",
})

var processorCommands []processorCommand
for _, processor := range processors.GetProcessors() {
if command, err := processor.Initialize(parser); err != nil {
fmt.Print(parser.Usage(err))
os.Exit(1)
} else {
processorCommands = append(processorCommands, processorCommand{
processor: processor,
command: command,
})
}
}

for _, outputType := range availableOutputTypes {
if err := outputType.Initialize(parser); err != nil {
fmt.Print(parser.Usage(err))
os.Exit(1)
}
}

if err := parser.Parse(os.Args); err != nil {
fmt.Print(parser.Usage(err))
os.Exit(1)
}

if loggerLevel, err := logrus.ParseLevel(*logLevel); err != nil {
logrus.Errorf("%s is not a valid log level", *logLevel)
fmt.Print(parser.Usage(err))
os.Exit(1)
} else {
logrus.SetLevel(loggerLevel)
}

if (os.File{}) == *inputFile {
inputFile = os.Stdin
}
if (os.File{}) == *outputFile {
outputFile = os.Stdout
}

logrus.Debug("Parsing input calendar")

var inputCalendar ical.Calendar
dec := ical.NewDecoder(inputFile)
if cal, err := dec.Decode(); err != nil {
fmt.Print(parser.Usage(err))
os.Exit(2)
} else {
inputCalendar = *cal
}

outputCalendar := ical.NewCalendar()
outputCalendar.Props = inputCalendar.Props

var dStart time.Time
if dateSelectorStart != nil {
dStart, _ = time.Parse(time.RFC3339, *dateSelectorStart)
}

var dEnd time.Time
if dateSelectorEnd != nil {
dEnd, _ = time.Parse(time.RFC3339, *dateSelectorEnd)
}

toolbox := processors.Toolbox{
TextSelectorPattern: regexp.MustCompile(fmt.Sprintf("(?i)%s", *selector)),
TextSelectorProps: *selectorProps,
DateRangeSelectorStart: dStart,
DateRangeSelectorEnd: dEnd,
}

for _, processorCommand := range processorCommands {
if processorCommand.command.Happened() {
logrus.Infof("Processor %s was selected. Starting process", processorCommand.command.GetName())
processorCommand.processor.SetToolbox(toolbox)
if err := processorCommand.processor.Process(inputCalendar, outputCalendar); err != nil {
fmt.Print(parser.Usage(err))
os.Exit(3)
}
}
}

logrus.Infof("Generating output type %s", *outputType)

if err := availableOutputTypes[*outputType].Generate(outputCalendar, outputFile); err != nil {
fmt.Print(parser.Usage(err))
os.Exit(4)
}

os.Exit(internal.Main())
}
4 changes: 3 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ require (
github.com/akamensky/argparse v1.4.0
github.com/emersion/go-ical v0.0.0-20220601085725-0864dccc089f
github.com/olekukonko/tablewriter v0.0.5
github.com/rogpeppe/go-internal v1.12.0
github.com/sirupsen/logrus v1.9.3
github.com/stretchr/testify v1.8.4
github.com/thoas/go-funk v0.9.3
Expand All @@ -16,6 +17,7 @@ require (
github.com/mattn/go-runewidth v0.0.9 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/teambition/rrule-go v1.7.2 // indirect
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 // indirect
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f // indirect
golang.org/x/tools v0.1.12 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
7 changes: 6 additions & 1 deletion go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
Expand All @@ -22,8 +24,11 @@ github.com/teambition/rrule-go v1.7.2 h1:goEajFWYydfCgavn2m/3w5U+1b3PGqPUHx/fFSV
github.com/teambition/rrule-go v1.7.2/go.mod h1:mBJ1Ht5uboJ6jexKdNUJg2NcwP8uUMNvStWXlJD3MvU=
github.com/thoas/go-funk v0.9.3 h1:7+nAEx3kn5ZJcnDm2Bh23N2yOtweO14bi//dvRtgLpw=
github.com/thoas/go-funk v0.9.3/go.mod h1:+IWnUfUmFO1+WVYQWQtIJHeRRdaIyyYglZN7xzUPe4Q=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 h1:0A+M6Uqn+Eje4kHMK80dtF3JCXC4ykBgQG4Fe06QRhQ=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f h1:v4INt8xihDGvnrfjMDVXGxw9wrfxYyCjk0KbXjhR55s=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
Expand Down
31 changes: 31 additions & 0 deletions internal/adapters/adapter.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// Package adapters holds CLI adapters that connect the icarus CLI to the processors
package adapters

import (
"github.com/akamensky/argparse"
"github.com/dploeger/icarus/v2/pkg/processors"
"github.com/emersion/go-ical"
)

// The Adapter connects the Icarus CLI with a processor
type Adapter interface {
// Initialize creates a new subcommand for the argparse parser.
Initialize(parser *argparse.Parser) (*argparse.Command, error)
// SetToolbox sets the toolbox that can be used by the processor
SetToolbox(toolbox processors.Toolbox)
// Process processes the incoming calendar and fills the output calendar
Process(input ical.Calendar, output *ical.Calendar) error
}

// GetAdapters returns a list of enabled processor adapters
func GetAdapters() []Adapter {
return []Adapter{
&FilterAdapter{},
&PrintAdapter{},
&ConvertAllDayAdapter{},
&AddDTStampAdapter{},
&AddAlarmAdapter{},
&AddPropertyAdapter{},
&DeletePropertyAdapter{},
}
}
20 changes: 20 additions & 0 deletions internal/adapters/adapter_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package adapters_test

import (
"github.com/dploeger/icarus/v2/internal"
"os"
"testing"
)
import "github.com/rogpeppe/go-internal/testscript"

func TestMain(m *testing.M) {
os.Exit(testscript.RunMain(m, map[string]func() int{
"icarus": internal.Main,
}))
}

func TestAdapters(t *testing.T) {
testscript.Run(t, testscript.Params{
Dir: "testdata/script",
})
}
34 changes: 34 additions & 0 deletions internal/adapters/add_alarm.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package adapters

import (
"github.com/akamensky/argparse"
"github.com/dploeger/icarus/v2/pkg/processors"
"github.com/emersion/go-ical"
)

// The AddAlarmAdapter adds an alarm definition to all selected events
type AddAlarmAdapter struct {
alarmBefore *int
toolbox processors.Toolbox
}

func (a *AddAlarmAdapter) Initialize(parser *argparse.Parser) (*argparse.Command, error) {
command := parser.NewCommand("addAlarm", "Add an alarm to all selected events")
a.alarmBefore = command.Int("A", "alarm-before", &argparse.Options{
Help: "Alarm should be set number of minutes before the event",
Required: true,
})
return command, nil
}

func (a *AddAlarmAdapter) SetToolbox(toolbox processors.Toolbox) {
a.toolbox = toolbox
}

func (a *AddAlarmAdapter) Process(input ical.Calendar, output *ical.Calendar) error {
p := processors.AddAlarmProcessor{AlarmBefore: *a.alarmBefore}
p.SetToolbox(a.toolbox)
return p.Process(input, output)
}

var _ Adapter = &AddAlarmAdapter{}
52 changes: 52 additions & 0 deletions internal/adapters/add_dtstamp.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package adapters

import (
"github.com/akamensky/argparse"
"github.com/dploeger/icarus/v2/pkg/processors"
"github.com/emersion/go-ical"
"time"
)

// The AddDTStampAdapter adds a DTSTAMP field to all selected events
type AddDTStampAdapter struct {
timestamp *string
overwrite *bool
toolbox processors.Toolbox
}

func (t *AddDTStampAdapter) Initialize(parser *argparse.Parser) (*argparse.Command, error) {
command := parser.NewCommand("addDTStamp", "Adds a DTStamp field to all selected events")
t.timestamp = command.String("T", "timestamp", &argparse.Options{
Help: "Set DTSTAMP to this timestamp. Defaults to the current timestamp.",
})
t.overwrite = command.Flag("O", "overwrite", &argparse.Options{
Help: "Overwrite DTSTAMP if event already has one",
Default: true,
})
return command, nil
}

func (t *AddDTStampAdapter) SetToolbox(toolbox processors.Toolbox) {
t.toolbox = toolbox
}

func (t *AddDTStampAdapter) Process(input ical.Calendar, output *ical.Calendar) error {
var parsedTimestamp time.Time
if t.timestamp == nil || *t.timestamp == "" {
parsedTimestamp = time.Now().In(time.UTC)
} else {
if parsed, err := time.Parse("20060102T150405Z", *t.timestamp); err != nil {
return err
} else {
parsedTimestamp = parsed
}
}
p := processors.AddDTStampProcessor{
Timestamp: parsedTimestamp,
Overwrite: *t.overwrite,
}
p.SetToolbox(t.toolbox)
return p.Process(input, output)
}

var _ Adapter = &AddDTStampAdapter{}
49 changes: 49 additions & 0 deletions internal/adapters/add_property.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package adapters

import (
"github.com/akamensky/argparse"
"github.com/dploeger/icarus/v2/pkg/processors"
"github.com/emersion/go-ical"
)

// The AddPropertyAdapter adds an ICS property to each selected event
type AddPropertyAdapter struct {
propertyName *string
propertyValue *string
overwrite *bool
toolbox processors.Toolbox
}

func (a *AddPropertyAdapter) Initialize(parser *argparse.Parser) (*argparse.Command, error) {
c := parser.NewCommand("addProperty", "Adds a new property to each selected event")
a.propertyName = c.String("N", "name", &argparse.Options{
Help: "Name of the new property",
Required: true,
})
a.propertyValue = c.String("V", "value", &argparse.Options{
Help: "Value of the new property (only text values allowed)",
Required: true,
})
a.overwrite = c.Flag("O", "overwrite", &argparse.Options{
Help: "Overwrite property if it exists",
Required: false,
Default: true,
})
return c, nil
}

func (a *AddPropertyAdapter) SetToolbox(toolbox processors.Toolbox) {
a.toolbox = toolbox
}

func (a *AddPropertyAdapter) Process(input ical.Calendar, output *ical.Calendar) error {
p := processors.AddPropertyProcessor{
PropertyName: *a.propertyName,
PropertyValue: *a.propertyValue,
Overwrite: *a.overwrite,
}
p.SetToolbox(a.toolbox)
return p.Process(input, output)
}

var _ Adapter = &AddPropertyAdapter{}
Loading

0 comments on commit 3002e7b

Please sign in to comment.