Skip to content

Commit dc2e21a

Browse files
committed
template support, DYE_COLORS unset fix
1 parent 8a4dd5a commit dc2e21a

File tree

7 files changed

+234
-72
lines changed

7 files changed

+234
-72
lines changed

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,11 @@
11
# 1.0.0
22

33
Initial version.
4+
5+
# 1.1.0
6+
7+
- Adds [templates](./README.md#templates) and the "dye print" and "dye write" commands to use them. Templates are more concise than any other way of using dye and are expected to be the main interface in future major versions.
8+
9+
- Deprecated [wrapping text](./README.md#wrapping-text) because of its sharp edges.
10+
11+
- Fixed an issue where, if dye decided to disable color, "unbound variable" errors could be printed in some circumstances.

README.md

Lines changed: 85 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -34,20 +34,22 @@ It's respectful because:
3434

3535
- it is written to only put functions and environment variables prefixed with "dye" or "DYE" into the shell's global namespace, and carefully avoids clobbering any existing shell variables during operation.
3636

37-
dye does call tput(1) for every terminal sequence it needs to output, so it's not screamingly fast. However, in practice, it's more than fast enough for the job I need it for: making the output of shell scripts colorful to make them easier to read and scan. If you're working with lots of color (for example, creating [ANSI art](https://en.wikipedia.org/wiki/ANSI_art)), it's probably best to stick to a solution that caches ANSI sequences and forgo the portability.
37+
dye does call [tput](https://pubs.opengroup.org/onlinepubs/9799919799/utilities/tput.html) for every terminal sequence it needs to output, so it's not screamingly fast. However, in practice, it's more than fast enough for the job I need it for: making the output of shell scripts colorful to make them easier to read and scan. If you're working with lots of color (for example, creating [ANSI art](https://en.wikipedia.org/wiki/ANSI_art)), it's probably best to stick to a solution that caches ANSI sequences and forgo the portability.
3838

3939
I wrote dye to replace my previous project, [portable-color](https://mattiebee.dev/portable-color). portable-color was fine, but would load lots of functions into the shell's global namespace. dye has a better API, with more capabilities and conveniences.
4040

4141
## Usage
4242

4343
### Embedding
4444

45-
You can use dye in your script by copying the contents of [dye.sh](./dye.sh) above the place you will use it.
45+
You can use dye in your script by copying the contents of [dye.sh](./dye.sh) into your script so that the functions are defined before you use them.
4646

4747
This the method I recommend. Shell scripts that I write are generally made to be self-contained, and embedding makes them very easy to download and use.
4848

4949
A couple notes on this strategy:
5050

51+
- If you don't want dye's over 100 lines of code at the top of your script, wrap your main code in a function, then insert all of dye's code, and finally call your main function at the end.
52+
5153
- If you end up changing dye's code, consider changing the names of the dye functions and variables that end up in the shell's global namespace, to avoid conflicts with the standard dye code that may be depended on elsewhere—particularly if you're removing functionality you do not use.
5254

5355
- If you distribute your script with dye's code inline, you must include a copy of [the license](./LICENSE.md) in some way with your script. This specifically so others understand their rights in regard to the use of this software.
@@ -82,8 +84,69 @@ An alternate mode for "setup" will not enable color by default, but will enable
8284
dye setup default-off
8385
```
8486

87+
### Templates
88+
89+
The best way to use dye is with its built-in templates.
90+
91+
Templates embed other dye commands inside a single string.
92+
93+
```shell
94+
dye print "{{green}}It's not easy being... well, you know.{{reset}}"
95+
```
96+
97+
Like ["echo"](https://pubs.opengroup.org/onlinepubs/9799919799/utilities/echo.html), "dye print" ends its output with a newline.
98+
("dye p" is a shorthand for "dye print" and highly recommended.)
99+
100+
Note that _unlike_ "echo" on many systems, control characters are not automatically parsed. Whatever the shell passes to "dye print" is only processed according to [two simple syntax rules](#syntax).
101+
102+
```shell
103+
dye write "So {{bold}}bold{{reset}}, it's not recommended "
104+
dye write "for human consumption!"
105+
```
106+
107+
"dye write" is the same, except it does not end its output with a newline.
108+
109+
#### Syntax
110+
111+
Templates have two basic syntax rules:
112+
113+
1. Text between double curly braces ("{{" and "}}") is interpreted as a dye command. The curly braces are not printed.
114+
115+
2. A backslash ("\") escapes the next character if it is either a backlash or left curly brace. The backslash itself is not printed. Useful if you want to print two left curly braces.
116+
117+
#### Multiline strings
118+
119+
dye commands between double curly braces, and as such can have trailing spaces added to enable multiline strings:
120+
121+
```shell
122+
dye print "We have an {{bold
123+
}}awful{{reset}} lot to say across {{bold
124+
}}many{{reset}} lines."
125+
```
126+
127+
#### Escaping backslashes
128+
129+
There is one caveat to the syntax rules. If you're using double quotes, the backslash behavior can be a bit unintuitive in one case.
130+
131+
```shell
132+
dye print "a\\{{red}}/b"
133+
```
134+
135+
This actually prints "a{{red}}/b", because the double backlash is interpreted as a single backslash by the shell before dye even sees it. dye then interprets the resulting "\\{" as escaping "{".
136+
137+
Escaping both backslashes passes through "\\\\" to dye's engine, which in turn interprets it as an escaped single-backslash. This prints `a\/b` with a red color change in the middle:
138+
139+
```shell
140+
dye print "a\\\\{{red}}/b"
141+
```
142+
143+
Using single-quoted strings works around this issue, but of course sacrifices the variable interpolation you get with double-quoted strings.
144+
85145
### Wrapping text
86146

147+
> [!NOTE]
148+
> Wrapping text is deprecated due to the awkwardness of dealing with [resets](#resets) and the extra weirdness of [unquoted wrapping](#unquoted-wrapping), and is likely to be removed in a future major version.
149+
87150
For simple [color](#colors) and [emphasis](#emphases), using dye to wrap quoted text is the most convenient method.
88151

89152
```shell
@@ -94,6 +157,8 @@ echo "$(dye green "It's not easy being... well, you know.")"
94157
echo "So $(dye bold "bold"), it's not recommended for human consumption\!"
95158
```
96159

160+
#### Unquoted wrapping
161+
97162
Quoting text is also not *strictly* necessary, but can result in the need to use many more escapes (just like it would if using "echo" straight up). It also means whitespace gets collapsed, so beware!
98163

99164
```shell
@@ -108,7 +173,7 @@ dye will send this reset sequence at the end of wrapped text for colors and sele
108173

109174
### Manual control
110175

111-
More complex markup is easier to manage with manual control:
176+
You can generally use [command substitution](https://pubs.opengroup.org/onlinepubs/9799919799/utilities/V3_chap02.html#tag_19_06_03) to capture the output from manual control commands so it can all be strung together:
112177

113178
```shell
114179
echo "$(dye cyan)$(dye bold)Cyan$(dye reset), $(dye magenta)$(dye bold
@@ -118,6 +183,8 @@ echo "$(dye cyan)$(dye bold)Cyan$(dye reset), $(dye magenta)$(dye bold
118183

119184
Using lots of manual control can make lines pretty long, but as you can see, you can also leverage the fact that line breaks are valid inside command substitution to break them up.
120185

186+
It's better (and shorter!) to use [templates](#templates), though. Capturing the output of dye works in most cases, but could break in some obscure situations.
187+
121188
### Colors
122189

123190
Many colors are available for use, subject to terminal support.
@@ -144,20 +211,22 @@ There's the basic ANSI color set:
144211
"dye yellow" will set the foreground color to yellow, for example. There are also "fg" and "bg" commands that will explicitly set the foreground or background color, respectively:
145212

146213
```shell
147-
echo "$(dye bg blue)$(dye fg yellow)In Ann Arbor, everything is this color.$(dye reset)"
214+
dye print '{{bg blue}}{{fg yellow}}In Ann Arbor, everything is this color.{{reset}}'
148215
```
149216

217+
Turning colors off requires replacing them with a different color, or a [reset](#resets).
218+
150219
#### High colors
151220

152-
Some terminal definitions, like "ansi" and "xterm", don't recognize colors higher than 7. If "DYE_COLORS" is 8, indicating this scenario, dye will synthesize "bright" colors by turning on "bold" and setting the non-bright equivalent.
221+
Some terminal definitions, like "ansi" and "xterm", don't recognize colors higher than 7. If only colors 0-7 are supported, indicated by "DYE_COLORS" being set to 8 (via [setup](#initialization)), dye will synthesize "bright" colors by turning on "bold" and setting the non-bright equivalent color. (Some terminals will, in turn, render this with a bright color!)
153222

154223
Note that this also means that "bold" may be turned on unexpectedly if you're using "bright" colors—so keep this situation in mind:
155224

156-
- If you're nesting wrapped text, make sure that nested text deals with the fact that "bold" might be on if you're using a "bright" color.
225+
- If you're using templates or manual control, be sure to reset at the appropriate time if the possiblity that "bold" might be on.
157226

158-
- If you're using manual control, be sure to reset at the appropriate time if the possiblity that "bold" might be on.
227+
- If you're nesting wrapped text, make sure that nested text deals with the fact that "bold" might be on if you're using a "bright" color.
159228

160-
You can test to see how your code is working by setting TERM to "ansi" or "xterm" on many systems.
229+
You can test your code in this situation by setting "TERM" to "ansi" or "xterm" on many systems.
161230

162231
### Emphases
163232

@@ -181,17 +250,17 @@ The second group have "end" terminal sequences that can turn them off explicitly
181250
- `standout` (or `so`, often displayed as reversed foreground and background)
182251
- `underline` (or `ul`, or `u`)
183252

184-
When using one of these, you don't have to re-enable other modes:
253+
When using one of these, you don't have to re-enable other modes—just "end" them:
185254

186255
```shell
187-
echo "$(dye magenta)Mary $(dye italic "had") a little lamb.$(dye reset)"
188-
echo "$(dye magenta)Mary had a $(dye italic "little") lamb.$(dye reset)"
256+
dye print "{{magenta}}Mary {{italic}}had{{end italic}} a little lamb.{{reset}}"
257+
dye print "{{magenta}}Mary had a {{italic}}little{{end italic}} lamb.{{reset}}"
189258
```
190259

191-
For manual control, the "end" command can be used for these:
260+
They also support old-school wrapping:
192261

193262
```shell
194-
echo "Visit $(dye ul)https://mattiebee.dev/dye$(dye end ul) to get the code."
263+
echo "Visit $(dye ul "https://mattiebee.dev/dye") to get the code."
195264
```
196265

197266
To match "end", "begin" is also available (and works with all emphases). It behaves the same way as just using the emphasis, e.g. "dye begin italic" is equivalent to "dye italic".
@@ -214,8 +283,8 @@ This is particularly important because dye should run everywhere it can, includi
214283

215284
#### tput
216285

217-
During the development of dye, I did explore things like caching the output of tput(1) so it didn't have to be invoked quite so much.
286+
During the development of dye, I did explore things like caching the output of "tput" so it didn't have to be invoked quite so much.
218287

219-
The added complexity was really not worth it, since tput(1) is still fast enough (i.e. not at all noticeably slow) for most purposes where a shell script is doing work for at least a small amount of time. The cache would also need to be filled, and most scripts just don't switch colors enough to make it worthwhile.
288+
The added complexity was really not worth it, since "tput" is still fast enough (i.e. not at all noticeably slow) for most purposes where a shell script is doing work for at least a small amount of time. The cache would also need to be filled, and most scripts just don't switch colors enough to make it worthwhile.
220289

221-
The sequences dye generally uses are simple and unconcerned with this, but there are also interesting details with certain terminal control sequences on certain systems that tput(1) can handle if invoked directly, such as embedded delays. So, the practice also encourages maximum compatibility.
290+
The sequences dye generally uses are simple and unconcerned with this, but there are also interesting details with certain terminal control sequences on certain systems that "tput" can handle if invoked directly, such as embedded delays. So, the practice also encourages maximum compatibility—especially if using the new template engine, which passes control back and forth between "tput" and "printf".

demo

Lines changed: 34 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,6 @@
33
#
44
# Demos many of dye's features.
55
#
6-
# The command use is intentionally varied, using wrapped mode or manual,
7-
# as well as several aliases for several emphases.
8-
#
96

107
set -eu
118

@@ -14,58 +11,45 @@ set -eu
1411

1512
dye setup
1613

17-
cat <<EOT
14+
dye write "\
1815
19-
$(dye so "| |")
20-
$(dye so "| dye's delightful demo |")
21-
$(dye so "| |")
16+
{{so}}| |{{end so}}
17+
{{so}}| dye's delightful demo |{{end so}}
18+
{{so}}| |{{end so}}
2219
23-
dye is a $(dye bold "$(dye magenta "portable")"
24-
) and $(dye bold "$(dye cyan "respectful")"
25-
) color library for shell scripts.
20+
dye is a {{bold}}{{magenta}}portable{{reset
21+
}} and {{bold}}{{cyan}}respectful{{reset
22+
}} color library for shell scripts.
2623
27-
It's $(dye bold "$(dye magenta "portable")"
28-
) because it should run anywhere a POSIX shell exists.
24+
It's {{bold}}{{magenta}}portable{{reset
25+
}} because it should run anywhere a POSIX shell exists.
2926
30-
It's $(dye bold "$(dye cyan "respectful")"
31-
) because it carefully determines whether to enable
27+
It's {{bold}}{{cyan}}respectful{{reset
28+
}} because it carefully determines whether to enable
3229
color based on environment as well as user and developer preferences.
33-
$(dye italic "(Try this demo again with NO_COLOR set!)")
30+
{{i}}(Try this demo again with NO_COLOR set!){{end i}}
3431
35-
$(dye yellow)It has lots of colors and emphases:$(dye reset)
32+
{{yellow}}{{bold}}It has lots of colors and emphases:{{reset}}
3633
37-
It lets you use many $(dye red "c"
38-
)$(dye yellow "o"
39-
)$(dye green "l"
40-
)$(dye cyan "o"
41-
)$(dye blue "r"
42-
)$(dye magenta "s"
43-
) as well as $(dye ul "several"
44-
) $(dye i "cool"
45-
) $(dye bold "text"
46-
) $(dye so "styles").
34+
It lets you use many {{red}}c{{yellow}}o{{green}}l{{cyan}}o{{blue
35+
}}r{{magenta}}s as well as {{ul}}several{{end ul}} {{i}}cool{{end i}} {{bold
36+
}}text{{reset}} {{so}}styles{{end so}}.
4737
48-
(And no, we didn't forget $(dye bg blue "$(dye yellow "background colors")")!)
38+
(And no, we didn't forget {{bg blue}}{{yellow}}background colors{{reset}}!)
4939
50-
$(dye dim "You can even do dark $(dye red "c")$(dye dim
51-
)$(dye yellow "o")$(dye dim
52-
)$(dye green "l")$(dye dim
53-
)$(dye cyan "o")$(dye dim
54-
)$(dye blue "r")$(dye dim
55-
)$(dye magenta "s")$(dye dim
56-
) if you want...")
40+
{{dim}}You can even do dark {{red}}c{{yellow}}o{{green}}l{{cyan}}o{{blue
41+
}}r{{magenta}}s {{white}}if you want...{{reset}}
5742
58-
$(dye yellow)It supports $(dye begin ul)256-color terminals$(dye end ul)*:$(dye reset)
43+
{{yellow}}{{bold}}It supports {{ul}}256-color terminals{{end ul}}*:{{reset}}
5944
60-
EOT
45+
"
6146

6247
print_range() (
6348
i="$1"
6449
end="$2"
6550
while [ "${i}" -le "${end}" ]; do
6651
dye fg "${i}"
6752
printf "%3s " "${i}"
68-
dye reset
6953
i=$((i + 1))
7054
done
7155
printf "\n"
@@ -91,27 +75,24 @@ printf "\n"
9175
print_range 232 243
9276
print_range 244 255
9377

94-
cat <<EOT
95-
96-
$(dye i "*Not what you expected? Check your TERM environment variable.")
97-
98-
$(dye yellow)It can be used several ways:$(dye reset)
78+
dye reset
9979

100-
$(dye bold "$(dye blue "==>")") Wrapping quoted text: $(
101-
dye so "Like \$(dye green \"this\").")
80+
dye write "\
10281
103-
$(dye bold "$(dye blue "==>")") Unquoted text: $(
104-
dye so "Like \$(dye green this, if whitespace is not important.)")
82+
{{i
83+
}}*Not what you expected? Check your TERM environment variable.{{end i}}
10584
106-
$(dye bold "$(dye blue "==>")") Manual: $(
107-
dye so "Like \$(dye green)this\$(dye reset).")
85+
{{yellow}}{{bold}}It's super easy to use:{{reset}}
10886
109-
You can embed it or source it, depending on your needs.
87+
{{so}} {{end so}}
88+
{{so}} dye print 'Just like \{{red}}\{{bold}}this\{{reset}}!' {{end so}}
89+
{{so}} {{end so}}
11090
111-
You can also just use parts of it, like the enable-color detection.
91+
You can embed it or source it, depending on your needs. You can also
92+
just use parts of it, like the enable-color detection.
11293
113-
And it's $(dye ul "completely tested") with $(dye bold "shellspec")!
94+
{{i}}And it's {{ul}}completely tested{{end ul}} with {{bold}}shellspec{{reset}}!
11495
115-
$(dye brightblue)$(dye bold)https://mattiebee.dev/dye$(dye reset)
96+
Get it at {{brightblue}}{{ul}}https://mattiebee.dev/dye{{reset}}.
11697
117-
EOT
98+
"

doc/demo.png

-126 KB
Loading

0 commit comments

Comments
 (0)