bob.hpp
Loading...
Searching...
No Matches
bob.hpp
1/* ============================================================================== *
2 * MIT License *
3 * *
4 * Copyright (c) 2025 Balder W. Holst *
5 * *
6 * Permission is hereby granted, free of charge, to any person obtaining a copy *
7 * of this software and associated documentation files (the "Software"), to deal *
8 * in the Software without restriction, including without limitation the rights *
9 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell *
10 * copies of the Software, and to permit persons to whom the Software is *
11 * furnished to do so, subject to the following conditions: *
12 * *
13 * The above copyright notice and this permission notice shall be included in all *
14 * copies or substantial portions of the Software. *
15 * *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR *
17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, *
18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE *
19 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER *
20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, *
21 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE *
22 * SOFTWARE. *
23 * ============================================================================== */
24
25#ifndef BOB_H_
26#define BOB_H_
27
28#include <cstring>
29#include <filesystem>
30#include <iostream>
31#include <string>
32#include <string_view>
33#include <vector>
34#include <thread>
35#include <functional>
36#include <cassert>
37
38#include <unistd.h>
39#include <fcntl.h>
40#include <sys/wait.h>
41#include <sys/ioctl.h>
42#include <pty.h>
43
44namespace fs = std::filesystem;
45
47namespace bob {
48 using std::string;
49 using std::vector;
50 using fs::path;
51
52 #ifndef BOB_REBUILD_CMD
53 #define BOB_REBUILD_CMD { "g++", "-o", "_PROGRAM_", "_SOURCE_" }
54 #endif
55
59 #define GO_REBUILD_YOURSELF(argc, argv) bob::go_rebuild_yourself(argc, argv, __FILE__, bob::RebuildConfig(BOB_REBUILD_CMD))
60
61 // Forward declarations
62 class Cmd;
63 class CliCommand;
64
90 const std::string_view PROGRAM = "_PROGRAM_";
91
93 const std::string_view SOURCE = "_SOURCE_";
94
96 vector<string> parts;
97
99 RebuildConfig() = default;
100
102 RebuildConfig(vector<string> &&parts) : parts(std::move(parts)) {}
103
106 Cmd cmd(string source = "bob.cpp", string program = "bob") const;
107 };
108
113 void go_rebuild_yourself(int argc, char* argv[], path source_file_name);
114
117 int run_yourself(fs::path bin, int argc, char* argv[]);
118
131 bool file_needs_rebuild(path input, path output);
132
133 #define PANIC(msg) bob::_panic(__FILE__, __LINE__, msg)
134 #define WARNING(msg) bob::_warning(__FILE__, __LINE__, msg)
135
137 [[noreturn]] void _panic(path file, int line, string msg);
138 void _warning(path file, int line, string msg);
140
155 path mkdirs(path dir);
156
167 string I(path p);
168
181 path search_path(const string &bin_name);
182
192 void checklist(const vector<string> &items, const vector<bool> &statuses);
193
203 void ensure_installed(vector<string> packages);
204
218 bool find_root(path * root, string marker_file);
219
232 bool git_root(path * root);
233
235 struct CmdFuture {
237 pid_t cpid;
239 bool done;
245 bool silent = false;
246
247 CmdFuture();
248
250 int await(string * output = nullptr);
251
254 bool poll(string * output = nullptr);
255
257 bool kill();
258 };
259
287 class Cmd {
288 vector<string> parts;
289 public:
291 bool capture_output = false;
292
294 bool silent = false;
295
297 string output_str = "";
298
300 path root = ".";
301
303 Cmd() = default;
304
314 Cmd(vector<string> &&parts, path root = ".");
315
326 Cmd& push(const string &part);
327
342 Cmd& push_many(const vector<string> &parts);
343
353 string render() const;
354
366
377 int run();
378
388 void check();
389
402 void clear();
403
420
433 };
435
437 class CmdRunner {
438
440 struct CmdRunnerSlot {
441 CmdFuture fut;
442 int index;
443 CmdRunnerSlot() : index{-1} {}
444 };
445
447 size_t cursor = 0;
449 size_t process_count;
451 vector<CmdRunnerSlot> slots;
453 void init_slots();
455 bool populate_slots();
457 void await_slots();
459 void set_exit_code(CmdRunnerSlot &slot);
461 bool any_waiting();
462
463 public:
465 vector<Cmd> cmds;
467 vector<int> exit_codes;
469 CmdRunner(size_t process_count);
473 CmdRunner(vector<Cmd> cmds);
476 CmdRunner(vector<Cmd> cmds, size_t process_count);
482 size_t size();
484 void push(Cmd cmd);
486 void push_many(vector<Cmd> cmds);
488 void clear();
490 bool run();
498 void capture_output(bool capture = true);
499 };
501
503 typedef std::vector<fs::path> Paths;
504
506 typedef std::function<void(const vector<path>&, const vector<path>&)> RecipeFunc;
507
509 class Recipe {
510 public:
517
521 bool needs_rebuild() const;
523 void build() const;
524 };
526
528 enum class CliFlagType {
530 Bool,
532 Value
533 };
534
536 struct CliFlag {
537
539 char short_name = '\0';
541 string long_name = "";
543 string description = "";
545 bool set = false;
549 string value = "";
552
554 CliFlag(const string &long_name, CliFlagType type, string description = "");
558 CliFlag(char short_name, const string &long_name, CliFlagType type, string description = "");
559
561 bool is_flag() const;
562
564 bool is_option() const;
565 };
566
568 typedef std::vector<CliFlag> CliFlags;
569
571 void print_cli_args(const CliFlags &args);
572
574 typedef std::function<int(CliCommand&)> CliCommandFunc;
575
580 public:
582 vector<string> path;
584 vector<string> args;
585 // TODO: Make this function
587 string name;
593 vector<CliFlag> flags;
595 vector<CliCommand> commands; // TODO: Make this a fixed size array, to avoid invalidating references in push
596
597 private:
598 [[noreturn]]
599 void cli_panic(const string &msg);
600 string parse_args(int argc, string argv[]);
601 int call_func();
602
603 public:
604
606 CliCommand(const string &name, CliCommandFunc func, const string &description = "");
607
609 CliCommand(const string &name, const string &description = "");
610
612 bool is_menu() const;
614 void usage() const;
622 int run(int argc, string argv[]);
624 void set_description(const string &desc);
633 CliCommand& add_command(const string &name, string description);
638 CliCommand& add_command(const string &name);
642 CliCommand& add_flag(char short_name, CliFlagType type, string description = "");
644 CliCommand& add_flag(const string &long_name, CliFlagType type, string description = "");
646 CliCommand& add_flag(char short_name, const string &long_name, CliFlagType type, string description = "");
648 CliCommand& add_flag(const string &long_name, char short_name, CliFlagType type, string description = "");
650 CliCommand alias(const string &name, const string &description = "");
651 };
652
654 class Cli : public CliCommand {
655 vector<string> raw_args;
656 void set_defaults(int argc, char* argv[]);
657 public:
659 Cli(int argc, char* argv[]);
661 Cli(string title, int argc, char* argv[]);
663 int serve();
664 };
666
668 namespace term {
669
670 //------------------------------------------------------------------------
673 const std::string RESET = "\033[0m";
675
676 //------------------------------------------------------------------------
679 const std::string BLACK = "\033[30m";
680 const std::string RED = "\033[31m";
681 const std::string GREEN = "\033[32m";
682 const std::string YELLOW = "\033[33m";
683 const std::string BLUE = "\033[34m";
684 const std::string MAGENTA = "\033[35m";
685 const std::string CYAN = "\033[36m";
686 const std::string WHITE = "\033[37m";
688
689 //------------------------------------------------------------------------
692 const std::string BRIGHT_BLACK = "\033[90m";
693 const std::string BRIGHT_RED = "\033[91m";
694 const std::string BRIGHT_GREEN = "\033[92m";
695 const std::string BRIGHT_YELLOW = "\033[93m";
696 const std::string BRIGHT_BLUE = "\033[94m";
697 const std::string BRIGHT_MAGENTA = "\033[95m";
698 const std::string BRIGHT_CYAN = "\033[96m";
699 const std::string BRIGHT_WHITE = "\033[97m";
701
702 //------------------------------------------------------------------------
705 const std::string BG_BLACK = "\033[40m";
706 const std::string BG_RED = "\033[41m";
707 const std::string BG_GREEN = "\033[42m";
708 const std::string BG_YELLOW = "\033[43m";
709 const std::string BG_BLUE = "\033[44m";
710 const std::string BG_MAGENTA = "\033[45m";
711 const std::string BG_CYAN = "\033[46m";
712 const std::string BG_WHITE = "\033[47m";
714
715 //------------------------------------------------------------------------
718 const std::string BG_BRIGHT_BLACK = "\033[100m";
719 const std::string BG_BRIGHT_RED = "\033[101m";
720 const std::string BG_BRIGHT_GREEN = "\033[102m";
721 const std::string BG_BRIGHT_YELLOW = "\033[103m";
722 const std::string BG_BRIGHT_BLUE = "\033[104m";
723 const std::string BG_BRIGHT_MAGENTA = "\033[105m";
724 const std::string BG_BRIGHT_CYAN = "\033[106m";
725 const std::string BG_BRIGHT_WHITE = "\033[107m";
727
728 //------------------------------------------------------------------------
731 const std::string BOLD = "\033[1m";
732 const std::string DIM = "\033[2m";
733 const std::string UNDERLINE = "\033[4m";
734 const std::string BLINK = "\033[5m";
735 const std::string INVERT = "\033[7m";
736 const std::string HIDDEN = "\033[8m";
738
740 struct TermSize {
741 size_t w;
742 size_t h;
743 };
744
747 }
748}
749
750#endif // BOB_H_
751
752#ifdef BOB_IMPLEMENTATION
754
755namespace bob {
756
757 typedef std::vector<fs::path> Paths;
758
759 int run_yourself(fs::path bin, int argc, char* argv[]) {
760 fs::path bin_path = fs::relative(bin);
761 auto run_cmd = Cmd({"./" + bin_path.string()});
762 for (int i = 1; i < argc; ++i) run_cmd.push(argv[i]);
763 std::cout << std::endl;
764 return run_cmd.run();
765 }
766
767 bool file_needs_rebuild(path input, path output) {
768
769 assert(!output.empty());
770 assert(!input.empty());
771
772 if (!fs::exists(output)) return true;
773
774 auto output_mtime = fs::last_write_time(output);
775 auto input_mtime = fs::last_write_time(input);
776
777 return output_mtime < input_mtime;
778 }
779
780
781 Cmd RebuildConfig::cmd(string source, string program) const {
782 Cmd cmd;
783 for (const string &part : parts) {
784 if (part == PROGRAM) cmd.push(program);
785 else if (part == SOURCE) cmd.push(source);
786 else cmd.push(part);
787 }
788 return cmd;
789 }
790
791 void go_rebuild_yourself(int argc, char* argv[], path source_file_name, RebuildConfig config) {
792 assert(argc > 0 && "No program provided via argv[0]");
793
794 path root = fs::current_path();
795
796 path binary_path = fs::relative(argv[0], root);
797 path source_path = fs::relative(source_file_name, root);
798 path header_path = __FILE__;
799
800 if (source_path.has_parent_path()) {
801 WARNING("Source file is not next to executable. This may cause issues.");
802 }
803
804 if (source_path.empty() || header_path.empty() || binary_path.empty()) {
805 PANIC("Failed to determine paths for source, header, or binary.\n\n"
806 "The bob executable must be compiled and run from the same directory as the source file.");
807 }
808
809 bool rebuild_needed = false;
810
811 auto rebuild_yourself = Recipe(
812 {binary_path},
813 {source_path, header_path},
814 [binary_path, source_path, config](Paths, Paths) {
815 config.cmd(source_path.string(), binary_path.string()).check();
816 }
817 );
818
819 if (rebuild_yourself.needs_rebuild()) {
820 rebuild_yourself.build();
821 exit(run_yourself(binary_path, argc, argv));
822 }
823
824 }
825
826 bool find_root(path * root, string marker_file) {
827 path &root_path = *root;
828 root_path = fs::current_path();
829 while (root_path != root_path.root_path()) {
830 if (fs::exists(root_path / marker_file)) {
831 return true;
832 }
833 root_path = root_path.parent_path();
834 }
835 return false;
836 }
837
838 bool git_root(path * root) {
839 return find_root(root, ".git");
840 }
841
842 // Create the directory if it does not exist. Returns the absolute path of the directory.
843 path mkdirs(path dir) {
844 if (!fs::exists(dir)) {
845 std::cout << "Creating directory: " + dir.string() << std::endl;
846 if (!fs::create_directories(dir)) {
847 std::cerr << "Failed to create directory: " << dir << std::endl;
848 exit(EXIT_FAILURE);
849 }
850 }
851 return fs::absolute(dir);
852 }
853
854 void _warning(path file, int line, string msg) {
855 std::cerr << term::YELLOW << "[WARNING] " << file.string() << ":" << line << ": " << msg << term::RESET << std::endl;
856 }
857
858 [[noreturn]]
859 void _panic(path file, int line, string msg) {
860 std::cerr << term::RED << "[ERROR] " << file.string() << ":" << line << ": " << msg << term::RESET << std::endl;
861 exit(1);
862 }
863
864 string I(path p) { return "-I" + p.string(); }
865
866 // Check if a binary is in the system PATH
867 path search_path(const string &bin_name) {
868 string path_env = getenv("PATH");
869 if (path_env.empty()) PANIC("PATH environment variable is not set.");
870
871 // Iterate through each directory in PATH seperated by ':'
872 size_t start = 0;
873 size_t end = path_env.find(':');
874
875 while (end != string::npos) {
876 string dir = path_env.substr(start, end - start);
877 path bin = fs::path(dir) / bin_name;
878 if (fs::exists(bin)) {
879 return bin;
880 }
881 start = end + 1;
882 end = path_env.find(':', start);
883 }
884
885 return "";
886 }
887
888 void checklist(const vector<string> &items, const vector<bool> &statuses) {
889 if (items.size() != statuses.size()) {
890 PANIC("Checklist items and statuses must have the same length.");
891 }
892
893 size_t max_length = 0;
894 for (const auto &item : items) {
895 max_length = std::max(max_length, item.length());
896 }
897
898 std::cout << std::endl;
899 for (size_t i = 0; i < items.size(); ++i) {
900 if (statuses[i]) std::cout << term::GREEN;
901 else std::cout << term::RED;
902
903 std::cout << " [" << (statuses[i] ? "✓" : "✗") << "] " << items[i];
904 for (size_t j = items[i].length(); j < max_length; ++j) {
905 std::cout << " ";
906 }
907 std::cout << term::RESET << std::endl;
908 }
909 std::cout << std::endl;
910 }
911
912 void ensure_installed(vector<string> packages) {
913 bool all_installed = true;
914 vector<path> installed_path(packages.size(), "");
915 vector<bool> installed(packages.size(), false);
916
917 for (int i = 0; i < packages.size(); ++i) {
918 const string &pkg = packages[i];
919 installed_path[i] = search_path(pkg);
920 installed[i] = !installed_path[i].empty();
921 all_installed &= installed[i];
922 }
923
924 if (all_installed) return;
925
926 checklist(packages, installed);
927
928 exit(EXIT_FAILURE);
929 }
930
931 bool read_fd(int fd, string * target) {
932 bool got_data = false;
933 for (;;) {
934 char buf[1024];
935 int n = read(fd, buf, sizeof(buf) - 1);
936 if (n <= 0) return got_data;
937 got_data = true;
938 target->append(buf, n);
939 }
940 return got_data;
941 }
942
943
944 CmdFuture::CmdFuture() : cpid(-1), done(false), exit_code(-1) {}
945
946 int CmdFuture::await(string * output) {
947 while (!done) {
948 done = poll(output);
949 std::this_thread::sleep_for(std::chrono::milliseconds(20));
950 }
951 return exit_code;
952 }
953
954 bool CmdFuture::poll(string * output) {
955 if (done) return true;
956
957 string new_output = "";
958 bool got_data = read_fd(output_fd, &new_output);
959 if (got_data && !silent) {
960 std::cout << new_output;
961 }
962
963 if (output && got_data) {
964 *output += new_output;
965 }
966
967 int status;
968 pid_t result = waitpid(cpid, &status, WNOHANG);
969
970 if (result == -1) PANIC("Error while polling child process: " + string(strerror(errno)));
971
972 if (result == 0) return false;
973
974 done = true;
975 if (WIFEXITED(status)) exit_code = WEXITSTATUS(status);
976 else PANIC("Child process did not terminate normally.");
977 return true;
978 }
979
980 bool CmdFuture::kill() {
981 if (cpid < 0) return false;
982 if (::kill(cpid, SIGKILL) < 0) {
983 std::cerr << "Failed to kill child process: " << strerror(errno) << std::endl;
984 return false;
985 }
986 // Reset the state
987 cpid = -1;
988 done = true;
989 exit_code = -1;
990
991 return true;
992 }
993
994 Cmd::Cmd(vector<string> &&parts, path root) : parts(std::move(parts)), root(root) {}
995
996 Cmd& Cmd::push(const string &part) {
997 parts.push_back(part);
998 return *this;
999 }
1000
1001 Cmd& Cmd::push_many(const vector<string> &parts) {
1002 for (const auto &part : parts) {
1003 this->parts.push_back(part);
1004 }
1005 return *this;
1006 }
1007
1008 string Cmd::render() const {
1009 string result;
1010 for (const auto &part : parts) {
1011 if (!result.empty()) result += " ";
1012 result += part;
1013 }
1014 if (root != ".") {
1015 path rel_root = fs::relative(root, fs::current_path());
1016 result = "[from '" + rel_root.string() + "'] " + result;
1017 }
1018 return result;
1019 }
1020
1021 CmdFuture Cmd::run_async() const {
1022 if (parts.empty() || parts[0].empty()) {
1023 PANIC("No command to run.");
1024 }
1025
1026 std::cout << "CMD: " << render() << std::endl;
1027
1028 // Pseudo-terminal for line-buffered output
1029 int output_fd;
1030 pid_t cpid = forkpty(&output_fd, nullptr, nullptr, nullptr);
1031 if (cpid < 0) {
1032 PANIC("Could not forkpty: " + std::string(strerror(errno)));
1033 }
1034
1035 if (cpid == 0) {
1036 // --- Child process ---
1037
1038 std::vector<char *> args;
1039 for (auto &part : parts) {
1040 args.push_back(const_cast<char *>(part.c_str()));
1041 }
1042 args.push_back(nullptr);
1043
1044 // Set the current working directory if specified
1045 if (!root.empty()) {
1046 if (chdir(root.c_str()) < 0) {
1047 std::cerr << "Could not change directory to " << root << ": " << strerror(errno) << std::endl;
1048 exit(EXIT_FAILURE);
1049 }
1050 }
1051
1052 // Note: With forkpty, stdout/stderr are already connected to the PTY.
1053 // No need for manual dup2 redirection.
1054
1055 if (execvp(args[0], args.data()) < 0) {
1056 std::cerr << "Could not exec child process: " << strerror(errno) << std::endl;
1057 exit(EXIT_FAILURE);
1058 }
1059 exit(EXIT_SUCCESS);
1060 }
1061
1062 // --- Parent process ---
1063
1064 // Set master PTY file descriptor to non-blocking mode
1065 fcntl(output_fd, F_SETFL, O_NONBLOCK);
1066
1067 CmdFuture future;
1068 future.cpid = cpid;
1069 future.output_fd = output_fd;
1070 future.done = false;
1071 future.silent = silent;
1072
1073 return future;
1074 }
1075
1076 int Cmd::run() {
1077 CmdFuture fut = run_async();
1078 return await_future(fut);
1079 }
1080
1081 void Cmd::check() {
1082 int exit_code = run();
1083 if (exit_code != 0) PANIC("Command '" + render() + "' failed with exit status: " + std::to_string(exit_code));
1084 }
1085
1086 void Cmd::clear() {
1087 parts.clear();
1088 }
1089
1090 bool Cmd::poll_future(CmdFuture &fut) {
1091 bool done = fut.poll(&output_str);
1092 return done;
1093 }
1094
1095 int Cmd::await_future(CmdFuture &fut) {
1096 bool done = false;
1097 while (!done) {
1098 done = poll_future(fut);
1099 }
1100 return fut.exit_code;
1101 }
1102
1103 bool CmdRunner::populate_slots() {
1104 bool did_work = false;
1105 for (size_t i = 0; i < process_count; ++i) {
1106 auto &slot = slots[i];
1107
1108 if (slot.index >= 0) {
1109 bool fut_done = cmds[slot.index].poll_future(slot.fut);
1110 if (!fut_done) continue;
1111 }
1112
1113 // Slot is ready!
1114
1115 set_exit_code(slot);
1116
1117 if (!any_waiting()) continue;
1118
1119 // Populate slot with a new command
1120 size_t index = cursor++;
1121 Cmd cmd = cmds[index];
1122
1123 slot.fut = cmd.run_async();
1124 slot.index = index;
1125
1126 did_work = true;
1127 }
1128 return did_work;
1129 }
1130
1131 void CmdRunner::await_slots() {
1132 bool all_done = false;
1133 while (!all_done) {
1134 all_done = true;
1135 for (auto &slot : slots) {
1136 if (slot.index < 0) continue;
1137 bool done = cmds[slot.index].poll_future(slot.fut);
1138 if (done) {
1139 set_exit_code(slot);
1140 }
1141 all_done &= done;
1142 }
1143 std::this_thread::sleep_for(std::chrono::milliseconds(20));
1144 }
1145 }
1146
1147 void CmdRunner::set_exit_code(CmdRunnerSlot &slot) {
1148 if (slot.index < 0) return;
1149 exit_codes[slot.index] = slot.fut.exit_code;
1150 }
1151
1152 bool CmdRunner::any_waiting() {
1153 return cursor < cmds.size();
1154 }
1155
1156 void CmdRunner::init_slots() {
1157 for (auto &slot : slots) {
1158 slot.fut.done = true;
1159 slot.fut.output_fd = -1;
1160 slot.index = -1; // Mark as empty
1161 }
1162 }
1163
1164 CmdRunner::CmdRunner(size_t process_count):
1165 process_count(process_count),
1166 slots(vector<CmdRunnerSlot>(process_count))
1167 {
1168 init_slots();
1169 assert(process_count > 0 && "Process count must be greater than 0");
1170 };
1171
1172 CmdRunner::CmdRunner(vector<Cmd> cmds) :
1173 process_count(sysconf(_SC_NPROCESSORS_ONLN)),
1174 slots(vector<CmdRunnerSlot>(process_count)),
1175 cmds(std::move(cmds))
1176 {
1177 init_slots();
1178 if (process_count == 0) process_count = 1; // Fallback
1179 }
1180
1181 CmdRunner::CmdRunner(vector<Cmd> cmds, size_t process_count) :
1182 process_count(process_count),
1183 slots(vector<CmdRunnerSlot>(process_count)),
1184 cmds(std::move(cmds))
1185 {
1186 init_slots();
1187 if (process_count == 0) process_count = 1; // Fallback
1188 }
1189
1191 process_count(sysconf(_SC_NPROCESSORS_ONLN)),
1192 slots(vector<CmdRunnerSlot>(process_count))
1193 {
1194 init_slots();
1195 if (process_count == 0) process_count = 1; // Fallback
1196 }
1197
1198 size_t CmdRunner::size() {
1199 return cmds.size();
1200 }
1201
1202 void CmdRunner::push(Cmd cmd) {
1203 cmd.silent = true;
1204 cmds.push_back(cmd);
1205 }
1206
1207 void CmdRunner::push_many(vector<Cmd> cmds) {
1208 for (const auto &cmd : cmds) {
1209 push(cmd);
1210 }
1211 }
1212
1213 void CmdRunner::clear() {
1214 cmds.clear();
1215 exit_codes.clear();
1216 }
1217
1218 bool CmdRunner::run() {
1219 exit_codes.resize(cmds.size(), -1);
1220 cursor = 0;
1221 populate_slots();
1222 while (any_waiting()) {
1223 if (populate_slots()) continue;
1224 std::this_thread::sleep_for(std::chrono::milliseconds(20));
1225 }
1226 await_slots();
1227 return !any_failed();
1228 }
1229
1231 return !any_failed();
1232 }
1233
1234 bool CmdRunner::any_failed() {
1235 for (auto &exit_code : exit_codes) {
1236 if (exit_code != 0) return true;
1237 }
1238 return false;
1239 }
1240
1242 for (size_t i = 0; i < cmds.size(); ++i) {
1243 if (exit_codes[i] != 0) {
1244 std::cerr << term::RED << "[FAILED] " << cmds[i].render() << " (exit code: " << exit_codes[i] << ")" << term::RESET << std::endl;
1245 if (!cmds[i].output_str.empty()) {
1246 std::cerr << cmds[i].output_str;
1247 }
1248 }
1249 }
1250 }
1251
1252 void CmdRunner::capture_output(bool capture) {
1253 for (auto &cmd : cmds) {
1254 cmd.capture_output = capture;
1255 }
1256 }
1257
1258 Recipe::Recipe(const Paths &outputs, const Paths &inputs, RecipeFunc func)
1259 : inputs(inputs), outputs(outputs), func(func) {}
1260
1261 bool Recipe::needs_rebuild() const {
1262 for (const auto &input : inputs) {
1263 for (const auto &output : outputs) {
1264 if (file_needs_rebuild(input, output)) {
1265 return true;
1266 }
1267 }
1268 }
1269 return false;
1270 }
1271
1272 void Recipe::build() const {
1273 {
1274 vector<bool> exists(inputs.size(), true);
1275 bool any_missing = false;
1276 for (int i = 0; i < inputs.size(); ++i) {
1277 if (!fs::exists(inputs[i])) {
1278 exists[i] = false;
1279 any_missing = true;
1280 }
1281 }
1282 if (any_missing) {
1283 std::cerr << "[ERROR] Recipe inputs are missing:" << std::endl;
1284 vector<string> input_strings;
1285 for (auto &input : inputs) input_strings.push_back(input.string());
1286 checklist(input_strings, exists);
1287 exit(EXIT_FAILURE);
1288 }
1289 }
1290
1291 if (!needs_rebuild()) return;
1292
1294
1295 {
1296 vector<bool> exists(outputs.size(), true);
1297 bool any_missing = false;
1298 for (int i = 0; i < outputs.size(); ++i) {
1299 if (!fs::exists(outputs[i])) {
1300 exists[i] = false;
1301 any_missing = true;
1302 }
1303 }
1304 if (any_missing) {
1305 std::cerr << "[ERROR] Recipe did not produce expected outputs:" << std::endl;
1306 vector<string> output_strings;
1307 for (auto &input : outputs) output_strings.push_back(input.string());
1308 checklist(output_strings, exists);
1309 exit(EXIT_FAILURE);
1310 }
1311 }
1312 }
1313
1314 void print_cli_args(const CliFlags &args) {
1315 auto arg_len = [](const CliFlag &arg) {
1316 size_t len = 0;
1317 if (arg.short_name != '\0') len += 2; // "-x"
1318 if (!arg.long_name.empty()) len += 2 + arg.long_name.length(); // "--long"
1319 if (arg.short_name != '\0' && !arg.long_name.empty()) len += 2; // ", "
1320 return len;
1321 };
1322
1323 size_t max_arg_length = 0;
1324 for (const auto &arg : args) {
1325 max_arg_length = std::max(max_arg_length, arg_len(arg));
1326 }
1327
1328 auto arg_placeholder = [](const CliFlag &arg) -> string {
1329 if (arg.type == CliFlagType::Bool) return "";
1330 return arg.long_name.empty() ? "<value>" : "<" + arg.long_name + ">";;
1331 };
1332
1333 size_t max_placeholder_length = 0;
1334 for (const auto &arg : args) {
1335 max_placeholder_length = std::max(max_placeholder_length, arg_placeholder(arg).length());
1336 }
1337
1338 for (const auto &arg : args) {
1339 // Initial indentation
1340 std::cout << " ";
1341
1342 // Print short argument name
1343 if (arg.short_name != '\0') {
1344 std::cout << "-" << arg.short_name;
1345 if (!arg.long_name.empty()) std::cout << ", ";
1346 }
1347
1348 // Print long argument name
1349 if (!arg.long_name.empty()) {
1350 std::cout << "--" << arg.long_name;
1351 }
1352
1353 // Padding for alignment
1354 for (size_t i = 0; i < max_arg_length - arg_len(arg); ++i) {
1355 std::cout << " ";
1356 }
1357
1358 // Print argument value if it's an option
1359 string placeholder = arg_placeholder(arg);
1360 std::cout << " " << placeholder;
1361 for (size_t i = 0; i < max_placeholder_length - placeholder.length(); ++i) {
1362 std::cout << " ";
1363 }
1364
1365 std::cout << " " << arg.description << std::endl;
1366 }
1367 }
1368
1369 CliFlag::CliFlag(const string &long_name, CliFlagType type, string description):
1370 long_name(long_name), type(type), description(description) {};
1371
1372 CliFlag::CliFlag(char short_name, CliFlagType type, string description):
1373 short_name(short_name), type(type), description(description) {};
1374
1375 CliFlag::CliFlag(char short_name, const string &long_name, CliFlagType type, string description):
1376 short_name(short_name), long_name(long_name), type(type), description(description) {};
1377
1378 bool CliFlag::is_flag() const {
1379 return type == CliFlagType::Bool;
1380 }
1381
1382 bool CliFlag::is_option() const {
1383 return type == CliFlagType::Value;
1384 }
1385
1386 [[noreturn]]
1387 void CliCommand::cli_panic(const string &msg) {
1388 std::cerr << "[ERROR] " << msg << "\n" << std::endl;
1389 usage();
1390 exit(EXIT_FAILURE);
1391 }
1392
1393 string CliCommand::parse_args(int argc, string argv[]) {
1394 assert(argc >= 0 && "Argc must be non-negative");
1395
1396 for (; argc > 0; --argc, ++argv) {
1397
1398 string arg_name = *argv;
1399
1400 if (arg_name.empty()) {
1401 cli_panic("Empty argument found in command line arguments.");
1402 }
1403
1404 if (arg_name[0] != '-') {
1405 if (is_menu()) return arg_name; // This is the next command name
1406 else {
1407 args.push_back(arg_name); // This is a value argument
1408 continue;
1409 }
1410 }
1411
1412 bool found = false;
1413
1414 for (CliFlag &arg : flags) {
1415
1416 bool short_name_matches = arg.short_name != '\0'
1417 && arg_name.length() == 2
1418 && arg_name[0] == '-'
1419 && arg_name[1] == arg.short_name;
1420
1421 bool long_name_matches = !arg.long_name.empty()
1422 && arg_name.length() > 2
1423 && arg_name.substr(0, 2) == "--"
1424 && arg_name.substr(2) == arg.long_name;
1425
1426 // Short argument
1427 if (short_name_matches || long_name_matches) {
1428 found = true;
1429 switch (arg.type) {
1430 case CliFlagType::Bool:
1431 arg.set = true;
1432 break;
1433 case CliFlagType::Value:
1434 if (argc <= 1) cli_panic("Expected value for argument: " + arg_name);
1435 argc--;
1436 argv++;
1437 arg.value = *argv;
1438 arg.set = true;
1439 break;
1440 }
1441 }
1442 }
1443
1444 if (!found) cli_panic("Unknown argument: " + arg_name);
1445 }
1446
1447 return ""; // No next command
1448 }
1449
1450 int CliCommand::call_func() {
1451 if (!func) cli_panic("No function set for command: " + name);
1452 return func(*this);
1453 }
1454
1455 CliCommand::CliCommand(const string &name, CliCommandFunc func, const string &description)
1456 : name(name), func(func), description(description) {}
1457
1458 CliCommand::CliCommand(const string &name, const string &description)
1459 : CliCommand(name, nullptr, description) {
1460 func = [] (CliCommand &cmd) {
1461 std::cout << "No command provided.\n" << std::endl;
1462 cmd.usage();
1463 return EXIT_FAILURE;
1464 };
1465 }
1466
1467 bool CliCommand::is_menu() const {
1468 return !commands.empty();
1469 }
1470
1471 void CliCommand::usage() const {
1472 if (!description.empty()) {
1473 std::cout << description << std::endl;
1474 }
1475
1476 size_t max_name_length = 0;
1477 for (const auto &cmd : commands) {
1478 max_name_length = std::max(max_name_length, cmd.name.length());
1479 }
1480
1481 if (!commands.empty()) {
1482 std::cout << "\nAvailable commands:" << std::endl;
1483 for (const auto &cmd : commands) {
1484 std::cout << " " << cmd.name << " ";
1485 size_t padding = max_name_length - cmd.name.length();
1486 for (size_t i = 0; i < padding; ++i) std::cout << " ";
1487 std::cout << cmd.description << std::endl;
1488 }
1489 }
1490
1491 if (!flags.empty()) {
1492 std::cout << "\nArguments:" << std::endl;
1494 }
1495 }
1496
1497 CliFlag* CliCommand::find_short(char name) {
1498 for (CliFlag &arg : flags) {
1499 if (arg.short_name == name) return &arg;
1500 }
1501 return nullptr; // Not found
1502 }
1503
1504 CliFlag* CliCommand::find_long(string name) {
1505 for (CliFlag &arg : flags) {
1506 if (arg.long_name == name) return &arg;
1507 }
1508 return nullptr; // Not found
1509 }
1510
1512 CliFlag *help_arg = find_long("help");
1513 if (!help_arg) help_arg = find_short('h');
1514 if (!help_arg) return; // No help argument found
1515
1516 if (!help_arg->set) return;
1517
1518 usage();
1519 exit(EXIT_SUCCESS);
1520 }
1521
1522 int CliCommand::run(int argc, string argv[]) {
1523 string subcommand_name = parse_args(argc, argv);
1524
1525 if (subcommand_name.empty()) return call_func();
1526
1527 if (!is_menu()) return call_func();
1528
1529 vector<string> subcommand_path = path;
1530 subcommand_path.push_back(subcommand_name);
1531
1532 // Find the subcommand
1533 for (auto &cmd : commands) {
1534 if (cmd.name == subcommand_name) {
1535
1536 // Pass parent args to subcommand in reverse
1537 // order to keep --help as the last argument
1538 for (int i = flags.size() - 1; i >= 0; --i) {
1539 CliFlag &arg = flags[i];
1540
1541 // Skip if the argument is already set
1542 if (cmd.find_short(arg.short_name)) continue;
1543 if (cmd.find_long(arg.long_name)) continue;
1544
1545 cmd.add_flag(arg);
1546 }
1547
1548 cmd.path = subcommand_path;
1549
1550 // Skip this command name
1551 return cmd.run(argc-1, argv+1);
1552 }
1553 }
1554
1555 cli_panic("Unknown command: " + subcommand_name);
1556 }
1557
1558 void CliCommand::set_description(const string &desc) {
1559 description = desc;
1560 }
1561
1563 func = f;
1564 }
1565
1566 CliCommand CliCommand::alias(const string &name, const string &description) {
1567 CliCommand alias_cmd = *this;
1568 alias_cmd.name = name;
1569 if (description.empty()) {
1570 alias_cmd.description = "Alias for command: " + this->name;
1571 }
1572 else {
1573 alias_cmd.description = description;
1574 }
1575 return alias_cmd;
1576 }
1577
1578 CliCommand& CliCommand::add_command(CliCommand command) {
1579 // TODO: This may reallocate, thus invalidating previously returned references
1580 commands.push_back(command);
1581 return commands[commands.size() - 1];
1582 }
1583
1584 CliCommand& CliCommand::add_command(const string &name, string description, CliCommandFunc func) {
1586 }
1587
1588 CliCommand& CliCommand::add_command(const string &name, string description) {
1590 }
1591
1592 CliCommand& CliCommand::add_command(const string &name, CliCommandFunc func) {
1593 return add_command(CliCommand(name, func));
1594 }
1595
1596 CliCommand& CliCommand::add_command(const string &name) {
1597 return add_command(CliCommand(name));
1598 }
1599
1600 CliCommand& CliCommand::add_flag(const CliFlag &arg) {
1601 CliFlag * short_existing = find_short(arg.short_name);
1602 CliFlag * long_existing = find_long(arg.long_name);
1603
1604 if (short_existing) PANIC("Short argument already exists: " + string({arg.short_name}));
1605 if (long_existing) PANIC("Long argument already exists: " + arg.long_name);
1606
1607 flags.push_back(arg);
1608
1609 return *this;
1610 }
1611
1612 CliCommand& CliCommand::add_flag(char short_name, CliFlagType type, string description) {
1613 return add_flag(CliFlag(short_name, type, description));
1614 }
1615
1616 CliCommand& CliCommand::add_flag(const string &long_name, CliFlagType type, string description) {
1617 return add_flag(CliFlag(long_name, type, description));
1618 }
1619
1620 CliCommand& CliCommand::add_flag(char short_name, const string &long_name, CliFlagType type, string description) {
1621 return add_flag(CliFlag(short_name, long_name, type, description));
1622 }
1623
1624 CliCommand& CliCommand::add_flag(const string &long_name, char short_name, CliFlagType type, string description) {
1625 return add_flag(CliFlag(short_name, long_name, type, description));
1626 }
1627
1628 void Cli::set_defaults(int argc, char* argv[]) {
1629 assert(argc > 0 && "No program provided via argv[0]");
1630
1631 name = argv[0];
1632 path.push_back(name);
1633
1634 raw_args.assign(argv + 1, argv + argc);
1635
1636 // Add help argument. This will be inherited by all sub commands.
1637 add_flag("help", 'h', CliFlagType::Bool, "Prints this help message");
1638 }
1639
1640 Cli::Cli(int argc, char* argv[]): CliCommand("") {
1641 set_defaults(argc, argv);
1642 }
1643
1644 Cli::Cli(string title, int argc, char* argv[]) : CliCommand("", title) {
1645 set_defaults(argc, argv);
1646 }
1647
1648 int Cli::serve() {
1649 return run(raw_args.size(), raw_args.data());
1650 }
1651
1652 namespace term {
1653 TermSize size() {
1654 struct winsize w;
1655 ioctl(STDOUT_FILENO, TIOCGWINSZ, &w);
1656 return {w.ws_col, w.ws_row};
1657 }
1658 }
1659}
1660
1662#endif // BOB_IMPLEMENTATION
Represents a CLI command.
Definition bob.hpp:579
int run(int argc, string argv[])
Runs the command with the given arguments.
CliCommand & add_command(const string &name)
Adds a subcommand without a description to this command. Returns a reference to the new command.
CliCommand & add_command(CliCommand command)
CliCommand & add_flag(const string &long_name, CliFlagType type, string description="")
Adds a long flag argument to this command.
string description
Description of the command, e.g. "Run tests".
Definition bob.hpp:591
string name
The name of the command.
Definition bob.hpp:587
CliCommand & add_flag(char short_name, CliFlagType type, string description="")
Adds a short flag argument to this command.
CliCommand alias(const string &name, const string &description="")
Creates a command which is an alias for this command.
CliCommandFunc func
The function that will be called when this command is executed.
Definition bob.hpp:589
void set_default_command(CliCommandFunc f)
Sets the function to be called this command is executed without a subcommand.
CliCommand(const string &name, const string &description="")
Create a CLI command which prints its subcommands and flags when invoked.
vector< string > args
Arguments that are not flags.
Definition bob.hpp:584
vector< CliCommand > commands
Subcommands of this command.
Definition bob.hpp:595
void usage() const
Prints the command's help message.
CliCommand & add_command(const string &name, CliCommandFunc func)
CliCommand & add_flag(char short_name, const string &long_name, CliFlagType type, string description="")
Adds a short and long flag argument to this command.
vector< string > path
Path to the command, e.g. {"./bob", "test", "run"}.
Definition bob.hpp:582
bool is_menu() const
Checks if this command is a menu command (has subcommands).
CliCommand(const string &name, CliCommandFunc func, const string &description="")
Create a CLI command with a command function to be executed when this command is invoked.
CliCommand & add_flag(const CliFlag &arg)
Adds a flag argument to this command.
void handle_help()
Prints the help message if the --help flag is set.
CliFlag * find_long(string name)
Finds a flag by its long name.
CliCommand & add_flag(const string &long_name, char short_name, CliFlagType type, string description="")
Adds a short and long flag argument to this command.
CliCommand & add_command(const string &name, string description, CliCommandFunc func)
Adds a subcommand with a command function to this command. Returns a reference to the new command.
void set_description(const string &desc)
Set the description of the command.
CliCommand & add_command(const string &name, string description)
Adds a subcommand to this command. Returns a reference to the new command.
CliFlag * find_short(char name)
Finds a flag by its short name.
vector< CliFlag > flags
The flags provided to this command.
Definition bob.hpp:593
A command line interface (CLI) that can be used to run commands and subcommands.
Definition bob.hpp:654
Cli(string title, int argc, char *argv[])
Create a new CLI interface with a title.
int serve()
Run the CLI and handle the commands.
Cli(int argc, char *argv[])
Create a new CLI interface.
A class for running many commands in parallel.
Definition bob.hpp:437
CmdRunner(vector< Cmd > cmds, size_t process_count)
vector< int > exit_codes
Exit codes for each command in cmds.
Definition bob.hpp:467
bool run()
Runs all commands in the runner concurrently.
CmdRunner(vector< Cmd > cmds)
CmdRunner(size_t process_count)
Create a CmdRunner with a specified number of processes.
void print_failed()
Prints the output of all commands that failed.
bool any_failed()
Returns true if any command in the runner failed (non-zero exit code).
bool all_succeded()
Returns true if all commands in the runner succeeded (exit code 0).
void push_many(vector< Cmd > cmds)
Push multiple commands to the runner.
void push(Cmd cmd)
Push a single command to the runner.
void clear()
Clears all commands in the runner for reuse.
void capture_output(bool capture=true)
Sets the capture_output flag for all commands in the runner.
size_t size()
Returns the number of commands in the runner.
vector< Cmd > cmds
The commands to be run by this runner.
Definition bob.hpp:465
Represents a command to be executed in the operating system shell.
Definition bob.hpp:287
string render() const
CmdFuture run_async() const
bool capture_output
If true, the command's output will be captured.
Definition bob.hpp:291
int await_future(CmdFuture &fut)
bool poll_future(CmdFuture &fut)
void clear()
int run()
path root
The root directory from which the command is executed.
Definition bob.hpp:300
void check()
Cmd(vector< string > &&parts, path root=".")
Cmd & push(const string &part)
bool silent
If true, the command will not print its output to stdout.
Definition bob.hpp:294
string output_str
The command's output captured during execution (stdout and stderr).
Definition bob.hpp:297
Cmd()=default
Creates an empty command.
Cmd & push_many(const vector< string > &parts)
A build recipe that defiles how to produce outputs from inputs.
Definition bob.hpp:509
Recipe(const Paths &outputs, const Paths &inputs, RecipeFunc func)
Constructs a recipe with the given outputs, inputs, and build function.
Paths outputs
The output files that are expected to be produced by the recipe.
Definition bob.hpp:514
void build() const
Builds the outputs from the inputs using the recipe function.
RecipeFunc func
The function that will be called to build the outputs from the inputs.
Definition bob.hpp:516
bool needs_rebuild() const
Use the modified time of the inputs and outputs to determine if the recipe needs to be rebuilt.
Paths inputs
The input files that are used to build the outputs.
Definition bob.hpp:512
const std::string BG_BLUE
Blue background.
Definition bob.hpp:709
const std::string BG_BRIGHT_GREEN
Bright green background.
Definition bob.hpp:720
const std::string BG_YELLOW
Yellow background.
Definition bob.hpp:708
const std::string BG_BRIGHT_YELLOW
Bright yellow background.
Definition bob.hpp:721
const std::string BRIGHT_YELLOW
Bright yellow text.
Definition bob.hpp:695
const std::string DIM
Dim style.
Definition bob.hpp:732
const std::string BG_BRIGHT_BLACK
Bright black background.
Definition bob.hpp:718
const std::string CYAN
Cyan text.
Definition bob.hpp:685
const std::string BG_RED
Red background.
Definition bob.hpp:706
const std::string BRIGHT_GREEN
Bright green text.
Definition bob.hpp:694
const std::string MAGENTA
Magenta text.
Definition bob.hpp:684
const std::string BLINK
Blink style.
Definition bob.hpp:734
const std::string RED
Red text.
Definition bob.hpp:680
const std::string RESET
Definition bob.hpp:673
TermSize size()
Returns the size of the terminal in characters.
const std::string BG_MAGENTA
Magenta background.
Definition bob.hpp:710
const std::string BG_BRIGHT_MAGENTA
Bright magenta background.
Definition bob.hpp:723
const std::string BLUE
Blue text.
Definition bob.hpp:683
const std::string BG_BRIGHT_WHITE
Definition bob.hpp:725
const std::string BOLD
Bold style.
Definition bob.hpp:731
const std::string GREEN
Green text.
Definition bob.hpp:681
const std::string BG_WHITE
Definition bob.hpp:712
const std::string BG_CYAN
Cyan background.
Definition bob.hpp:711
const std::string BRIGHT_BLACK
Bright black text.
Definition bob.hpp:692
const std::string BRIGHT_RED
Bright red text.
Definition bob.hpp:693
const std::string INVERT
Invert style.
Definition bob.hpp:735
const std::string BLACK
Black text.
Definition bob.hpp:679
const std::string BG_BLACK
Black background.
Definition bob.hpp:705
const std::string BRIGHT_WHITE
Definition bob.hpp:699
const std::string BG_BRIGHT_CYAN
Bright cyan background.
Definition bob.hpp:724
const std::string WHITE
Definition bob.hpp:686
const std::string BRIGHT_BLUE
Bright blue text.
Definition bob.hpp:696
const std::string BG_GREEN
Green background.
Definition bob.hpp:707
const std::string YELLOW
Yellow text.
Definition bob.hpp:682
const std::string HIDDEN
Definition bob.hpp:736
const std::string UNDERLINE
Underline style.
Definition bob.hpp:733
const std::string BRIGHT_CYAN
Bright cyan text.
Definition bob.hpp:698
const std::string BG_BRIGHT_RED
Bright red background.
Definition bob.hpp:719
const std::string BRIGHT_MAGENTA
Bright magenta text.
Definition bob.hpp:697
const std::string BG_BRIGHT_BLUE
Bright blue background.
Definition bob.hpp:722
Contains the functionality of the Bob build system.
Definition bob.hpp:47
std::function< int(CliCommand &)> CliCommandFunc
A function that can be used to handle a command in a CLI.
Definition bob.hpp:574
std::function< void(const vector< path > &, const vector< path > &)> RecipeFunc
A function that can be used in a Recipe to build outputs from inputs.
Definition bob.hpp:506
path mkdirs(path dir)
CliFlagType
Types of command line flags.
Definition bob.hpp:528
@ Value
Option with a value, e.g. -o file.txt or --output file.txt.
@ Bool
Boolean flag, e.g. -v or --verbose.
void checklist(const vector< string > &items, const vector< bool > &statuses)
void ensure_installed(vector< string > packages)
int run_yourself(fs::path bin, int argc, char *argv[])
void go_rebuild_yourself(int argc, char *argv[], path source_file_name)
std::vector< CliFlag > CliFlags
A list of command line flags.
Definition bob.hpp:568
path search_path(const string &bin_name)
string I(path p)
bool find_root(path *root, string marker_file)
bool file_needs_rebuild(path input, path output)
bool git_root(path *root)
void print_cli_args(const CliFlags &args)
Prints the command line flags and their descriptions. Used for help output.
std::vector< fs::path > Paths
A list of file paths.
Definition bob.hpp:503
Represents a command flag argument.
Definition bob.hpp:536
string description
Description of the flag, e.g. "Enable verbose output".
Definition bob.hpp:543
bool is_flag() const
Checks if the flag is a boolean flag (no value).
CliFlag(char short_name, const string &long_name, CliFlagType type, string description="")
Create a CLI flag.
CliFlag(const string &long_name, CliFlagType type, string description="")
Create a CLI flag.
string long_name
The long name of the flag, e.g. --verbose.
Definition bob.hpp:541
bool set
Whether the flag is set or not.
Definition bob.hpp:545
CliFlag(char short_name, CliFlagType type, string description="")
Create a CLI flag.
CliFlagType type
The type of the flag..
Definition bob.hpp:551
char short_name
The short name of the flag, e.g. -v.
Definition bob.hpp:539
string value
Definition bob.hpp:549
bool is_option() const
Checks if the flag is an option (has a value).
Represents a command that being executed in the background.
Definition bob.hpp:235
int output_fd
File descriptor for reading the command's output (stdout and stderr).
Definition bob.hpp:243
pid_t cpid
Process ID of the child process.
Definition bob.hpp:237
bool done
Indicates whether the command has completed.
Definition bob.hpp:239
int await(string *output=nullptr)
Blocks until the command completes and returns the exit code.
bool kill()
Kills the command if it is still running.
bool silent
If true, the command's output will not be printed to stdout while it is running.
Definition bob.hpp:245
int exit_code
Exit code of the command. Only valid after the command has completed.
Definition bob.hpp:241
bool poll(string *output=nullptr)
Configuration for rebuilding the current executable.
Definition bob.hpp:88
RebuildConfig(vector< string > &&parts)
Create a rebuild configuration with the given parts.
Definition bob.hpp:102
const std::string_view SOURCE
Special string to represent the source file name in the command parts.
Definition bob.hpp:93
const std::string_view PROGRAM
Special string to represent the program name in the command parts.
Definition bob.hpp:90
RebuildConfig()=default
Create an empty rebuild configuration.
vector< string > parts
A vector of strings representing the command parts before substitution.
Definition bob.hpp:96
Cmd cmd(string source="bob.cpp", string program="bob") const
Terminal size in characters.
Definition bob.hpp:740
size_t h
Terminal Height.
Definition bob.hpp:742
size_t w
Terminal Width.
Definition bob.hpp:741