Uni20
Asynchronous tensor and runtime library.
Loading...
Searching...
No Matches
terminal.hpp
Go to the documentation of this file.
1#pragma once
2
3#include "string_util.hpp"
4#include <algorithm>
5#include <cstdio>
6#include <cstdlib>
7#include <mutex>
8#include <optional>
9#include <ranges>
10#include <string>
11#include <string_view>
12#include <utility>
13#include <variant>
14
15namespace terminal
16{
17
20std::pair<int, int> size();
21
25std::pair<int, int> size(std::FILE* stream);
26
29int rows();
30
34int rows(std::FILE* stream);
35
38int columns();
39
43int columns(std::FILE* stream);
44
48bool is_a_terminal(std::FILE* stream);
49
52inline bool no_color_requested()
53{
54 if (char const* no_color = std::getenv("NO_COLOR"))
55 {
56 return no_color[0] != '\0';
57 }
58 return false;
59}
60
61inline std::string quote_shell(std::string_view s)
62{
63 // Tokens outside this conservative POSIX-safe alphabet need double quotes.
64 constexpr std::string_view safe_chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_@%+=:,./-";
65
66 bool needs_quote =
67 s.empty() || std::ranges::any_of(s, [&](char c) { return safe_chars.find(c) == std::string_view::npos; });
68
69 if (needs_quote)
70 {
71 std::string result;
72 // Reserve extra space (worst-case: every character may need escaping)
73 result.reserve(s.size() * 2);
74 for (char c : s)
75 {
76 if (c == '\\')
77 {
78 result.append("\\\\");
79 }
80 else if (c == '"' || c == '$' || c == '`')
81 {
82 result.push_back('\\');
83 result.push_back(c);
84 }
85 else
86 {
87 result.push_back(c);
88 }
89 }
90 return "\"" + result + "\"";
91 }
92 return std::string(s);
93}
94
95// convert argc/argv into a command line string, with shell escapes
96inline std::string cmdline(int argc, char** argv)
97{
98 std::string result;
99 if (argc > 0) result = quote_shell(argv[0]);
100 for (int i = 1; i < argc; ++i)
101 result = result + ' ' + quote_shell(argv[i]);
102 return result;
103}
104
105// expands components of the form ${XXX} as the corresponding environment string
106std::string expand_environment(std::string const& s);
107
108inline bool env_exists(std::string const& str) { return getenv(str.c_str()) != nullptr; }
109
110// getenv_or_default
111// Get an environment string, or if the environment string is not defined, return the specified default_value
112// template <typename T> T getenv_or_default(const std::string& var, const T& default_value)
113// {
114// if (const char* env = std::getenv(var.c_str()))
115// {
116// try
117// {
118// return from_string<T>(env);
119// }
120// catch (...)
121// {
122// // Conversion failed; fall through to return default_value.
123// }
124// }
125// return default_value;
126// }
127
128template <typename T, typename U> T getenv_or_default(const std::string& var, const U& default_value)
129{
130 if (const char* env = std::getenv(var.c_str()))
131 {
132 try
133 {
134 return from_string<T>(env);
135 }
136 catch (...)
137 {
138 // Conversion failed; fall through to return default_value.
139 }
140 }
141 return T(default_value);
142}
143
144inline char const* getenv_or_default(std::string const& str, char const* Default)
145{
146 char const* Str = getenv(str.c_str());
147 return Str ? Str : Default;
148}
149
150// color output
152{
153 Default = 39,
154 Black = 30,
155 Red = 31,
156 Green = 32,
157 Yellow = 33,
158 Blue = 34,
159 Magenta = 35,
160 Cyan = 36,
161 LightGray = 37,
162 DarkGray = 90,
163 LightRed = 91,
164 LightGreen = 92,
165 LightYellow = 93,
166 LightBlue = 94,
167 LightMagenta = 95,
168 LightCyan = 96,
169 White = 97
170};
171
173{
174 Default = 49,
175 Black = 40,
176 Red = 41,
177 Green = 42,
178 Yellow = 43,
179 Blue = 44,
180 Magenta = 45,
181 Cyan = 46,
182 LightGray = 47,
183 // Extended bright background colors:
184 DarkGray = 100, // often used as bright black
185 LightRed = 101,
186 LightGreen = 102,
187 LightYellow = 103,
188 LightBlue = 104,
189 LightMagenta = 105,
190 LightCyan = 106,
191 White = 107
192};
193
195{
196 int r, g, b;
197};
198
199using FGColor = std::variant<ForegroundColor, RGBColor>;
200using BGColor = std::variant<BackgroundColor, RGBColor>;
201
202// --- Bitmask for Attributes (unchanged) ---
203enum class ColorAttribute : unsigned
204{
205 None = 0,
206 Bold = 1 << 0, // ANSI code 1
207 Dim = 1 << 1, // ANSI code 2
208 Underline = 1 << 2 // ANSI code 4
209};
210
211// bitwise OR operator for ColorAttribute
213{
214 return static_cast<ColorAttribute>(static_cast<unsigned>(lhs) | static_cast<unsigned>(rhs));
215}
216
217// compound assignment operator for ColorAttribute
219{
220 lhs = lhs | rhs;
221 return lhs;
222}
223
229struct toggle
230{
231 bool value;
232
233 toggle(bool value_) : value(value_) {}
234
238 toggle(std::string_view str, bool default_value = true)
239 {
240 if (str.empty())
241 {
242 value = default_value;
243 }
244 else if (iequals(str, "no") || iequals(str, "false") || iequals(str, "0"))
245 {
246 value = false;
247 }
248 else if (iequals(str, "yes") || iequals(str, "true") || iequals(str, "1"))
249 {
250 value = true;
251 }
252 else
253 {
254 value = default_value;
255 }
256 }
257
259 operator bool() const { return value; }
260};
261
263 public:
264 // Foreground and background can be either standard colors or RGB
265 std::optional<FGColor> fg;
266 std::optional<BGColor> bg;
268
269 TerminalStyle() = default;
270
271 // Constructors for standard colors or attributes
275
276 // Constructors for RGB colors
277 TerminalStyle(const RGBColor& rgb, bool foreground = true)
278 {
279 if (foreground)
280 fg = rgb;
281 else
282 bg = rgb;
283 }
284
285 // Conversion from a string
286 TerminalStyle(const std::string& s);
287
288 TerminalStyle(char const* s) : TerminalStyle(s != nullptr ? std::string_view(s) : std::string_view{}) {}
289
290 TerminalStyle(std::string_view sv) : TerminalStyle(std::string(sv)) {}
291
292 // Combine two TerminalStyle objects
294 {
295 TerminalStyle result = *this;
296 if (other.fg.has_value()) result.fg = other.fg;
297 if (other.bg.has_value()) result.bg = other.bg;
298 result.attrs = result.attrs | other.attrs;
299 return result;
300 }
301
302 // Overloads for combining with a standard color or attribute
303 friend TerminalStyle operator|(ForegroundColor f, const TerminalStyle& ts) { return TerminalStyle(f) | ts; }
304 friend TerminalStyle operator|(BackgroundColor b, const TerminalStyle& ts) { return TerminalStyle(b) | ts; }
305 friend TerminalStyle operator|(ColorAttribute a, const TerminalStyle& ts) { return TerminalStyle(a) | ts; }
306 friend TerminalStyle operator|(const RGBColor& rgb, const TerminalStyle& ts) { return TerminalStyle(rgb) | ts; }
307
308 // Helper functions to produce ANSI escape sequences from RGB values
309 static std::string rgb_fg_code(const RGBColor& rgb)
310 {
311 return "38;2;" + std::to_string(rgb.r) + ";" + std::to_string(rgb.g) + ";" + std::to_string(rgb.b);
312 }
313 static std::string rgb_bg_code(const RGBColor& rgb)
314 {
315 return "48;2;" + std::to_string(rgb.r) + ";" + std::to_string(rgb.g) + ";" + std::to_string(rgb.b);
316 }
317
318 // Convert this TerminalStyle into an ANSI escape sequence.
319 [[nodiscard]] std::string to_string() const;
320};
321
322// parseTerminalStyle expects a style string made up of one or more style components,
323// separated by commas. Each component can optionally specify a target using "fg:" for
324// foreground or "bg:" for background (default is foreground), followed by a color spec,
325// and then optional attributes separated by semicolons.
326//
327// Color specifications can be provided as:
328// - A named color (e.g., "Red", "Blue", "lightgray", etc.)
329// - An RGB color in function notation (e.g., "rgb(255,0,0)")
330// - A hexadecimal color (e.g., "#FF0000" or "#F00")
331//
332// Any tokens following the color (separated by semicolons) are treated as text attributes,
333// such as "Bold", "Dim", or "Underline".
334//
335// Examples:
336// "Red;Bold" -> Sets foreground to Red with Bold.
337// "fg:rgb(255,0,0);Bold" -> Sets foreground to rgb(255,0,0) with Bold.
338// "bg:#00FF00;Dim" -> Sets background to green (#00FF00) with Dim.
339// "fg:LightGray;Underline, bg:darkgray"
340// -> Sets foreground to LightGray with Underline,
341// and background to darkgray.
342TerminalStyle parseTerminalStyle(const std::string& styleStr);
343
344// Convenience function to colorize text.
345inline std::string color_text(std::string_view s, const TerminalStyle& ts)
346{
347 return ts.to_string() + std::string(s) + "\033[0m";
348}
349
350inline std::string color_if(std::string_view s, bool b, const TerminalStyle& ts)
351{
352 return b ? (ts.to_string() + s + "\033[0m") : std::string(s);
353}
354
355} // namespace terminal
Definition terminal.hpp:262
TerminalStyle(char const *s)
Definition terminal.hpp:288
std::optional< FGColor > fg
Definition terminal.hpp:265
TerminalStyle(ForegroundColor f)
Definition terminal.hpp:272
static std::string rgb_bg_code(const RGBColor &rgb)
Definition terminal.hpp:313
friend TerminalStyle operator|(ForegroundColor f, const TerminalStyle &ts)
Definition terminal.hpp:303
TerminalStyle(const RGBColor &rgb, bool foreground=true)
Definition terminal.hpp:277
friend TerminalStyle operator|(const RGBColor &rgb, const TerminalStyle &ts)
Definition terminal.hpp:306
TerminalStyle(std::string_view sv)
Definition terminal.hpp:290
TerminalStyle operator|(const TerminalStyle &other) const
Definition terminal.hpp:293
friend TerminalStyle operator|(ColorAttribute a, const TerminalStyle &ts)
Definition terminal.hpp:305
TerminalStyle(BackgroundColor b)
Definition terminal.hpp:273
TerminalStyle(ColorAttribute a)
Definition terminal.hpp:274
std::optional< BGColor > bg
Definition terminal.hpp:266
ColorAttribute attrs
Definition terminal.hpp:267
std::string to_string() const
Definition terminal.cpp:34
friend TerminalStyle operator|(BackgroundColor b, const TerminalStyle &ts)
Definition terminal.hpp:304
static std::string rgb_fg_code(const RGBColor &rgb)
Definition terminal.hpp:309
Definition qnum.hpp:403
Definition terminal.cpp:32
bool is_a_terminal(std::FILE *stream)
Test whether a C output stream refers to a terminal.
Definition terminal.cpp:508
T getenv_or_default(const std::string &var, const U &default_value)
Definition terminal.hpp:128
int columns()
Return the output terminal column count.
Definition terminal.cpp:502
constexpr ColorAttribute operator|(ColorAttribute lhs, ColorAttribute rhs)
Definition terminal.hpp:212
ForegroundColor
Definition terminal.hpp:152
std::string color_if(std::string_view s, bool b, const TerminalStyle &ts)
Definition terminal.hpp:350
TerminalStyle parseTerminalStyle(const std::string &styleStr)
Definition terminal.cpp:346
std::string quote_shell(std::string_view s)
Definition terminal.hpp:61
std::variant< ForegroundColor, RGBColor > FGColor
Definition terminal.hpp:199
ColorAttribute
Definition terminal.hpp:204
bool env_exists(std::string const &str)
Definition terminal.hpp:108
std::string color_text(std::string_view s, const TerminalStyle &ts)
Definition terminal.hpp:345
std::string cmdline(int argc, char **argv)
Definition terminal.hpp:96
BackgroundColor
Definition terminal.hpp:173
std::string expand_environment(std::string const &s)
Definition terminal.cpp:515
bool no_color_requested()
Return whether NO_COLOR requests suppressing default ANSI color output.
Definition terminal.hpp:52
std::pair< int, int > size()
Return the output terminal size as (rows, columns).
Definition terminal.cpp:506
std::variant< BackgroundColor, RGBColor > BGColor
Definition terminal.hpp:200
constexpr ColorAttribute & operator|=(ColorAttribute &lhs, ColorAttribute rhs)
Definition terminal.hpp:218
int rows()
Return the output terminal row count.
Definition terminal.cpp:491
Definition cli.cpp:6
bool iequals(std::string_view a, std::string_view b)
Definition string_util.hpp:52
Definition terminal.hpp:195
int b
Definition terminal.hpp:196
int g
Definition terminal.hpp:196
int r
Definition terminal.hpp:196
A simple yes/no toggle that parses from a string.
Definition terminal.hpp:230
toggle(bool value_)
Definition terminal.hpp:233
toggle(std::string_view str, bool default_value=true)
Construct from a string (e.g.
Definition terminal.hpp:238
bool value
Definition terminal.hpp:231