diff --git a/.golangci.yml b/.golangci.yml new file mode 100644 index 0000000..9ee1fee --- /dev/null +++ b/.golangci.yml @@ -0,0 +1,50 @@ +version: "2" +run: + allow-parallel-runners: true +linters: + default: none + enable: + - dupl + - errcheck + - ginkgolinter + - goconst + - gocyclo + - govet + - ineffassign + - lll + - misspell + - nakedret + - prealloc + - revive + - staticcheck + - unconvert + - unparam + - unused + settings: + revive: + rules: + - name: comment-spacings + exclusions: + generated: lax + rules: + - linters: + - lll + path: api/* + - linters: + - dupl + - lll + path: internal/* + paths: + - third_party$ + - builtin$ + - examples$ +formatters: + enable: + - gofmt + - goimports + exclusions: + generated: lax + paths: + - third_party$ + - builtin$ + - examples$ diff --git a/api/generate.go b/api/generate.go index aa313fd..5d2af27 100644 --- a/api/generate.go +++ b/api/generate.go @@ -1,3 +1,3 @@ package api -//go:generate oapi-codegen --config=oapi-codegen.yaml epgstation-schema.json \ No newline at end of file +//go:generate oapi-codegen --config=oapi-codegen.yaml epgstation-schema.json diff --git a/cmd/epgstationctl/main.go b/cmd/epgstationctl/main.go index 353936a..242d09c 100644 --- a/cmd/epgstationctl/main.go +++ b/cmd/epgstationctl/main.go @@ -1,10 +1,10 @@ package main import ( - "github.com/miscord-dev/epgstationctl/internal/commands/root" _ "github.com/miscord-dev/epgstationctl/internal/commands/channels" _ "github.com/miscord-dev/epgstationctl/internal/commands/programs" _ "github.com/miscord-dev/epgstationctl/internal/commands/recordings" + "github.com/miscord-dev/epgstationctl/internal/commands/root" ) func main() { diff --git a/internal/client/helpers.go b/internal/client/helpers.go index 756dfab..38ca48a 100644 --- a/internal/client/helpers.go +++ b/internal/client/helpers.go @@ -34,4 +34,4 @@ func parseJSONResponse(resp *http.Response, target interface{}) error { } return nil -} \ No newline at end of file +} diff --git a/internal/client/wrapper.go b/internal/client/wrapper.go index e1f137f..ef76542 100644 --- a/internal/client/wrapper.go +++ b/internal/client/wrapper.go @@ -35,7 +35,7 @@ func (c *EPGStationClient) GetChannels() (*epgstation.ChannelItems, error) { if err != nil { return nil, err } - defer resp.Body.Close() + defer func() { _ = resp.Body.Close() }() if resp.StatusCode != http.StatusOK { return nil, handleErrorResponse(resp) @@ -54,7 +54,7 @@ func (c *EPGStationClient) GetSchedules(params *epgstation.GetSchedulesParams) ( if err != nil { return nil, err } - defer resp.Body.Close() + defer func() { _ = resp.Body.Close() }() if resp.StatusCode != http.StatusOK { return nil, handleErrorResponse(resp) @@ -73,7 +73,7 @@ func (c *EPGStationClient) GetBroadcastingPrograms(params *epgstation.GetSchedul if err != nil { return nil, err } - defer resp.Body.Close() + defer func() { _ = resp.Body.Close() }() if resp.StatusCode != http.StatusOK { return nil, handleErrorResponse(resp) @@ -92,7 +92,7 @@ func (c *EPGStationClient) GetRecordings(params *epgstation.GetRecordingParams) if err != nil { return nil, err } - defer resp.Body.Close() + defer func() { _ = resp.Body.Close() }() if resp.StatusCode != http.StatusOK { return nil, handleErrorResponse(resp) @@ -111,7 +111,7 @@ func (c *EPGStationClient) GetSchedulesChannelId(ctx interface{}, channelId epgs if err != nil { return nil, err } - defer resp.Body.Close() + defer func() { _ = resp.Body.Close() }() if resp.StatusCode != http.StatusOK { return nil, handleErrorResponse(resp) @@ -130,7 +130,7 @@ func (c *EPGStationClient) PostSchedulesSearch(ctx interface{}, body epgstation. if err != nil { return nil, err } - defer resp.Body.Close() + defer func() { _ = resp.Body.Close() }() if resp.StatusCode != http.StatusOK { return nil, handleErrorResponse(resp) @@ -142,4 +142,4 @@ func (c *EPGStationClient) PostSchedulesSearch(ctx interface{}, body epgstation. } return &programs, nil -} \ No newline at end of file +} diff --git a/internal/commands/channels/channels.go b/internal/commands/channels/channels.go index bd33c05..3bdd75c 100644 --- a/internal/commands/channels/channels.go +++ b/internal/commands/channels/channels.go @@ -90,4 +90,4 @@ func init() { channelsCmd.AddCommand(listCmd) channelsCmd.AddCommand(showCmd) root.AddCommand(channelsCmd) -} \ No newline at end of file +} diff --git a/internal/commands/programs/programs.go b/internal/commands/programs/programs.go index cec019d..e92504d 100644 --- a/internal/commands/programs/programs.go +++ b/internal/commands/programs/programs.go @@ -11,13 +11,16 @@ import ( "github.com/spf13/cobra" ) +const ( + outputFormatJSON = "json" +) + var ( - channelID int - date string - days int - halfWidth bool - keyword string - limit int + channelID int + date string + days int + halfWidth bool + limit int ) var programsCmd = &cobra.Command{ @@ -56,7 +59,7 @@ var listCmd = &cobra.Command{ // Add channel filter if provided if channelID > 0 { - chID := epgstation.ChannelId(channelID) + chID := channelID // Get specific channel schedule channelParams := &epgstation.GetSchedulesChannelIdParams{ IsHalfWidth: halfWidth, @@ -65,7 +68,7 @@ var listCmd = &cobra.Command{ channelParams.StartAt = params.StartAt } if days > 0 { - channelParams.Days = epgstation.Days(days) + channelParams.Days = days } schedules, err := client.GetSchedulesChannelId(nil, chID, channelParams) @@ -75,13 +78,14 @@ var listCmd = &cobra.Command{ var formatter output.Formatter switch cfg.Output.Format { - case "json": + case outputFormatJSON: formatter = output.NewJSONFormatter(nil) + return formatter.Format(*schedules) default: formatter = output.NewTableFormatter(nil, cfg.Output.NoHeader) + // Custom formatting for Schedules - flatten to show programs with channel info + return formatSchedulesAsTable(*schedules, formatter) } - - return formatter.Format(*schedules) } schedules, err := client.GetSchedules(params) @@ -91,13 +95,14 @@ var listCmd = &cobra.Command{ var formatter output.Formatter switch cfg.Output.Format { - case "json": + case outputFormatJSON: formatter = output.NewJSONFormatter(nil) + return formatter.Format(*schedules) default: formatter = output.NewTableFormatter(nil, cfg.Output.NoHeader) + // Custom formatting for Schedules - flatten to show programs with channel info + return formatSchedulesAsTable(*schedules, formatter) } - - return formatter.Format(*schedules) }, } @@ -123,13 +128,14 @@ var currentCmd = &cobra.Command{ var formatter output.Formatter switch cfg.Output.Format { - case "json": + case outputFormatJSON: formatter = output.NewJSONFormatter(nil) + return formatter.Format(*schedules) default: formatter = output.NewTableFormatter(nil, cfg.Output.NoHeader) + // Custom formatting for Schedules - flatten to show programs with channel info + return formatSchedulesAsTable(*schedules, formatter) } - - return formatter.Format(*schedules) }, } @@ -172,7 +178,7 @@ var searchCmd = &cobra.Command{ var formatter output.Formatter switch cfg.Output.Format { - case "json": + case outputFormatJSON: formatter = output.NewJSONFormatter(nil) default: formatter = output.NewTableFormatter(nil, cfg.Output.NoHeader) @@ -200,4 +206,57 @@ func init() { programsCmd.AddCommand(currentCmd) programsCmd.AddCommand(searchCmd) root.AddCommand(programsCmd) -} \ No newline at end of file +} + +// formatSchedulesAsTable formats schedule data in a user-friendly table format +func formatSchedulesAsTable(schedules epgstation.Schedules, formatter output.Formatter) error { + if len(schedules) == 0 { + fmt.Println("No programs found") + return nil + } + + // Create a flat list of programs with channel information + type ProgramWithChannel struct { + ChannelName string + ChannelType string + ProgramName string + StartTime string + EndTime string + Description string + } + + var programs []ProgramWithChannel + for _, schedule := range schedules { + channelName := schedule.Channel.Name + + for _, program := range schedule.Programs { + startTime := formatUnixTime(int64(program.StartAt)) + endTime := formatUnixTime(int64(program.EndAt)) + + description := "" + if program.Description != nil { + description = *program.Description + if len(description) > 50 { + description = description[:50] + "..." + } + } + + programs = append(programs, ProgramWithChannel{ + ChannelName: channelName, + ChannelType: string(schedule.Channel.ChannelType), + ProgramName: program.Name, + StartTime: startTime, + EndTime: endTime, + Description: description, + }) + } + } + + return formatter.Format(programs) +} + +// formatUnixTime converts Unix timestamp (in milliseconds) to readable time +func formatUnixTime(unixTimeMS int64) string { + t := time.Unix(unixTimeMS/1000, 0) + return t.Format("15:04") +} diff --git a/internal/commands/recordings/recordings.go b/internal/commands/recordings/recordings.go index 18be1a1..0ac9eed 100644 --- a/internal/commands/recordings/recordings.go +++ b/internal/commands/recordings/recordings.go @@ -38,13 +38,11 @@ var listCmd = &cobra.Command{ } if offset > 0 { - offsetParam := epgstation.Offset(offset) - params.Offset = &offsetParam + params.Offset = &offset } if limit > 0 { - limitParam := epgstation.Limit(limit) - params.Limit = &limitParam + params.Limit = &limit } recordings, err := client.GetRecordings(params) @@ -117,4 +115,4 @@ func init() { recordingsCmd.AddCommand(listCmd) recordingsCmd.AddCommand(statusCmd) root.AddCommand(recordingsCmd) -} \ No newline at end of file +} diff --git a/internal/commands/root/root.go b/internal/commands/root/root.go index d90ad77..af6d3f9 100644 --- a/internal/commands/root/root.go +++ b/internal/commands/root/root.go @@ -92,4 +92,4 @@ func GetConfig() *config.Config { func AddCommand(cmd *cobra.Command) { rootCmd.AddCommand(cmd) -} \ No newline at end of file +} diff --git a/internal/config/config.go b/internal/config/config.go index 618108c..4bf7762 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -43,4 +43,4 @@ func NewConfig(serverURL string, timeout int, outputFormat string, noHeader bool func (c *Config) GetAPIBaseURL() string { return c.Server.URL + "/api" -} \ No newline at end of file +} diff --git a/internal/output/formatter.go b/internal/output/formatter.go index 4e3efe1..747d995 100644 --- a/internal/output/formatter.go +++ b/internal/output/formatter.go @@ -6,7 +6,6 @@ import ( "io" "os" "reflect" - "github.com/olekukonko/tablewriter" ) @@ -67,7 +66,7 @@ func (f *TableFormatter) Format(data interface{}) error { func (f *TableFormatter) formatSlice(v reflect.Value) error { if v.Len() == 0 { - fmt.Fprintln(f.writer, "No data found") + _, _ = fmt.Fprintln(f.writer, "No data found") return nil } @@ -202,7 +201,19 @@ func (f *TableFormatter) formatValue(v reflect.Value) string { return "" } return fmt.Sprintf("[%d items]", v.Len()) + case reflect.Struct: + // For nested structs, show a summary instead of full structure + typeName := v.Type().Name() + if typeName == "" { + typeName = "struct" + } + return fmt.Sprintf("<%s>", typeName) default: - return fmt.Sprintf("%v", v.Interface()) + // Avoid showing memory addresses and internal representations + typeName := v.Type().Name() + if typeName != "" { + return fmt.Sprintf("<%s>", typeName) + } + return fmt.Sprintf("<%s>", v.Type().String()) } -} \ No newline at end of file +}