aboutsummaryrefslogtreecommitdiff
path: root/lib/format/format.go
blob: 9a675ef08b9968dc7df085b660aa16b9e5603d95 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
package format

import (
	"fmt"
	"regexp"
	"strings"
	"time"
	"unicode"

	"github.com/emersion/go-message/mail"
	"github.com/mattn/go-runewidth"
	"github.com/rivo/uniseg"
)

// AddressForHumans formats the address. If the address's name
// contains non-ASCII characters it will be quoted but not encoded.
// Meant for display purposes to the humans, not for sending over the wire.
func AddressForHumans(a *mail.Address) string {
	if a.Name != "" {
		if atom.MatchString(a.Name) {
			return fmt.Sprintf("%s <%s>", a.Name, a.Address)
		} else {
			return fmt.Sprintf("\"%s\" <%s>",
				strings.ReplaceAll(a.Name, "\"", "'"), a.Address)
		}
	} else {
		return fmt.Sprintf("<%s>", a.Address)
	}
}

var atom *regexp.Regexp = regexp.MustCompile("^[a-z0-9!#$%7'*+-/=?^_`{}|~ ]+$")

// FormatAddresses formats a list of addresses into a human readable string
func FormatAddresses(l []*mail.Address) string {
	formatted := make([]string, len(l))
	for i, a := range l {
		formatted[i] = AddressForHumans(a)
	}
	return strings.Join(formatted, ", ")
}

// CompactPath reduces a directory path into a compact form.  The directory
// name will be split with the provided separator and each part will be reduced
// to the first letter in its name: INBOX/01_WORK/PROJECT  will become
// I/W/PROJECT.
func CompactPath(name string, sep rune) (compact string) {
	parts := strings.Split(name, string(sep))
	for i, part := range parts {
		if i == len(parts)-1 {
			compact += part
		} else {
			if len(part) != 0 {
				r := part[0]
				for i := 0; i < len(part)-1; i++ {
					if unicode.IsLetter(rune(part[i])) {
						r = part[i]
						break
					}
				}
				compact += fmt.Sprintf("%c%c", r, sep)
			} else {
				compact += fmt.Sprintf("%c", sep)
			}
		}
	}
	return
}

func TruncateHead(s string, w int, head string) string {
	width := runewidth.StringWidth(s)
	if width <= w {
		return s
	}
	w -= runewidth.StringWidth(head)
	pos := 0
	g := uniseg.NewGraphemes(s)
	for g.Next() {
		var chWidth int
		for _, r := range g.Runes() {
			chWidth = runewidth.RuneWidth(r)
			if chWidth > 0 {
				break
			}
		}
		if width-chWidth <= w {
			pos, _ = g.Positions()
			break
		}
		width -= chWidth
	}
	return head + s[pos:]
}

func ShellQuote(args []string) string {
	quoted := make([]string, len(args))

	for i, arg := range args {
		if strings.ContainsAny(arg, " '\"|&!#$;[](){}<>*\n\t") {
			if strings.ContainsAny(arg, "!\"$") {
				arg = "'" + arg + "'"
			} else {
				arg = "\"" + arg + "\""
			}
		}
		quoted[i] = arg
	}

	return strings.Join(quoted, " ")
}

func DummyIfZeroDate(date time.Time, format string, todayFormat string,
	thisWeekFormat string, thisYearFormat string,
) string {
	if date.IsZero() {
		return strings.Repeat("?", len(format))
	}
	year := date.Year()
	day := date.YearDay()
	now := time.Now()
	thisYear := now.Year()
	thisDay := now.YearDay()
	if year == thisYear {
		if day == thisDay && todayFormat != "" {
			return date.Format(todayFormat)
		}
		if day > thisDay-7 && thisWeekFormat != "" {
			return date.Format(thisWeekFormat)
		}
		if thisYearFormat != "" {
			return date.Format(thisYearFormat)
		}
	}
	return date.Format(format)
}