Skip to content

Commit 663895f

Browse files
committed
feat: optional colored output for help/usage
1 parent f736ecf commit 663895f

File tree

7 files changed

+556
-160
lines changed

7 files changed

+556
-160
lines changed

CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ endif()
3939
add_library(clasp
4040
src/clasp.cpp
4141
src/command.cpp
42+
src/color.cpp
4243
src/flag.cpp
4344
)
4445

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
- **Command and Subcommand Handling**: Easily define and manage commands with subcommands and associated actions.
1414
- **Argument Parsing**: Supports both positional and named arguments with type safety.
1515
- **Flag Management**: Persistent/local flags, required/hidden/deprecated, groups, repeated flags, optional values (NoOptDefVal), and extra helpers like bytes/count/IP/CIDR/IPNet/IPMask/URL.
16+
- **Optional Colored Output**: Opt-in ANSI color for built-in help/usage/errors, with built-in themes (`vscode`, `sublime`, `iterm2`) and `--color=auto|always|never`.
1617
- **Cobra-Like Ergonomics**: Hooks, aliases, suggestions, `TraverseChildren`, sorted help output, examples, and custom help/usage/version templates.
1718
- **Help and Usage Generation**: Automatically generate help text and usage instructions based on defined commands and flags.
1819
- **Shell Completion**: bash/zsh/fish/powershell completion generation + `__complete` directives and configurable completion command names.

