func (r *CommitCommentReporter) Report(ctx context.Context, issues []db.Issue) error {
	if len(issues) == 0 {
		return nil
	}
	plural := ""
	if len(issues) > 1 {
		plural = "s"
	}
	msg := fmt.Sprintf("GopherCI found **%d** issue%s in the last **%d** commits, see: %s",
		len(issues), plural, r.commits, r.analysisURL,
	)
	comment := &github.RepositoryComment{
		Body: github.String(msg),
	}
	_, _, err := r.client.Repositories.CreateComment(ctx, r.owner, r.repo, r.commit, comment)
	return errors.Wrapf(err, "could not post comment commit: %q, body: %q", r.commit, *comment.Body)
}
Report implements the analyser.Reporter interface.
package github
import (
	"bytes"
	"context"
	"encoding/json"
	"fmt"
	"net/http"
	"github.com/bradleyfalzon/gopherci/internal/analyser"
	"github.com/bradleyfalzon/gopherci/internal/db"
	"github.com/bradleyfalzon/gopherci/internal/logger"
	"github.com/google/go-github/github"
	"github.com/pkg/errors"
)
type PRCommentReporter struct {
	client *github.Client
	owner  string
	repo   string
	number int
	commit string
}
var _ analyser.Reporter = &PRCommentReporter{}
func NewPRCommentReporter(client *github.Client, owner, repo string, number int, commit string) *PRCommentReporter {
	return &PRCommentReporter{
		client: client,
		owner:  owner,
		repo:   repo,
		number: number,
		commit: commit,
	}
}
func dedupePRIssues(ctx context.Context, client *github.Client, owner, repo string, number int, issues []db.Issue) (filtered []db.Issue, err error) {
	ecomments, _, err := client.PullRequests.ListComments(ctx, owner, repo, number, nil)
	if err != nil {
		return nil, errors.Wrap(err, "could not list existing comments")
	}
	for i := len(issues) - 1; i >= 0; i-- {
		issue := issues[i]
		for _, ec := range ecomments {
			if ec.Path == nil || ec.Position == nil || ec.Body == nil {
				continue
			}
			if issue.Path == *ec.Path && issue.HunkPos == *ec.Position && issue.Issue == *ec.Body {
				issues = append(issues[:i], issues[i+1:]...)
				break
			}
		}
	}
	return issues, nil
}
func (r *PRCommentReporter) Report(ctx context.Context, issues []db.Issue) error {
	filtered, err := dedupePRIssues(ctx, r.client, r.owner, r.repo, r.number, issues)
	if err != nil {
		return err
	}
	_, issues = analyser.Suppress(filtered, analyser.MaxIssueComments)
	for _, issue := range issues {
		comment := &github.PullRequestComment{
			Body:     github.String(issue.Issue),
			CommitID: github.String(r.commit),
			Path:     github.String(issue.Path),
			Position: github.Int(issue.HunkPos),
		}
		_, _, err := r.client.PullRequests.CreateComment(ctx, r.owner, r.repo, r.number, comment)
		if err != nil {
			return errors.Wrapf(err, "could not post comment path: %q, position: %v, commitID: %q, body: %q",
				*comment.Path, *comment.Position, *comment.CommitID, *comment.Body,
			)
		}
	}
	return nil
}
type StatusState string
const (
	StatusStatePending StatusState = "pending"
	StatusStateSuccess StatusState = "success"
	StatusStateError   StatusState = "error"
	StatusStateFailure StatusState = "failure"
)
type StatusAPIReporter struct {
	logger    logger.Logger
	client    *github.Client
	statusURL string
	context   string
	targetURL string
}
var _ analyser.Reporter = &StatusAPIReporter{}
func NewStatusAPIReporter(logger logger.Logger, client *github.Client, statusURL, context, targetURL string) *StatusAPIReporter {
	return &StatusAPIReporter{
		logger:    logger,
		client:    client,
		statusURL: statusURL,
		context:   context,
		targetURL: targetURL,
	}
}
func (r *StatusAPIReporter) SetStatus(ctx context.Context, status StatusState, description string) error {
	s := struct {
		State       string `json:"state,omitempty"`
		TargetURL   string `json:"target_url,omitempty"`
		Description string `json:"description,omitempty"`
		Context     string `json:"context,omitempty"`
	}{
		string(status), r.targetURL, description, r.context,
	}
	r.logger.Infof("Setting %v state: %q, context: %q, description: %q", r.statusURL, status, r.context, description)
	js, err := json.Marshal(&s)
	if err != nil {
		return errors.Wrap(err, "could not marshal status")
	}
	req, err := http.NewRequest("POST", r.statusURL, bytes.NewBuffer(js))
	if err != nil {
		return errors.Wrapf(err, "could not make status request")
	}
	resp, err := r.client.Do(ctx, req, nil)
	if err != nil {
		return errors.Wrapf(err, "could not set status to %s for %s", status, r.statusURL)
	}
	defer resp.Body.Close()
	if resp.StatusCode < 200 || resp.StatusCode >= 300 {
		return fmt.Errorf("received status code %d from %s", resp.StatusCode, r.statusURL)
	}
	return nil
}
func (r *StatusAPIReporter) Report(ctx context.Context, issues []db.Issue) error {
	suppressed, _ := analyser.Suppress(issues, analyser.MaxIssueComments)
	return r.SetStatus(ctx, StatusStateSuccess, r.statusDesc(issues, suppressed))
}
func (StatusAPIReporter) statusDesc(issues []db.Issue, suppressed int) string {
	desc := fmt.Sprintf("Found %d issues", len(issues))
	switch {
	case len(issues) == 0:
		return `Found no issues \ʕ◔ϖ◔ʔ/`
	case len(issues) == 1:
		return `Found 1 issue`
	case suppressed == 1:
		desc += fmt.Sprintf(" (%v comment suppressed)", suppressed)
	case suppressed > 1:
		desc += fmt.Sprintf(" (%v comments suppressed)", suppressed)
	}
	return desc
}
type CommitCommentReporter struct {
	client      *github.Client
	owner       string
	repo        string
	commit      string
	commits     int
	analysisURL string
}
var _ analyser.Reporter = &CommitCommentReporter{}
func NewCommitCommentReporter(client *github.Client, owner, repo, commit string, commits int, analysisURL string) *CommitCommentReporter {
	return &CommitCommentReporter{
		client:      client,
		owner:       owner,
		repo:        repo,
		commit:      commit,
		commits:     commits,
		analysisURL: analysisURL,
	}
}