Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Migrating to CLI adapters to make the package #11

Merged
merged 2 commits into from
Jan 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v3
with:
go-version: 1.19
go-version: 1.21

- name: Build
run: go build -o icarus cmd/icarus.go
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v3
with:
go-version: 1.19
go-version: 1.21

- name: Test
run: go test -v ./...
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{}
Loading
Loading