checkpoint: printer now prints statuses and status lists
This commit is contained in:
parent
d842233c98
commit
09cd13a2f7
6 changed files with 203 additions and 89 deletions
|
@ -171,7 +171,7 @@ func (s *ShowExecutor) showStatus(gtsClient *client.Client) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
utilities.Display(status, false, "")
|
s.printer.PrintStatus(status)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -220,7 +220,7 @@ func (s *ShowExecutor) showTimeline(gtsClient *client.Client) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
utilities.Display(timeline, false, "")
|
s.printer.PrintStatusList(timeline)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -333,7 +333,7 @@ func (s *ShowExecutor) showBookmarks(gtsClient *client.Client) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(bookmarks.Statuses) > 0 {
|
if len(bookmarks.Statuses) > 0 {
|
||||||
utilities.Display(bookmarks, false, "")
|
s.printer.PrintStatusList(bookmarks)
|
||||||
} else {
|
} else {
|
||||||
s.printer.PrintInfo("You have no bookmarks.\n")
|
s.printer.PrintInfo("You have no bookmarks.\n")
|
||||||
}
|
}
|
||||||
|
@ -348,7 +348,7 @@ func (s *ShowExecutor) showLiked(gtsClient *client.Client) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(liked.Statuses) > 0 {
|
if len(liked.Statuses) > 0 {
|
||||||
utilities.Display(liked, false, "")
|
s.printer.PrintStatusList(liked)
|
||||||
} else {
|
} else {
|
||||||
s.printer.PrintInfo("You have no " + s.resourceType + " statuses.\n")
|
s.printer.PrintInfo("You have no " + s.resourceType + " statuses.\n")
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,11 +5,7 @@
|
||||||
package model
|
package model
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"codeflow.dananglin.me.uk/apollo/enbas/internal/utilities"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Status struct {
|
type Status struct {
|
||||||
|
@ -139,86 +135,7 @@ type MediaDimensions struct {
|
||||||
Width int `json:"width"`
|
Width int `json:"width"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s Status) Display(noColor bool) string {
|
|
||||||
indent := " "
|
|
||||||
|
|
||||||
var builder strings.Builder
|
|
||||||
|
|
||||||
// The account information
|
|
||||||
builder.WriteString(utilities.FullDisplayNameFormat(noColor, s.Account.DisplayName, s.Account.Acct) + "\n\n")
|
|
||||||
|
|
||||||
// The content of the status.
|
|
||||||
builder.WriteString(utilities.HeaderFormat(noColor, "CONTENT:"))
|
|
||||||
builder.WriteString(utilities.WrapLines(utilities.ConvertHTMLToText(s.Content), "\n ", 80))
|
|
||||||
|
|
||||||
// If a poll exists in a status, write the contents to the builder.
|
|
||||||
if s.Poll != nil {
|
|
||||||
displayPollContent(&builder, *s.Poll, noColor, indent)
|
|
||||||
}
|
|
||||||
|
|
||||||
// The ID of the status
|
|
||||||
builder.WriteString("\n\n" + utilities.HeaderFormat(noColor, "STATUS ID:") + "\n" + indent + s.ID)
|
|
||||||
|
|
||||||
// Status creation time
|
|
||||||
builder.WriteString("\n\n" + utilities.HeaderFormat(noColor, "CREATED AT:") + "\n" + indent + utilities.FormatTime(s.CreatedAt))
|
|
||||||
|
|
||||||
// Status stats
|
|
||||||
builder.WriteString(
|
|
||||||
"\n\n" +
|
|
||||||
utilities.HeaderFormat(noColor, "STATS:") +
|
|
||||||
"\n" + indent + utilities.FieldFormat(noColor, "Boosts: ") + strconv.Itoa(s.ReblogsCount) +
|
|
||||||
"\n" + indent + utilities.FieldFormat(noColor, "Likes: ") + strconv.Itoa(s.FavouritesCount) +
|
|
||||||
"\n" + indent + utilities.FieldFormat(noColor, "Replies: ") + strconv.Itoa(s.RepliesCount),
|
|
||||||
)
|
|
||||||
|
|
||||||
// Status visibility
|
|
||||||
builder.WriteString("\n\n" + utilities.HeaderFormat(noColor, "VISIBILITY:") + "\n" + indent + s.Visibility.String())
|
|
||||||
|
|
||||||
// Status URL
|
|
||||||
builder.WriteString("\n\n" + utilities.HeaderFormat(noColor, "URL:") + "\n" + indent + s.URL)
|
|
||||||
|
|
||||||
return builder.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
type StatusList struct {
|
type StatusList struct {
|
||||||
Name string
|
Name string
|
||||||
Statuses []Status
|
Statuses []Status
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s StatusList) Display(noColor bool) string {
|
|
||||||
var builder strings.Builder
|
|
||||||
|
|
||||||
separator := strings.Repeat("─", 80)
|
|
||||||
|
|
||||||
builder.WriteString(utilities.HeaderFormat(noColor, s.Name) + "\n")
|
|
||||||
|
|
||||||
for _, status := range s.Statuses {
|
|
||||||
builder.WriteString("\n" + utilities.FullDisplayNameFormat(noColor, status.Account.DisplayName, status.Account.Acct) + "\n")
|
|
||||||
|
|
||||||
statusID := status.ID
|
|
||||||
createdAt := status.CreatedAt
|
|
||||||
|
|
||||||
if status.Reblog != nil {
|
|
||||||
builder.WriteString("reposted this status from " + utilities.FullDisplayNameFormat(noColor, status.Reblog.Account.DisplayName, status.Reblog.Account.Acct) + "\n")
|
|
||||||
statusID = status.Reblog.ID
|
|
||||||
createdAt = status.Reblog.CreatedAt
|
|
||||||
}
|
|
||||||
|
|
||||||
builder.WriteString(utilities.WrapLines(utilities.ConvertHTMLToText(status.Content), "\n", 80))
|
|
||||||
|
|
||||||
if status.Poll != nil {
|
|
||||||
displayPollContent(&builder, *status.Poll, noColor, "")
|
|
||||||
}
|
|
||||||
|
|
||||||
builder.WriteString(
|
|
||||||
"\n\n" +
|
|
||||||
utilities.FieldFormat(noColor, "Status ID:") + " " + statusID + "\t" +
|
|
||||||
utilities.FieldFormat(noColor, "Created at:") + " " + utilities.FormatTime(createdAt) +
|
|
||||||
"\n",
|
|
||||||
)
|
|
||||||
|
|
||||||
builder.WriteString(separator + "\n")
|
|
||||||
}
|
|
||||||
|
|
||||||
return builder.String()
|
|
||||||
}
|
|
||||||
|
|
|
@ -102,7 +102,7 @@ func (p Printer) PrintAccountRelationship(relationship model.AccountRelationship
|
||||||
builder.WriteString("\n" + utilities.WrapLines(relationship.PrivateNote, "\n", p.maxTerminalWidth))
|
builder.WriteString("\n" + utilities.WrapLines(relationship.PrivateNote, "\n", p.maxTerminalWidth))
|
||||||
}
|
}
|
||||||
|
|
||||||
builder.WriteString("\n")
|
builder.WriteString("\n\n")
|
||||||
|
|
||||||
printToStdout(builder.String())
|
printToStdout(builder.String())
|
||||||
}
|
}
|
||||||
|
|
62
internal/printer/poll.go
Normal file
62
internal/printer/poll.go
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
package printer
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"codeflow.dananglin.me.uk/apollo/enbas/internal/model"
|
||||||
|
"codeflow.dananglin.me.uk/apollo/enbas/internal/utilities"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (p Printer) pollContent(poll model.Poll) string {
|
||||||
|
var builder strings.Builder
|
||||||
|
|
||||||
|
for ind, option := range poll.Options {
|
||||||
|
var (
|
||||||
|
votage float64
|
||||||
|
percentage int
|
||||||
|
)
|
||||||
|
|
||||||
|
if poll.VotesCount == 0 {
|
||||||
|
percentage = 0
|
||||||
|
} else {
|
||||||
|
votage = float64(option.VotesCount) / float64(poll.VotesCount)
|
||||||
|
percentage = int(math.Floor(100 * votage))
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.WriteString("\n\n" + "[" + strconv.Itoa(ind) + "] " + option.Title)
|
||||||
|
builder.WriteString(p.pollMeter(votage))
|
||||||
|
builder.WriteString("\n" + strconv.Itoa(option.VotesCount) + " votes " + "(" + strconv.Itoa(percentage) + "%)")
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.WriteString("\n\n" + p.fieldFormat("Total votes:") + " " + strconv.Itoa(poll.VotesCount))
|
||||||
|
builder.WriteString("\n" + p.fieldFormat("Poll ID:") + " " + poll.ID)
|
||||||
|
builder.WriteString("\n" + p.fieldFormat("Poll is open until:") + " " + utilities.FormatTime(poll.ExpiredAt))
|
||||||
|
|
||||||
|
return builder.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p Printer) pollMeter(votage float64) string {
|
||||||
|
numVoteBlocks := int(math.Floor(float64(p.maxTerminalWidth) * votage))
|
||||||
|
numBackgroundBlocks := p.maxTerminalWidth - numVoteBlocks
|
||||||
|
|
||||||
|
voteBlockColor := p.theme.boldgreen
|
||||||
|
backgroundBlockColor := p.theme.grey
|
||||||
|
|
||||||
|
if p.noColor {
|
||||||
|
voteBlockColor = p.theme.reset
|
||||||
|
|
||||||
|
if numVoteBlocks == 0 {
|
||||||
|
numVoteBlocks = 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
meter := "\n" + voteBlockColor + strings.Repeat(p.pollMeterSymbol, numVoteBlocks) + p.theme.reset
|
||||||
|
|
||||||
|
if !p.noColor {
|
||||||
|
meter += backgroundBlockColor + strings.Repeat(p.pollMeterSymbol, numBackgroundBlocks) + p.theme.reset
|
||||||
|
}
|
||||||
|
|
||||||
|
return meter
|
||||||
|
}
|
|
@ -6,6 +6,7 @@ package printer
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
|
"os/exec"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
@ -16,6 +17,8 @@ type theme struct {
|
||||||
boldblue string
|
boldblue string
|
||||||
boldmagenta string
|
boldmagenta string
|
||||||
green string
|
green string
|
||||||
|
boldgreen string
|
||||||
|
grey string
|
||||||
}
|
}
|
||||||
|
|
||||||
type Printer struct {
|
type Printer struct {
|
||||||
|
@ -42,13 +45,15 @@ func NewPrinter(
|
||||||
boldblue: "\033[34;1m",
|
boldblue: "\033[34;1m",
|
||||||
boldmagenta: "\033[35;1m",
|
boldmagenta: "\033[35;1m",
|
||||||
green: "\033[32m",
|
green: "\033[32m",
|
||||||
|
boldgreen: "\033[32;1m",
|
||||||
|
grey: "\033[90m",
|
||||||
}
|
}
|
||||||
|
|
||||||
return &Printer{
|
return &Printer{
|
||||||
noColor: noColor,
|
noColor: noColor,
|
||||||
maxTerminalWidth: maxTerminalWidth,
|
maxTerminalWidth: maxTerminalWidth,
|
||||||
pager: pager,
|
pager: pager,
|
||||||
statusSeparator: strings.Repeat("─", maxTerminalWidth),
|
statusSeparator: strings.Repeat("\u2501", maxTerminalWidth),
|
||||||
bullet: "\u2022",
|
bullet: "\u2022",
|
||||||
pollMeterSymbol: "\u2501",
|
pollMeterSymbol: "\u2501",
|
||||||
successSymbol: "\u2714",
|
successSymbol: "\u2714",
|
||||||
|
@ -117,6 +122,43 @@ func (p Printer) formatDateTime(date time.Time) string {
|
||||||
return date.Local().Format(p.dateTimeFormat)
|
return date.Local().Format(p.dateTimeFormat)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p Printer) print(text string) {
|
||||||
|
if p.pager == "" {
|
||||||
|
printToStdout(text)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
cmdSplit := strings.Split(p.pager, " ")
|
||||||
|
|
||||||
|
pager := new(exec.Cmd)
|
||||||
|
|
||||||
|
if len(cmdSplit) == 1 {
|
||||||
|
pager = exec.Command(cmdSplit[0]) //nolint:gosec
|
||||||
|
} else {
|
||||||
|
pager = exec.Command(cmdSplit[0], cmdSplit[1:]...) //nolint:gosec
|
||||||
|
}
|
||||||
|
|
||||||
|
pipe, err := pager.StdinPipe()
|
||||||
|
if err != nil {
|
||||||
|
printToStdout(text)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
pager.Stdout = os.Stdout
|
||||||
|
pager.Stderr = os.Stderr
|
||||||
|
|
||||||
|
_ = pager.Start()
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
_ = pipe.Close()
|
||||||
|
_ = pager.Wait()
|
||||||
|
}()
|
||||||
|
|
||||||
|
_, _ = pipe.Write([]byte(text))
|
||||||
|
}
|
||||||
|
|
||||||
func printToStdout(text string) {
|
func printToStdout(text string) {
|
||||||
os.Stdout.WriteString(text)
|
os.Stdout.WriteString(text)
|
||||||
}
|
}
|
||||||
|
|
93
internal/printer/status.go
Normal file
93
internal/printer/status.go
Normal file
|
@ -0,0 +1,93 @@
|
||||||
|
package printer
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"codeflow.dananglin.me.uk/apollo/enbas/internal/model"
|
||||||
|
"codeflow.dananglin.me.uk/apollo/enbas/internal/utilities"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (p Printer) PrintStatus(status model.Status) {
|
||||||
|
var builder strings.Builder
|
||||||
|
|
||||||
|
// The account information
|
||||||
|
builder.WriteString("\n" + p.fullDisplayNameFormat(status.Account.DisplayName, status.Account.Acct))
|
||||||
|
|
||||||
|
// The content of the status.
|
||||||
|
builder.WriteString("\n\n" + p.headerFormat("CONTENT:"))
|
||||||
|
builder.WriteString(utilities.WrapLines(utilities.ConvertHTMLToText(status.Content), "\n", p.maxTerminalWidth))
|
||||||
|
|
||||||
|
// If a poll exists in a status, write the contents to the builder.
|
||||||
|
if status.Poll != nil {
|
||||||
|
builder.WriteString(p.pollContent(*status.Poll))
|
||||||
|
}
|
||||||
|
|
||||||
|
// The ID of the status
|
||||||
|
builder.WriteString("\n\n" + p.headerFormat("STATUS ID:"))
|
||||||
|
builder.WriteString("\n" + status.ID)
|
||||||
|
|
||||||
|
// Status creation time
|
||||||
|
builder.WriteString("\n\n" + p.headerFormat("CREATED AT:"))
|
||||||
|
builder.WriteString("\n" + p.formatDateTime(status.CreatedAt))
|
||||||
|
|
||||||
|
// Status stats
|
||||||
|
builder.WriteString("\n\n" + p.headerFormat("STATS:"))
|
||||||
|
builder.WriteString("\n" + p.fieldFormat("Boosts: ") + strconv.Itoa(status.ReblogsCount))
|
||||||
|
builder.WriteString("\n" + p.fieldFormat("Likes: ") + strconv.Itoa(status.FavouritesCount))
|
||||||
|
builder.WriteString("\n" + p.fieldFormat("Replies: ") + strconv.Itoa(status.RepliesCount))
|
||||||
|
|
||||||
|
// Status visibility
|
||||||
|
builder.WriteString("\n\n" + p.headerFormat("VISIBILITY:"))
|
||||||
|
builder.WriteString("\n" + status.Visibility.String())
|
||||||
|
|
||||||
|
// Status URL
|
||||||
|
builder.WriteString("\n\n" + p.headerFormat("URL:"))
|
||||||
|
builder.WriteString("\n" + status.URL)
|
||||||
|
builder.WriteString("\n\n")
|
||||||
|
|
||||||
|
p.print(builder.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p Printer) PrintStatusList(list model.StatusList) {
|
||||||
|
var builder strings.Builder
|
||||||
|
|
||||||
|
builder.WriteString(p.headerFormat(list.Name) + "\n")
|
||||||
|
|
||||||
|
for _, status := range list.Statuses {
|
||||||
|
builder.WriteString("\n" + p.fullDisplayNameFormat(status.Account.DisplayName, status.Account.Acct))
|
||||||
|
|
||||||
|
statusID := status.ID
|
||||||
|
createdAt := status.CreatedAt
|
||||||
|
|
||||||
|
if status.Reblog != nil {
|
||||||
|
builder.WriteString(utilities.WrapLines(
|
||||||
|
"\n"+
|
||||||
|
"reposted this status from "+
|
||||||
|
p.fullDisplayNameFormat(status.Reblog.Account.DisplayName, status.Reblog.Account.Acct),
|
||||||
|
"\n",
|
||||||
|
p.maxTerminalWidth,
|
||||||
|
))
|
||||||
|
|
||||||
|
statusID = status.Reblog.ID
|
||||||
|
createdAt = status.Reblog.CreatedAt
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.WriteString(utilities.WrapLines(utilities.ConvertHTMLToText(status.Content), "\n", p.maxTerminalWidth))
|
||||||
|
|
||||||
|
if status.Poll != nil {
|
||||||
|
builder.WriteString(p.pollContent(*status.Poll))
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.WriteString(
|
||||||
|
"\n\n" +
|
||||||
|
p.fieldFormat("Status ID:") + " " + statusID + "\t" +
|
||||||
|
p.fieldFormat("Created at:") + " " + utilities.FormatTime(createdAt) +
|
||||||
|
"\n",
|
||||||
|
)
|
||||||
|
|
||||||
|
builder.WriteString(p.statusSeparator + "\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
p.print(builder.String())
|
||||||
|
}
|
Loading…
Reference in a new issue