mirror of
https://git.sr.ht/~rjarry/aerc
synced 2025-02-23 07:53:59 +01:00

Add a {{.IsForwarded}} template to tell if a message has been forwarded or not. Changelog-added: Forwarded messages can be identified with the {{.IsForwarded}} template. Signed-off-by: inwit <inwit@sindominio.net> Acked-by: Robin Jarry <robin@jarry.cc>
769 lines
16 KiB
Go
769 lines
16 KiB
Go
package state
|
|
|
|
import (
|
|
"bufio"
|
|
"bytes"
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"os/exec"
|
|
"strings"
|
|
"time"
|
|
|
|
"git.sr.ht/~rjarry/aerc/config"
|
|
"git.sr.ht/~rjarry/aerc/lib/log"
|
|
"git.sr.ht/~rjarry/aerc/lib/ui"
|
|
"git.sr.ht/~rjarry/aerc/lib/xdg"
|
|
"git.sr.ht/~rjarry/aerc/models"
|
|
"github.com/danwakefield/fnmatch"
|
|
sortthread "github.com/emersion/go-imap-sortthread"
|
|
"github.com/emersion/go-message/mail"
|
|
)
|
|
|
|
type Composer interface {
|
|
AddAttachment(string)
|
|
}
|
|
|
|
type DataSetter interface {
|
|
Data() models.TemplateData
|
|
SetHeaders(*mail.Header, *models.OriginalMail)
|
|
SetInfo(*models.MessageInfo, int, bool)
|
|
SetVisual(bool)
|
|
SetThreading(ThreadInfo)
|
|
SetComposer(Composer)
|
|
SetAccount(*config.AccountConfig)
|
|
SetFolder(*models.Directory)
|
|
SetRUE([]string, func(string) (int, int, int))
|
|
SetState(s *AccountState)
|
|
SetPendingKeys([]config.KeyStroke)
|
|
}
|
|
|
|
type ThreadInfo struct {
|
|
SameSubject bool
|
|
Prefix string
|
|
Count int
|
|
Unread int
|
|
Folded bool
|
|
Context bool
|
|
Orphan bool
|
|
}
|
|
|
|
type templateData struct {
|
|
// only available when composing/replying/forwarding
|
|
headers *mail.Header
|
|
// only available when replying with a quote
|
|
parent *models.OriginalMail
|
|
// only available for the message list
|
|
info *models.MessageInfo
|
|
marked bool
|
|
msgNum int
|
|
visual bool
|
|
|
|
// message list threading
|
|
threadInfo ThreadInfo
|
|
|
|
// selected account
|
|
account *config.AccountConfig
|
|
myAddresses map[string]bool
|
|
folder *models.Directory // selected folder
|
|
folders []string
|
|
getRUEcount func(string) (int, int, int)
|
|
|
|
state *AccountState
|
|
pendingKeys []config.KeyStroke
|
|
|
|
composer Composer
|
|
}
|
|
|
|
func NewDataSetter() DataSetter {
|
|
return &templateData{}
|
|
}
|
|
|
|
// Data returns the template data
|
|
func (d *templateData) Data() models.TemplateData {
|
|
return d
|
|
}
|
|
|
|
// only used for compose/reply/forward
|
|
func (d *templateData) SetHeaders(h *mail.Header, o *models.OriginalMail) {
|
|
d.headers = h
|
|
d.parent = o
|
|
}
|
|
|
|
// only used for message list templates
|
|
func (d *templateData) SetInfo(info *models.MessageInfo, num int, marked bool,
|
|
) {
|
|
d.info = info
|
|
d.msgNum = num
|
|
d.marked = marked
|
|
}
|
|
|
|
func (d *templateData) SetVisual(visual bool) {
|
|
d.visual = visual
|
|
}
|
|
|
|
func (d *templateData) SetThreading(info ThreadInfo) {
|
|
d.threadInfo = info
|
|
}
|
|
|
|
func (d *templateData) SetAccount(acct *config.AccountConfig) {
|
|
d.account = acct
|
|
d.myAddresses = make(map[string]bool)
|
|
if acct != nil {
|
|
d.myAddresses[acct.From.Address] = true
|
|
for _, addr := range acct.Aliases {
|
|
d.myAddresses[addr.Address] = true
|
|
}
|
|
}
|
|
}
|
|
|
|
func (d *templateData) SetFolder(folder *models.Directory) {
|
|
d.folder = folder
|
|
}
|
|
|
|
func (d *templateData) SetComposer(c Composer) {
|
|
d.composer = c
|
|
}
|
|
|
|
func (d *templateData) SetRUE(folders []string,
|
|
cb func(string) (int, int, int),
|
|
) {
|
|
d.folders = folders
|
|
d.getRUEcount = cb
|
|
}
|
|
|
|
func (d *templateData) SetState(state *AccountState) {
|
|
d.state = state
|
|
}
|
|
|
|
func (d *templateData) SetPendingKeys(keys []config.KeyStroke) {
|
|
d.pendingKeys = keys
|
|
}
|
|
|
|
func (d *templateData) Attach(s string) string {
|
|
if d.composer != nil {
|
|
d.composer.AddAttachment(s)
|
|
return ""
|
|
}
|
|
return fmt.Sprintf("Failed to attach: %s", s)
|
|
}
|
|
|
|
func (d *templateData) Account() string {
|
|
if d.account != nil {
|
|
return d.account.Name
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func (d *templateData) AccountBackend() string {
|
|
if d.account != nil {
|
|
return d.account.Backend
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func (d *templateData) AccountFrom() *mail.Address {
|
|
if d.account != nil {
|
|
return d.account.From
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (d *templateData) Folder() string {
|
|
if d.folder != nil {
|
|
return d.folder.Name
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func (d *templateData) Role() string {
|
|
if d.folder != nil {
|
|
return string(d.folder.Role)
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func (d *templateData) ui() *config.UIConfig {
|
|
return config.Ui.ForAccount(d.Account()).ForFolder(d.Folder())
|
|
}
|
|
|
|
func (d *templateData) To() []*mail.Address {
|
|
var to []*mail.Address
|
|
switch {
|
|
case d.info != nil && d.info.Envelope != nil:
|
|
to = d.info.Envelope.To
|
|
case d.headers != nil:
|
|
to, _ = d.headers.AddressList("to")
|
|
}
|
|
return to
|
|
}
|
|
|
|
func (d *templateData) Cc() []*mail.Address {
|
|
var cc []*mail.Address
|
|
switch {
|
|
case d.info != nil && d.info.Envelope != nil:
|
|
cc = d.info.Envelope.Cc
|
|
case d.headers != nil:
|
|
cc, _ = d.headers.AddressList("cc")
|
|
}
|
|
return cc
|
|
}
|
|
|
|
func (d *templateData) Bcc() []*mail.Address {
|
|
var bcc []*mail.Address
|
|
switch {
|
|
case d.info != nil && d.info.Envelope != nil:
|
|
bcc = d.info.Envelope.Bcc
|
|
case d.headers != nil:
|
|
bcc, _ = d.headers.AddressList("bcc")
|
|
}
|
|
return bcc
|
|
}
|
|
|
|
func (d *templateData) From() []*mail.Address {
|
|
var from []*mail.Address
|
|
switch {
|
|
case d.info != nil && d.info.Envelope != nil:
|
|
from = d.info.Envelope.From
|
|
case d.headers != nil:
|
|
from, _ = d.headers.AddressList("from")
|
|
}
|
|
return from
|
|
}
|
|
|
|
func (d *templateData) Peer() []*mail.Address {
|
|
var from, to []*mail.Address
|
|
switch {
|
|
case d.info != nil && d.info.Envelope != nil:
|
|
from = d.info.Envelope.From
|
|
to = d.info.Envelope.To
|
|
case d.headers != nil:
|
|
from, _ = d.headers.AddressList("from")
|
|
to, _ = d.headers.AddressList("to")
|
|
}
|
|
for _, addr := range from {
|
|
for myAddr := range d.myAddresses {
|
|
if fnmatch.Match(myAddr, addr.Address, 0) {
|
|
return to
|
|
}
|
|
}
|
|
}
|
|
return from
|
|
}
|
|
|
|
func (d *templateData) ReplyTo() []*mail.Address {
|
|
var replyTo []*mail.Address
|
|
switch {
|
|
case d.info != nil && d.info.Envelope != nil:
|
|
replyTo = d.info.Envelope.ReplyTo
|
|
case d.headers != nil:
|
|
replyTo, _ = d.headers.AddressList("reply-to")
|
|
}
|
|
return replyTo
|
|
}
|
|
|
|
func (d *templateData) Date() time.Time {
|
|
var date time.Time
|
|
switch {
|
|
case d.info != nil && d.info.Envelope != nil:
|
|
date = d.info.Envelope.Date
|
|
case d.info != nil:
|
|
date = d.info.InternalDate
|
|
default:
|
|
date = time.Now()
|
|
}
|
|
return date
|
|
}
|
|
|
|
func (d *templateData) DateAutoFormat(date time.Time) string {
|
|
if date.IsZero() {
|
|
return ""
|
|
}
|
|
ui := d.ui()
|
|
year := date.Year()
|
|
day := date.YearDay()
|
|
now := time.Now()
|
|
thisYear := now.Year()
|
|
thisDay := now.YearDay()
|
|
fmt := ui.TimestampFormat
|
|
if year == thisYear {
|
|
switch {
|
|
case day == thisDay && ui.ThisDayTimeFormat != "":
|
|
fmt = ui.ThisDayTimeFormat
|
|
case day > thisDay-7 && ui.ThisWeekTimeFormat != "":
|
|
fmt = ui.ThisWeekTimeFormat
|
|
case ui.ThisYearTimeFormat != "":
|
|
fmt = ui.ThisYearTimeFormat
|
|
}
|
|
}
|
|
return date.Format(fmt)
|
|
}
|
|
|
|
func (d *templateData) Header(name string) string {
|
|
var h *mail.Header
|
|
switch {
|
|
case d.headers != nil:
|
|
h = d.headers
|
|
case d.info != nil && d.info.RFC822Headers != nil:
|
|
h = d.info.RFC822Headers
|
|
default:
|
|
return ""
|
|
}
|
|
text, err := h.Text(name)
|
|
if err != nil {
|
|
text = h.Get(name)
|
|
}
|
|
return text
|
|
}
|
|
|
|
func (d *templateData) ThreadPrefix() string {
|
|
return d.threadInfo.Prefix
|
|
}
|
|
|
|
func (d *templateData) ThreadCount() int {
|
|
return d.threadInfo.Count
|
|
}
|
|
|
|
func (d *templateData) ThreadUnread() int {
|
|
return d.threadInfo.Unread
|
|
}
|
|
|
|
func (d *templateData) ThreadFolded() bool {
|
|
return d.threadInfo.Folded
|
|
}
|
|
|
|
func (d *templateData) ThreadContext() bool {
|
|
return d.threadInfo.Context
|
|
}
|
|
|
|
func (d *templateData) ThreadOrphan() bool {
|
|
return d.threadInfo.Orphan
|
|
}
|
|
|
|
func (d *templateData) Subject() string {
|
|
var subject string
|
|
switch {
|
|
case d.info != nil && d.info.Envelope != nil:
|
|
subject = d.info.Envelope.Subject
|
|
case d.headers != nil:
|
|
subject = d.Header("subject")
|
|
}
|
|
if d.threadInfo.SameSubject {
|
|
subject = ""
|
|
} else if subject == "" {
|
|
subject = config.Ui.EmptySubject
|
|
}
|
|
return subject
|
|
}
|
|
|
|
func (d *templateData) SubjectBase() string {
|
|
var subject string
|
|
switch {
|
|
case d.info != nil && d.info.Envelope != nil:
|
|
subject = d.info.Envelope.Subject
|
|
case d.headers != nil:
|
|
subject = d.Header("subject")
|
|
}
|
|
base, _ := sortthread.GetBaseSubject(subject)
|
|
return base
|
|
}
|
|
|
|
func (d *templateData) Number() int {
|
|
return d.msgNum
|
|
}
|
|
|
|
func (d *templateData) Labels() []string {
|
|
if d.info == nil {
|
|
return nil
|
|
}
|
|
return d.info.Labels
|
|
}
|
|
|
|
func (d *templateData) Filename() string {
|
|
if d.info == nil {
|
|
return ""
|
|
}
|
|
if (d.info.Filenames != nil) && len(d.info.Filenames) > 0 {
|
|
return d.info.Filenames[0]
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func (d *templateData) Filenames() []string {
|
|
if d.info == nil {
|
|
return nil
|
|
}
|
|
return d.info.Filenames
|
|
}
|
|
|
|
func (d *templateData) Flags() []string {
|
|
var flags []string
|
|
if d.info == nil {
|
|
return flags
|
|
}
|
|
|
|
switch {
|
|
case d.info.Flags.Has(models.SeenFlag | models.AnsweredFlag):
|
|
flags = append(flags, d.ui().IconReplied) // message has been replied to
|
|
case d.info.Flags.Has(models.SeenFlag):
|
|
break
|
|
case d.info.Flags.Has(models.RecentFlag):
|
|
flags = append(flags, d.ui().IconNew) // message is unread and new
|
|
default:
|
|
flags = append(flags, d.ui().IconOld) // message is unread and old
|
|
}
|
|
if d.info.Flags.Has(models.DraftFlag) {
|
|
flags = append(flags, d.ui().IconDraft)
|
|
}
|
|
if d.info.Flags.Has(models.DeletedFlag) {
|
|
flags = append(flags, d.ui().IconDeleted)
|
|
}
|
|
if d.info.Flags.Has(models.ForwardedFlag) {
|
|
flags = append(flags, d.ui().IconForwarded)
|
|
}
|
|
if d.info.BodyStructure != nil {
|
|
for _, bS := range d.info.BodyStructure.Parts {
|
|
if strings.ToLower(bS.Disposition) == "attachment" {
|
|
flags = append(flags, d.ui().IconAttachment)
|
|
break
|
|
}
|
|
}
|
|
}
|
|
if d.info.Flags.Has(models.FlaggedFlag) {
|
|
flags = append(flags, d.ui().IconFlagged)
|
|
}
|
|
if d.marked {
|
|
flags = append(flags, d.ui().IconMarked)
|
|
}
|
|
return flags
|
|
}
|
|
|
|
func (d *templateData) IsReplied() bool {
|
|
if d.info != nil && d.info.Flags.Has(models.AnsweredFlag) {
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (d *templateData) IsForwarded() bool {
|
|
if d.info != nil && d.info.Flags.Has(models.ForwardedFlag) {
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (d *templateData) HasAttachment() bool {
|
|
if d.info != nil && d.info.BodyStructure != nil {
|
|
for _, bS := range d.info.BodyStructure.Parts {
|
|
if strings.ToLower(bS.Disposition) == "attachment" {
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (d *templateData) IsRecent() bool {
|
|
if d.info != nil && d.info.Flags.Has(models.RecentFlag) {
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (d *templateData) IsUnread() bool {
|
|
if d.info != nil && !d.info.Flags.Has(models.SeenFlag) {
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (d *templateData) IsFlagged() bool {
|
|
if d.info != nil && d.info.Flags.Has(models.FlaggedFlag) {
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (d *templateData) IsDraft() bool {
|
|
if d.info != nil && d.info.Flags.Has(models.DraftFlag) {
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (d *templateData) IsMarked() bool {
|
|
return d.marked
|
|
}
|
|
|
|
func (d *templateData) MessageId() string {
|
|
if d.info == nil || d.info.Envelope == nil {
|
|
return ""
|
|
}
|
|
return d.info.Envelope.MessageId
|
|
}
|
|
|
|
func (d *templateData) Size() int {
|
|
if d.info == nil || d.info.Envelope == nil {
|
|
return 0
|
|
}
|
|
return int(d.info.Size)
|
|
}
|
|
|
|
func (d *templateData) OriginalText() string {
|
|
if d.parent == nil {
|
|
return ""
|
|
}
|
|
return d.parent.Text
|
|
}
|
|
|
|
func (d *templateData) OriginalDate() time.Time {
|
|
if d.parent == nil {
|
|
return time.Time{}
|
|
}
|
|
return d.parent.Date
|
|
}
|
|
|
|
func (d *templateData) OriginalFrom() []*mail.Address {
|
|
if d.parent == nil || d.parent.RFC822Headers == nil {
|
|
return nil
|
|
}
|
|
from, _ := d.parent.RFC822Headers.AddressList("from")
|
|
return from
|
|
}
|
|
|
|
func (d *templateData) OriginalMIMEType() string {
|
|
if d.parent == nil {
|
|
return ""
|
|
}
|
|
return d.parent.MIMEType
|
|
}
|
|
|
|
func (d *templateData) OriginalHeader(name string) string {
|
|
if d.parent == nil || d.parent.RFC822Headers == nil {
|
|
return ""
|
|
}
|
|
text, err := d.parent.RFC822Headers.Text(name)
|
|
if err != nil {
|
|
text = d.parent.RFC822Headers.Get(name)
|
|
}
|
|
return text
|
|
}
|
|
|
|
func (d *templateData) rue(folders ...string) (int, int, int) {
|
|
var recent, unread, exists int
|
|
if d.getRUEcount != nil {
|
|
if len(folders) == 0 {
|
|
folders = d.folders
|
|
}
|
|
for _, dir := range folders {
|
|
r, u, e := d.getRUEcount(dir)
|
|
recent += r
|
|
unread += u
|
|
exists += e
|
|
}
|
|
}
|
|
return recent, unread, exists
|
|
}
|
|
|
|
func (d *templateData) Recent(folders ...string) int {
|
|
r, _, _ := d.rue(folders...)
|
|
return r
|
|
}
|
|
|
|
func (d *templateData) Unread(folders ...string) int {
|
|
_, u, _ := d.rue(folders...)
|
|
return u
|
|
}
|
|
|
|
func (d *templateData) Exists(folders ...string) int {
|
|
_, _, e := d.rue(folders...)
|
|
return e
|
|
}
|
|
|
|
func (d *templateData) RUE(folders ...string) string {
|
|
r, u, e := d.rue(folders...)
|
|
switch {
|
|
case r > 0:
|
|
return fmt.Sprintf("%d/%d/%d", r, u, e)
|
|
case u > 0:
|
|
return fmt.Sprintf("%d/%d", u, e)
|
|
case e > 0:
|
|
return fmt.Sprintf("%d", e)
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func (d *templateData) Connected() bool {
|
|
if d.state != nil {
|
|
return d.state.Connected
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (d *templateData) ConnectionInfo() string {
|
|
switch {
|
|
case d.state == nil:
|
|
return ""
|
|
case d.state.connActivity != "":
|
|
return d.state.connActivity
|
|
case d.state.Connected:
|
|
return texter().Connected()
|
|
default:
|
|
return texter().Disconnected()
|
|
}
|
|
}
|
|
|
|
func (d *templateData) ContentInfo() string {
|
|
if d.state == nil {
|
|
return ""
|
|
}
|
|
var content []string
|
|
fldr := d.state.folderState(d.Folder())
|
|
if fldr.FilterActivity != "" {
|
|
content = append(content, fldr.FilterActivity)
|
|
} else if fldr.Filter != "" {
|
|
content = append(content, texter().FormatFilter(fldr.Filter))
|
|
}
|
|
if fldr.Search != "" {
|
|
content = append(content, texter().FormatSearch(fldr.Search))
|
|
}
|
|
return strings.Join(content, config.Statusline.Separator)
|
|
}
|
|
|
|
func (d *templateData) StatusInfo() string {
|
|
stat := d.ConnectionInfo()
|
|
if content := d.ContentInfo(); content != "" {
|
|
stat += config.Statusline.Separator + content
|
|
}
|
|
return stat
|
|
}
|
|
|
|
func (d *templateData) TrayInfo() string {
|
|
if d.state == nil {
|
|
return ""
|
|
}
|
|
var tray []string
|
|
fldr := d.state.folderState(d.Folder())
|
|
if fldr.Sorting {
|
|
tray = append(tray, texter().Sorting())
|
|
}
|
|
if fldr.Threading {
|
|
tray = append(tray, texter().Threading())
|
|
}
|
|
if d.state.passthrough {
|
|
tray = append(tray, texter().Passthrough())
|
|
}
|
|
if d.visual {
|
|
tray = append(tray, texter().Visual())
|
|
}
|
|
return strings.Join(tray, config.Statusline.Separator)
|
|
}
|
|
|
|
func (d *templateData) PendingKeys() string {
|
|
return config.FormatKeyStrokes(d.pendingKeys)
|
|
}
|
|
|
|
func (d *templateData) Style(content, name string) string {
|
|
cfg := config.Ui.ForAccount(d.Account())
|
|
style := cfg.GetUserStyle(name)
|
|
return ui.ApplyStyle(style, content)
|
|
}
|
|
|
|
func (d *templateData) StyleSwitch(content string, cases ...models.Case) string {
|
|
for _, c := range cases {
|
|
if c.Matches(content) {
|
|
cfg := config.Ui.ForAccount(d.Account())
|
|
style := cfg.GetUserStyle(c.Value())
|
|
return ui.ApplyStyle(style, content)
|
|
}
|
|
}
|
|
return content
|
|
}
|
|
|
|
func (d *templateData) StyleMap(elems []string, cases ...models.Case) []string {
|
|
mapped := make([]string, 0, len(elems))
|
|
top:
|
|
for _, e := range elems {
|
|
for _, c := range cases {
|
|
if c.Matches(e) {
|
|
if c.Skip() {
|
|
continue top
|
|
}
|
|
cfg := config.Ui.ForAccount(d.Account())
|
|
style := cfg.GetUserStyle(c.Value())
|
|
e = ui.ApplyStyle(style, e)
|
|
break
|
|
}
|
|
}
|
|
mapped = append(mapped, e)
|
|
}
|
|
return mapped
|
|
}
|
|
|
|
func (d *templateData) Signature() string {
|
|
if d.account == nil {
|
|
return ""
|
|
}
|
|
var signature []byte
|
|
if d.account.SignatureCmd != "" {
|
|
var err error
|
|
signature, err = d.readSignatureFromCmd()
|
|
if err != nil {
|
|
var execErr *exec.ExitError
|
|
if errors.As(err, &execErr) {
|
|
log.Warnf("signature command failed with error (%d): %s", execErr.ExitCode(), execErr.Stderr)
|
|
}
|
|
signature = d.readSignatureFromFile()
|
|
}
|
|
} else {
|
|
signature = d.readSignatureFromFile()
|
|
}
|
|
if len(bytes.TrimSpace(signature)) == 0 {
|
|
return ""
|
|
}
|
|
signature = d.ensureSignatureDelimiter(signature)
|
|
return string(signature)
|
|
}
|
|
|
|
func (d *templateData) readSignatureFromCmd() ([]byte, error) {
|
|
sigCmd := d.account.SignatureCmd
|
|
cmd := exec.Command("sh", "-c", sigCmd)
|
|
env := os.Environ()
|
|
env = append(env, fmt.Sprintf("AERC_ACCOUNT=%s", d.account.Name))
|
|
env = append(env, fmt.Sprintf("AERC_FOLDER=%s", d.folder.Name))
|
|
cmd.Env = env
|
|
signature, err := cmd.Output()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return signature, nil
|
|
}
|
|
|
|
func (d *templateData) readSignatureFromFile() []byte {
|
|
sigFile := d.account.SignatureFile
|
|
if sigFile == "" {
|
|
return nil
|
|
}
|
|
sigFile = xdg.ExpandHome(sigFile)
|
|
signature, err := os.ReadFile(sigFile)
|
|
if err != nil {
|
|
log.Errorf(" Error loading signature from file: %v", sigFile)
|
|
return nil
|
|
}
|
|
return signature
|
|
}
|
|
|
|
func (d *templateData) ensureSignatureDelimiter(signature []byte) []byte {
|
|
buf := bytes.NewBuffer(signature)
|
|
scanner := bufio.NewScanner(buf)
|
|
for scanner.Scan() {
|
|
line := scanner.Text()
|
|
if line == "-- " {
|
|
// signature contains standard delimiter, we're good
|
|
return signature
|
|
}
|
|
}
|
|
// signature does not contain standard delimiter, prepend one
|
|
sig := "\n\n-- \n" + strings.TrimLeft(string(signature), " \t\r\n")
|
|
return []byte(sig)
|
|
}
|