examples/api_coverage_example.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ struct SimpleValue final : clasp::Value {
2424
int main() {
2525
clasp::Command root("app", "API coverage");
2626

27+
root.enableColor();
28+
2729
root.disableFlagParsing(false)
2830
.allowUnknownFlags(false)
2931
.shortFlagGrouping(true)
@@ -83,4 +85,3 @@ int main() {
8385
std::cout << "ok\n";
8486
return 0;
8587
}
86-

include/clasp/clasp.hpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#ifndef CLASP_HPP
22
#define CLASP_HPP
3+
#include "color.hpp"
34
#include "command.hpp"
45
#include "flag.hpp"
56
#include "value.hpp"

include/clasp/color.hpp

Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
#ifndef CLASP_COLOR_HPP
2+
#define CLASP_COLOR_HPP
3+
4+
#include <cstdlib>
5+
#include <cstdint>
6+
#include <optional>
7+
#include <string>
8+
#include <string_view>
9+
10+
namespace clasp {
11+
12+
enum class ColorMode {
13+
Auto,
14+
Always,
15+
Never,
16+
};
17+
18+
enum class ColorThemeName {
19+
Vscode,
20+
Sublime,
21+
Iterm2,
22+
};
23+
24+
enum class ColorRole {
25+
Section,
26+
Command,
27+
Flag,
28+
Type,
29+
Meta,
30+
Error,
31+
};
32+
33+
struct ColorTheme {
34+
std::string reset{"\x1b[0m"};
35+
std::string section;
36+
std::string command;
37+
std::string flag;
38+
std::string type;
39+
std::string meta;
40+
std::string error;
41+
};
42+
43+
namespace color {
44+
45+
enum class Stream {
46+
Stdout,
47+
Stderr,
48+
Other,
49+
};
50+
51+
// Returns true if the underlying stream is a terminal.
52+
bool isTty(Stream stream);
53+
54+
// Windows: enables VT sequences for the console. Non-Windows: no-op returning true.
55+
bool enableVirtualTerminalProcessing(Stream stream);
56+
57+
inline bool envNoColor() {
58+
// https://no-color.org/
59+
return std::getenv("NO_COLOR") != nullptr;
60+
}
61+
62+
inline bool envTermDumb() {
63+
const char* term = std::getenv("TERM");
64+
return term != nullptr && std::string_view(term) == "dumb";
65+
}
66+
67+
inline std::string ansiRgbFg(std::uint8_t r, std::uint8_t g, std::uint8_t b) {
68+
return "\x1b[38;2;" + std::to_string(r) + ";" + std::to_string(g) + ";" + std::to_string(b) + "m";
69+
}
70+
71+
inline std::string ansiBold() { return "\x1b[1m"; }
72+
inline std::string ansiDim() { return "\x1b[2m"; }
73+
74+
inline const ColorTheme& builtinTheme(ColorThemeName name) {
75+
// Note: these themes intentionally use ANSI truecolor for close matches. Terminals without truecolor
76+
// will typically approximate, and this feature is opt-in via Command::enableColor().
77+
static const ColorTheme vscode = [] {
78+
ColorTheme t;
79+
t.section = ansiBold() + ansiRgbFg(86, 156, 214); // blue
80+
t.command = ansiRgbFg(78, 201, 176); // teal
81+
t.flag = ansiRgbFg(156, 220, 254); // light blue
82+
t.type = ansiRgbFg(206, 145, 120); // orange
83+
t.meta = ansiDim() + ansiRgbFg(160, 160, 160); // dim gray
84+
t.error = ansiBold() + ansiRgbFg(244, 71, 71); // red
85+
return t;
86+
}();
87+
static const ColorTheme sublime = [] {
88+
ColorTheme t;
89+
t.section = ansiBold() + ansiRgbFg(249, 38, 114); // pink
90+
t.command = ansiRgbFg(166, 226, 46); // green
91+
t.flag = ansiRgbFg(102, 217, 239); // cyan
92+
t.type = ansiRgbFg(253, 151, 31); // orange
93+
t.meta = ansiDim() + ansiRgbFg(160, 160, 160); // dim gray
94+
t.error = ansiBold() + ansiRgbFg(249, 38, 114); // pink
95+
return t;
96+
}();
97+
static const ColorTheme iterm2 = [] {
98+
ColorTheme t;
99+
t.section = "\x1b[1m\x1b[36m"; // bold cyan
100+
t.command = "\x1b[32m"; // green
101+
t.flag = "\x1b[33m"; // yellow
102+
t.type = "\x1b[35m"; // magenta
103+
t.meta = "\x1b[2m"; // dim
104+
t.error = "\x1b[1m\x1b[31m"; // bold red
105+
return t;
106+
}();
107+
switch (name) {
108+
case ColorThemeName::Vscode: return vscode;
109+
case ColorThemeName::Sublime: return sublime;
110+
case ColorThemeName::Iterm2: return iterm2;
111+
}
112+
return vscode;
113+
}
114+
115+
inline std::optional<ColorMode> parseMode(std::string_view s) {
116+
if (s == "auto") return ColorMode::Auto;
117+
if (s == "always") return ColorMode::Always;
118+
if (s == "never") return ColorMode::Never;
119+
return std::nullopt;
120+
}
121+
122+
inline std::string_view modeName(ColorMode m) {
123+
switch (m) {
124+
case ColorMode::Auto: return "auto";
125+
case ColorMode::Always: return "always";
126+
case ColorMode::Never: return "never";
127+
}
128+
return "auto";
129+
}
130+
131+
inline std::optional<ColorThemeName> parseTheme(std::string_view s) {
132+
if (s == "vscode") return ColorThemeName::Vscode;
133+
if (s == "sublime") return ColorThemeName::Sublime;
134+
if (s == "iterm2") return ColorThemeName::Iterm2;
135+
return std::nullopt;
136+
}
137+
138+
inline std::string_view themeName(ColorThemeName t) {
139+
switch (t) {
140+
case ColorThemeName::Vscode: return "vscode";
141+
case ColorThemeName::Sublime: return "sublime";
142+
case ColorThemeName::Iterm2: return "iterm2";
143+
}
144+
return "vscode";
145+
}
146+
147+
} // namespace color
148+
} // namespace clasp
149+
150+
#endif // CLASP_COLOR_HPP

0 commit comments

Comments
 (0)