fix: improve how ordered and unordered lists are displayed #34
8 changed files with 138 additions and 94 deletions
|
@ -9,7 +9,6 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"codeflow.dananglin.me.uk/apollo/enbas/internal/model"
|
"codeflow.dananglin.me.uk/apollo/enbas/internal/model"
|
||||||
"codeflow.dananglin.me.uk/apollo/enbas/internal/utilities"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func (p Printer) PrintAccount(account model.Account, relationship *model.AccountRelationship, preferences *model.Preferences) {
|
func (p Printer) PrintAccount(account model.Account, relationship *model.AccountRelationship, preferences *model.Preferences) {
|
||||||
|
@ -28,11 +27,11 @@ func (p Printer) PrintAccount(account model.Account, relationship *model.Account
|
||||||
builder.WriteString("\n" + p.fieldFormat("Statuses:"))
|
builder.WriteString("\n" + p.fieldFormat("Statuses:"))
|
||||||
builder.WriteString(" " + strconv.Itoa(account.StatusCount))
|
builder.WriteString(" " + strconv.Itoa(account.StatusCount))
|
||||||
builder.WriteString("\n\n" + p.headerFormat("BIOGRAPHY:"))
|
builder.WriteString("\n\n" + p.headerFormat("BIOGRAPHY:"))
|
||||||
builder.WriteString(utilities.WrapLines(utilities.ConvertHTMLToText(account.Note), "\n", p.maxTerminalWidth))
|
builder.WriteString(p.convertHTMLToText(account.Note, true))
|
||||||
builder.WriteString("\n\n" + p.headerFormat("METADATA:"))
|
builder.WriteString("\n\n" + p.headerFormat("METADATA:"))
|
||||||
|
|
||||||
for _, field := range account.Fields {
|
for _, field := range account.Fields {
|
||||||
builder.WriteString("\n" + p.fieldFormat(field.Name) + ": " + utilities.ConvertHTMLToText(field.Value))
|
builder.WriteString("\n" + p.fieldFormat(field.Name) + ": " + p.convertHTMLToText(field.Value, false))
|
||||||
}
|
}
|
||||||
|
|
||||||
builder.WriteString("\n\n" + p.headerFormat("ACCOUNT URL:"))
|
builder.WriteString("\n\n" + p.headerFormat("ACCOUNT URL:"))
|
||||||
|
@ -80,7 +79,7 @@ func (p Printer) accountRelationship(relationship *model.AccountRelationship) st
|
||||||
|
|
||||||
if relationship.PrivateNote != "" {
|
if relationship.PrivateNote != "" {
|
||||||
builder.WriteString("\n\n" + p.headerFormat("YOUR PRIVATE NOTE ABOUT THIS ACCOUNT:"))
|
builder.WriteString("\n\n" + p.headerFormat("YOUR PRIVATE NOTE ABOUT THIS ACCOUNT:"))
|
||||||
builder.WriteString("\n" + utilities.WrapLines(relationship.PrivateNote, "\n", p.maxTerminalWidth))
|
builder.WriteString("\n" + p.wrapLines(relationship.PrivateNote, 0))
|
||||||
}
|
}
|
||||||
|
|
||||||
return builder.String()
|
return builder.String()
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
//
|
//
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
package utilities
|
package printer
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"io"
|
"io"
|
||||||
|
@ -23,7 +23,7 @@ type htmlConvertState struct {
|
||||||
orderedListIndex int
|
orderedListIndex int
|
||||||
}
|
}
|
||||||
|
|
||||||
func ConvertHTMLToText(text string) string {
|
func (p Printer) convertHTMLToText(text string, wrapLines bool) string {
|
||||||
var builder strings.Builder
|
var builder strings.Builder
|
||||||
|
|
||||||
state := htmlConvertState{
|
state := htmlConvertState{
|
||||||
|
@ -37,6 +37,10 @@ func ConvertHTMLToText(text string) string {
|
||||||
tt := token.Next()
|
tt := token.Next()
|
||||||
switch tt {
|
switch tt {
|
||||||
case html.ErrorToken:
|
case html.ErrorToken:
|
||||||
|
if wrapLines {
|
||||||
|
return p.wrapLines(builder.String(), 0)
|
||||||
|
}
|
||||||
|
|
||||||
return builder.String()
|
return builder.String()
|
||||||
case html.TextToken:
|
case html.TextToken:
|
||||||
text := token.Token().Data
|
text := token.Token().Data
|
||||||
|
@ -66,7 +70,7 @@ func processTagToken(state *htmlConvertState, writer io.StringWriter, tag string
|
||||||
case "<li>":
|
case "<li>":
|
||||||
switch state.htmlListType {
|
switch state.htmlListType {
|
||||||
case htmlUnorderedList:
|
case htmlUnorderedList:
|
||||||
_, _ = writer.WriteString("• ")
|
_, _ = writer.WriteString(symbolBullet + " ")
|
||||||
case htmlOrderedList:
|
case htmlOrderedList:
|
||||||
_, _ = writer.WriteString(strconv.Itoa(state.orderedListIndex) + ". ")
|
_, _ = writer.WriteString(strconv.Itoa(state.orderedListIndex) + ". ")
|
||||||
state.orderedListIndex++
|
state.orderedListIndex++
|
|
@ -8,7 +8,6 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"codeflow.dananglin.me.uk/apollo/enbas/internal/model"
|
"codeflow.dananglin.me.uk/apollo/enbas/internal/model"
|
||||||
"codeflow.dananglin.me.uk/apollo/enbas/internal/utilities"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func (p Printer) PrintInstance(instance model.InstanceV2) {
|
func (p Printer) PrintInstance(instance model.InstanceV2) {
|
||||||
|
@ -18,13 +17,13 @@ func (p Printer) PrintInstance(instance model.InstanceV2) {
|
||||||
builder.WriteString("\n" + instance.Title)
|
builder.WriteString("\n" + instance.Title)
|
||||||
|
|
||||||
builder.WriteString("\n\n" + p.headerFormat("INSTANCE DESCRIPTION:"))
|
builder.WriteString("\n\n" + p.headerFormat("INSTANCE DESCRIPTION:"))
|
||||||
builder.WriteString("\n" + utilities.WrapLines(instance.DescriptionText, "\n", p.maxTerminalWidth))
|
builder.WriteString("\n" + p.wrapLines(instance.DescriptionText, 0))
|
||||||
|
|
||||||
builder.WriteString("\n\n" + p.headerFormat("DOMAIN:"))
|
builder.WriteString("\n\n" + p.headerFormat("DOMAIN:"))
|
||||||
builder.WriteString("\n" + instance.Domain)
|
builder.WriteString("\n" + instance.Domain)
|
||||||
|
|
||||||
builder.WriteString("\n\n" + p.headerFormat("TERMS AND CONDITIONS:"))
|
builder.WriteString("\n\n" + p.headerFormat("TERMS AND CONDITIONS:"))
|
||||||
builder.WriteString("\n" + utilities.WrapLines(instance.TermsText, "\n ", p.maxTerminalWidth))
|
builder.WriteString("\n" + p.wrapLines(instance.TermsText, 2))
|
||||||
|
|
||||||
builder.WriteString("\n\n" + p.headerFormat("VERSION:"))
|
builder.WriteString("\n\n" + p.headerFormat("VERSION:"))
|
||||||
builder.WriteString("\nRunning GoToSocial " + instance.Version)
|
builder.WriteString("\nRunning GoToSocial " + instance.Version)
|
||||||
|
|
|
@ -71,8 +71,8 @@ func (p Printer) pollOptions(poll model.Poll) string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p Printer) pollMeter(votage float64) string {
|
func (p Printer) pollMeter(votage float64) string {
|
||||||
numVoteBlocks := int(math.Floor(float64(p.maxTerminalWidth) * votage))
|
numVoteBlocks := int(math.Floor(float64(p.lineWrapCharacterLimit) * votage))
|
||||||
numBackgroundBlocks := p.maxTerminalWidth - numVoteBlocks
|
numBackgroundBlocks := p.lineWrapCharacterLimit - numVoteBlocks
|
||||||
|
|
||||||
voteBlockColor := p.theme.boldgreen
|
voteBlockColor := p.theme.boldgreen
|
||||||
backgroundBlockColor := p.theme.grey
|
backgroundBlockColor := p.theme.grey
|
||||||
|
|
|
@ -43,17 +43,17 @@ type theme struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type Printer struct {
|
type Printer struct {
|
||||||
theme theme
|
theme theme
|
||||||
noColor bool
|
noColor bool
|
||||||
maxTerminalWidth int
|
lineWrapCharacterLimit int
|
||||||
pager string
|
pager string
|
||||||
statusSeparator string
|
statusSeparator string
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewPrinter(
|
func NewPrinter(
|
||||||
noColor bool,
|
noColor bool,
|
||||||
pager string,
|
pager string,
|
||||||
maxTerminalWidth int,
|
lineWrapCharacterLimit int,
|
||||||
) *Printer {
|
) *Printer {
|
||||||
theme := theme{
|
theme := theme{
|
||||||
reset: "\033[0m",
|
reset: "\033[0m",
|
||||||
|
@ -68,16 +68,16 @@ func NewPrinter(
|
||||||
boldyellow: "\033[33;1m",
|
boldyellow: "\033[33;1m",
|
||||||
}
|
}
|
||||||
|
|
||||||
if maxTerminalWidth < minTerminalWidth {
|
if lineWrapCharacterLimit < minTerminalWidth {
|
||||||
maxTerminalWidth = minTerminalWidth
|
lineWrapCharacterLimit = minTerminalWidth
|
||||||
}
|
}
|
||||||
|
|
||||||
return &Printer{
|
return &Printer{
|
||||||
theme: theme,
|
theme: theme,
|
||||||
noColor: noColor,
|
noColor: noColor,
|
||||||
maxTerminalWidth: maxTerminalWidth,
|
lineWrapCharacterLimit: lineWrapCharacterLimit,
|
||||||
pager: pager,
|
pager: pager,
|
||||||
statusSeparator: strings.Repeat("\u2501", maxTerminalWidth),
|
statusSeparator: strings.Repeat("\u2501", lineWrapCharacterLimit),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,6 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"codeflow.dananglin.me.uk/apollo/enbas/internal/model"
|
"codeflow.dananglin.me.uk/apollo/enbas/internal/model"
|
||||||
"codeflow.dananglin.me.uk/apollo/enbas/internal/utilities"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func (p Printer) PrintStatus(status model.Status) {
|
func (p Printer) PrintStatus(status model.Status) {
|
||||||
|
@ -24,7 +23,7 @@ func (p Printer) PrintStatus(status model.Status) {
|
||||||
|
|
||||||
// The content of the status.
|
// The content of the status.
|
||||||
builder.WriteString("\n\n" + p.headerFormat("CONTENT:"))
|
builder.WriteString("\n\n" + p.headerFormat("CONTENT:"))
|
||||||
builder.WriteString(utilities.WrapLines(utilities.ConvertHTMLToText(status.Content), "\n", p.maxTerminalWidth))
|
builder.WriteString(p.convertHTMLToText(status.Content, true))
|
||||||
|
|
||||||
// Details of media attachments (if any).
|
// Details of media attachments (if any).
|
||||||
if len(status.MediaAttachments) > 0 {
|
if len(status.MediaAttachments) > 0 {
|
||||||
|
@ -93,7 +92,10 @@ func (p Printer) PrintStatusList(list model.StatusList) {
|
||||||
|
|
||||||
if status.Reblog != nil {
|
if status.Reblog != nil {
|
||||||
builder.WriteString(
|
builder.WriteString(
|
||||||
"\n" + utilities.WrapLines("reposted this status from "+p.fullDisplayNameFormat(status.Reblog.Account.DisplayName, status.Reblog.Account.Acct), "\n", p.maxTerminalWidth),
|
"\n" + p.wrapLines(
|
||||||
|
"reposted this status from "+p.fullDisplayNameFormat(status.Reblog.Account.DisplayName, status.Reblog.Account.Acct),
|
||||||
|
0,
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
statusID = status.Reblog.ID
|
statusID = status.Reblog.ID
|
||||||
|
@ -103,22 +105,25 @@ func (p Printer) PrintStatusList(list model.StatusList) {
|
||||||
mediaAttachments = status.Reblog.MediaAttachments
|
mediaAttachments = status.Reblog.MediaAttachments
|
||||||
}
|
}
|
||||||
|
|
||||||
builder.WriteString("\n" + utilities.WrapLines(utilities.ConvertHTMLToText(content), "\n", p.maxTerminalWidth))
|
builder.WriteString("\n" + p.convertHTMLToText(content, true))
|
||||||
|
|
||||||
if poll != nil {
|
if poll != nil {
|
||||||
builder.WriteString(p.pollOptions(*poll))
|
builder.WriteString(p.pollOptions(*poll))
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, media := range mediaAttachments {
|
for _, media := range mediaAttachments {
|
||||||
builder.WriteString("\n\n" + symbolImage + " Media attachment: " + media.ID)
|
builder.WriteString("\n\n" + symbolImage + " " + p.fieldFormat("Media attachment: ") + media.ID)
|
||||||
builder.WriteString("\n Media type: " + media.Type + "\n ")
|
builder.WriteString("\n " + p.fieldFormat("Media type: ") + media.Type + "\n")
|
||||||
|
|
||||||
description := media.Description
|
description := " " + p.fieldFormat("Description: ")
|
||||||
if description == "" {
|
|
||||||
description = noMediaDescription
|
if media.Description == "" {
|
||||||
|
description += noMediaDescription
|
||||||
|
} else {
|
||||||
|
description += media.Description
|
||||||
}
|
}
|
||||||
|
|
||||||
builder.WriteString(utilities.WrapLines(description, "\n ", p.maxTerminalWidth-3))
|
builder.WriteString(p.wrapLines(description, 2))
|
||||||
}
|
}
|
||||||
|
|
||||||
boosted := symbolBoosted
|
boosted := symbolBoosted
|
||||||
|
|
96
internal/printer/wrap.go
Normal file
96
internal/printer/wrap.go
Normal file
|
@ -0,0 +1,96 @@
|
||||||
|
// SPDX-FileCopyrightText: 2024 Dan Anglin <d.n.i.anglin@gmail.com>
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
package printer
|
||||||
|
|
||||||
|
import (
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
"unicode"
|
||||||
|
)
|
||||||
|
|
||||||
|
type extraIndentConditiion struct {
|
||||||
|
pattern *regexp.Regexp
|
||||||
|
indent string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p Printer) wrapLines(text string, nIndent int) string {
|
||||||
|
if nIndent >= p.lineWrapCharacterLimit {
|
||||||
|
nIndent = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
separator := "\n" + strings.Repeat(" ", nIndent)
|
||||||
|
|
||||||
|
lines := strings.Split(text, "\n")
|
||||||
|
|
||||||
|
if len(lines) == 1 {
|
||||||
|
return wrapLine(lines[0], separator, p.lineWrapCharacterLimit-nIndent)
|
||||||
|
}
|
||||||
|
|
||||||
|
var builder strings.Builder
|
||||||
|
|
||||||
|
extraIndentConditions := []extraIndentConditiion{
|
||||||
|
{
|
||||||
|
pattern: regexp.MustCompile(`^[-*` + symbolBullet + `]\s.*$`),
|
||||||
|
indent: " ",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
pattern: regexp.MustCompile(`^[0-9]{1}\.\s.*$`),
|
||||||
|
indent: " ",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
pattern: regexp.MustCompile(`^[0-9]{2}\.\s.*$`),
|
||||||
|
indent: " ",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for ind, line := range lines {
|
||||||
|
builder.WriteString(wrapLine(line, separator+extraIndent(line, extraIndentConditions), p.lineWrapCharacterLimit-nIndent))
|
||||||
|
|
||||||
|
if ind < len(lines)-1 {
|
||||||
|
builder.WriteString(separator)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return builder.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func wrapLine(line, separator string, charLimit int) string {
|
||||||
|
if len(line) <= charLimit {
|
||||||
|
return line
|
||||||
|
}
|
||||||
|
|
||||||
|
leftcursor, rightcursor := 0, 0
|
||||||
|
|
||||||
|
var builder strings.Builder
|
||||||
|
|
||||||
|
for rightcursor < (len(line) - charLimit) {
|
||||||
|
rightcursor += (charLimit - 1)
|
||||||
|
|
||||||
|
for (rightcursor > leftcursor) && !unicode.IsSpace(rune(line[rightcursor-1])) {
|
||||||
|
rightcursor--
|
||||||
|
}
|
||||||
|
|
||||||
|
if rightcursor == leftcursor {
|
||||||
|
rightcursor = leftcursor + charLimit
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.WriteString(line[leftcursor:rightcursor] + separator)
|
||||||
|
leftcursor = rightcursor
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.WriteString(line[rightcursor:])
|
||||||
|
|
||||||
|
return builder.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func extraIndent(line string, conditions []extraIndentConditiion) string {
|
||||||
|
for ind := range conditions {
|
||||||
|
if conditions[ind].pattern.MatchString(line) {
|
||||||
|
return conditions[ind].indent
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ""
|
||||||
|
}
|
|
@ -1,59 +0,0 @@
|
||||||
// SPDX-FileCopyrightText: 2024 Dan Anglin <d.n.i.anglin@gmail.com>
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
|
|
||||||
package utilities
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strings"
|
|
||||||
"unicode"
|
|
||||||
)
|
|
||||||
|
|
||||||
func WrapLines(text, separator string, charLimit int) string {
|
|
||||||
lines := strings.Split(text, "\n")
|
|
||||||
|
|
||||||
if len(lines) == 1 {
|
|
||||||
return wrapLine(lines[0], separator, charLimit)
|
|
||||||
}
|
|
||||||
|
|
||||||
var builder strings.Builder
|
|
||||||
|
|
||||||
for i, line := range lines {
|
|
||||||
builder.WriteString(wrapLine(line, separator, charLimit))
|
|
||||||
|
|
||||||
if i < len(lines)-1 {
|
|
||||||
builder.WriteString(separator)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return builder.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
func wrapLine(line, separator string, charLimit int) string {
|
|
||||||
if len(line) <= charLimit {
|
|
||||||
return line
|
|
||||||
}
|
|
||||||
|
|
||||||
leftcursor, rightcursor := 0, 0
|
|
||||||
|
|
||||||
var builder strings.Builder
|
|
||||||
|
|
||||||
for rightcursor < (len(line) - charLimit) {
|
|
||||||
rightcursor += (charLimit - 1)
|
|
||||||
|
|
||||||
for (rightcursor > leftcursor) && !unicode.IsSpace(rune(line[rightcursor-1])) {
|
|
||||||
rightcursor--
|
|
||||||
}
|
|
||||||
|
|
||||||
if rightcursor == leftcursor {
|
|
||||||
rightcursor = leftcursor + charLimit
|
|
||||||
}
|
|
||||||
|
|
||||||
builder.WriteString(line[leftcursor:rightcursor] + separator)
|
|
||||||
leftcursor = rightcursor
|
|
||||||
}
|
|
||||||
|
|
||||||
builder.WriteString(line[rightcursor:])
|
|
||||||
|
|
||||||
return builder.String()
|
|
||||||
}
|
|
Loading…
Reference in a new issue