workgroups/vendor/github.com/atc0005/go-teams-notify/v2/format.go
Marvin Preuss 1d4ae27878
All checks were successful
continuous-integration/drone/push Build is passing
ci: drone yaml with reusable anchors
2021-09-24 17:34:17 +02:00

210 lines
7.2 KiB
Go

// Copyright 2021 Adam Chalkley
//
// https://github.com/atc0005/go-teams-notify
//
// Licensed under the MIT License. See LICENSE file in the project root for
// full license information.
package goteamsnotify
import (
"bytes"
"encoding/json"
"errors"
"strings"
)
// Newline patterns stripped out of text content sent to Microsoft Teams (by
// request) and replacement break value used to provide equivalent formatting.
const (
// CR LF \r\n (windows)
windowsEOLActual = "\r\n"
windowsEOLEscaped = `\r\n`
// CF \r (mac)
macEOLActual = "\r"
macEOLEscaped = `\r`
// LF \n (unix)
unixEOLActual = "\n"
unixEOLEscaped = `\n`
// Used by Teams to separate lines
breakStatement = "<br>"
)
// Even though Microsoft Teams doesn't show the additional newlines,
// https://messagecardplayground.azurewebsites.net/ DOES show the results
// as a formatted code block. Including the newlines now is an attempt at
// "future proofing" the codeblock support in MessageCard values sent to
// Microsoft Teams.
const (
// msTeamsCodeBlockSubmissionPrefix is the prefix appended to text input
// to indicate that the text should be displayed as a codeblock by
// Microsoft Teams.
msTeamsCodeBlockSubmissionPrefix string = "\n```\n"
// msTeamsCodeBlockSubmissionPrefix string = "```"
// msTeamsCodeBlockSubmissionSuffix is the suffix appended to text input
// to indicate that the text should be displayed as a codeblock by
// Microsoft Teams.
msTeamsCodeBlockSubmissionSuffix string = "```\n"
// msTeamsCodeBlockSubmissionSuffix string = "```"
// msTeamsCodeSnippetSubmissionPrefix is the prefix appended to text input
// to indicate that the text should be displayed as a code formatted
// string of text by Microsoft Teams.
msTeamsCodeSnippetSubmissionPrefix string = "`"
// msTeamsCodeSnippetSubmissionSuffix is the suffix appended to text input
// to indicate that the text should be displayed as a code formatted
// string of text by Microsoft Teams.
msTeamsCodeSnippetSubmissionSuffix string = "`"
)
// TryToFormatAsCodeBlock acts as a wrapper for FormatAsCodeBlock. If an
// error is encountered in the FormatAsCodeBlock function, this function will
// return the original string, otherwise if no errors occur the newly formatted
// string will be returned.
func TryToFormatAsCodeBlock(input string) string {
result, err := FormatAsCodeBlock(input)
if err != nil {
logger.Printf("TryToFormatAsCodeBlock: error occurred when calling FormatAsCodeBlock: %v\n", err)
logger.Println("TryToFormatAsCodeBlock: returning original string")
return input
}
logger.Println("TryToFormatAsCodeBlock: no errors occurred when calling FormatAsCodeBlock")
return result
}
// TryToFormatAsCodeSnippet acts as a wrapper for FormatAsCodeSnippet. If
// an error is encountered in the FormatAsCodeSnippet function, this function will
// return the original string, otherwise if no errors occur the newly formatted
// string will be returned.
func TryToFormatAsCodeSnippet(input string) string {
result, err := FormatAsCodeSnippet(input)
if err != nil {
logger.Printf("TryToFormatAsCodeSnippet: error occurred when calling FormatAsCodeBlock: %v\n", err)
logger.Println("TryToFormatAsCodeSnippet: returning original string")
return input
}
logger.Println("TryToFormatAsCodeSnippet: no errors occurred when calling FormatAsCodeSnippet")
return result
}
// FormatAsCodeBlock accepts an arbitrary string, quoted or not, and calls a
// helper function which attempts to format as a valid Markdown code block for
// submission to Microsoft Teams
func FormatAsCodeBlock(input string) (string, error) {
if input == "" {
return "", errors.New("received empty string, refusing to format")
}
result, err := formatAsCode(
input,
msTeamsCodeBlockSubmissionPrefix,
msTeamsCodeBlockSubmissionSuffix,
)
return result, err
}
// FormatAsCodeSnippet accepts an arbitrary string, quoted or not, and calls a
// helper function which attempts to format as a single-line valid Markdown
// code snippet for submission to Microsoft Teams
func FormatAsCodeSnippet(input string) (string, error) {
if input == "" {
return "", errors.New("received empty string, refusing to format")
}
result, err := formatAsCode(
input,
msTeamsCodeSnippetSubmissionPrefix,
msTeamsCodeSnippetSubmissionSuffix,
)
return result, err
}
// formatAsCode is a helper function which accepts an arbitrary string, quoted
// or not, a desired prefix and a suffix for the string and attempts to format
// as a valid Markdown formatted code sample for submission to Microsoft Teams
func formatAsCode(input string, prefix string, suffix string) (string, error) {
var err error
var byteSlice []byte
switch {
// required; protects against slice out of range panics
case input == "":
return "", errors.New("received empty string, refusing to format as code block")
// If the input string is already valid JSON, don't double-encode and
// escape the content
case json.Valid([]byte(input)):
logger.Printf("formatAsCode: input string already valid JSON; input: %+v", input)
logger.Printf("formatAsCode: Calling json.RawMessage([]byte(input)); input: %+v", input)
// FIXME: Is json.RawMessage() really needed if the input string is
// *already* JSON? https://golang.org/pkg/encoding/json/#RawMessage
// seems to imply a different use case.
byteSlice = json.RawMessage([]byte(input))
//
// From light testing, it appears to not be necessary:
//
// logger.Printf("formatAsCode: Skipping json.RawMessage, converting string directly to byte slice; input: %+v", input)
// byteSlice = []byte(input)
default:
logger.Printf("formatAsCode: input string not valid JSON; input: %+v", input)
logger.Printf("formatAsCode: Calling json.Marshal(input); input: %+v", input)
byteSlice, err = json.Marshal(input)
if err != nil {
return "", err
}
}
logger.Println("formatAsCode: byteSlice as string:", string(byteSlice))
var prettyJSON bytes.Buffer
logger.Println("formatAsCode: calling json.Indent")
err = json.Indent(&prettyJSON, byteSlice, "", "\t")
if err != nil {
return "", err
}
formattedJSON := prettyJSON.String()
logger.Println("formatAsCode: Formatted JSON:", formattedJSON)
// handle both cases: where the formatted JSON string was not wrapped with
// double-quotes and when it was
codeContentForSubmission := prefix + strings.Trim(formattedJSON, "\"") + suffix
logger.Printf("formatAsCode: formatted JSON as-is:\n%s\n\n", formattedJSON)
logger.Printf("formatAsCode: formatted JSON wrapped with code prefix/suffix: \n%s\n\n", codeContentForSubmission)
// err should be nil if everything worked as expected
return codeContentForSubmission, err
}
// ConvertEOLToBreak converts \r\n (windows), \r (mac) and \n (unix) into <br>
// HTML/Markdown break statements.
func ConvertEOLToBreak(s string) string {
logger.Printf("ConvertEOLToBreak: Received %#v", s)
s = strings.ReplaceAll(s, windowsEOLActual, breakStatement)
s = strings.ReplaceAll(s, windowsEOLEscaped, breakStatement)
s = strings.ReplaceAll(s, macEOLActual, breakStatement)
s = strings.ReplaceAll(s, macEOLEscaped, breakStatement)
s = strings.ReplaceAll(s, unixEOLActual, breakStatement)
s = strings.ReplaceAll(s, unixEOLEscaped, breakStatement)
logger.Printf("ConvertEOLToBreak: Returning %#v", s)
return s
}