From ecae36e7d35dd2202cb113273a2db446d19aca6c Mon Sep 17 00:00:00 2001 From: SteveBroshar Date: Wed, 19 Feb 2025 16:08:43 -0600 Subject: [PATCH 01/90] CLI and feedback enhancements --- .gitignore | 3 +- libuuu/buffer.cpp | 73 +++++-- libuuu/fastboot.cpp | 4 +- libuuu/usbhotplug.cpp | 2 +- uuu/autocomplete.cpp | 35 ++-- uuu/buildincmd.cpp | 4 +- uuu/uuu.cpp | 446 +++++++++++++++++++++++++----------------- 7 files changed, 341 insertions(+), 226 deletions(-) diff --git a/.gitignore b/.gitignore index 9c22502f..6e323bf1 100644 --- a/.gitignore +++ b/.gitignore @@ -14,4 +14,5 @@ CMakeCache.txt *.clst *.snap node_modules -build \ No newline at end of file +build +bin/ diff --git a/libuuu/buffer.cpp b/libuuu/buffer.cpp index 1649bc88..8c6c3024 100644 --- a/libuuu/buffer.cpp +++ b/libuuu/buffer.cpp @@ -62,6 +62,7 @@ static map> g_filebuffer_map; static mutex g_mutex_map; static bool g_small_memory = true; +// [what is the point/value of this value?] #define MAGIC_PATH '>' string g_current_dir = ">"; @@ -127,6 +128,9 @@ int DataBuffer::ref_other_buffer(std::shared_ptr p, size_t offset, s return 0; }; +/** + * @brie Base class for FS things [whatever 'FS' means/is]. + */ class FSBasic { public: @@ -142,29 +146,60 @@ class FSBasic virtual std::shared_ptr ScanCompressblock(const string& /*backfile*/, size_t& /*input_offset*/, size_t& /*output_offset*/) { return NULL; }; virtual int PreloadWorkThread(shared_ptroutp); - virtual int split(const string &filename, string *outbackfile, string *outfilename, bool dir=false) - { - string path = str_to_upper(filename); + /** + * @brief Splits a file system path into directory and final name parts + * @param path Input path + * @param[out] dir_part Directory path info from input path + * @param[out] name_part Final name part from input path + * @param dir [what does this mean/imply?] + * @return 0 for success; -1 for error + * @details + * If no/blank m_ext: + * If dir: + * Load dir_part with directory path part of path; >./ if none + * Load name_part with file name part of path; blank if none + * Else: + * Load dir_part with input path + * Else: + * ext = m_ext + * Append slash to ext if !dir + * Find last ext in path + * If not found: fail + * Load dir_part with path up to and including ext + * Load name_part with path after found ext + * @example + * path:"f", m_ext:"", !dir ==> ">./", "f" + * path:"d/f", m_ext:"", !dir ==> "d", "f" + * path:"d/", m_ext:"", !dir ==> "d", "" + * path:"f", m_ext:"z", !dir ==> error + * path:"d/z/f", m_ext:"z", !dir ==> "d/z", "f" + * path:"d/z/", m_ext:"z", !dir ==> "d/z", "" + * @note + * Passing a bool like dir is bad style since it only controls flow. + */ + virtual int split(const string &path, string *dir_part, string *name_part, bool dir=false) + { + string upper_path = str_to_upper(path); if (m_ext == nullptr || strlen(m_ext) == 0) { if(dir) { - size_t pos = path.rfind("/"); + size_t pos = upper_path.rfind("/"); if(pos == string::npos) { - *outbackfile = MAGIC_PATH; - *outbackfile += "./"; - *outfilename = filename; + *dir_part = MAGIC_PATH; + *dir_part += "./"; + *name_part = path; } else { - *outbackfile = filename.substr(0, pos); - if(filename.size() >= pos + 1) - *outfilename = filename.substr(pos + 1); + *dir_part = path.substr(0, pos); + if(path.size() >= pos + 1) + *name_part = path.substr(pos + 1); else - outfilename->clear(); + name_part->clear(); } }else { - *outbackfile = filename; + *dir_part = path; } return 0; } @@ -172,25 +207,25 @@ class FSBasic string ext = m_ext; if(!dir) ext += "/"; - size_t pos = path.rfind(ext); + size_t pos = upper_path.rfind(ext); if (pos == string::npos) { - string err = "can't find ext name in path: "; - err += filename; + string err = "Expected '" + ext + "' in path '" + path + "'"; set_last_err_string(err); return -1; } - *outbackfile = filename.substr(0, pos + strlen(m_ext)); + *dir_part = path.substr(0, pos + strlen(m_ext)); - if(filename.size() >= pos + strlen(m_ext) + 1) - *outfilename = filename.substr(pos + strlen(m_ext) + 1); + if(path.size() >= pos + strlen(m_ext) + 1) + *name_part = path.substr(pos + strlen(m_ext) + 1); else - outfilename->clear(); + name_part->clear(); return 0; } protected: + FSBasic() {} // enforce that this class is abstract base; only creatable via a superclass const char * m_ext = nullptr; const char * m_Prefix = nullptr; public: diff --git a/libuuu/fastboot.cpp b/libuuu/fastboot.cpp index 98dd5d5d..e36b9135 100644 --- a/libuuu/fastboot.cpp +++ b/libuuu/fastboot.cpp @@ -570,12 +570,12 @@ int FBFlashCmd::parser(char *p) } if (!check_file_exist(m_filename)) { - set_last_err_string("FB: image file not found"); + set_last_err_string("FB: image file not found: " + m_filename); return -1; } if (m_use_bmap && m_bmap_filename.size() && !check_file_exist(m_bmap_filename)) { - set_last_err_string("FB: bmap file not found"); + set_last_err_string("FB: bmap file not found: " + m_bmap_filename); return -1; } diff --git a/libuuu/usbhotplug.cpp b/libuuu/usbhotplug.cpp index 48607c6f..91122fa7 100644 --- a/libuuu/usbhotplug.cpp +++ b/libuuu/usbhotplug.cpp @@ -574,7 +574,7 @@ int CmdUsbCtx::look_for_match_device(const char *pro) uuu_notify nt; nt.type = nt.NOTIFY_WAIT_FOR; - nt.str = (char*)"Wait for Known USB"; + nt.str = (char*)"DWait for Known USB"; call_notify(nt); if (check_usb_timeout(usb_timer)) diff --git a/uuu/autocomplete.cpp b/uuu/autocomplete.cpp index 9ca49734..ecdd7b81 100644 --- a/uuu/autocomplete.cpp +++ b/uuu/autocomplete.cpp @@ -203,28 +203,21 @@ int auto_complete(int argc, char**argv) void print_autocomplete_help() { - + cout << "\nEnable auto/tab completion:" << endl << endl; #ifndef _MSC_VER - { - cout << "Enjoy auto [tab] command complete by put below script into /etc/bash_completion.d/uuu" << endl; - cout << g_vt_kcyn; - cout << " _uuu_autocomplete()" <first.c_str()); @@ -237,7 +237,7 @@ void BuiltInScriptMap::ShowCmds(FILE * const file) const fprintf(file, "|"); } } - fprintf(file, ">"); + //fprintf(file, ">"); } /** diff --git a/uuu/uuu.cpp b/uuu/uuu.cpp index cafd2f8e..899b2797 100644 --- a/uuu/uuu.cpp +++ b/uuu/uuu.cpp @@ -90,7 +90,7 @@ class AutoCursor public: ~AutoCursor() { - printf("\x1b[?25h\n\n\n"); + printf("\x1b[?25h\n"); } }; @@ -119,7 +119,7 @@ class string_ex : public std::string std::vsnprintf((char*)c_str(), len + 1, fmt, args); va_end(args); - return 0; + return EXIT_SUCCESS; } }; @@ -163,63 +163,80 @@ int ask_passwd(char* prompt, char user[MAX_USER_LEN], char passwd[MAX_USER_LEN]) tcsetattr(STDIN_FILENO, TCSANOW, &old); if(pd.size() > MAX_USER_LEN -1) - return -1; + return EXIT_FAILURE; memcpy(passwd, pd.data(), pd.size()); i=pd.size(); #endif passwd[i] = 0; cout << endl; - return 0; + return EXIT_SUCCESS; } -void print_help(bool detail = false) +void print_cli_help() { - const char help[] = - "uuu [-d -m -v -V -bmap -no-bmap] <" "bootloader|cmdlists|cmd" ">\n\n" - " bootloader download bootloader to board by usb\n" - " cmdlist run all commands in cmdlist file\n" - " If it is path, search uuu.auto in dir\n" - " If it is zip, search uuu.auto in zip\n" - " cmd Run one command, use -H see detail\n" - " example: SDPS: boot -f flash.bin\n" - " -d Daemon mode, wait for forever.\n" - " -v -V verbose mode, -V enable libusb error\\warning info\n" - " -dry Dry run mode, check if script or cmd correct \n" - " -bmap Try using .bmap files even if flash commands do not specify them\n" - " -no-bmap Ignore .bmap files even if flash commands specify them\n" - " -m USBPATH Only monitor these paths.\n" - " -m 1:2 -m 1:3\n\n" - " -ms serial_no Monitor the serial number prefix of the device using 'serial_no'.\n" - " -t Timeout second for wait known usb device appeared\n" - " -T Timeout second for wait next known usb device appeared at stage switch\n" - " -e set environment variable key=value\n" - " -pp usb polling period in milliseconds\n" - " -dm disable small memory\n" - "uuu -s Enter shell mode. uuu.inputlog record all input commands\n" - " you can use \"uuu uuu.inputlog\" next time to run all commands\n\n" - "uuu -udev linux: show udev rule to avoid sudo each time \n" - "uuu -lsusb List connected know devices\n" - "uuu -IgSerNum Set windows registry to ignore USB serial number for known uuu devices\n" - "uuu -h show general help\n" - "uuu -H show general help and detailed help for commands\n\n"; - printf("%s", help); - printf("uuu [-d -m -v -bmap -no-bmap] -b[run] "); + const char default_mode[] = + "uuu [OPTION...] SPEC|CMD|BOOTLOADER\n" + " SPEC\tSpecifies a script to run without parameters; use -b for parameters;\n" + " \t\tFor a directory, use contained uuu.auto at root;\n" + " \t\tFor a zip file, expand, then use uuu.auto at root of expanded content\n" + " CMD\t\tRun a command; see -H for details\n" + " \t\tExample: SDPS: boot -f flash.bin\n" + " BOOTLOADER\tBoot device from bootloader image file\n" + " -d\t\tProduction (daemon) mode\n" + " -v\t\tVerbose feedback\n" + " -V\t\tExtends verbose feedback to include USB library feedback\n" + " -dry\tDry-run; displays verbose output without performing actions\n" + " -bmap\tUse .bmap files even if flash commands do not specify them\n" + " -no-bmap\tIgnore .bmap files even if flash commands specify them\n" + " -m PATH\tLimits USB port monitoring. Example: -m 1:2 -m 1:3\n" + " -ms SN\tMonitor the serial number prefix of the device\n" + " -t #\tSeconds to wait for a device to appear\n" + " -T #\tSeconds to wait for a device to appeared at stage switch [for deamon mode?]\n" + " -e KEY=VAL\tSet environment variable KEY to value VAL\n" + " -pp #\tUSB polling period in milliseconds\n" + " -dm\t\tDisable small memory\n"; + + const char param_and_builtin_mode1[] = + "uuu [OPTION...] -b SPEC|BUILTIN [PARAM...]\n" + " SPEC\tSame as for default mode\n" + " BUILTIN\tBuilt-in script: "; + + const char param_and_builtin_mode2[] = + " OPTION...\tSame as for default mode\n" + " PARAM...\tScript parameter values\n"; + + const char special_modes[] = + "uuu -ls-devices\tList connected devices\n" + "uuu -ls-builtin\tList built-in scripts\n" + "uuu -cat-builtin BUILTIN\n" + " \t\tOutput built-in script\n" + "uuu -s\t\tInteractive (shell) mode; records commands in uuu.inputlog\n" + "uuu -udev\tFor Linux, output udev rule for avoiding using sudo each time\n" + "uuu -IgSerNum\tFor Windows, modify registry to ignore USB serial number to find devices\n" + "uuu -h\t\tOutput basic help\n" + "uuu -h-protocol-commands\n" + " \t\tOutput protocol command help info\n" + "uuu -h-protocol-support\n" + " \t\tOutput protocol support by device info\n" + "uuu -h-auto-complete\n" + " \t\tOutput auto/tab completion help info\n"; + + printf("\nDefault mode:\n%s", default_mode); + + printf("\nParameter & built-in script mode:\n%s", param_and_builtin_mode1); g_BuildScripts.ShowCmds(); - printf(" arg...\n"); - printf("\tRun Built-in scripts\n"); - g_BuildScripts.ShowAll(); - printf("\nuuu -bshow "); - g_BuildScripts.ShowCmds(); - printf("\n"); - printf("\tShow built-in script\n"); - printf("\n"); + printf("\n%s", param_and_builtin_mode2); - print_autocomplete_help(); + printf("\nSpecial modes:\n%s", special_modes); +} - if (detail == false) - return; +void print_script_directory() { + printf("\nBuilt-in scripts:\n"); + g_BuildScripts.ShowAll(); +} +void print_protocol_help() { size_t start = 0, pos = 0; string str= g_sample_cmd_list; @@ -241,9 +258,10 @@ void print_help(bool detail = false) start = pos; } } -void print_version() + +void print_app_title() { - printf("uuu (Universal Update Utility) for nxp imx chips -- %s\n\n", uuu_get_version_string()); + printf("Universal Update Utility for NXP i.MX chips -- %s\n", uuu_get_version_string()); } int print_cfg(const char *pro, const char * chip, const char * /*compatible*/, uint16_t vid, uint16_t pid, uint16_t bcdmin, uint16_t bcdmax, void * /*p*/) @@ -258,7 +276,14 @@ int print_cfg(const char *pro, const char * chip, const char * /*compatible*/, u printf("\t%s\t %s\t%s 0x%04x\t 0x%04x\n", pro, chip, ext, vid, pid); else printf("\t%s\t %s\t%s 0x%04x\t 0x%04x\t [0x%04x..0x%04x]\n", pro, chip, ext, vid, pid, bcdmin, bcdmax); - return 0; + return EXIT_SUCCESS; +} + +void print_protocol_support_help() { + printf("Protocol support for devices:\n"); + printf("\tPctl\t Chip\t\t Vid\t Pid\t BcdVersion\t Serial_No\n"); + printf("\t==================================================\n"); + uuu_for_each_cfg(print_cfg, NULL); } int print_udev_rule(const char * /*pro*/, const char * /*chip*/, const char * /*compatible*/, @@ -266,7 +291,7 @@ int print_udev_rule(const char * /*pro*/, const char * /*chip*/, const char * /* { printf("SUBSYSTEM==\"usb\", ATTRS{idVendor}==\"%04x\", ATTRS{idProduct}==\"%04x\", TAG+=\"uaccess\"\n", vid, pid); - return 0; + return EXIT_SUCCESS; } int polling_usb(std::atomic& bexit); @@ -659,7 +684,7 @@ int progress(uuu_notify nt, void *p) str.format("\rSuccess %d Failure %d ", g_overall_okay, g_overall_failure); if (g_map_path_nt.empty()) - str += "Wait for Known USB Device Appear..."; + str += "Waiting for device..."; if (!g_usb_path_filter.empty()) { @@ -699,7 +724,7 @@ int progress(uuu_notify nt, void *p) if(np->find(nt.id) != np->end()) np->erase(nt.id); } - return 0; + return EXIT_SUCCESS; } #ifdef _MSC_VER @@ -795,13 +820,14 @@ int runshell(int shell) prompt = "U>"; cout << "Exit u-boot cmd mode" << endl; cout << "Okay" << endl; - }else if (cmd == "help" || cmd == "?") + } + else if (cmd == "help" || cmd == "?") { - print_help(); + print_cli_help(); } else if (cmd == "q" || cmd == "quit") { - return 0; + return EXIT_SUCCESS; } else { @@ -818,25 +844,27 @@ int runshell(int shell) cout << "Okay" << endl; } } - return 0; + return EXIT_SUCCESS; } - return -1; + return EXIT_FAILURE; } void print_udev() { uuu_for_each_cfg(print_udev_rule, NULL); - fprintf(stderr, "\n1: put above udev run into /etc/udev/rules.d/70-uuu.rules\n"); - fprintf(stderr, "\tsudo sh -c \"uuu -udev >> /etc/udev/rules.d/70-uuu.rules\"\n"); - fprintf(stderr, "2: update udev rule\n"); - fprintf(stderr, "\tsudo udevadm control --reload\n"); + + cerr << endl << + "Enable udev rules via:" << endl << + "\tsudo sh -c \"uuu -udev >> /etc/udev/rules.d/70-uuu.rules\"" << endl << + "\tsudo udevadm control --reload" << endl << + "Note: These instructions output to standard error so are excluded" << endl << endl; } int print_usb_device(const char *path, const char *chip, const char *pro, uint16_t vid, uint16_t pid, uint16_t bcd, const char *serial_no, void * /*p*/) { printf("\t%s\t %s\t %s\t 0x%04X\t0x%04X\t 0x%04X\t %s\n", path, chip, pro, vid, pid, bcd, serial_no); - return 0; + return EXIT_SUCCESS; } void print_lsusb() @@ -862,10 +890,10 @@ int ignore_serial_number(const char *pro, const char *chip, const char */*comp*/ "SYSTEM\\CurrentControlSet\\Control\\UsbFlags", sub, REG_BINARY, &value, 1); if(ret == ERROR_SUCCESS) - return 0; + return EXIT_SUCCESS; printf("Set key failure, try run as administrator permission\n"); - return -1; + return EXIT_FAILURE; } #endif @@ -873,179 +901,230 @@ int set_ignore_serial_number() { #ifndef WIN32 printf("Only windows system need set ignore serial number registry"); - return -1; + return EXIT_FAILURE; #else printf("Set window registry to ignore usb hardware serial number for known uuu device:\n"); return uuu_for_each_cfg(ignore_serial_number, NULL); #endif } +void log_error(const string& message) { + cerr << "Error: " << message << endl; +} + +void log_syntax_error(const string& message) { + log_error(message); + print_cli_help(); +} + int main(int argc, char **argv) { - if (auto_complete(argc, argv) == 0) - return 0; + // commented out causes failure when pass script file name/path as first arg plus -v after + //if (auto_complete(argc, argv) == 0) return EXIT_SUCCESS; + // handle modes that should _not_ print the app title if (argc >= 2) { string s = argv[1]; if(s == "-udev") { print_udev(); - return 0; + return EXIT_SUCCESS; } - if (s == "-bshow") + if (s == "-cat-builtin") { - if (2 == argc || g_BuildScripts.find(argv[2]) == g_BuildScripts.end()) + if (2 == argc) { - fprintf(stderr, "Error, must be have script name: "); + fprintf(stderr, "Error: Missing built-in script name; options: "); g_BuildScripts.ShowCmds(stderr); - fprintf(stderr,"\n"); - return -1; + fprintf(stderr, "\n"); + return EXIT_FAILURE; } - else + if (g_BuildScripts.find(argv[2]) == g_BuildScripts.end()) { - string str = g_BuildScripts[argv[2]].m_text; - while (str.size() > 0 && (str[0] == '\n' || str[0] == ' ')) - str = str.erase(0,1); - - printf("%s", str.c_str()); - return 0; + fprintf(stderr, "Error: Unknown built-in script name; options: "); + g_BuildScripts.ShowCmds(stderr); + fprintf(stderr, "\n"); + return EXIT_FAILURE; } + string str = g_BuildScripts[argv[2]].m_text; + while (str.size() > 0 && (str[0] == '\n' || str[0] == ' ')) + str = str.erase(0,1); + printf("%s", str.c_str()); + return EXIT_SUCCESS; } } AutoCursor a; - print_version(); + print_app_title(); if (!enable_vt_mode()) { - cout << "Your console don't support VT mode, fail back to verbose mode" << endl; + // [why enable verbose in this case?] + cout << "Warning: Console doesn't support VT mode; enabling verbose feedback" << endl; g_verbose = 1; } if (argc == 1) { - print_help(); - return 0; + log_error("Invalid input"); + print_cli_help(); + return EXIT_FAILURE; } int deamon = 0; int shell = 0; - string filename; - string cmd; - int ret; - int dryrun = 0; - + int dryrun = 0; + string input_path; + string protocol_cmd; string cmd_script; for (int i = 1; i < argc; i++) { - string s = argv[i]; - if (!s.empty() && s[0] == '-') + string arg = argv[i]; + if (!arg.empty() && arg[0] == '-') { - if (s == "-d") + if (arg == "-d") { deamon = 1; uuu_set_small_mem(0); - } - else if (s == "-dm") + else if (arg == "-dm") { uuu_set_small_mem(0); } - else if (s == "-s") + else if (arg == "-s") { shell = 1; + // why set verbose for shell mode? g_verbose = 1; } - else if (s == "-v") + else if (arg == "-v") { g_verbose = 1; } - else if (s == "-V") + else if (arg == "-V") { g_verbose = 1; uuu_set_debug_level(2); - }else if (s == "-dry") + }else if (arg == "-dry") { dryrun = 1; + // why is verbose set for dry-run? g_verbose = 1; } - else if (s == "-h") + else if (arg == "-h") + { + print_cli_help(); + return EXIT_SUCCESS; + } + else if (arg == "-h-auto-complete") { - print_help(false); - return 0; + print_autocomplete_help(); + return EXIT_SUCCESS; } - else if (s == "-H") + else if (arg == "-h-protocol-commands") { - print_help(true); - return 0; + print_protocol_help(); + return EXIT_SUCCESS; } - else if (s == "-m") + else if (arg == "-h-protocol-support") { - i++; + print_protocol_support_help(); + return EXIT_SUCCESS; + } + else if (arg == "-ls-builtin") + { + print_script_directory(); + return EXIT_SUCCESS; + } + else if (arg == "-m") + { + if (++i >= argc) + { + log_syntax_error("Missing USB path argument"); + return EXIT_FAILURE; + } uuu_add_usbpath_filter(argv[i]); g_usb_path_filter.push_back(argv[i]); } - else if (s == "-ms") + else if (arg == "-ms") { - i++; + if (++i >= argc) + { + log_syntax_error("Missing serial # argument"); + return EXIT_FAILURE; + } uuu_add_usbserial_no_filter(argv[i]); g_usb_serial_no_filter.push_back(argv[i]); } - else if (s == "-t") + else if (arg == "-t") { - i++; + if (++i >= argc) + { + log_syntax_error("Missing seconds argument"); + return EXIT_FAILURE; + } uuu_set_wait_timeout(atoll(argv[i])); } - else if (s == "-T") + else if (arg == "-T") { - i++; + if (++i >= argc) + { + log_syntax_error("Missing seconds argument"); + return EXIT_FAILURE; + } uuu_set_wait_next_timeout(atoll(argv[i])); } - else if (s == "-pp") + else if (arg == "-pp") { - i++; + if (++i >= argc) + { + log_syntax_error("Missing milliseconds argument"); + return EXIT_FAILURE; + } uuu_set_poll_period(atoll(argv[i])); } - else if (s == "-lsusb") + else if (arg == "-ls-devices") { print_lsusb(); - return 0; + return EXIT_SUCCESS; } - else if (s == "-IgSerNum") + else if (arg == "-IgSerNum") { return set_ignore_serial_number(); } - else if (s == "-bmap") + else if (arg == "-bmap") { g_bmap_mode = bmap_mode::Force; } - else if (s == "-no-bmap") + else if (arg == "-no-bmap") { g_bmap_mode = bmap_mode::Ignore; } - else if (s == "-e") + else if (arg == "-e") { #ifndef WIN32 #define _putenv putenv #endif - i++; + if (++i >= argc) + { + log_syntax_error("Missing key=value argument"); + return EXIT_FAILURE; + } if (_putenv(argv[i])) { - printf("error, failed to set '%s', environment parameter must have the form key=value\n", argv[i]); - return -1; + printf("Error: Failed to set '%s'. Hint: parameter must have the form key=value\n", argv[i]); + return EXIT_FAILURE; } } - else if (s == "-b" || s == "-brun") + else if (arg == "-b" || arg == "-brun") { if (i + 1 == argc) { - printf("error, must be have script name: "); - g_BuildScripts.ShowCmds(); - printf("\n"); - return -1; + log_syntax_error("Missing path or built-in script name"); + return EXIT_FAILURE; } vector args; @@ -1060,7 +1139,7 @@ int main(int argc, char **argv) args.push_back(s); } - // if script name is not build-in, try to look for a file + // if script name is not built-in, try to look for a file if (g_BuildScripts.find(argv[i + 1]) == g_BuildScripts.end()) { const string tmpCmdFileName{argv[i + 1]}; @@ -1070,7 +1149,7 @@ int main(int argc, char **argv) if (fileContents.empty()) { printf("%s is not built-in script or fail load external script file", tmpCmdFileName.c_str()); - return -1; + return EXIT_FAILURE; } BuiltInScriptRawData tmpCmd{ @@ -1090,29 +1169,36 @@ int main(int argc, char **argv) } else { - cout << "Unknown option: " << s.c_str(); - return -1; + cout << "Error: Unknown option: " << arg << endl; + print_cli_help(); + return EXIT_FAILURE; } - }else if (!s.empty() && s[s.size() - 1] == ':') + }else if (!arg.empty() && arg[arg.size() - 1] == ':') { + // looks like a protocol command for (int j = i; j < argc; j++) { - s = argv[j]; - if (s.find(' ') != string::npos && s[s.size() - 1] != ':') + arg = argv[j]; + if (arg.find(' ') != string::npos && arg[arg.size() - 1] != ':') { - s.insert(s.begin(), '"'); - s.insert(s.end(), '"'); + arg.insert(arg.begin(), '"'); + arg.insert(arg.end(), '"'); } - cmd.append(s); + protocol_cmd.append(arg); if(j != (argc -1)) /* Don't add space at last arg */ - cmd.append(" "); + protocol_cmd.append(" "); } break; } else { - filename = s; - break; + // treat as a file system path + if (!input_path.empty()) + { + printf("Error: Too many path arguments - %s\n", arg.c_str()); + return EXIT_FAILURE; + } + input_path = arg; } } @@ -1122,42 +1208,40 @@ int main(int argc, char **argv) if (deamon && shell) { - printf("Error: -d -s Can't apply at the same time\n"); - return -1; + log_error("Can't use deamon (-d) and shell (-s) together"); + return EXIT_FAILURE; } if (deamon && dryrun) { - printf("Error: -d -dry Can't apply at the same time\n"); - return -1; + log_error("Can't use deamon (-d) and dry-run (-dry) together"); + return EXIT_FAILURE; } if (shell && dryrun) { - printf("Error: -dry -s Can't apply at the same time\n"); - return -1; + log_error("Error: Can't use shell (-s) and dry-run (-dry) together"); + return EXIT_FAILURE; } if (g_verbose) { - printf("%sBuild in config:%s\n", g_vt_boldwhite, g_vt_default); - printf("\tPctl\t Chip\t\t Vid\t Pid\t BcdVersion\t Serial_No\n"); - printf("\t==================================================\n"); - uuu_for_each_cfg(print_cfg, NULL); - - if (!cmd_script.empty()) - printf("\n%sRun built-in script:%s\n %s\n\n", g_vt_boldwhite, g_vt_default, cmd_script.c_str()); - - if (!shell) - cout << "Wait for Known USB Device Appear..."; - - print_usb_filter(); - - printf("\n"); + // commented out since seems overkill since can jprint it via -cat-builtin + //if (!cmd_script.empty()) + // printf("\n%sRunning built-in script:%s\n %s\n\n", g_vt_boldwhite, g_vt_default, cmd_script.c_str()); + + // why not log for !shell? it's logged for !g_verbose regardless + if (!shell) { + cout << "Waiting for device"; + print_usb_filter(); + cout << "..."; + printf("\n"); + } } else { - cout << "Wait for Known USB Device Appear..."; + cout << "Waiting for device"; print_usb_filter(); + cout << "..."; cout << "\r"; cout << "\x1b[?25l"; cout.flush(); @@ -1167,15 +1251,14 @@ int main(int argc, char **argv) uuu_register_notify_callback(progress, &nt_session); - - if (!cmd.empty()) + if (!protocol_cmd.empty()) { - ret = uuu_run_cmd(cmd.c_str(), dryrun); + int ret = uuu_run_cmd(protocol_cmd.c_str(), dryrun); for (size_t i = 0; i < g_map_path_nt.size()+3; i++) printf("\n"); if(ret) - printf("\nError: %s\n", uuu_get_last_err_string()); + printf("Error: %s\n", uuu_get_last_err_string()); else printf("Okay\n"); @@ -1183,30 +1266,33 @@ int main(int argc, char **argv) return ret; } - if (!cmd_script.empty()) - ret = uuu_run_cmd_script(cmd_script.c_str(), dryrun); - else - ret = uuu_auto_detect_file(filename.c_str()); - - if (ret) { - ret = runshell(shell); - if(ret) - cout << g_vt_red << "\nError: " << g_vt_default << uuu_get_last_err_string(); - return ret; + int ret; + if (!cmd_script.empty()) + ret = uuu_run_cmd_script(cmd_script.c_str(), dryrun); + else + ret = uuu_auto_detect_file(input_path.c_str()); + + if (ret) + { + ret = runshell(shell); + if (ret) + cout << g_vt_red << "\nError: " << g_vt_default << uuu_get_last_err_string(); + return ret; + } } if (uuu_wait_uuu_finish(deamon, dryrun)) { cout << g_vt_red << "\nError: " << g_vt_default << uuu_get_last_err_string(); - return -1; + return EXIT_FAILURE; } runshell(shell); - /*Wait for the other thread exit, after send out CMD_DONE*/ + // wait for the other thread exit, after send out CMD_DONE std::this_thread::sleep_for(std::chrono::milliseconds(100)); - if(!g_verbose) - printf("\n\n\n"); + //if(!g_verbose) + // printf("\n"); return g_overall_status; } From 8be2c0c5fd504260c93ce219538bcf42bac9a1f9 Mon Sep 17 00:00:00 2001 From: SteveBroshar Date: Fri, 21 Feb 2025 09:06:11 -0600 Subject: [PATCH 02/90] refactoring --- libuuu/buffer.cpp | 87 +-- libuuu/buffer.h | 3 +- libuuu/cmd.cpp | 64 ++- libuuu/error.cpp | 11 +- libuuu/fastboot.cpp | 8 +- libuuu/libcomm.h | 2 +- libuuu/liberror.h | 4 +- libuuu/string_man.h | 43 ++ msvc/libuuu.vcxproj | 1 + msvc/libuuu.vcxproj.filters | 3 + msvc/uuu.vcxproj | 5 +- msvc/uuu.vcxproj.filters | 9 + uuu/VtEmulation.h | 134 +++++ uuu/autocomplete.cpp | 6 +- uuu/buildincmd.cpp | 77 ++- uuu/buildincmd.h | 66 ++- uuu/logger.h | 55 ++ uuu/progress.h | 453 ++++++++++++++++ uuu/uuu.cpp | 1018 +++++++++-------------------------- 19 files changed, 1136 insertions(+), 913 deletions(-) create mode 100644 libuuu/string_man.h create mode 100644 uuu/VtEmulation.h create mode 100644 uuu/logger.h create mode 100644 uuu/progress.h diff --git a/libuuu/buffer.cpp b/libuuu/buffer.cpp index 8c6c3024..0a6f4f52 100644 --- a/libuuu/buffer.cpp +++ b/libuuu/buffer.cpp @@ -29,25 +29,30 @@ * */ -#include #include "buffer.h" -#include -#include "liberror.h" -#include -#include + #include "libcomm.h" #include "libuuu.h" -#include "zip.h" -#include "fat.h" -#include "tar.h" -#include + #include "bzlib.h" -#include "stdio.h" -#include +#include "fat.h" #include "http.h" +#include "string_man.h" +#include "tar.h" +#include "zip.h" #include "zstd.h" + #include "libusb.h" +#include +#include +#include + +#include +#include +#include +#include + #ifdef WIN32 #define stat_os _stat64 #elif defined(__APPLE__) @@ -752,20 +757,16 @@ static class FS_DATA } int load(const string &filename, shared_ptr p) { - for (size_t i = 0; i < m_pFs.size(); i++) + for (auto& fs : m_pFs) { string back, fn; - if (m_pFs[i]->split(filename, &back, &fn) == 0) { - if (m_pFs[i]->load(back, fn, p) == 0) + if (fs->split(filename, &back, &fn) == 0) { + if (fs->load(back, fn, p) == 0) return 0; } } - string err; - err = "fail open file: "; - err += filename; - set_last_err_string(err); - return -1; + return set_last_err_string("Unable to open file: " + filename); } }g_fs_data; @@ -1209,10 +1210,19 @@ uint64_t get_file_timesample(string filename) return time; } +/** + * [what does this do?] + * @param filename File system path + * @param async TBD + * @details + * As a side-effect, this conforms filename by stripping quotes and if doesn't start with + * MAGIC_PATH prepending g_current_dir and then replacing backslashes with forward. + * @note + * The side-effect should be eliminated since it adds to cognative load. + */ shared_ptr get_file_buffer(string filename, bool async) { - filename = remove_quota(filename); - + filename = strip_quotes(filename); if (!filename.empty() && filename[0] != MAGIC_PATH) { if (filename == "..") @@ -1220,21 +1230,15 @@ shared_ptr get_file_buffer(string filename, bool async) else filename = g_current_dir + filename; } + replace(filename, "\\", "/"); - string_ex path; - path += filename; - - path.replace('\\', '/'); - - filename = path; - - bool find; + bool found; { std::lock_guard lock(g_mutex_map); - find = (g_filebuffer_map.find(filename) == g_filebuffer_map.end()); + found = (g_filebuffer_map.find(filename) == g_filebuffer_map.end()); } - if (find) + if (found) { shared_ptr p(new FileBuffer); @@ -1417,6 +1421,9 @@ int FileBuffer::ref_other_buffer(shared_ptr p, size_t offset, size_t return 0; } +/** + * [what does this do?] + */ int FileBuffer::reload(string filename, bool async) { if(async) { @@ -1837,10 +1844,16 @@ int FileBuffer::unmapfile() return 0; } -bool check_file_exist(string filename, bool /*start_async_load*/) +bool path_exists(const string& path) +{ + struct stat_os st; + return stat_os(path.c_str(), &st) == 0; +} + +int verify_file_exist(string filename) { string_ex fn; - fn += remove_quota(filename); + fn += strip_quotes(filename); string_ex path; if (!fn.empty() && fn[0] != MAGIC_PATH) { @@ -1857,7 +1870,13 @@ bool check_file_exist(string filename, bool /*start_async_load*/) if (path.empty()) path += "./"; - return g_fs_data.exist(path); + + bool exists = g_fs_data.exist(path); + if (!exists) + { + return set_last_err_string("File not found: " + filename); + } + return 0; } #ifdef WIN32 diff --git a/libuuu/buffer.h b/libuuu/buffer.h index 8813e342..4ce48f95 100644 --- a/libuuu/buffer.h +++ b/libuuu/buffer.h @@ -340,7 +340,8 @@ class FileBuffer: public std::enable_shared_from_this }; std::shared_ptr get_file_buffer(std::string filename, bool async=false); -bool check_file_exist(std::string filename, bool start_async_load=true); +int verify_file_exist(std::string filename); +bool path_exists(const std::string& path); void set_current_dir(const std::string &dir); diff --git a/libuuu/cmd.cpp b/libuuu/cmd.cpp index 485a5698..0bff2202 100644 --- a/libuuu/cmd.cpp +++ b/libuuu/cmd.cpp @@ -29,6 +29,8 @@ * */ +#include "string_man.h" + #include #include #include @@ -47,8 +49,8 @@ #include #include -#include -#include +#include +#include static CmdMap g_cmd_map; static CmdObjCreateMap g_cmd_create_map; @@ -167,7 +169,7 @@ int CmdBase::parser(char *p) param = get_next_param(m_cmd, pos); *(string*)pp->pData = param; - if (!check_file_exist(param)) + if (verify_file_exist(param)) return -1; } @@ -175,7 +177,7 @@ int CmdBase::parser(char *p) { if (!m_NoKeyParam) param = get_next_param(m_cmd, pos); - *(string*)pp->pData = remove_quota(param); + *(string*)pp->pData = strip_quotes(param); } if (pp->type == Param::Type::e_bool) @@ -295,7 +297,7 @@ int CmdMap::run_all(const std::string &protocol, CmdCtx *p, bool dry_run) { if (find(protocol) == end()) { - set_last_err_id(-1); + //set_last_err_id(-1); std::string err; err.append("Unknown Protocol:"); err.append(protocol); @@ -496,7 +498,7 @@ CmdObjCreateMap::CmdObjCreateMap() } -shared_ptr create_cmd_obj(string cmd) +static shared_ptr create_cmd_obj(string cmd) { string param; size_t pos = 0; @@ -523,10 +525,7 @@ shared_ptr create_cmd_obj(string cmd) return g_cmd_create_map[param]((char*)cmd.c_str()); } - string err; - err = "Unknown Command:"; - err += cmd; - set_last_err_string(err); + set_last_err_string("Unknown command: " + cmd); return nullptr; } @@ -964,7 +963,7 @@ static int insert_one_cmd(const char * cmd, CmdMap *pCmdMap) if (p->parser()) return -1; - + if (pCmdMap->find(pro) == pCmdMap->end()) { shared_ptr list(new CmdList); @@ -1022,7 +1021,7 @@ static int added_default_boot_cmd(const char *filename) return 0; } -int check_version(string str) +static int check_version(string str) { int x = 0; int ver = 0; @@ -1046,10 +1045,7 @@ int check_version(string str) if (ver > cur) { - string str; - str = "This version of uuu is too old, please download the latest one"; - set_last_err_string(str); - return -1; + return set_last_err_string("This version of uuu is too old, please download the latest one"); } return 0; } @@ -1100,23 +1096,30 @@ int parser_cmd_list_file(shared_ptr pbuff, CmdMap *pCmdMap) return 0; } -int uuu_auto_detect_file(const char *filename) +int uuu_auto_detect_file(const char *path) { - string_ex fn; - fn += remove_quota(filename); - fn.replace('\\', '/'); - + string fn = strip_quotes(path); + replace(fn, "\\", "/"); if (fn.empty()) fn += "./"; + const string clean_input_path = fn; - string oldfn =fn; + string ss = path; + if (!path_exists(ss)) + { + return set_last_err_string("Path not found: " + ss); + } - fn += "/uuu.auto"; + // look for default file name as if path is a directory + // TODO seems that uuu is the file type so a better name is 'auto.uuu' + static const string default_script_name = "uuu.auto"; + fn += "/" + default_script_name; shared_ptr buffer = get_file_buffer(fn); + if (buffer == nullptr) { - fn.clear(); - fn += oldfn; + // default file not found; look for ?? + fn = clean_input_path; size_t pos = str_to_upper(fn).find("ZIP"); if(pos == string::npos || pos != fn.size() - 3) { @@ -1125,14 +1128,19 @@ int uuu_auto_detect_file(const char *filename) buffer = get_file_buffer(fn); //we don't try open a zip file here } - if(buffer == nullptr) - return -1; + if (buffer == nullptr) + { + return set_last_err_string("Unsure what to do with path: " + string(path)); + } } string str= "uuu_version"; shared_ptr pData = buffer->request_data(0, UINT_MAX); if (!pData) - return -1; + { + return set_last_err_string("Unable read data from path: " + string(path)); + } + void *p1 = pData->data(); void *p2 = (void*)str.data(); if (memcmp(p1, p2, str.size()) == 0) diff --git a/libuuu/error.cpp b/libuuu/error.cpp index 6d8b567a..1638bc33 100644 --- a/libuuu/error.cpp +++ b/libuuu/error.cpp @@ -66,10 +66,11 @@ const char * uuu_get_last_err_string() return g_last_error_str.c_str(); } -void set_last_err_string(const string &str) +int set_last_err_string(const string &str) { lock_guard l(g_last_error_str_mutex); g_last_error_str = str; + return -1; } int uuu_get_last_err() @@ -77,7 +78,7 @@ int uuu_get_last_err() return g_last_err_id.load(); } -void set_last_err_id(int id) -{ - g_last_err_id = id; -} +//void set_last_err_id(int id) +//{ +// g_last_err_id = id; +//} diff --git a/libuuu/fastboot.cpp b/libuuu/fastboot.cpp index e36b9135..6bac2b52 100644 --- a/libuuu/fastboot.cpp +++ b/libuuu/fastboot.cpp @@ -569,12 +569,12 @@ int FBFlashCmd::parser(char *p) return -1; } - if (!check_file_exist(m_filename)) { + if (verify_file_exist(m_filename)) { set_last_err_string("FB: image file not found: " + m_filename); return -1; } - if (m_use_bmap && m_bmap_filename.size() && !check_file_exist(m_bmap_filename)) { + if (m_use_bmap && m_bmap_filename.size() && verify_file_exist(m_bmap_filename)) { set_last_err_string("FB: bmap file not found: " + m_bmap_filename); return -1; } @@ -584,13 +584,13 @@ int FBFlashCmd::parser(char *p) auto p = m_bmap_filename.rfind('.'); if (p != string::npos) { m_bmap_filename.replace(p, string::npos, ".bmap"); - if (check_file_exist(m_bmap_filename)) + if (!verify_file_exist(m_bmap_filename)) return 0; } m_bmap_filename = m_filename; m_bmap_filename.append(".bmap"); - if (check_file_exist(m_bmap_filename)) + if (!verify_file_exist(m_bmap_filename)) return 0; m_use_bmap = false; diff --git a/libuuu/libcomm.h b/libuuu/libcomm.h index 918ab1e6..fe76f350 100644 --- a/libuuu/libcomm.h +++ b/libuuu/libcomm.h @@ -121,7 +121,7 @@ inline string str_to_upper(const string &str) return s; } -inline string remove_quota(string str) +inline string strip_quotes(string str) { if (!str.empty()) { diff --git a/libuuu/liberror.h b/libuuu/liberror.h index ce3aa6ef..9610d343 100644 --- a/libuuu/liberror.h +++ b/libuuu/liberror.h @@ -33,8 +33,8 @@ #include -void set_last_err_string(const std::string &str); -void set_last_err_id(int id); +int set_last_err_string(const std::string &str); +//void set_last_err_id(int id); #define ERR_OUT_MEMORY -2 #define ERR_ACCESS_DENIED -3 diff --git a/libuuu/string_man.h b/libuuu/string_man.h new file mode 100644 index 00000000..d1d0f206 --- /dev/null +++ b/libuuu/string_man.h @@ -0,0 +1,43 @@ + +#pragma once + +#include +#include + +#include + +/** + * @brief Formats like printf with output to std::string and minimal size allocation + */ +inline void format(std::string s, const char* fmt, ...) +{ + va_list args; + va_start(args, fmt); + size_t len = std::vsnprintf(NULL, 0, fmt, args); + va_end(args); + + s.resize(len); + + va_start(args, fmt); + std::vsnprintf((char*)s.c_str(), len + 1, fmt, args); + va_end(args); +} + +/** + * @brief Replaces each occurance of a substring + * @param text Input text + * @param from Substring to replace + * @param to Text to replace found substring with + * @return Reference to text (supports chaining) + */ +inline std::string& replace(std::string& text, const std::string& from, const std::string& to) { + if (!from.empty()) + { + size_t start_pos = 0; + while ((start_pos = text.find(from, start_pos)) != std::string::npos) { + text.replace(start_pos, from.length(), to); + start_pos += to.length(); + } + } + return text; +} diff --git a/msvc/libuuu.vcxproj b/msvc/libuuu.vcxproj index 375367b2..69cd25ae 100644 --- a/msvc/libuuu.vcxproj +++ b/msvc/libuuu.vcxproj @@ -56,6 +56,7 @@ + diff --git a/msvc/libuuu.vcxproj.filters b/msvc/libuuu.vcxproj.filters index 9fe75877..4f4c6099 100644 --- a/msvc/libuuu.vcxproj.filters +++ b/msvc/libuuu.vcxproj.filters @@ -66,6 +66,9 @@ Header Files + + Header Files + diff --git a/msvc/uuu.vcxproj b/msvc/uuu.vcxproj index fd01aa04..cc96b3fe 100644 --- a/msvc/uuu.vcxproj +++ b/msvc/uuu.vcxproj @@ -25,6 +25,9 @@ + + + 15.0 @@ -337,4 +340,4 @@ echo )####^" >> $(SolutionDir)\..\uuu\nand_burn_loader.clst - + \ No newline at end of file diff --git a/msvc/uuu.vcxproj.filters b/msvc/uuu.vcxproj.filters index 1aee5934..ac713cda 100644 --- a/msvc/uuu.vcxproj.filters +++ b/msvc/uuu.vcxproj.filters @@ -29,5 +29,14 @@ Header Files + + Header Files + + + Header Files + + + Header Files + \ No newline at end of file diff --git a/uuu/VtEmulation.h b/uuu/VtEmulation.h new file mode 100644 index 00000000..cac15a5f --- /dev/null +++ b/uuu/VtEmulation.h @@ -0,0 +1,134 @@ + +#pragma once + +#include + +#include + +/** + * @brief [what exactly does this do? what is it for?] + */ +class AutoCursor final +{ +public: + ~AutoCursor() + { + // not sure what this does, but maybe it ensures that output color is normal + printf("\x1b[?25h\n"); + } +}; + +/** + * @brief Base class for VT terminal emulation + */ +class VtEmulation +{ +public: + VtEmulation() { + select_default_palette(); + } + const char* yellow; + const char* default_fg; + const char* green; + const char* red; + const char* kcyn; + const char* boldwhite; + + void select_default_palette() { + yellow = "\x1B[93m"; + default_fg = "\x1B[0m"; + green = "\x1B[92m"; + red = "\x1B[91m"; + kcyn = "\x1B[36m"; + boldwhite = "\x1B[97m"; + } + + virtual bool enable() = 0; + virtual int get_console_width() = 0; +}; + +/** + * @brief Global singleton for VT terminal emulation + */ +extern std::shared_ptr g_vt; + +#ifdef _MSC_VER + +#define DEFINE_CONSOLEV2_PROPERTIES +#include +#include +#include + +/** + * @brief VT terminal emulation for Windows + */ +class PlatformVtEmulation final : public VtEmulation +{ + void clear_pallet() noexcept + { + yellow = + default_fg = + green = + red = + kcyn = + boldwhite = ""; + } + +public: + bool enable() override + { + // Set output mode to handle virtual terminal sequences + HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE); + if (hOut == INVALID_HANDLE_VALUE) + { + clear_pallet(); + return false; + } + + DWORD dwMode = 0; + if (!GetConsoleMode(hOut, &dwMode)) + { + clear_pallet(); + return false; + } + + dwMode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING; + if (!SetConsoleMode(hOut, dwMode)) + { + clear_pallet(); + return false; + } + return true; + } + + int get_console_width() override + { + CONSOLE_SCREEN_BUFFER_INFO sbInfo; + GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &sbInfo); + return sbInfo.dwSize.X; + } +}; + +#else + +#include + +/** + * @brief VT terminal emulation for Linux + */ +class PlatformVtEmulation final : public VtEmulation +{ + bool enable() override + { + return true; + } + + int get_console_width() override + { + struct winsize w; + ioctl(0, TIOCGWINSZ, &w); + return w.ws_col; + } +}; + +#endif \ No newline at end of file diff --git a/uuu/autocomplete.cpp b/uuu/autocomplete.cpp index ecdd7b81..f4b1b642 100644 --- a/uuu/autocomplete.cpp +++ b/uuu/autocomplete.cpp @@ -114,7 +114,7 @@ void linux_autocomplete(int argc, char **argv) } else if (last == "-b") { - return g_BuildScripts.PrintAutoComplete(cur); + return g_ScriptCatalog.print_auto_complete(cur); }else if(last[0] == '-') { @@ -158,8 +158,8 @@ void power_shell_autocomplete(const char *p) if (prev == "-b") cur = last; - if (g_BuildScripts.find(cur) == g_BuildScripts.end()) - g_BuildScripts.PrintAutoComplete(cur, ""); + if (g_ScriptCatalog.find(cur) == g_ScriptCatalog.end()) + g_ScriptCatalog.print_auto_complete(cur, ""); last.clear(); } diff --git a/uuu/buildincmd.cpp b/uuu/buildincmd.cpp index 7f5b9f8f..e996f7e6 100644 --- a/uuu/buildincmd.cpp +++ b/uuu/buildincmd.cpp @@ -30,8 +30,9 @@ */ #include "buildincmd.h" +#include "VtEmulation.h" -#include +#include #include #include @@ -158,19 +159,19 @@ void BuiltInScript::show() const } /** - * @brief Print the script's name, its description and its arguments to stdout + * @brief Print the script name, description and formal arguments to stdout */ void BuiltInScript::show_cmd() const { - printf("\t%s%s%s\t%s\n", g_vt_boldwhite, m_name.c_str(), g_vt_default, m_desc.c_str()); + printf("\t%s%s%s\t%s\n", g_vt->boldwhite, m_name.c_str(), g_vt->default_fg, m_desc.c_str()); for (auto i = 0u; i < m_args.size(); ++i) { std::string desc{m_args[i].m_name}; if (m_args[i].m_flags & Arg::ARG_OPTION) { - desc += g_vt_boldwhite; + desc += g_vt->boldwhite; desc += "[Optional]"; - desc += g_vt_default; + desc += g_vt->default_fg; } desc += " "; desc += m_args[i].m_desc; @@ -192,12 +193,37 @@ BuiltInScriptMap::BuiltInScriptMap(const BuiltInScriptRawData*p) } /** - * @brief Auto-complete names of built-in scripts if they match `match` - * @param[in] match The string against which the scripts' names will be matched - * @param[in] space A separator character which shall be printed after the - * completed script name + * @brief Loads a file as a script; adding it to the catalog + * @param path File system path + * @return Success indication */ -void BuiltInScriptMap::PrintAutoComplete(const std::string &match, const char *space) const +bool BuiltInScriptMap::add_from_file(const std::string& path) +{ + std::ifstream t(path); + std::string fileContents((std::istreambuf_iterator(t)), + std::istreambuf_iterator()); + + if (fileContents.empty()) { + return false; + } + + BuiltInScriptRawData script_definition{ + path.c_str(), + fileContents.c_str(), + "Script loaded from file" + }; + + emplace(path, &script_definition); + + return true; +} + +/** + * @brief Print the name of each script that matches; for use with auto-complete + * @param[in] match Search text + * @param[in] space Text printed after each script name + */ +void BuiltInScriptMap::print_auto_complete(const std::string &match, const char *space) const { for (const auto &script_pair : *this) { @@ -209,9 +235,9 @@ void BuiltInScriptMap::PrintAutoComplete(const std::string &match, const char *s } /** - * @brief Print information about all contained scripts to stdout + * @brief Print (to stdout) usage information about each script */ -void BuiltInScriptMap::ShowAll() const +void BuiltInScriptMap::print_usage() const { for (const auto &script_pair : *this) { @@ -220,24 +246,17 @@ void BuiltInScriptMap::ShowAll() const } /** - * @brief Print the names of all contained scripts to the given stream - * @param[in] file The stream to which the names shall be printed + * @brief Get a string that lists each script name separated by comma */ -void BuiltInScriptMap::ShowCmds(FILE * const file) const +std::string BuiltInScriptMap::get_names() const { - //fprintf(file, "<"); - for (auto iCol = begin(); iCol != end(); ++iCol) + std::string text; + for (const auto& item : *this) { - fprintf(file, "%s", iCol->first.c_str()); - - auto i = iCol; - i++; - if(i != end()) - { - fprintf(file, "|"); - } + text += item.first + ","; } - //fprintf(file, ">"); + text.pop_back(); + return text; } /** @@ -309,7 +328,7 @@ static std::string str_to_upper(const std::string &str) return s; } -//! Array containing raw information about all the built-in scripts of uuu +//! Information about the built-in scripts static constexpr BuiltInScriptRawData g_builtin_cmd[] = { { @@ -369,5 +388,5 @@ static constexpr BuiltInScriptRawData g_builtin_cmd[] = } }; -//! A map of the built-in scripts' names to their BuiltInScript representations -BuiltInScriptMap g_BuildScripts(g_builtin_cmd); +//! Script catalog +BuiltInScriptMap g_ScriptCatalog(g_builtin_cmd); diff --git a/uuu/buildincmd.h b/uuu/buildincmd.h index 3a946793..90c42515 100644 --- a/uuu/buildincmd.h +++ b/uuu/buildincmd.h @@ -36,34 +36,31 @@ #include #include -extern const char * g_vt_boldwhite; -extern const char * g_vt_default; -extern const char * g_vt_kcyn; -extern const char * g_vt_green; -extern const char * g_vt_red ; -extern const char * g_vt_yellow; - /** - * @brief Structure to hold the raw data of a built-in script + * @brief Script definition data */ -struct BuiltInScriptRawData +struct BuiltInScriptRawData final { - //! The name of the built-in script + //! Script name const char * const m_name = nullptr; - //! The actual built-in script itself + //! Script content const char * const m_text = nullptr; - //! A description of the built-in script's purpose + //! Script description/documentation const char * const m_desc = nullptr; }; -class BuiltInScript +/** + * @brief Parameterized script + * @note + * Is mostly for built-in scripts, but is also used for custom scripts sometimes. + */ +class BuiltInScript final { public: /** - * @brief A class for representing arguments of built-in scripts represented - * by BuiltInScript + * @brief Defines a formal argument to a script */ - class Arg + class Arg final { public: enum @@ -75,14 +72,14 @@ class BuiltInScript void parser(const std::string &option); - //! The name of the argument + //! Argument name std::string m_name; - //! A description of the argument + //! Argument description/documentation std::string m_desc; - //! Flags of the argument (basically if it's optional or not) + //! Flags (basically if it's optional or not) uint32_t m_flags = ARG_MUST; - //! The argument whose value this one will fall back to if it's optional - //! and not given explicitly + //! Argument whose value this one defaults to if this is optional + //! and not specified std::string m_fallback_option; }; @@ -93,13 +90,13 @@ class BuiltInScript void show() const; void show_cmd() const; - //! The actual script which is being represented + //! Script content const std::string m_text; - //! A description of the script's purpose + //! Script description/documentation const std::string m_desc; - //! A short name of the built-in script + //! Script name const std::string m_name; - //! The arguments of the built-in script + //! Arguments std::vector m_args; private: @@ -107,19 +104,16 @@ class BuiltInScript }; /** - * @brief A map of all built-in scripts indexed by their names - * - * Each built-in script is represented by a BuiltInScript instance. + * @brief Script catalog; indexed by name */ -class BuiltInScriptMap : public std::map +class BuiltInScriptMap final : public std::map { public: - BuiltInScriptMap(const BuiltInScriptRawData*p); - - void PrintAutoComplete(const std::string &match, const char *space = " ") const; - void ShowAll() const; - void ShowCmds(FILE * file=stdout) const; + BuiltInScriptMap(const BuiltInScriptRawData *p); + bool add_from_file(const std::string& path); + void print_auto_complete(const std::string &match, const char *space = " ") const; + void print_usage() const; + std::string get_names() const; }; -//! A map of the built-in scripts' names to their BuiltInScript representations -extern BuiltInScriptMap g_BuildScripts; +extern BuiltInScriptMap g_ScriptCatalog; diff --git a/uuu/logger.h b/uuu/logger.h new file mode 100644 index 00000000..262ef06f --- /dev/null +++ b/uuu/logger.h @@ -0,0 +1,55 @@ + +#pragma once + +#include "VtEmulation.h" + +#include +#include +#include + +/** + * @brief Application logging + */ +class Logger final +{ +public: + bool is_color_output_enabled = false; + + void log_error(const std::string& message) const + { + if (is_color_output_enabled) + { + std::cerr << g_vt->red << "Error: " << g_vt->default_fg << message << std::endl; + } + else + { + std::cerr << "Error: " << message << std::endl; + } + } + + void log_internal_error(const std::string& message) const + { + if (is_color_output_enabled) + { + std::cerr << g_vt->red << "INTERNAL ERROR: " << g_vt->default_fg << message << std::endl; + } + else + { + std::cerr << "Error: " << message << std::endl; + } + } + + void log_info(const std::string& message) const + { + std::cout << message << std::endl; + } + + void log_verbose(const std::string& message) const + { + extern int g_verbose; + if (g_verbose) + { + std::cout << "Verbose: " << message << std::endl; + } + } +}; diff --git a/uuu/progress.h b/uuu/progress.h new file mode 100644 index 00000000..bb53c15b --- /dev/null +++ b/uuu/progress.h @@ -0,0 +1,453 @@ + +#pragma once + +#include "../libuuu/libuuu.h" +#include "../libuuu/string_man.h" + +#include + +#include +#include +#include +#include +#include + +extern int g_verbose; + +// TODO should not have static vars in header file! + +static std::vector usb_serial_no_filter; +static bool start_usb_transfer; +static std::vector usb_path_filter; + +static int g_overall_status; +static int g_overall_okay; +static int g_overall_failure; +static char g_wait[] = "|/-\\"; +static int g_wait_index; + +static void print_oneline(std::string str) +{ + size_t w = g_vt->get_console_width(); + if (w <= 3) + return; + + if (str.size() >= w) + { + str.resize(w - 1); + str[str.size() - 1] = '.'; + str[str.size() - 2] = '.'; + str[str.size() - 3] = '.'; + } + else + { + str.resize(w, ' '); + } + std::cout << str << std::endl; +} + +static std::string build_process_bar(size_t width, size_t pos, size_t total) +{ + std::string str; + str.resize(width, ' '); + str[0] = '['; + str[width - 1] = ']'; + + if (total == 0) + { + if (pos == 0) + return str; + + std::string loc; + size_t s = pos / (1024 * 1024); + format(loc, "%dM", s); + str.replace(1, loc.size(), loc); + return str; + } + + size_t i; + + if (pos > total) + pos = total; + + for (i = 1; i < (width - 2) * pos / total; i++) + { + str[i] = '='; + } + + if (i > 1) + str[i] = '>'; + + if (pos == total) + str[str.size() - 2] = '='; + + std::string per; + format(per, "%d%%", pos * 100 / total); + + size_t start = (width - per.size()) / 2; + str.replace(start, per.size(), per); + str.insert(start, g_vt->yellow); + str.insert(start + per.size() + strlen(g_vt->yellow), g_vt->default_fg); + return str; +} + +static void print_auto_scroll(std::string str, size_t len, size_t start) +{ + if (str.size() <= len) + { + str.resize(len, ' '); + std::cout << str; + return; + } + + if (str.size()) + start = start % str.size(); + else + start = 0; + + std::string s = str.substr(start, len); + s.resize(len, ' '); + std::cout << s; +} + +class ShowNotify +{ +public: + std::string m_cmd; + std::string m_dev; + size_t m_trans_pos = 0; + int m_status = 0; + size_t m_cmd_total = 0; + size_t m_cmd_index = 0; + std::string m_last_err; + int m_done = 0; + size_t m_start_pos = 0; + size_t m_trans_size = 0; + clock_t m_start_time; + uint64_t m_cmd_start_time; + uint64_t m_cmd_end_time; + bool m_IsEmptyLine = false; + + ShowNotify() : m_start_time{ clock() } {} + + bool update(uuu_notify nt) + { + if (nt.type == uuu_notify::NOTIFY_DEV_ATTACH) + { + m_dev = nt.str; + m_done = 0; + m_status = 0; + } + if (nt.type == uuu_notify::NOTIFY_CMD_START) + { + m_start_pos = 0; + m_cmd = nt.str; + m_cmd_start_time = nt.timestamp; + } + if (nt.type == uuu_notify::NOTIFY_DECOMPRESS_START) + { + m_start_pos = 0; + m_cmd = nt.str; + m_cmd_start_time = nt.timestamp; + m_dev = "Prep"; + } + if (nt.type == uuu_notify::NOTIFY_DOWNLOAD_START) + { + m_start_pos = 0; + m_cmd = nt.str; + m_cmd_start_time = nt.timestamp; + m_dev = "Prep"; + } + if (nt.type == uuu_notify::NOTIFY_DOWNLOAD_END) + { + m_IsEmptyLine = true; + } + if (nt.type == uuu_notify::NOTIFY_TRANS_SIZE || nt.type == uuu_notify::NOTIFY_DECOMPRESS_SIZE) + { + m_trans_size = nt.total; + return false; + } + if (nt.type == uuu_notify::NOTIFY_CMD_TOTAL) + { + m_cmd_total = nt.total; + return false; + } + if (nt.type == uuu_notify::NOTIFY_CMD_INDEX) + { + m_cmd_index = nt.index; + return false; + } + if (nt.type == uuu_notify::NOTIFY_DONE) + { + if (m_status) + g_overall_failure++; + else + g_overall_okay++; + + m_done = 1; + } + if (nt.type == uuu_notify::NOTIFY_CMD_END) + { + m_cmd_end_time = nt.timestamp; + if (nt.status) + { + g_overall_status = nt.status; + m_last_err = uuu_get_last_err_string(); + } + m_status |= nt.status; + if (m_status) + g_overall_failure++; + } + if (nt.type == uuu_notify::NOTIFY_TRANS_POS || nt.type == uuu_notify::NOTIFY_DECOMPRESS_POS) + { + if (m_trans_size == 0) { + + m_trans_pos = nt.index; + return true; + } + + if ((nt.index - m_trans_pos) < (m_trans_size / 100) + && nt.index != m_trans_size) + return false; + + m_trans_pos = nt.index; + } + + return true; + } + void print_verbose(uuu_notify* nt) const + { + if (this->m_dev == "Prep" && start_usb_transfer) + return; + + if (nt->type == uuu_notify::NOTIFY_DEV_ATTACH) + { + std::cout << "New USB Device Attached at " << nt->str << std::endl; + } + if (nt->type == uuu_notify::NOTIFY_CMD_START) + { + std::cout << m_dev << ">" << "Start Cmd:" << nt->str << std::endl; + } + if (nt->type == uuu_notify::NOTIFY_CMD_END) + { + double diff = m_cmd_end_time - m_cmd_start_time; + diff /= 1000; + if (nt->status) + { + std::cout << m_dev << ">" << g_vt->red << "Fail " << uuu_get_last_err_string() << "(" << std::setprecision(4) << diff << "s)" << g_vt->default_fg << std::endl; + } + else + { + std::cout << m_dev << ">" << g_vt->green << "Okay (" << std::setprecision(4) << diff << "s)" << g_vt->default_fg << std::endl; + } + } + + if (nt->type == uuu_notify::NOTIFY_TRANS_POS || nt->type == uuu_notify::NOTIFY_DECOMPRESS_POS) + { + if (m_trans_size) + std::cout << g_vt->yellow << "\r" << m_trans_pos * 100 / m_trans_size << "%" << g_vt->default_fg; + else + std::cout << "\r" << m_trans_pos; + + std::cout.flush(); + } + + if (nt->type == uuu_notify::NOTIFY_CMD_INFO) + std::cout << nt->str; + + if (nt->type == uuu_notify::NOTIFY_WAIT_FOR) + std::cout << "\r" << nt->str << " " << g_wait[((g_wait_index++) & 0x3)]; + + if (nt->type == uuu_notify::NOTIFY_DECOMPRESS_START) + std::cout << "Decompress file:" << nt->str << std::endl; + + if (nt->type == uuu_notify::NOTIFY_DOWNLOAD_START) + std::cout << "Download file:" << nt->str << std::endl; + + } + void print(int verbose = 0, uuu_notify* nt = NULL) + { + verbose ? print_verbose(nt) : print_simple(); + } + std::string get_print_dev_string() + { + std::string str; + str = m_dev; + str.resize(12, ' '); + + std::string s; + format(s, "%2d/%2d", m_cmd_index + 1, m_cmd_total); + + str += s; + return str; + } + void print_simple() + { + int width = g_vt->get_console_width(); + int info, bar; + info = 18; + bar = 40; + + if (m_IsEmptyLine) + { + std::string str(width, ' '); + std::cout << str; + return; + } + if (width <= bar + info + 3) + { + std::string str; + + str += get_print_dev_string(); + + str += g_wait[(g_wait_index++) & 0x3]; + + print_oneline(str); + return; + } + else + { + std::string str; + str += get_print_dev_string(); + + str.resize(info, ' '); + std::cout << str; + + if (m_done || m_status) + { + std::string str; + str.resize(bar, ' '); + str[0] = '['; + str[str.size() - 1] = ']'; + std::string err; + if (m_status) + { + err = uuu_get_last_err_string(); + err.resize(bar - 2, ' '); + str.replace(1, err.size(), err); + str.insert(1, g_vt->red); + str.insert(1 + strlen(g_vt->red) + err.size(), g_vt->default_fg); + } + else + { + str.replace(1, 4, "Done"); + str.insert(1, g_vt->green); + str.insert(1 + strlen(g_vt->green) + strlen("Done"), g_vt->default_fg); + } + std::cout << str; + } + else { + std::cout << build_process_bar(bar, m_trans_pos, m_trans_size); + } + std::cout << " "; + print_auto_scroll(m_cmd, width - bar - info - 1, m_start_pos); + + if (clock() - m_start_time > CLOCKS_PER_SEC / 4) + { + m_start_pos++; + m_start_time = clock(); + } + std::cout << std::endl; + + return; + } + } +}; + +static std::map g_map_path_nt; +static std::mutex g_callback_mutex; + +static ShowNotify Summary(std::map* np) +{ + ShowNotify sn; + for (auto it = np->begin(); it != np->end(); it++) + { + if (it->second.m_dev == "Prep") + { + sn.m_trans_size += it->second.m_trans_size; + sn.m_trans_pos += it->second.m_trans_pos; + } + else + { + if (it->second.m_trans_pos || it->second.m_cmd_index) + start_usb_transfer = true; // Hidden HTTP download when USB start transfer + } + } + + if (start_usb_transfer) + sn.m_IsEmptyLine = true; // Hidden HTTP download when USB start transfer + + sn.m_dev = "Prep"; + sn.m_cmd = "Http Download\\Uncompress"; + return sn; +} + +static int update_progress(uuu_notify nt, void* p) +{ + std::map* np = (std::map*)p; + std::map::iterator it; + + std::lock_guard lock(g_callback_mutex); + + if ((*np)[nt.id].update(nt)) + { + if (!(*np)[nt.id].m_dev.empty()) + if ((*np)[nt.id].m_dev != "Prep") + g_map_path_nt[(*np)[nt.id].m_dev] = (*np)[nt.id]; + + if (g_verbose) + { + if ((*np)[nt.id].m_dev == "Prep") + Summary(np).print(g_verbose, &nt); + else + (*np)[nt.id].print(g_verbose, &nt); + } + else + { + std::string str; + format(str, "\rSuccess %d Failure %d ", g_overall_okay, g_overall_failure); + + if (g_map_path_nt.empty()) + str += "Waiting for device..."; + + if (!usb_path_filter.empty()) + { + str += " at path "; + for (size_t i = 0; i < usb_path_filter.size(); i++) + str += usb_path_filter[i] + " "; + } + + if (!usb_serial_no_filter.empty()) + { + str += " at serial_no "; + for (auto it : usb_serial_no_filter) + str += it + "*"; + } + + print_oneline(str); + print_oneline(""); + if ((*np)[nt.id].m_dev == "Prep" && !start_usb_transfer) + { + Summary(np).print(); + } + else + print_oneline(""); + + for (it = g_map_path_nt.begin(); it != g_map_path_nt.end(); it++) + it->second.print(); + + for (size_t i = 0; i < g_map_path_nt.size() + 3; i++) + std::cout << "\x1B[1F"; + + } + + //(*np)[nt.id] = g_map_path_nt[(*np)[nt.id].m_dev]; + } + + if (nt.type == uuu_notify::NOTIFY_THREAD_EXIT) + { + if (np->find(nt.id) != np->end()) + np->erase(nt.id); + } + return EXIT_SUCCESS; +} \ No newline at end of file diff --git a/uuu/uuu.cpp b/uuu/uuu.cpp index 899b2797..bd6dffb6 100644 --- a/uuu/uuu.cpp +++ b/uuu/uuu.cpp @@ -29,100 +29,56 @@ * */ -#include +#include "buildincmd.h" +#include "logger.h" +#include "progress.h" + +#include "../libuuu/string_man.h" + +#include #include -#include -#include -#include + +#include +#include #include -#include -#include #include -#include -#include -#include -#include -#include -#include "buildincmd.h" #include #include +#include +#include -#include "../libuuu/libuuu.h" - -const char * g_vt_yellow = "\x1B[93m"; -const char * g_vt_default = "\x1B[0m"; -const char * g_vt_green = "\x1B[92m"; -const char * g_vt_red = "\x1B[91m"; -const char * g_vt_kcyn = "\x1B[36m"; -const char * g_vt_boldwhite = "\x1B[97m"; - -void clean_vt_color() noexcept -{ - g_vt_yellow = ""; - g_vt_default = g_vt_yellow; - g_vt_green = g_vt_yellow; - g_vt_red = g_vt_yellow; - g_vt_kcyn = g_vt_yellow; - g_vt_boldwhite = g_vt_yellow; -} +int auto_complete(int argc, char **argv); +void print_autocomplete_help(); using namespace std; -int get_console_width(); -void print_oneline(string str); -int auto_complete(int argc, char**argv); -void print_autocomplete_help(); +int g_verbose = 0; +bmap_mode g_bmap_mode = bmap_mode::Default; +std::shared_ptr g_vt = std::make_shared(); -char g_sample_cmd_list[] = { +static Logger logger; +static char sample_cmd_list[] = { #include "uuu.clst" }; -vector g_usb_path_filter; -vector g_usb_serial_no_filter; - -int g_verbose = 0; -static bool g_start_usb_transfer; - -bmap_mode g_bmap_mode = bmap_mode::Default; - -class AutoCursor +/** + * @brief Exits after outputting feedback about interrupt + */ +static void interrupt(int) { -public: - ~AutoCursor() + // not sure what this does, but maybe it ensures that output color is normal { - printf("\x1b[?25h\n"); + AutoCursor a; } -}; -void ctrl_c_handle(int) -{ - do { - AutoCursor a; - } while(0); + // move cursor below status output area + printf("\n\n\n"); + + printf("INTERRUPTED\n"); exit(1); } -class string_ex : public std::string -{ -public: - int format(const char *fmt, ...) - { - va_list args; - va_start(args, fmt); - size_t len = std::vsnprintf(NULL, 0, fmt, args); - va_end(args); - - this->resize(len); - - va_start(args, fmt); - std::vsnprintf((char*)c_str(), len + 1, fmt, args); - va_end(args); - - return EXIT_SUCCESS; - } -}; - #ifdef _WIN32 #include #else @@ -130,7 +86,7 @@ class string_ex : public std::string #include #endif -int ask_passwd(char* prompt, char user[MAX_USER_LEN], char passwd[MAX_USER_LEN]) +static int ask_passwd(char* prompt, char user[MAX_USER_LEN], char passwd[MAX_USER_LEN]) { cout << endl << prompt << " Required Login"<= 7) @@ -279,14 +233,14 @@ int print_cfg(const char *pro, const char * chip, const char * /*compatible*/, u return EXIT_SUCCESS; } -void print_protocol_support_help() { +static void print_protocol_support_info() { printf("Protocol support for devices:\n"); printf("\tPctl\t Chip\t\t Vid\t Pid\t BcdVersion\t Serial_No\n"); printf("\t==================================================\n"); uuu_for_each_cfg(print_cfg, NULL); } -int print_udev_rule(const char * /*pro*/, const char * /*chip*/, const char * /*compatible*/, +static int print_udev_rule(const char * /*pro*/, const char * /*chip*/, const char * /*compatible*/, uint16_t vid, uint16_t pid, uint16_t /*bcdmin*/, uint16_t /*bcdmax*/, void * /*p*/) { printf("SUBSYSTEM==\"usb\", ATTRS{idVendor}==\"%04x\", ATTRS{idProduct}==\"%04x\", TAG+=\"uaccess\"\n", @@ -294,563 +248,76 @@ int print_udev_rule(const char * /*pro*/, const char * /*chip*/, const char * /* return EXIT_SUCCESS; } -int polling_usb(std::atomic& bexit); - -int g_overall_status; -int g_overall_okay; -int g_overall_failure; -char g_wait[] = "|/-\\"; -int g_wait_index; - - -string build_process_bar(size_t width, size_t pos, size_t total) +static void print_usb_filter() { - string str; - str.resize(width, ' '); - str[0] = '['; - str[width - 1] = ']'; - - if (total == 0) - { - if (pos == 0) - return str; - - string_ex loc; - size_t s = pos / (1024 * 1024); - loc.format("%dM", s); - str.replace(1, loc.size(), loc); - return str; - } - - size_t i; - - if (pos > total) - pos = total; - - for (i = 1; i < (width-2) * pos / total; i++) + if (!usb_path_filter.empty()) { - str[i] = '='; + cout << " at path "; + for (size_t i = 0; i < usb_path_filter.size(); i++) + cout << usb_path_filter[i] << " "; } - - if (i > 1) - str[i] = '>'; - - if (pos == total) - str[str.size() - 2] = '='; - - string_ex per; - per.format("%d%%", pos * 100 / total); - - size_t start = (width - per.size()) / 2; - str.replace(start, per.size(), per); - str.insert(start, g_vt_yellow); - str.insert(start + per.size() + strlen(g_vt_yellow), g_vt_default); - return str; } -void print_auto_scroll(string str, size_t len, size_t start) -{ - if (str.size() <= len) - { - str.resize(len, ' '); - cout << str; - return; - } - - if(str.size()) - start = start % str.size(); - else - start = 0; - - string s = str.substr(start, len); - s.resize(len, ' '); - cout << s; -} -class ShowNotify +static void proces_interactive_commands() { -public: - string m_cmd; - string m_dev; - size_t m_trans_pos = 0; - int m_status = 0; - size_t m_cmd_total = 0; - size_t m_cmd_index = 0; - string m_last_err; - int m_done = 0; - size_t m_start_pos = 0; - size_t m_trans_size = 0; - clock_t m_start_time; - uint64_t m_cmd_start_time; - uint64_t m_cmd_end_time; - bool m_IsEmptyLine = false; - - ShowNotify() : m_start_time{clock()} {} - - bool update(uuu_notify nt) - { - if (nt.type == uuu_notify::NOTIFY_DEV_ATTACH) - { - m_dev = nt.str; - m_done = 0; - m_status = 0; - } - if (nt.type == uuu_notify::NOTIFY_CMD_START) - { - m_start_pos = 0; - m_cmd = nt.str; - m_cmd_start_time = nt.timestamp; - } - if (nt.type == uuu_notify::NOTIFY_DECOMPRESS_START) - { - m_start_pos = 0; - m_cmd = nt.str; - m_cmd_start_time = nt.timestamp; - m_dev = "Prep"; - } - if (nt.type == uuu_notify::NOTIFY_DOWNLOAD_START) - { - m_start_pos = 0; - m_cmd = nt.str; - m_cmd_start_time = nt.timestamp; - m_dev = "Prep"; - } - if (nt.type == uuu_notify::NOTIFY_DOWNLOAD_END) - { - m_IsEmptyLine = true; - } - if (nt.type == uuu_notify::NOTIFY_TRANS_SIZE || nt.type == uuu_notify::NOTIFY_DECOMPRESS_SIZE) - { - m_trans_size = nt.total; - return false; - } - if (nt.type == uuu_notify::NOTIFY_CMD_TOTAL) - { - m_cmd_total = nt.total; - return false; - } - if (nt.type == uuu_notify::NOTIFY_CMD_INDEX) - { - m_cmd_index = nt.index; - return false; - } - if (nt.type == uuu_notify::NOTIFY_DONE) - { - if (m_status) - g_overall_failure++; - else - g_overall_okay++; - - m_done = 1; - } - if (nt.type == uuu_notify::NOTIFY_CMD_END) - { - m_cmd_end_time = nt.timestamp; - if(nt.status) - { - g_overall_status = nt.status; - m_last_err = uuu_get_last_err_string(); - } - m_status |= nt.status; - if (m_status) - g_overall_failure++; - } - if (nt.type == uuu_notify::NOTIFY_TRANS_POS || nt.type == uuu_notify::NOTIFY_DECOMPRESS_POS) - { - if (m_trans_size == 0) { - - m_trans_pos = nt.index; - return true; - } - - if ((nt.index - m_trans_pos) < (m_trans_size / 100) - && nt.index != m_trans_size) - return false; - - m_trans_pos = nt.index; - } - - return true; - } - void print_verbose(uuu_notify*nt) - { - if (this->m_dev == "Prep" && g_start_usb_transfer) - return; + int uboot_cmd = 0; + string prompt = "U>"; - if (nt->type == uuu_notify::NOTIFY_DEV_ATTACH) + cout << "Please input command: " << endl; + string cmd; + ofstream log("uuu.inputlog", ofstream::binary); + log << "uuu_version " + << ((uuu_get_version() & 0xFF000000) >> 24) + << "." + << ((uuu_get_version() & 0xFFF000) >> 12) + << "." + << ((uuu_get_version() & 0xFFF)) + << endl; + while (1) + { + cout << prompt; + getline(cin, cmd); + + if (cmd == "uboot") { - cout << "New USB Device Attached at " << nt->str << endl; + uboot_cmd = 1; + prompt = "=>"; + cout << "Enter into u-boot cmd mode" << endl; + cout << "Okay" << endl; } - if (nt->type == uuu_notify::NOTIFY_CMD_START) + else if (cmd == "exit" && uboot_cmd == 1) { - cout << m_dev << ">" << "Start Cmd:" << nt->str << endl; + uboot_cmd = 0; + prompt = "U>"; + cout << "Exit u-boot cmd mode" << endl; + cout << "Okay" << endl; } - if (nt->type == uuu_notify::NOTIFY_CMD_END) + else if (cmd == "help" || cmd == "?") { - double diff = m_cmd_end_time - m_cmd_start_time; - diff /= 1000; - if (nt->status) - { - cout << m_dev << ">" << g_vt_red <<"Fail " << uuu_get_last_err_string() << "("<< std::setprecision(4) << diff << "s)" << g_vt_default << endl; - } - else - { - cout << m_dev << ">" << g_vt_green << "Okay ("<< std::setprecision(4) << diff << "s)" << g_vt_default << endl; - } + print_cli_help(); } - - if (nt->type == uuu_notify::NOTIFY_TRANS_POS || nt->type == uuu_notify::NOTIFY_DECOMPRESS_POS) - { - if (m_trans_size) - cout << g_vt_yellow << "\r" << m_trans_pos * 100 / m_trans_size <<"%" << g_vt_default; - else - cout << "\r" << m_trans_pos; - - cout.flush(); - } - - if (nt->type == uuu_notify::NOTIFY_CMD_INFO) - cout << nt->str; - - if (nt->type == uuu_notify::NOTIFY_WAIT_FOR) - cout << "\r" << nt->str << " "<< g_wait[((g_wait_index++) & 0x3)]; - - if (nt->type == uuu_notify::NOTIFY_DECOMPRESS_START) - cout << "Decompress file:" << nt->str << endl; - - if (nt->type == uuu_notify::NOTIFY_DOWNLOAD_START) - cout << "Download file:" << nt->str << endl; - - } - void print(int verbose = 0, uuu_notify*nt=NULL) - { - verbose ? print_verbose(nt) : print_simple(); - } - string get_print_dev_string() - { - string str; - str = m_dev; - str.resize(12, ' '); - - string_ex s; - s.format("%2d/%2d", m_cmd_index+1, m_cmd_total); - - str += s; - return str; - } - void print_simple() - { - int width = get_console_width(); - int info, bar; - info = 18; - bar = 40; - - if (m_IsEmptyLine) + else if (cmd == "q" || cmd == "quit") { - string str(width, ' '); - cout << str; return; } - if (width <= bar + info + 3) - { - string_ex str; - - str += get_print_dev_string(); - - str += g_wait[(g_wait_index++) & 0x3]; - - print_oneline(str); - return ; - } - else - { - string_ex str; - str += get_print_dev_string(); - - str.resize(info, ' '); - cout << str; - - if (m_done || m_status) - { - string str; - str.resize(bar, ' '); - str[0] = '['; - str[str.size() - 1] = ']'; - string err; - if (m_status) - { - err = uuu_get_last_err_string(); - err.resize(bar - 2, ' '); - str.replace(1, err.size(), err); - str.insert(1, g_vt_red); - str.insert(1 + strlen(g_vt_red) + err.size(), g_vt_default); - } - else - { - str.replace(1, 4, "Done"); - str.insert(1, g_vt_green); - str.insert(1 + strlen(g_vt_green) + strlen("Done"), g_vt_default); - } - cout << str; - } else { - cout << build_process_bar(bar, m_trans_pos, m_trans_size); - } - cout << " "; - print_auto_scroll(m_cmd, width - bar - info-1, m_start_pos); - -if (clock() - m_start_time > CLOCKS_PER_SEC / 4) -{ - m_start_pos++; - m_start_time = clock(); -} -cout << endl; - -return; - } - } -}; - -static map g_map_path_nt; -mutex g_callback_mutex; - -void print_oneline(string str) -{ - size_t w = get_console_width(); - if (w <= 3) - return; - - if (str.size() >= w) - { - str.resize(w - 1); - str[str.size() - 1] = '.'; - str[str.size() - 2] = '.'; - str[str.size() - 3] = '.'; - } - else - { - str.resize(w, ' '); - } - cout << str << endl; - -} - -ShowNotify Summary(map *np) -{ - ShowNotify sn; - for (auto it = np->begin(); it != np->end(); it++) - { - if (it->second.m_dev == "Prep") - { - sn.m_trans_size += it->second.m_trans_size; - sn.m_trans_pos += it->second.m_trans_pos; - } else { - if (it->second.m_trans_pos || it->second.m_cmd_index) - g_start_usb_transfer = true; // Hidden HTTP download when USB start transfer - } - } - - if(g_start_usb_transfer) - sn.m_IsEmptyLine = true; // Hidden HTTP download when USB start transfer - - sn.m_dev = "Prep"; - sn.m_cmd = "Http Download\\Uncompress"; - return sn; -} - -int progress(uuu_notify nt, void *p) -{ - map *np = (map*)p; - map::iterator it; - - std::lock_guard lock(g_callback_mutex); + log << cmd << endl; + log.flush(); - if ((*np)[nt.id].update(nt)) - { - if (!(*np)[nt.id].m_dev.empty()) - if ((*np)[nt.id].m_dev != "Prep") - g_map_path_nt[(*np)[nt.id].m_dev] = (*np)[nt.id]; + if (uboot_cmd) + cmd = "fb: ucmd " + cmd; - if (g_verbose) - { - if((*np)[nt.id].m_dev == "Prep") - Summary(np).print(g_verbose, &nt); + int ret = uuu_run_cmd(cmd.c_str(), 0); + if (ret) + cout << uuu_get_last_err_string() << endl; else - (*np)[nt.id].print(g_verbose, &nt); - } - else - { - string_ex str; - str.format("\rSuccess %d Failure %d ", g_overall_okay, g_overall_failure); - - if (g_map_path_nt.empty()) - str += "Waiting for device..."; - - if (!g_usb_path_filter.empty()) - { - str += " at path "; - for (size_t i = 0; i < g_usb_path_filter.size(); i++) - str += g_usb_path_filter[i] + " "; - } - - if (!g_usb_serial_no_filter.empty()) - { - str += " at serial_no "; - for (auto it: g_usb_serial_no_filter) - str += it + "*"; - } - - print_oneline(str); - print_oneline(""); - if ((*np)[nt.id].m_dev == "Prep" && !g_start_usb_transfer) - { - Summary(np).print(); - }else - print_oneline(""); - - for (it = g_map_path_nt.begin(); it != g_map_path_nt.end(); it++) - it->second.print(); - - for (size_t i = 0; i < g_map_path_nt.size() + 3; i++) - cout << "\x1B[1F"; - - } - - //(*np)[nt.id] = g_map_path_nt[(*np)[nt.id].m_dev]; - } - - if (nt.type == uuu_notify::NOTIFY_THREAD_EXIT) - { - if(np->find(nt.id) != np->end()) - np->erase(nt.id); - } - return EXIT_SUCCESS; -} -#ifdef _MSC_VER - -#define DEFINE_CONSOLEV2_PROPERTIES -#include -#include -#include - -bool enable_vt_mode() -{ - // Set output mode to handle virtual terminal sequences - HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE); - if (hOut == INVALID_HANDLE_VALUE) - { - clean_vt_color(); - return false; - } - - DWORD dwMode = 0; - if (!GetConsoleMode(hOut, &dwMode)) - { - clean_vt_color(); - return false; - } - - dwMode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING; - if (!SetConsoleMode(hOut, dwMode)) - { - clean_vt_color(); - return false; - } - return true; -} - -int get_console_width() -{ - CONSOLE_SCREEN_BUFFER_INFO sbInfo; - GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &sbInfo); - return sbInfo.dwSize.X; -} -#else -#include -bool enable_vt_mode() { return true; } -int get_console_width() -{ - struct winsize w; - ioctl(0, TIOCGWINSZ, &w); - return w.ws_col; -} -#endif -void print_usb_filter() -{ - if (!g_usb_path_filter.empty()) - { - cout << " at path "; - for (size_t i = 0; i < g_usb_path_filter.size(); i++) - cout << g_usb_path_filter[i] << " "; - } -} - -int runshell(int shell) -{ - int uboot_cmd = 0; - string prompt = "U>"; - - if (shell) - { - cout << "Please input command: " << endl; - string cmd; - ofstream log("uuu.inputlog", ofstream::binary); - log << "uuu_version " - << ((uuu_get_version() & 0xFF000000) >> 24) - << "." - << ((uuu_get_version() & 0xFFF000) >> 12) - << "." - << ((uuu_get_version() & 0xFFF)) - << endl; - while (1) - { - cout << prompt; - getline(cin, cmd); - - if (cmd == "uboot") - { - uboot_cmd = 1; - prompt = "=>"; - cout << "Enter into u-boot cmd mode" << endl; cout << "Okay" << endl; - } - else if (cmd == "exit" && uboot_cmd == 1) - { - uboot_cmd = 0; - prompt = "U>"; - cout << "Exit u-boot cmd mode" << endl; - cout << "Okay" << endl; - } - else if (cmd == "help" || cmd == "?") - { - print_cli_help(); - } - else if (cmd == "q" || cmd == "quit") - { - return EXIT_SUCCESS; - } - else - { - log << cmd << endl; - log.flush(); - - if (uboot_cmd) - cmd = "fb: ucmd " + cmd; - - int ret = uuu_run_cmd(cmd.c_str(), 0); - if (ret) - cout << uuu_get_last_err_string() << endl; - else - cout << "Okay" << endl; - } } - return EXIT_SUCCESS; } - - return EXIT_FAILURE; } -void print_udev() +static void print_udev_info() { uuu_for_each_cfg(print_udev_rule, NULL); @@ -861,24 +328,24 @@ void print_udev() "Note: These instructions output to standard error so are excluded" << endl << endl; } -int print_usb_device(const char *path, const char *chip, const char *pro, uint16_t vid, uint16_t pid, uint16_t bcd, const char *serial_no, void * /*p*/) +static int print_device_info(const char *path, const char *chip, const char *pro, uint16_t vid, uint16_t pid, uint16_t bcd, const char *serial_no, void * /*p*/) { printf("\t%s\t %s\t %s\t 0x%04X\t0x%04X\t 0x%04X\t %s\n", path, chip, pro, vid, pid, bcd, serial_no); return EXIT_SUCCESS; } -void print_lsusb() +static void print_device_list() { - cout << "Connected Known USB Devices\n"; + cout << "Connected devices\n"; printf("\tPath\t Chip\t Pro\t Vid\t Pid\t BcdVersion\t Serial_no\n"); printf("\t====================================================================\n"); - uuu_for_each_devices(print_usb_device, NULL); + uuu_for_each_devices(print_device_info, NULL); } #ifdef WIN32 -int ignore_serial_number(const char *pro, const char *chip, const char */*comp*/, uint16_t vid, uint16_t pid, uint16_t /*bcdlow*/, uint16_t /*bcdhigh*/, void */*p*/) +static int ignore_serial_number(const char *pro, const char *chip, const char */*comp*/, uint16_t vid, uint16_t pid, uint16_t /*bcdlow*/, uint16_t /*bcdhigh*/, void */*p*/) { printf("\t %s\t %s\t 0x%04X\t0x%04X\n", chip, pro, vid, pid); @@ -895,81 +362,89 @@ int ignore_serial_number(const char *pro, const char *chip, const char */*comp*/ printf("Set key failure, try run as administrator permission\n"); return EXIT_FAILURE; } -#endif -int set_ignore_serial_number() +static int set_ignore_serial_number() { -#ifndef WIN32 - printf("Only windows system need set ignore serial number registry"); - return EXIT_FAILURE; -#else - printf("Set window registry to ignore usb hardware serial number for known uuu device:\n"); + printf("Modifying registry to ignore serial number for finding devices...\n"); return uuu_for_each_cfg(ignore_serial_number, NULL); -#endif } -void log_error(const string& message) { - cerr << "Error: " << message << endl; +#define os_putenv _putenv + +#else + +#define os_putenv putenv + +#endif + +static int set_environment_variable(const string& key_and_value) +{ + if (os_putenv(key_and_value.c_str())) + { + logger.log_error("Failed to set environment variable with expression '" + key_and_value + "'. Hint: parameter must have the form: key=value"); + return EXIT_FAILURE; + } + return EXIT_SUCCESS; } -void log_syntax_error(const string& message) { - log_error(message); - print_cli_help(); +static void print_script(const BuiltInScript& script) { + string text = script.m_text; + while (text.size() > 0 && (text[0] == '\n' || text[0] == ' ')) + text = text.erase(0, 1); + cout << text; } int main(int argc, char **argv) { - // commented out causes failure when pass script file name/path as first arg plus -v after + // commented out since causes failure when pass script file name/path as first arg plus -v after //if (auto_complete(argc, argv) == 0) return EXIT_SUCCESS; + std::unique_ptr auto_cursor; + if (g_vt->enable()) + { + logger.is_color_output_enabled = true; + auto_cursor = std::make_unique(); + } + else + { + // [why enable verbose in this case?] + cout << "Warning: Console doesn't support VT mode; enabling verbose feedback" << endl; + g_verbose = 1; + } + // handle modes that should _not_ print the app title if (argc >= 2) { - string s = argv[1]; - if(s == "-udev") + string arg = argv[1]; + if(arg == "-udev") { - print_udev(); + print_udev_info(); return EXIT_SUCCESS; } - if (s == "-cat-builtin") + if (arg == "-cat-builtin") { if (2 == argc) { - fprintf(stderr, "Error: Missing built-in script name; options: "); - g_BuildScripts.ShowCmds(stderr); - fprintf(stderr, "\n"); + print_syntax_error("Missing built-in script name; options: " + g_ScriptCatalog.get_names()); return EXIT_FAILURE; } - if (g_BuildScripts.find(argv[2]) == g_BuildScripts.end()) + string script_name = argv[2]; + auto item = g_ScriptCatalog.find(script_name); + if (item == g_ScriptCatalog.end()) { - fprintf(stderr, "Error: Unknown built-in script name; options: "); - g_BuildScripts.ShowCmds(stderr); - fprintf(stderr, "\n"); + print_syntax_error("Unknown built-in script '" + script_name + "'; options: " + g_ScriptCatalog.get_names()); return EXIT_FAILURE; } - string str = g_BuildScripts[argv[2]].m_text; - while (str.size() > 0 && (str[0] == '\n' || str[0] == ' ')) - str = str.erase(0,1); - printf("%s", str.c_str()); + print_script(item->second); return EXIT_SUCCESS; } } - AutoCursor a; - print_app_title(); - if (!enable_vt_mode()) - { - // [why enable verbose in this case?] - cout << "Warning: Console doesn't support VT mode; enabling verbose feedback" << endl; - g_verbose = 1; - } - if (argc == 1) { - log_error("Invalid input"); - print_cli_help(); + print_syntax_error("Missing arguments"); return EXIT_FAILURE; } @@ -978,7 +453,8 @@ int main(int argc, char **argv) int dryrun = 0; string input_path; string protocol_cmd; - string cmd_script; + string script_spec; + string script_text; for (int i = 1; i < argc; i++) { @@ -1031,39 +507,39 @@ int main(int argc, char **argv) } else if (arg == "-h-protocol-support") { - print_protocol_support_help(); + print_protocol_support_info(); return EXIT_SUCCESS; } else if (arg == "-ls-builtin") { - print_script_directory(); + print_script_catalog(); return EXIT_SUCCESS; } else if (arg == "-m") { if (++i >= argc) { - log_syntax_error("Missing USB path argument"); + print_syntax_error("Missing USB path argument"); return EXIT_FAILURE; } uuu_add_usbpath_filter(argv[i]); - g_usb_path_filter.push_back(argv[i]); + usb_path_filter.push_back(argv[i]); } else if (arg == "-ms") { if (++i >= argc) { - log_syntax_error("Missing serial # argument"); + print_syntax_error("Missing serial # argument"); return EXIT_FAILURE; } uuu_add_usbserial_no_filter(argv[i]); - g_usb_serial_no_filter.push_back(argv[i]); + usb_serial_no_filter.push_back(argv[i]); } else if (arg == "-t") { if (++i >= argc) { - log_syntax_error("Missing seconds argument"); + print_syntax_error("Missing seconds argument"); return EXIT_FAILURE; } uuu_set_wait_timeout(atoll(argv[i])); @@ -1072,7 +548,7 @@ int main(int argc, char **argv) { if (++i >= argc) { - log_syntax_error("Missing seconds argument"); + print_syntax_error("Missing seconds argument"); return EXIT_FAILURE; } uuu_set_wait_next_timeout(atoll(argv[i])); @@ -1081,20 +557,22 @@ int main(int argc, char **argv) { if (++i >= argc) { - log_syntax_error("Missing milliseconds argument"); + print_syntax_error("Missing milliseconds argument"); return EXIT_FAILURE; } uuu_set_poll_period(atoll(argv[i])); } else if (arg == "-ls-devices") { - print_lsusb(); + print_device_list(); return EXIT_SUCCESS; } + #ifdef WIN32 else if (arg == "-IgSerNum") { return set_ignore_serial_number(); } + #endif else if (arg == "-bmap") { g_bmap_mode = bmap_mode::Force; @@ -1105,25 +583,18 @@ int main(int argc, char **argv) } else if (arg == "-e") { -#ifndef WIN32 - #define _putenv putenv -#endif if (++i >= argc) { - log_syntax_error("Missing key=value argument"); - return EXIT_FAILURE; - } - if (_putenv(argv[i])) - { - printf("Error: Failed to set '%s'. Hint: parameter must have the form key=value\n", argv[i]); + print_syntax_error("Missing key=value argument"); return EXIT_FAILURE; } + return set_environment_variable(argv[i]); } else if (arg == "-b" || arg == "-brun") { - if (i + 1 == argc) + if (++i >= argc) { - log_syntax_error("Missing path or built-in script name"); + print_syntax_error("Missing path or built-in script name"); return EXIT_FAILURE; } @@ -1139,43 +610,34 @@ int main(int argc, char **argv) args.push_back(s); } - // if script name is not built-in, try to look for a file - if (g_BuildScripts.find(argv[i + 1]) == g_BuildScripts.end()) { - const string tmpCmdFileName{argv[i + 1]}; - - std::ifstream t(tmpCmdFileName); - std::string fileContents((std::istreambuf_iterator(t)), - std::istreambuf_iterator()); - - if (fileContents.empty()) { - printf("%s is not built-in script or fail load external script file", tmpCmdFileName.c_str()); + string name = argv[i]; + auto script = g_ScriptCatalog.find(name); + if (script == g_ScriptCatalog.end()) { + script_spec = "(custom)"; + if (!g_ScriptCatalog.add_from_file(name)) + { + logger.log_error("Unable to load script from file: " + name); return EXIT_FAILURE; } - - BuiltInScriptRawData tmpCmd{ - tmpCmdFileName.c_str(), - fileContents.c_str(), - "Script loaded from file" - }; - - g_BuildScripts.emplace(tmpCmdFileName, &tmpCmd); - - cmd_script = g_BuildScripts[tmpCmdFileName].replace_script_args(args); } - else { - cmd_script = g_BuildScripts[argv[i + 1]].replace_script_args(args); + else + { + script_spec = "(built-in)"; } + script_spec += name; + script_text = g_ScriptCatalog[name].replace_script_args(args); break; } else { - cout << "Error: Unknown option: " << arg << endl; - print_cli_help(); + print_syntax_error("Unknown option: " + arg); return EXIT_FAILURE; } - }else if (!arg.empty() && arg[arg.size() - 1] == ':') + } + else if (!arg.empty() && arg[arg.size() - 1] == ':') { - // looks like a protocol command + // treat as a protocol command + for (int j = i; j < argc; j++) { arg = argv[j]; @@ -1185,7 +647,7 @@ int main(int argc, char **argv) arg.insert(arg.end(), '"'); } protocol_cmd.append(arg); - if(j != (argc -1)) /* Don't add space at last arg */ + if (j != (argc -1)) // don't add space after last arg protocol_cmd.append(" "); } break; @@ -1193,42 +655,39 @@ int main(int argc, char **argv) else { // treat as a file system path - if (!input_path.empty()) + + if (argc - 1 > i) { - printf("Error: Too many path arguments - %s\n", arg.c_str()); + print_syntax_error("Too many arguments - " + string(argv[i + 1]) + "; Hint: use -b to pass parameters to a script"); return EXIT_FAILURE; } input_path = arg; } } - signal(SIGINT, ctrl_c_handle); - - uuu_set_askpasswd(ask_passwd); - if (deamon && shell) { - log_error("Can't use deamon (-d) and shell (-s) together"); + logger.log_error("Incompatible options: deamon (-d) and shell (-s)"); return EXIT_FAILURE; } if (deamon && dryrun) { - log_error("Can't use deamon (-d) and dry-run (-dry) together"); + logger.log_error("Incompatible options: deamon (-d) and dry-run (-dry)"); return EXIT_FAILURE; } if (shell && dryrun) { - log_error("Error: Can't use shell (-s) and dry-run (-dry) together"); + logger.log_error("Incompatible options: shell (-s) and dry-run (-dry)"); return EXIT_FAILURE; } if (g_verbose) { - // commented out since seems overkill since can jprint it via -cat-builtin + // commented out since can print script content via -cat-builtin; print here is noise //if (!cmd_script.empty()) - // printf("\n%sRunning built-in script:%s\n %s\n\n", g_vt_boldwhite, g_vt_default, cmd_script.c_str()); + // printf("\n%sRunning built-in script:%s\n %s\n\n", g_vt->boldwhite, g_vt->default_foreground, cmd_script.c_str()); // why not log for !shell? it's logged for !g_verbose regardless if (!shell) { @@ -1242,57 +701,78 @@ int main(int argc, char **argv) cout << "Waiting for device"; print_usb_filter(); cout << "..."; - cout << "\r"; - cout << "\x1b[?25l"; + cout << "\r"; // why is this needed? + cout << "\x1b[?25l"; // what does this do? cout.flush(); } - map nt_session; + signal(SIGINT, interrupt); - uuu_register_notify_callback(progress, &nt_session); + uuu_set_askpasswd(ask_passwd); + map nt_session; + uuu_register_notify_callback(update_progress, &nt_session); - if (!protocol_cmd.empty()) + if (shell) { + proces_interactive_commands(); + return EXIT_SUCCESS; + } + else if (!protocol_cmd.empty()) + { + logger.log_verbose("Executing single command: " + protocol_cmd); int ret = uuu_run_cmd(protocol_cmd.c_str(), dryrun); + // what is the purpose of printing blank lines? Don't know about success, but on error, there are several blank lines on screen for (size_t i = 0; i < g_map_path_nt.size()+3; i++) printf("\n"); - if(ret) - printf("Error: %s\n", uuu_get_last_err_string()); - else - printf("Okay\n"); - runshell(shell); - return ret; - } + if (ret) + { + logger.log_error(uuu_get_last_err_string()); + return EXIT_FAILURE; + } - { - int ret; - if (!cmd_script.empty()) - ret = uuu_run_cmd_script(cmd_script.c_str(), dryrun); - else - ret = uuu_auto_detect_file(input_path.c_str()); + logger.log_info("Command succeeded :)"); - if (ret) + if (shell) proces_interactive_commands(); + + return EXIT_SUCCESS; + } + else if (!script_text.empty()) + { + if (script_spec.empty()) { - ret = runshell(shell); - if (ret) - cout << g_vt_red << "\nError: " << g_vt_default << uuu_get_last_err_string(); - return ret; + logger.log_internal_error("Expected non-empty script_spec"); + return EXIT_FAILURE; + } + logger.log_verbose("Running command script: " + script_spec); + if (uuu_run_cmd_script(script_text.c_str(), dryrun)) + { + logger.log_error(uuu_get_last_err_string()); + return EXIT_FAILURE; + } + } + else if (!input_path.empty()) + { + logger.log_verbose("Running as auto detect file: " + input_path); + if (uuu_auto_detect_file(input_path.c_str())) + { + logger.log_error(uuu_get_last_err_string()); + return EXIT_FAILURE; } } if (uuu_wait_uuu_finish(deamon, dryrun)) { - cout << g_vt_red << "\nError: " << g_vt_default << uuu_get_last_err_string(); + logger.log_error(uuu_get_last_err_string()); return EXIT_FAILURE; } - runshell(shell); - - // wait for the other thread exit, after send out CMD_DONE + // wait for the thread exit, after send out CMD_DONE std::this_thread::sleep_for(std::chrono::milliseconds(100)); - //if(!g_verbose) - // printf("\n"); - return g_overall_status; + + // move cursor below status area + if(!g_verbose) printf("\n\n\n"); + + return g_overall_status; // why return this value?? } From 76b36580a6ca354ab7ae62ba4de754d2d582aaeb Mon Sep 17 00:00:00 2001 From: SteveBroshar Date: Sat, 22 Feb 2025 07:09:58 -0600 Subject: [PATCH 03/90] Enhance script classes --- libuuu/buffer.cpp | 2 +- libuuu/cmd.cpp | 2 +- libuuu/string_man.h | 88 ++++--- msvc/uuu.vcxproj | 7 +- msvc/uuu.vcxproj.filters | 9 +- uuu/Script.cpp | 290 +++++++++++++++++++++++ uuu/Script.h | 203 ++++++++++++++++ uuu/TransferFeedback.h | 488 +++++++++++++++++++++++++++++++++++++++ uuu/autocomplete.cpp | 4 +- uuu/buildincmd.cpp | 392 ------------------------------- uuu/buildincmd.h | 119 ---------- uuu/environment.h | 92 ++++++++ uuu/progress.h | 453 ------------------------------------ uuu/uuu.cpp | 172 ++++---------- 14 files changed, 1183 insertions(+), 1138 deletions(-) create mode 100644 uuu/Script.cpp create mode 100644 uuu/Script.h create mode 100644 uuu/TransferFeedback.h delete mode 100644 uuu/buildincmd.cpp delete mode 100644 uuu/buildincmd.h create mode 100644 uuu/environment.h delete mode 100644 uuu/progress.h diff --git a/libuuu/buffer.cpp b/libuuu/buffer.cpp index 0a6f4f52..526d5d9a 100644 --- a/libuuu/buffer.cpp +++ b/libuuu/buffer.cpp @@ -1230,7 +1230,7 @@ shared_ptr get_file_buffer(string filename, bool async) else filename = g_current_dir + filename; } - replace(filename, "\\", "/"); + string_man::replace(filename, "\\", "/"); bool found; { diff --git a/libuuu/cmd.cpp b/libuuu/cmd.cpp index 0bff2202..7c0a8f24 100644 --- a/libuuu/cmd.cpp +++ b/libuuu/cmd.cpp @@ -1099,7 +1099,7 @@ int parser_cmd_list_file(shared_ptr pbuff, CmdMap *pCmdMap) int uuu_auto_detect_file(const char *path) { string fn = strip_quotes(path); - replace(fn, "\\", "/"); + string_man::replace(fn, "\\", "/"); if (fn.empty()) fn += "./"; const string clean_input_path = fn; diff --git a/libuuu/string_man.h b/libuuu/string_man.h index d1d0f206..447c8c49 100644 --- a/libuuu/string_man.h +++ b/libuuu/string_man.h @@ -4,40 +4,64 @@ #include #include +#include #include -/** - * @brief Formats like printf with output to std::string and minimal size allocation - */ -inline void format(std::string s, const char* fmt, ...) -{ - va_list args; - va_start(args, fmt); - size_t len = std::vsnprintf(NULL, 0, fmt, args); - va_end(args); - - s.resize(len); - - va_start(args, fmt); - std::vsnprintf((char*)s.c_str(), len + 1, fmt, args); - va_end(args); -} - -/** - * @brief Replaces each occurance of a substring - * @param text Input text - * @param from Substring to replace - * @param to Text to replace found substring with - * @return Reference to text (supports chaining) - */ -inline std::string& replace(std::string& text, const std::string& from, const std::string& to) { - if (!from.empty()) +namespace string_man { + + /** + * @brief Formats like printf with output to std::string and minimal size allocation + */ + inline void format(std::string s, const char* fmt, ...) + { + va_list args; + va_start(args, fmt); + size_t len = std::vsnprintf(NULL, 0, fmt, args); + va_end(args); + + s.resize(len); + + va_start(args, fmt); + std::vsnprintf((char*)s.c_str(), len + 1, fmt, args); + va_end(args); + } + + /** + * @brief Replaces each occurance of a substring + * @param text Input text + * @param from Substring to replace + * @param to Text to replace found substring with + * @return Reference to text (supports chaining) + */ + inline std::string& replace(std::string& text, const std::string& from, const std::string& to) { + if (!from.empty()) + { + size_t start_pos = 0; + while ((start_pos = text.find(from, start_pos)) != std::string::npos) { + text.replace(start_pos, from.length(), to); + start_pos += to.length(); + } + } + return text; + } + + /** + * @brief Returns the input text as uppercase + * @param text Input text + * @return Uppercase text + */ + static std::string toupper(const std::string& text) { - size_t start_pos = 0; - while ((start_pos = text.find(from, start_pos)) != std::string::npos) { - text.replace(start_pos, from.length(), to); - start_pos += to.length(); + const std::locale loc; + std::string upper; + upper.reserve(text.size()); + + for (size_t i = 0; i < text.size(); ++i) + { + upper.push_back(std::toupper(text[i], loc)); } + + return upper; } - return text; -} + +} \ No newline at end of file diff --git a/msvc/uuu.vcxproj b/msvc/uuu.vcxproj index cc96b3fe..0aca674d 100644 --- a/msvc/uuu.vcxproj +++ b/msvc/uuu.vcxproj @@ -20,13 +20,14 @@ - + - + + - + diff --git a/msvc/uuu.vcxproj.filters b/msvc/uuu.vcxproj.filters index ac713cda..42c29452 100644 --- a/msvc/uuu.vcxproj.filters +++ b/msvc/uuu.vcxproj.filters @@ -18,7 +18,7 @@ Source Files - + Source Files @@ -26,7 +26,7 @@ - + Header Files @@ -35,7 +35,10 @@ Header Files - + + Header Files + + Header Files diff --git a/uuu/Script.cpp b/uuu/Script.cpp new file mode 100644 index 00000000..daf32f5f --- /dev/null +++ b/uuu/Script.cpp @@ -0,0 +1,290 @@ +/* +* Copyright 2018-2021 NXP. +* +* Redistribution and use in source and binary forms, with or without modification, +* are permitted provided that the following conditions are met: +* +* Redistributions of source code must retain the above copyright notice, this +* list of conditions and the following disclaimer. +* +* Redistributions in binary form must reproduce the above copyright notice, this +* list of conditions and the following disclaimer in the documentation and/or +* other materials provided with the distribution. +* +* Neither the name of the NXP Semiconductor nor the names of its +* contributors may be used to endorse or promote products derived from this +* software without specific prior written permission. +* +* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +* POSSIBILITY OF SUCH DAMAGE. +* +*/ + +#include "Script.h" +#include "VtEmulation.h" + +#include "../libuuu/string_man.h" + +#include +#include + +/** + * @brief Parses characters between argument name and its description and checks if it's optional + * @param option Text between argument name and description + */ +void Script::Arg::parser(const std::string& option) +{ + const auto pos = option.find('['); + if (pos == std::string::npos) + { + return; + } + m_default_argument_value_name = option.substr(pos + 1, option.find(']') - pos - 1); + optionality = ARG_OPTION | ARG_OPTION_KEY; +} + +/** + * @brief Constructs from a config object; caches the config and parses arguments from the text + * @param config Configuation, name and desc can be null/empty, but text cannot + */ +Script::Script(const ScriptConfig& config) : + m_text{config.m_text}, + m_desc{config.m_desc ? config.m_desc : ""}, + m_name{config.m_name ? config.m_name : ""} +{ + // Regular expression to detect script argument name occurrences + static const std::regex arg_name_regexp{R"####((@| )(_\S+))####"}; + + for (std::sregex_iterator it + = std::sregex_iterator{m_text.cbegin(), m_text.cend(), arg_name_regexp}; + it != std::sregex_iterator{}; ++it) + { + const std::string arg_name{it->str(2)}; + if (!has_arg(arg_name)) + { + Arg arg; + arg.m_name = arg_name; + arg.optionality = Arg::ARG_MUST; + m_args.emplace_back(std::move(arg)); + } + } + + for (size_t i = 0; i < m_args.size(); i++) + { + std::string str; + str += "@"; + str += m_args[i].m_name; + const auto pos = m_text.find(str); + if (pos != std::string::npos) { + const auto start_descript = m_text.find('|', pos); + if (start_descript != std::string::npos) + { + m_args[i].m_desc = m_text.substr(start_descript + 1, + m_text.find('\n', start_descript) - start_descript - 1); + const std::string def{m_text.substr(pos, start_descript - pos)}; + m_args[i].parser(def); + } + } + } +} + +/** + * @brief Indicates whether the script has an argument specified by name + * @param name Argument name + */ +bool Script::has_arg(const std::string &name) const +{ + return std::any_of(m_args.cbegin(), m_args.cend(), + [&name](const Arg &arg){ return arg.m_name == name; }); +} + +/** + * @brief Replaces matching substrings plus unknown logic related to file extensions + * @param[in,out] text Input/output string + * @param[in] match Substring to be replaced + * @param[in,out] replace Text to substitute for match; oddly, this is modified + * @return Modified input string object + */ +static std::string replace_str(std::string text, const std::string& match, std::string replace) +{ + // conform replace text + { + std::string s5, s4; + std::string extensions[] = { ".BZ2", ".ZST" }; + if (replace.size() > 4) + { + if (replace[replace.size() - 1] == '\"') + { + s5 = string_man::toupper(replace.substr(replace.size() - 5)); + for (std::string it : extensions) + { + if (s5 == it) + { + replace = replace.substr(0, replace.size() - 1); + replace += "/*\""; + } + } + } + else + { + s4 = string_man::toupper(replace.substr(replace.size() - 4)); + for (std::string it : extensions) + { + if (it == s4) + { + replace += "/*"; + } + } + } + } + } + + for (size_t j = 0; (j = text.find(match, j)) != std::string::npos;) + { + if (j == 0 || (j != 0 && text[j - 1] == ' ')) + text.replace(j, match.size(), replace); + j += match.size(); + } + + return text; +} + +/** + * @brief Returns a copy of the script content with argument references replaced with values + * @param values Argument values; ordered per the arguments of the script definition + * @return Script text after replacement + * @details + * Ignores values in excess of defined argument count. + * For arguments indexed beyond the last value, if it is configured for replacement with the + * value of another argument (ARG_OPTION_KEY), the other argument occurs _before_ the target + * argument in the script definition, and the argument has a value in values, then argument + * references are replaced with the value of the other argument. + */ +std::string Script::replace_arguments(const std::vector& values) const +{ + std::string text = m_text; + for (size_t i = 0; i < values.size() && i < m_args.size(); i++) + { + text = replace_str(text, m_args[i].m_name, values[i]); + } + + // handle optional args + for (size_t i = values.size(); i < m_args.size(); i++) + { + if (m_args[i].optionality & Arg::ARG_OPTION_KEY) + { + for (size_t j = 0; j < values.size(); j++) + { + if (m_args[j].m_name == m_args[i].m_default_argument_value_name) + { + text = replace_str(text, m_args[i].m_name, values[j]); + break; + } + } + } + } + + return text; +} + +/** + * @brief Writes the script content to stdout + */ +void Script::print_content() const { + std::string text = m_text; + while (text.size() > 0 && (text[0] == '\n' || text[0] == ' ')) + text = text.erase(0, 1); + std::cout << text; +} + +/** + * @brief Writes the name, description and arguments to stdout + */ +void Script::print_definition() const +{ + printf("\t%s%s%s\t%s\n", g_vt->boldwhite, m_name.c_str(), g_vt->default_fg, m_desc.c_str()); + for (auto i = 0u; i < m_args.size(); ++i) + { + std::string desc{m_args[i].m_name}; + if (m_args[i].optionality & Arg::ARG_OPTION) + { + desc += g_vt->boldwhite; + desc += "[Optional]"; + desc += g_vt->default_fg; + } + desc += " "; + desc += m_args[i].m_desc; + printf("\t\targ%u: %s\n", i, desc.c_str()); + } +} + +static constexpr ScriptConfig builtin_script_configs[] = +{ + { + "emmc", +#include "emmc_burn_loader.clst" + ,"burn boot loader to eMMC boot partition" + }, + { + "emmc_all", +#include "emmc_burn_all.clst" + ,"burn whole image to eMMC" + }, + { + "fat_write", +#include "fat_write.clst" + ,"update one file in fat partition, require uboot fastboot running in board" + }, + { + "nand", +#include "nand_burn_loader.clst" + ,"burn boot loader to NAND flash" + }, + { + "qspi", +#include "qspi_burn_loader.clst" + ,"burn boot loader to qspi nor flash" + }, + { + "spi_nand", +#include "fspinand_burn_loader.clst" + ,"burn boot loader to spi nand flash" + }, + { + "sd", +#include "sd_burn_loader.clst" + ,"burn boot loader to sd card" + }, + { + "sd_all", +#include "sd_burn_all.clst" + ,"burn whole image to sd card" + }, + { + "spl", +#include "spl_boot.clst" + ,"boot spl and uboot" + }, + { + "nvme_all", +#include "nvme_burn_all.clst" + ,"burn whole image io nvme storage" + }, + { + nullptr, + nullptr, + nullptr, + } +}; + +//! Script catalog global instance +ScriptCatalog g_ScriptCatalog(builtin_script_configs); diff --git a/uuu/Script.h b/uuu/Script.h new file mode 100644 index 00000000..553f97e6 --- /dev/null +++ b/uuu/Script.h @@ -0,0 +1,203 @@ +/* +* Copyright 2018-2021 NXP. +* +* Redistribution and use in source and binary forms, with or without modification, +* are permitted provided that the following conditions are met: +* +* Redistributions of source code must retain the above copyright notice, this +* list of conditions and the following disclaimer. +* +* Redistributions in binary form must reproduce the above copyright notice, this +* list of conditions and the following disclaimer in the documentation and/or +* other materials provided with the distribution. +* +* Neither the name of the NXP Semiconductor nor the names of its +* contributors may be used to endorse or promote products derived from this +* software without specific prior written permission. +* +* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +* POSSIBILITY OF SUCH DAMAGE. +* +*/ + +#pragma once + +#include +#include +#include +#include + +/** + * @brief Script creation data + */ +struct ScriptConfig final +{ + //! Script name + const char *m_name = nullptr; + //! Script content + const char *m_text = nullptr; + //! Script description/documentation + const char *m_desc = nullptr; +}; + +/** + * @brief Script definition + */ +class Script final +{ + /** + * @brief Argument definition + */ + class Arg final + { + public: + enum + { + //! Required + ARG_MUST = 0x1, + //! Optional; no default + ARG_OPTION = 0x2, + //! Optional with default from another argument + ARG_OPTION_KEY = 0x4, + }; + + void parser(const std::string& option); + + //! Argument name + std::string m_name; + //! Argument description/documentation + std::string m_desc; + //! Optionality + uint32_t optionality = ARG_MUST; + //! Name of argument that has the default value if this is optional + //! and not explicitly specified + std::string m_default_argument_value_name; + }; + +public: + Script(const ScriptConfig&); + + std::string replace_arguments(const std::vector& args) const; + void print_definition() const; + void print_content() const; + + //! Script content + const std::string m_text; + //! Script description/documentation + const std::string m_desc; + //! Script name + const std::string m_name; + //! Arguments + std::vector m_args; + +private: + bool has_arg(const std::string &arg) const; +}; + +/** + * @brief Script catalog + */ +class ScriptCatalog final +{ + std::map items; + +public: + /** + * @brief Constructs from an array of config objects; terminated by an item with a null name + * @param[in] config Pointer to the first object + */ + ScriptCatalog(const ScriptConfig configs[]) + { + while (configs->m_name) + { + items.emplace(configs->m_name, *configs); + ++configs; + } + } + + /** + * @brief Returns a pointer to the script specified by name or null if not found + */ + const Script* find(const std::string& name) + { + auto item = items.find(name); + return item == items.end() ? nullptr : &item->second; + } + + /** + * @brief Loads a file as a script; adding it to the catalog + * @param path File system path + * @return Pointer to the new script object or null if unable to load + */ + const Script* add_from_file(const std::string& path) + { + std::ifstream t(path); + std::string fileContents((std::istreambuf_iterator(t)), + std::istreambuf_iterator()); + + if (fileContents.empty()) { + return nullptr; + } + + ScriptConfig script_definition{ + path.c_str(), + fileContents.c_str(), + "Script loaded from file" + }; + + auto item = items.emplace(path, script_definition); + return &item.first->second; + } + + /** + * @brief Gets a string that lists each script name separated by comma + */ + std::string get_names() const + { + std::string text; + for (const auto& item : items) + { + text += item.first + ","; + } + text.pop_back(); + return text; + } + + /** + * @brief Writes (to stdout) usage information about each script + */ + void print_usage() const + { + for (const auto& item : items) + { + item.second.print_definition(); + } + } + + /** + * @brief Prints the name of each script that matches; for use with auto-complete + * @param match Search text + * @param space Text printed after each script name + */ + void print_auto_complete(const std::string& match, const char* space = " ") const + { + for (const auto& item : items) + { + if (item.first.substr(0, match.size()) == match) + { + printf("%s%s\n", item.first.c_str(), space); + } + } + } +}; + +extern ScriptCatalog g_ScriptCatalog; diff --git a/uuu/TransferFeedback.h b/uuu/TransferFeedback.h new file mode 100644 index 00000000..9ed72347 --- /dev/null +++ b/uuu/TransferFeedback.h @@ -0,0 +1,488 @@ + +#pragma once + +#include "../libuuu/libuuu.h" +#include "../libuuu/string_man.h" + +#include + +#include +#include +#include +#include +#include + +extern int g_verbose; + +class TransferNotifyItem; + +class TransferContext final +{ +public: + std::map map_path_nt; + std::vector usb_serial_no_filter; + std::vector usb_path_filter; + bool start_usb_transfer = false; + int overall_status = 0; + int success_count = 0; + int failure_count = 0; + + void print_oneline(std::string str) const + { + size_t w = g_vt->get_console_width(); + if (w <= 3) + return; + + if (str.size() >= w) + { + str.resize(w - 1); + str[str.size() - 1] = '.'; + str[str.size() - 2] = '.'; + str[str.size() - 3] = '.'; + } + else + { + str.resize(w, ' '); + } + std::cout << str << std::endl; + } +}; + +extern TransferContext g_transfer_context; + +class TransferNotifyItem +{ + static constexpr char wait_chars[] = "|/-\\"; + int wait_index; + int m_status = 0; + size_t m_cmd_total = 0; + std::string m_last_err; + int m_done = 0; + size_t m_start_pos = 0; + clock_t m_start_time = 0; + uint64_t m_cmd_start_time = 0; + uint64_t m_cmd_end_time = 0; + + static std::string build_process_bar(size_t width, size_t pos, size_t total) + { + std::string str; + str.resize(width, ' '); + str[0] = '['; + str[width - 1] = ']'; + + if (total == 0) + { + if (pos == 0) + return str; + + std::string loc; + size_t s = pos / (1024 * 1024); + string_man::format(loc, "%dM", s); + str.replace(1, loc.size(), loc); + return str; + } + + size_t i; + + if (pos > total) + pos = total; + + for (i = 1; i < (width - 2) * pos / total; i++) + { + str[i] = '='; + } + + if (i > 1) + str[i] = '>'; + + if (pos == total) + str[str.size() - 2] = '='; + + std::string per; + string_man::format(per, "%d%%", pos * 100 / total); + + size_t start = (width - per.size()) / 2; + str.replace(start, per.size(), per); + str.insert(start, g_vt->yellow); + str.insert(start + per.size() + strlen(g_vt->yellow), g_vt->default_fg); + return str; + } + + static void print_auto_scroll(std::string str, size_t len, size_t start) + { + if (str.size() <= len) + { + str.resize(len, ' '); + std::cout << str; + return; + } + + if (str.size()) + start = start % str.size(); + else + start = 0; + + std::string s = str.substr(start, len); + s.resize(len, ' '); + std::cout << s; + } + + void render_verbose(const uuu_notify& nt) + { + if (m_dev == "Prep" && g_transfer_context.start_usb_transfer) + return; + + if (nt.type == uuu_notify::NOTIFY_DEV_ATTACH) + { + std::cout << "New USB Device Attached at " << nt.str << std::endl; + } + if (nt.type == uuu_notify::NOTIFY_CMD_START) + { + std::cout << m_dev << ">" << "Start Cmd:" << nt.str << std::endl; + } + if (nt.type == uuu_notify::NOTIFY_CMD_END) + { + double diff = m_cmd_end_time - m_cmd_start_time; + diff /= 1000; + if (nt.status) + { + std::cout << m_dev << ">" << g_vt->red << "Fail " << uuu_get_last_err_string() << "(" << std::setprecision(4) << diff << "s)" << g_vt->default_fg << std::endl; + } + else + { + std::cout << m_dev << ">" << g_vt->green << "Okay (" << std::setprecision(4) << diff << "s)" << g_vt->default_fg << std::endl; + } + } + + if (nt.type == uuu_notify::NOTIFY_TRANS_POS || nt.type == uuu_notify::NOTIFY_DECOMPRESS_POS) + { + if (m_trans_size) + std::cout << g_vt->yellow << "\r" << m_trans_pos * 100 / m_trans_size << "%" << g_vt->default_fg; + else + std::cout << "\r" << m_trans_pos; + + std::cout.flush(); + } + + if (nt.type == uuu_notify::NOTIFY_CMD_INFO) + std::cout << nt.str; + + if (nt.type == uuu_notify::NOTIFY_WAIT_FOR) + std::cout << "\r" << nt.str << " " << wait_chars[((wait_index++) & 0x3)]; + + if (nt.type == uuu_notify::NOTIFY_DECOMPRESS_START) + std::cout << "Decompress file:" << nt.str << std::endl; + + if (nt.type == uuu_notify::NOTIFY_DOWNLOAD_START) + std::cout << "Download file:" << nt.str << std::endl; + + } + + void render_simple() + { + int width = g_vt->get_console_width(); + int info, bar; + info = 18; + bar = 40; + + if (m_IsEmptyLine) + { + std::string str(width, ' '); + std::cout << str; + return; + } + if (width <= bar + info + 3) + { + std::string str; + + str += get_print_dev_string(); + + str += wait_chars[(wait_index++) & 0x3]; + + g_transfer_context.print_oneline(str); + return; + } + else + { + std::string str; + str += get_print_dev_string(); + + str.resize(info, ' '); + std::cout << str; + + if (m_done || m_status) + { + std::string str; + str.resize(bar, ' '); + str[0] = '['; + str[str.size() - 1] = ']'; + std::string err; + if (m_status) + { + err = uuu_get_last_err_string(); + err.resize(bar - 2, ' '); + str.replace(1, err.size(), err); + str.insert(1, g_vt->red); + str.insert(1 + strlen(g_vt->red) + err.size(), g_vt->default_fg); + } + else + { + str.replace(1, 4, "Done"); + str.insert(1, g_vt->green); + str.insert(1 + strlen(g_vt->green) + strlen("Done"), g_vt->default_fg); + } + std::cout << str; + } + else { + std::cout << build_process_bar(bar, m_trans_pos, m_trans_size); + } + std::cout << " "; + print_auto_scroll(m_cmd, width - bar - info - 1, m_start_pos); + + if (clock() - m_start_time > CLOCKS_PER_SEC / 4) + { + m_start_pos++; + m_start_time = clock(); + } + std::cout << std::endl; + + return; + } + } + +public: + std::string m_cmd; + std::string m_dev; + size_t m_trans_pos = 0; + size_t m_cmd_index = 0; + size_t m_trans_size = 0; + bool m_IsEmptyLine = false; + + TransferNotifyItem() : m_start_time{clock()} {} + + std::string get_print_dev_string() const + { + std::string str = m_dev; + str.resize(12, ' '); + + std::string s; + string_man::format(s, "%2d/%2d", m_cmd_index + 1, m_cmd_total); + + str += s; + return str; + } + + bool update(const uuu_notify& nt) + { + if (nt.type == uuu_notify::NOTIFY_DEV_ATTACH) + { + m_dev = nt.str; + m_done = 0; + m_status = 0; + } + if (nt.type == uuu_notify::NOTIFY_CMD_START) + { + m_start_pos = 0; + m_cmd = nt.str; + m_cmd_start_time = nt.timestamp; + } + if (nt.type == uuu_notify::NOTIFY_DECOMPRESS_START) + { + m_start_pos = 0; + m_cmd = nt.str; + m_cmd_start_time = nt.timestamp; + m_dev = "Prep"; + } + if (nt.type == uuu_notify::NOTIFY_DOWNLOAD_START) + { + m_start_pos = 0; + m_cmd = nt.str; + m_cmd_start_time = nt.timestamp; + m_dev = "Prep"; + } + if (nt.type == uuu_notify::NOTIFY_DOWNLOAD_END) + { + m_IsEmptyLine = true; + } + if (nt.type == uuu_notify::NOTIFY_TRANS_SIZE || nt.type == uuu_notify::NOTIFY_DECOMPRESS_SIZE) + { + m_trans_size = nt.total; + return false; + } + if (nt.type == uuu_notify::NOTIFY_CMD_TOTAL) + { + m_cmd_total = nt.total; + return false; + } + if (nt.type == uuu_notify::NOTIFY_CMD_INDEX) + { + m_cmd_index = nt.index; + return false; + } + if (nt.type == uuu_notify::NOTIFY_DONE) + { + if (m_status) + g_transfer_context.failure_count++; + else + g_transfer_context.success_count++; + + m_done = 1; + } + if (nt.type == uuu_notify::NOTIFY_CMD_END) + { + m_cmd_end_time = nt.timestamp; + if (nt.status) + { + g_transfer_context.overall_status = nt.status; + m_last_err = uuu_get_last_err_string(); + } + m_status |= nt.status; + if (m_status) + g_transfer_context.failure_count++; + } + if (nt.type == uuu_notify::NOTIFY_TRANS_POS || nt.type == uuu_notify::NOTIFY_DECOMPRESS_POS) + { + if (m_trans_size == 0) { + + m_trans_pos = nt.index; + return true; + } + + if ((nt.index - m_trans_pos) < (m_trans_size / 100) + && nt.index != m_trans_size) + return false; + + m_trans_pos = nt.index; + } + + return true; + } + + void render(const uuu_notify& nt) + { + g_verbose ? render_verbose(nt) : render_simple(); + } + + void render() + { + render_simple(); + } +}; + +class TransferFeedback final +{ + std::map nt_session; + std::mutex callback_mutex; + + TransferNotifyItem get_summary() + { + TransferNotifyItem sn; + for (auto it = nt_session.begin(); it != nt_session.end(); it++) + { + if (it->second.m_dev == "Prep") + { + sn.m_trans_size += it->second.m_trans_size; + sn.m_trans_pos += it->second.m_trans_pos; + } + else + { + if (it->second.m_trans_pos || it->second.m_cmd_index) + g_transfer_context.start_usb_transfer = true; // Hidden HTTP download when USB start transfer + } + } + + if (g_transfer_context.start_usb_transfer) + sn.m_IsEmptyLine = true; // Hidden HTTP download when USB start transfer + + sn.m_dev = "Prep"; + sn.m_cmd = "Http Download\\Uncompress"; + return sn; + } + + // TODO make param const + void update(const uuu_notify& nt) + { + std::map::iterator it; + + std::lock_guard lock(callback_mutex); + + if (nt_session[nt.id].update(nt)) + { + if (!nt_session[nt.id].m_dev.empty()) + if (nt_session[nt.id].m_dev != "Prep") + g_transfer_context.map_path_nt[nt_session[nt.id].m_dev] = nt_session[nt.id]; + + if (g_verbose) + { + if (nt_session[nt.id].m_dev == "Prep") + get_summary().render(nt); + else + nt_session[nt.id].render(nt); + } + else + { + std::string str; + string_man::format(str, "\rSuccess %d Failure %d ", g_transfer_context.success_count, g_transfer_context.failure_count); + + if (g_transfer_context.map_path_nt.empty()) + str += "Waiting for device..."; + + if (!g_transfer_context.usb_path_filter.empty()) + { + str += " at path "; + for (size_t i = 0; i < g_transfer_context.usb_path_filter.size(); i++) + str += g_transfer_context.usb_path_filter[i] + " "; + } + + if (!g_transfer_context.usb_serial_no_filter.empty()) + { + str += " at serial_no "; + for (auto it : g_transfer_context.usb_serial_no_filter) + str += it + "*"; + } + + g_transfer_context.print_oneline(str); + g_transfer_context.print_oneline(""); + if (nt_session[nt.id].m_dev == "Prep" && !g_transfer_context.start_usb_transfer) + { + get_summary().render(); + } + else + g_transfer_context.print_oneline(""); + + for (it = g_transfer_context.map_path_nt.begin(); it != g_transfer_context.map_path_nt.end(); it++) + it->second.render(); + + for (size_t i = 0; i < g_transfer_context.map_path_nt.size() + 3; i++) + std::cout << "\x1B[1F"; + + } + + //nt_session[nt.id] = g_map_path_nt[nt_session[nt.id].m_dev]; + } + + if (nt.type == uuu_notify::NOTIFY_THREAD_EXIT) + { + if (nt_session.find(nt.id) != nt_session.end()) + nt_session.erase(nt.id); + } + } + + static int update(uuu_notify nt, void* p) + { + auto p_progress = (TransferFeedback*)p; + p_progress->update(nt); + return EXIT_SUCCESS; + } + +public: + void enable() const + { + uuu_register_notify_callback(update, (void*)&nt_session); + } + + void disable() const + { + uuu_unregister_notify_callback(update); + } +}; \ No newline at end of file diff --git a/uuu/autocomplete.cpp b/uuu/autocomplete.cpp index f4b1b642..cf86e419 100644 --- a/uuu/autocomplete.cpp +++ b/uuu/autocomplete.cpp @@ -43,7 +43,7 @@ #include #include #include -#include "buildincmd.h" +#include "Script.h" #include "../libuuu/libuuu.h" @@ -158,7 +158,7 @@ void power_shell_autocomplete(const char *p) if (prev == "-b") cur = last; - if (g_ScriptCatalog.find(cur) == g_ScriptCatalog.end()) + if (g_ScriptCatalog.find(cur)) g_ScriptCatalog.print_auto_complete(cur, ""); last.clear(); diff --git a/uuu/buildincmd.cpp b/uuu/buildincmd.cpp deleted file mode 100644 index e996f7e6..00000000 --- a/uuu/buildincmd.cpp +++ /dev/null @@ -1,392 +0,0 @@ -/* -* Copyright 2018-2021 NXP. -* -* Redistribution and use in source and binary forms, with or without modification, -* are permitted provided that the following conditions are met: -* -* Redistributions of source code must retain the above copyright notice, this -* list of conditions and the following disclaimer. -* -* Redistributions in binary form must reproduce the above copyright notice, this -* list of conditions and the following disclaimer in the documentation and/or -* other materials provided with the distribution. -* -* Neither the name of the NXP Semiconductor nor the names of its -* contributors may be used to endorse or promote products derived from this -* software without specific prior written permission. -* -* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE -* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -* POSSIBILITY OF SUCH DAMAGE. -* -*/ - -#include "buildincmd.h" -#include "VtEmulation.h" - -#include -#include -#include - -static std::string replace_str(std::string str, std::string key, std::string replace); -static std::string str_to_upper(const std::string &str); - -/** - * @brief Parse characters between argument name and its description and check - * if its an optional one - * @param[in] option The characters between argument name and its description to - * be parsed - * @return `0` in any case - */ -void BuiltInScript::Arg::parser(const std::string &option) -{ - const auto pos = option.find('['); - if (pos == std::string::npos) - { - return; - } - m_fallback_option = option.substr(pos + 1, option.find(']') - pos - 1); - m_flags = ARG_OPTION | ARG_OPTION_KEY; -} - -/** - * @brief Create a new BuiltInScript instance by extracting information from a - * BuiltInScriptRawData instance - * @param[in] p The BuiltInScriptRawData containing all data of the script this - * BuiltInScript instance shall represent - */ -BuiltInScript::BuiltInScript(const BuiltInScriptRawData * const p) : - m_text{p->m_text}, - m_desc{p->m_desc ? p->m_desc : ""}, - m_name{p->m_name ? p->m_name : ""} -{ - // Regular expression to detect script argument name occurrences - static const std::regex arg_name_regexp{R"####((@| )(_\S+))####"}; - - for (std::sregex_iterator it - = std::sregex_iterator{m_text.cbegin(), m_text.cend(), arg_name_regexp}; - it != std::sregex_iterator{}; ++it) - { - const std::string param{it->str(2)}; - if (!find_args(param)) - { - Arg a; - a.m_name = param; - a.m_flags = Arg::ARG_MUST; - m_args.emplace_back(std::move(a)); - } - } - - for (size_t i = 0; i < m_args.size(); i++) - { - std::string str; - str += "@"; - str += m_args[i].m_name; - const auto pos = m_text.find(str); - if (pos != std::string::npos) { - const auto start_descript = m_text.find('|', pos); - if (start_descript != std::string::npos) - { - m_args[i].m_desc = m_text.substr(start_descript + 1, - m_text.find('\n', start_descript) - start_descript - 1); - const std::string def{m_text.substr(pos, start_descript - pos)}; - m_args[i].parser(def); - } - } - } -} - -/** - * @brief Check if the BuiltInScript instance has an argument called `arg` - * @param[in] arg The argument for which its existence in the BuiltInScript - * shall be checked - * @return `true` if BuiltInScript has an argument named `arg`, `false` - * otherwise - */ -bool BuiltInScript::find_args(const std::string &arg) const -{ - return std::any_of(m_args.cbegin(), m_args.cend(), - [&arg](const Arg &brg){ return brg.m_name == arg; }); -} - -/** - * @brief Replace built-in script's arguments by actual values given in `args` - * @param[in] args The actual values that shall replace the arguments (the order - * must fit the order of the arguments in the script) - * @return A copy of the built-in script with the arguments replaced by their - * actual values - */ -std::string BuiltInScript::replace_script_args(const std::vector &args) const -{ - std::string script = m_text; - for (size_t i = 0; i < args.size() && i < m_args.size(); i++) - { - script = replace_str(script, m_args[i].m_name, args[i]); - } - - //handle option args; - for (size_t i = args.size(); i < m_args.size(); i++) - { - if (m_args[i].m_flags & Arg::ARG_OPTION_KEY) - { - for (size_t j = 0; j < args.size(); j++) - { - if (m_args[j].m_name == m_args[i].m_fallback_option) - { - script = replace_str(script, m_args[i].m_name, args[j]); - break; - } - } - } - } - return script; -} - -/** - * @brief Print the built-in script to `stdout` followed by a newline - */ -void BuiltInScript::show() const -{ - printf("%s\n", m_text.c_str()); -} - -/** - * @brief Print the script name, description and formal arguments to stdout - */ -void BuiltInScript::show_cmd() const -{ - printf("\t%s%s%s\t%s\n", g_vt->boldwhite, m_name.c_str(), g_vt->default_fg, m_desc.c_str()); - for (auto i = 0u; i < m_args.size(); ++i) - { - std::string desc{m_args[i].m_name}; - if (m_args[i].m_flags & Arg::ARG_OPTION) - { - desc += g_vt->boldwhite; - desc += "[Optional]"; - desc += g_vt->default_fg; - } - desc += " "; - desc += m_args[i].m_desc; - printf("\t\targ%u: %s\n", i, desc.c_str()); - } -} - -/** - * @brief Create a new map by parsing an array of BuiltInScriptRawData instances - * @param[in] p Pointer to the first element of a BuiltInScriptRawData array - */ -BuiltInScriptMap::BuiltInScriptMap(const BuiltInScriptRawData*p) -{ - while (p->m_name) - { - emplace(p->m_name, p); - ++p; - } -} - -/** - * @brief Loads a file as a script; adding it to the catalog - * @param path File system path - * @return Success indication - */ -bool BuiltInScriptMap::add_from_file(const std::string& path) -{ - std::ifstream t(path); - std::string fileContents((std::istreambuf_iterator(t)), - std::istreambuf_iterator()); - - if (fileContents.empty()) { - return false; - } - - BuiltInScriptRawData script_definition{ - path.c_str(), - fileContents.c_str(), - "Script loaded from file" - }; - - emplace(path, &script_definition); - - return true; -} - -/** - * @brief Print the name of each script that matches; for use with auto-complete - * @param[in] match Search text - * @param[in] space Text printed after each script name - */ -void BuiltInScriptMap::print_auto_complete(const std::string &match, const char *space) const -{ - for (const auto &script_pair : *this) - { - if(script_pair.first.substr(0, match.size()) == match) - { - printf("%s%s\n", script_pair.first.c_str(), space); - } - } -} - -/** - * @brief Print (to stdout) usage information about each script - */ -void BuiltInScriptMap::print_usage() const -{ - for (const auto &script_pair : *this) - { - script_pair.second.show_cmd(); - } -} - -/** - * @brief Get a string that lists each script name separated by comma - */ -std::string BuiltInScriptMap::get_names() const -{ - std::string text; - for (const auto& item : *this) - { - text += item.first + ","; - } - text.pop_back(); - return text; -} - -/** - * @brief Replace a `key` substring of a string `str` by a replacement `replace` - * @param[in] str The string of which a copy with the replacements shall be - * created - * @param[in] key The string which shall be replaced - * @param[in] replace The string that shall replace occurrences of `key` - * @return A new string instance with the replacements conducted on it - */ -static std::string replace_str(std::string str, std::string key, std::string replace) -{ - std::string s5, s4; - std::string match[] = { ".BZ2", ".ZST" }; - if (replace.size() > 4) - { - if (replace[replace.size() - 1] == '\"') - { - s5 = str_to_upper(replace.substr(replace.size() - 5)); - for (std::string it : match) - { - if (s5 == it) - { - replace = replace.substr(0, replace.size() - 1); - replace += "/*\""; - } - } - - } - else - { - s4 = str_to_upper(replace.substr(replace.size() - 4)); - for (std::string it : match) - { - if (it == s4) - { - replace += "/*"; - } - } - } - } - - for (size_t j = 0; (j = str.find(key, j)) != std::string::npos;) - { - if (j == 0 || (j!=0 && str[j - 1] == ' ')) - str.replace(j, key.size(), replace); - j += key.size(); - } - return str; -} - -/** - * @brief Returns a copy of `str` with all applicable characters converted to - * uppercase - * @param[in] str The string for which an uppercase copy shall be created - * @return The copy of `str` converted to uppercase - */ -static std::string str_to_upper(const std::string &str) -{ - const std::locale loc; - std::string s; - s.reserve(str.size()); - - for (size_t i = 0; i < str.size(); ++i) - { - s.push_back(std::toupper(str[i], loc)); - } - - return s; -} - -//! Information about the built-in scripts -static constexpr BuiltInScriptRawData g_builtin_cmd[] = -{ - { - "emmc", -#include "emmc_burn_loader.clst" - ,"burn boot loader to eMMC boot partition" - }, - { - "emmc_all", -#include "emmc_burn_all.clst" - ,"burn whole image to eMMC" - }, - { - "fat_write", -#include "fat_write.clst" - ,"update one file in fat partition, require uboot fastboot running in board" - }, - { - "nand", -#include "nand_burn_loader.clst" - ,"burn boot loader to NAND flash" - }, - { - "qspi", -#include "qspi_burn_loader.clst" - ,"burn boot loader to qspi nor flash" - }, - { - "spi_nand", -#include "fspinand_burn_loader.clst" - ,"burn boot loader to spi nand flash" - }, - { - "sd", -#include "sd_burn_loader.clst" - ,"burn boot loader to sd card" - }, - { - "sd_all", -#include "sd_burn_all.clst" - ,"burn whole image to sd card" - }, - { - "spl", -#include "spl_boot.clst" - ,"boot spl and uboot" - }, - { - "nvme_all", -#include "nvme_burn_all.clst" - ,"burn whole image io nvme storage" - }, - { - nullptr, - nullptr, - nullptr, - } -}; - -//! Script catalog -BuiltInScriptMap g_ScriptCatalog(g_builtin_cmd); diff --git a/uuu/buildincmd.h b/uuu/buildincmd.h deleted file mode 100644 index 90c42515..00000000 --- a/uuu/buildincmd.h +++ /dev/null @@ -1,119 +0,0 @@ -/* -* Copyright 2018-2021 NXP. -* -* Redistribution and use in source and binary forms, with or without modification, -* are permitted provided that the following conditions are met: -* -* Redistributions of source code must retain the above copyright notice, this -* list of conditions and the following disclaimer. -* -* Redistributions in binary form must reproduce the above copyright notice, this -* list of conditions and the following disclaimer in the documentation and/or -* other materials provided with the distribution. -* -* Neither the name of the NXP Semiconductor nor the names of its -* contributors may be used to endorse or promote products derived from this -* software without specific prior written permission. -* -* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE -* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -* POSSIBILITY OF SUCH DAMAGE. -* -*/ - -#pragma once - -#include -#include -#include -#include - -/** - * @brief Script definition data - */ -struct BuiltInScriptRawData final -{ - //! Script name - const char * const m_name = nullptr; - //! Script content - const char * const m_text = nullptr; - //! Script description/documentation - const char * const m_desc = nullptr; -}; - -/** - * @brief Parameterized script - * @note - * Is mostly for built-in scripts, but is also used for custom scripts sometimes. - */ -class BuiltInScript final -{ -public: - /** - * @brief Defines a formal argument to a script - */ - class Arg final - { - public: - enum - { - ARG_MUST = 0x1, - ARG_OPTION = 0x2, - ARG_OPTION_KEY = 0x4, - }; - - void parser(const std::string &option); - - //! Argument name - std::string m_name; - //! Argument description/documentation - std::string m_desc; - //! Flags (basically if it's optional or not) - uint32_t m_flags = ARG_MUST; - //! Argument whose value this one defaults to if this is optional - //! and not specified - std::string m_fallback_option; - }; - - BuiltInScript() {}; - BuiltInScript(const BuiltInScriptRawData*p); - - std::string replace_script_args(const std::vector &args) const; - void show() const; - void show_cmd() const; - - //! Script content - const std::string m_text; - //! Script description/documentation - const std::string m_desc; - //! Script name - const std::string m_name; - //! Arguments - std::vector m_args; - -private: - bool find_args(const std::string &arg) const; -}; - -/** - * @brief Script catalog; indexed by name - */ -class BuiltInScriptMap final : public std::map -{ -public: - BuiltInScriptMap(const BuiltInScriptRawData *p); - bool add_from_file(const std::string& path); - void print_auto_complete(const std::string &match, const char *space = " ") const; - void print_usage() const; - std::string get_names() const; -}; - -extern BuiltInScriptMap g_ScriptCatalog; diff --git a/uuu/environment.h b/uuu/environment.h new file mode 100644 index 00000000..5174b9f0 --- /dev/null +++ b/uuu/environment.h @@ -0,0 +1,92 @@ + +#pragma once + +#include "../libuuu/libuuu.h" + +#ifdef _WIN32 +#include +#include +#else +#include +#include +#endif + +#include +#include + +static int ask_passwd(char* prompt, char user[MAX_USER_LEN], char passwd[MAX_USER_LEN]) +{ + std::cout << std::endl << prompt << " Required Login" << std::endl; + std::cout << "Username:"; + std::cin.getline(user, 128); + std::cout << "Password:"; + int i = 0; + +#ifdef _WIN32 + while ((passwd[i] = _getch()) != '\r') { + if (passwd[i] == '\b') { + if (i != 0) { + std::cout << "\b \b"; + i--; + } + } + else { + std::cout << '*'; + i++; + } + } +#else + struct termios old, tty; + tcgetattr(STDIN_FILENO, &tty); + old = tty; + tty.c_lflag &= ~ECHO; + tcsetattr(STDIN_FILENO, TCSANOW, &tty); + + string pd; + getline(std::cin, pd); + + tcsetattr(STDIN_FILENO, TCSANOW, &old); + if (pd.size() > MAX_USER_LEN - 1) + return EXIT_FAILURE; + memcpy(passwd, pd.data(), pd.size()); + i = pd.size(); + +#endif + passwd[i] = 0; + std::cout << std::endl; + return EXIT_SUCCESS; +} + +#ifdef _WIN32 + +static int ignore_serial_number(const char* pro, const char* chip, const char*/*comp*/, uint16_t vid, uint16_t pid, uint16_t /*bcdlow*/, uint16_t /*bcdhigh*/, void*/*p*/) +{ + printf("\t %s\t %s\t 0x%04X\t0x%04X\n", chip, pro, vid, pid); + + char sub[128]; + snprintf(sub, 128, "IgnoreHWSerNum%04x%04x", vid, pid); + const BYTE value = 1; + + LSTATUS ret = RegSetKeyValueA(HKEY_LOCAL_MACHINE, + "SYSTEM\\CurrentControlSet\\Control\\UsbFlags", + sub, REG_BINARY, &value, 1); + if (ret == ERROR_SUCCESS) + return EXIT_SUCCESS; + + printf("Set key failure, try run as administrator permission\n"); + return EXIT_FAILURE; +} + +static int set_ignore_serial_number() +{ + printf("Modifying registry to ignore serial number for finding devices...\n"); + return uuu_for_each_cfg(ignore_serial_number, NULL); +} + +#define os_putenv _putenv + +#else + +#define os_putenv putenv + +#endif \ No newline at end of file diff --git a/uuu/progress.h b/uuu/progress.h deleted file mode 100644 index bb53c15b..00000000 --- a/uuu/progress.h +++ /dev/null @@ -1,453 +0,0 @@ - -#pragma once - -#include "../libuuu/libuuu.h" -#include "../libuuu/string_man.h" - -#include - -#include -#include -#include -#include -#include - -extern int g_verbose; - -// TODO should not have static vars in header file! - -static std::vector usb_serial_no_filter; -static bool start_usb_transfer; -static std::vector usb_path_filter; - -static int g_overall_status; -static int g_overall_okay; -static int g_overall_failure; -static char g_wait[] = "|/-\\"; -static int g_wait_index; - -static void print_oneline(std::string str) -{ - size_t w = g_vt->get_console_width(); - if (w <= 3) - return; - - if (str.size() >= w) - { - str.resize(w - 1); - str[str.size() - 1] = '.'; - str[str.size() - 2] = '.'; - str[str.size() - 3] = '.'; - } - else - { - str.resize(w, ' '); - } - std::cout << str << std::endl; -} - -static std::string build_process_bar(size_t width, size_t pos, size_t total) -{ - std::string str; - str.resize(width, ' '); - str[0] = '['; - str[width - 1] = ']'; - - if (total == 0) - { - if (pos == 0) - return str; - - std::string loc; - size_t s = pos / (1024 * 1024); - format(loc, "%dM", s); - str.replace(1, loc.size(), loc); - return str; - } - - size_t i; - - if (pos > total) - pos = total; - - for (i = 1; i < (width - 2) * pos / total; i++) - { - str[i] = '='; - } - - if (i > 1) - str[i] = '>'; - - if (pos == total) - str[str.size() - 2] = '='; - - std::string per; - format(per, "%d%%", pos * 100 / total); - - size_t start = (width - per.size()) / 2; - str.replace(start, per.size(), per); - str.insert(start, g_vt->yellow); - str.insert(start + per.size() + strlen(g_vt->yellow), g_vt->default_fg); - return str; -} - -static void print_auto_scroll(std::string str, size_t len, size_t start) -{ - if (str.size() <= len) - { - str.resize(len, ' '); - std::cout << str; - return; - } - - if (str.size()) - start = start % str.size(); - else - start = 0; - - std::string s = str.substr(start, len); - s.resize(len, ' '); - std::cout << s; -} - -class ShowNotify -{ -public: - std::string m_cmd; - std::string m_dev; - size_t m_trans_pos = 0; - int m_status = 0; - size_t m_cmd_total = 0; - size_t m_cmd_index = 0; - std::string m_last_err; - int m_done = 0; - size_t m_start_pos = 0; - size_t m_trans_size = 0; - clock_t m_start_time; - uint64_t m_cmd_start_time; - uint64_t m_cmd_end_time; - bool m_IsEmptyLine = false; - - ShowNotify() : m_start_time{ clock() } {} - - bool update(uuu_notify nt) - { - if (nt.type == uuu_notify::NOTIFY_DEV_ATTACH) - { - m_dev = nt.str; - m_done = 0; - m_status = 0; - } - if (nt.type == uuu_notify::NOTIFY_CMD_START) - { - m_start_pos = 0; - m_cmd = nt.str; - m_cmd_start_time = nt.timestamp; - } - if (nt.type == uuu_notify::NOTIFY_DECOMPRESS_START) - { - m_start_pos = 0; - m_cmd = nt.str; - m_cmd_start_time = nt.timestamp; - m_dev = "Prep"; - } - if (nt.type == uuu_notify::NOTIFY_DOWNLOAD_START) - { - m_start_pos = 0; - m_cmd = nt.str; - m_cmd_start_time = nt.timestamp; - m_dev = "Prep"; - } - if (nt.type == uuu_notify::NOTIFY_DOWNLOAD_END) - { - m_IsEmptyLine = true; - } - if (nt.type == uuu_notify::NOTIFY_TRANS_SIZE || nt.type == uuu_notify::NOTIFY_DECOMPRESS_SIZE) - { - m_trans_size = nt.total; - return false; - } - if (nt.type == uuu_notify::NOTIFY_CMD_TOTAL) - { - m_cmd_total = nt.total; - return false; - } - if (nt.type == uuu_notify::NOTIFY_CMD_INDEX) - { - m_cmd_index = nt.index; - return false; - } - if (nt.type == uuu_notify::NOTIFY_DONE) - { - if (m_status) - g_overall_failure++; - else - g_overall_okay++; - - m_done = 1; - } - if (nt.type == uuu_notify::NOTIFY_CMD_END) - { - m_cmd_end_time = nt.timestamp; - if (nt.status) - { - g_overall_status = nt.status; - m_last_err = uuu_get_last_err_string(); - } - m_status |= nt.status; - if (m_status) - g_overall_failure++; - } - if (nt.type == uuu_notify::NOTIFY_TRANS_POS || nt.type == uuu_notify::NOTIFY_DECOMPRESS_POS) - { - if (m_trans_size == 0) { - - m_trans_pos = nt.index; - return true; - } - - if ((nt.index - m_trans_pos) < (m_trans_size / 100) - && nt.index != m_trans_size) - return false; - - m_trans_pos = nt.index; - } - - return true; - } - void print_verbose(uuu_notify* nt) const - { - if (this->m_dev == "Prep" && start_usb_transfer) - return; - - if (nt->type == uuu_notify::NOTIFY_DEV_ATTACH) - { - std::cout << "New USB Device Attached at " << nt->str << std::endl; - } - if (nt->type == uuu_notify::NOTIFY_CMD_START) - { - std::cout << m_dev << ">" << "Start Cmd:" << nt->str << std::endl; - } - if (nt->type == uuu_notify::NOTIFY_CMD_END) - { - double diff = m_cmd_end_time - m_cmd_start_time; - diff /= 1000; - if (nt->status) - { - std::cout << m_dev << ">" << g_vt->red << "Fail " << uuu_get_last_err_string() << "(" << std::setprecision(4) << diff << "s)" << g_vt->default_fg << std::endl; - } - else - { - std::cout << m_dev << ">" << g_vt->green << "Okay (" << std::setprecision(4) << diff << "s)" << g_vt->default_fg << std::endl; - } - } - - if (nt->type == uuu_notify::NOTIFY_TRANS_POS || nt->type == uuu_notify::NOTIFY_DECOMPRESS_POS) - { - if (m_trans_size) - std::cout << g_vt->yellow << "\r" << m_trans_pos * 100 / m_trans_size << "%" << g_vt->default_fg; - else - std::cout << "\r" << m_trans_pos; - - std::cout.flush(); - } - - if (nt->type == uuu_notify::NOTIFY_CMD_INFO) - std::cout << nt->str; - - if (nt->type == uuu_notify::NOTIFY_WAIT_FOR) - std::cout << "\r" << nt->str << " " << g_wait[((g_wait_index++) & 0x3)]; - - if (nt->type == uuu_notify::NOTIFY_DECOMPRESS_START) - std::cout << "Decompress file:" << nt->str << std::endl; - - if (nt->type == uuu_notify::NOTIFY_DOWNLOAD_START) - std::cout << "Download file:" << nt->str << std::endl; - - } - void print(int verbose = 0, uuu_notify* nt = NULL) - { - verbose ? print_verbose(nt) : print_simple(); - } - std::string get_print_dev_string() - { - std::string str; - str = m_dev; - str.resize(12, ' '); - - std::string s; - format(s, "%2d/%2d", m_cmd_index + 1, m_cmd_total); - - str += s; - return str; - } - void print_simple() - { - int width = g_vt->get_console_width(); - int info, bar; - info = 18; - bar = 40; - - if (m_IsEmptyLine) - { - std::string str(width, ' '); - std::cout << str; - return; - } - if (width <= bar + info + 3) - { - std::string str; - - str += get_print_dev_string(); - - str += g_wait[(g_wait_index++) & 0x3]; - - print_oneline(str); - return; - } - else - { - std::string str; - str += get_print_dev_string(); - - str.resize(info, ' '); - std::cout << str; - - if (m_done || m_status) - { - std::string str; - str.resize(bar, ' '); - str[0] = '['; - str[str.size() - 1] = ']'; - std::string err; - if (m_status) - { - err = uuu_get_last_err_string(); - err.resize(bar - 2, ' '); - str.replace(1, err.size(), err); - str.insert(1, g_vt->red); - str.insert(1 + strlen(g_vt->red) + err.size(), g_vt->default_fg); - } - else - { - str.replace(1, 4, "Done"); - str.insert(1, g_vt->green); - str.insert(1 + strlen(g_vt->green) + strlen("Done"), g_vt->default_fg); - } - std::cout << str; - } - else { - std::cout << build_process_bar(bar, m_trans_pos, m_trans_size); - } - std::cout << " "; - print_auto_scroll(m_cmd, width - bar - info - 1, m_start_pos); - - if (clock() - m_start_time > CLOCKS_PER_SEC / 4) - { - m_start_pos++; - m_start_time = clock(); - } - std::cout << std::endl; - - return; - } - } -}; - -static std::map g_map_path_nt; -static std::mutex g_callback_mutex; - -static ShowNotify Summary(std::map* np) -{ - ShowNotify sn; - for (auto it = np->begin(); it != np->end(); it++) - { - if (it->second.m_dev == "Prep") - { - sn.m_trans_size += it->second.m_trans_size; - sn.m_trans_pos += it->second.m_trans_pos; - } - else - { - if (it->second.m_trans_pos || it->second.m_cmd_index) - start_usb_transfer = true; // Hidden HTTP download when USB start transfer - } - } - - if (start_usb_transfer) - sn.m_IsEmptyLine = true; // Hidden HTTP download when USB start transfer - - sn.m_dev = "Prep"; - sn.m_cmd = "Http Download\\Uncompress"; - return sn; -} - -static int update_progress(uuu_notify nt, void* p) -{ - std::map* np = (std::map*)p; - std::map::iterator it; - - std::lock_guard lock(g_callback_mutex); - - if ((*np)[nt.id].update(nt)) - { - if (!(*np)[nt.id].m_dev.empty()) - if ((*np)[nt.id].m_dev != "Prep") - g_map_path_nt[(*np)[nt.id].m_dev] = (*np)[nt.id]; - - if (g_verbose) - { - if ((*np)[nt.id].m_dev == "Prep") - Summary(np).print(g_verbose, &nt); - else - (*np)[nt.id].print(g_verbose, &nt); - } - else - { - std::string str; - format(str, "\rSuccess %d Failure %d ", g_overall_okay, g_overall_failure); - - if (g_map_path_nt.empty()) - str += "Waiting for device..."; - - if (!usb_path_filter.empty()) - { - str += " at path "; - for (size_t i = 0; i < usb_path_filter.size(); i++) - str += usb_path_filter[i] + " "; - } - - if (!usb_serial_no_filter.empty()) - { - str += " at serial_no "; - for (auto it : usb_serial_no_filter) - str += it + "*"; - } - - print_oneline(str); - print_oneline(""); - if ((*np)[nt.id].m_dev == "Prep" && !start_usb_transfer) - { - Summary(np).print(); - } - else - print_oneline(""); - - for (it = g_map_path_nt.begin(); it != g_map_path_nt.end(); it++) - it->second.print(); - - for (size_t i = 0; i < g_map_path_nt.size() + 3; i++) - std::cout << "\x1B[1F"; - - } - - //(*np)[nt.id] = g_map_path_nt[(*np)[nt.id].m_dev]; - } - - if (nt.type == uuu_notify::NOTIFY_THREAD_EXIT) - { - if (np->find(nt.id) != np->end()) - np->erase(nt.id); - } - return EXIT_SUCCESS; -} \ No newline at end of file diff --git a/uuu/uuu.cpp b/uuu/uuu.cpp index bd6dffb6..e57953d2 100644 --- a/uuu/uuu.cpp +++ b/uuu/uuu.cpp @@ -29,9 +29,10 @@ * */ -#include "buildincmd.h" +#include "environment.h" #include "logger.h" -#include "progress.h" +#include "Script.h" +#include "TransferFeedback.h" #include "../libuuu/string_man.h" @@ -55,8 +56,10 @@ using namespace std; int g_verbose = 0; bmap_mode g_bmap_mode = bmap_mode::Default; std::shared_ptr g_vt = std::make_shared(); +TransferContext g_transfer_context; static Logger logger; +static TransferFeedback transfer_feedback; static char sample_cmd_list[] = { #include "uuu.clst" }; @@ -79,56 +82,6 @@ static void interrupt(int) exit(1); } -#ifdef _WIN32 -#include -#else -#include -#include -#endif - -static int ask_passwd(char* prompt, char user[MAX_USER_LEN], char passwd[MAX_USER_LEN]) -{ - cout << endl << prompt << " Required Login"< MAX_USER_LEN -1) - return EXIT_FAILURE; - memcpy(passwd, pd.data(), pd.size()); - i=pd.size(); - -#endif - passwd[i] = 0; - cout << endl; - return EXIT_SUCCESS; -} - static void print_app_title() { printf("Universal Update Utility for NXP i.MX chips -- %s\n", uuu_get_version_string()); @@ -182,12 +135,12 @@ static void print_cli_help() "uuu -h-auto-complete\n" " \t\tOutput auto/tab completion help info\n"; - cout << endl << replace(text, "[BUILTIN_NAMES]", g_ScriptCatalog.get_names()); + cout << endl << string_man::replace(text, "[BUILTIN_NAMES]", g_ScriptCatalog.get_names()); } static void print_syntax_error(const string& message) { logger.log_error(message); - print_cli_help(); + logger.log_info("Hint: see help output from 'uuu -h'"); } static void print_script_catalog() { @@ -250,11 +203,11 @@ static int print_udev_rule(const char * /*pro*/, const char * /*chip*/, const ch static void print_usb_filter() { - if (!usb_path_filter.empty()) + if (!g_transfer_context.usb_path_filter.empty()) { cout << " at path "; - for (size_t i = 0; i < usb_path_filter.size(); i++) - cout << usb_path_filter[i] << " "; + for (size_t i = 0; i < g_transfer_context.usb_path_filter.size(); i++) + cout << g_transfer_context.usb_path_filter[i] << " "; } } @@ -343,57 +296,6 @@ static void print_device_list() uuu_for_each_devices(print_device_info, NULL); } -#ifdef WIN32 - -static int ignore_serial_number(const char *pro, const char *chip, const char */*comp*/, uint16_t vid, uint16_t pid, uint16_t /*bcdlow*/, uint16_t /*bcdhigh*/, void */*p*/) -{ - printf("\t %s\t %s\t 0x%04X\t0x%04X\n", chip, pro, vid, pid); - - char sub[128]; - snprintf(sub, 128, "IgnoreHWSerNum%04x%04x", vid, pid); - const BYTE value = 1; - - LSTATUS ret = RegSetKeyValueA(HKEY_LOCAL_MACHINE, - "SYSTEM\\CurrentControlSet\\Control\\UsbFlags", - sub, REG_BINARY, &value, 1); - if(ret == ERROR_SUCCESS) - return EXIT_SUCCESS; - - printf("Set key failure, try run as administrator permission\n"); - return EXIT_FAILURE; -} - -static int set_ignore_serial_number() -{ - printf("Modifying registry to ignore serial number for finding devices...\n"); - return uuu_for_each_cfg(ignore_serial_number, NULL); -} - -#define os_putenv _putenv - -#else - -#define os_putenv putenv - -#endif - -static int set_environment_variable(const string& key_and_value) -{ - if (os_putenv(key_and_value.c_str())) - { - logger.log_error("Failed to set environment variable with expression '" + key_and_value + "'. Hint: parameter must have the form: key=value"); - return EXIT_FAILURE; - } - return EXIT_SUCCESS; -} - -static void print_script(const BuiltInScript& script) { - string text = script.m_text; - while (text.size() > 0 && (text[0] == '\n' || text[0] == ' ')) - text = text.erase(0, 1); - cout << text; -} - int main(int argc, char **argv) { // commented out since causes failure when pass script file name/path as first arg plus -v after @@ -429,13 +331,13 @@ int main(int argc, char **argv) return EXIT_FAILURE; } string script_name = argv[2]; - auto item = g_ScriptCatalog.find(script_name); - if (item == g_ScriptCatalog.end()) + const Script *script = g_ScriptCatalog.find(script_name); + if (!script) { print_syntax_error("Unknown built-in script '" + script_name + "'; options: " + g_ScriptCatalog.get_names()); return EXIT_FAILURE; } - print_script(item->second); + script->print_content(); return EXIT_SUCCESS; } } @@ -444,7 +346,7 @@ int main(int argc, char **argv) if (argc == 1) { - print_syntax_error("Missing arguments"); + print_cli_help(); return EXIT_FAILURE; } @@ -453,7 +355,7 @@ int main(int argc, char **argv) int dryrun = 0; string input_path; string protocol_cmd; - string script_spec; + string script_name_feedback; string script_text; for (int i = 1; i < argc; i++) @@ -523,7 +425,7 @@ int main(int argc, char **argv) return EXIT_FAILURE; } uuu_add_usbpath_filter(argv[i]); - usb_path_filter.push_back(argv[i]); + g_transfer_context.usb_path_filter.push_back(argv[i]); } else if (arg == "-ms") { @@ -533,7 +435,7 @@ int main(int argc, char **argv) return EXIT_FAILURE; } uuu_add_usbserial_no_filter(argv[i]); - usb_serial_no_filter.push_back(argv[i]); + g_transfer_context.usb_serial_no_filter.push_back(argv[i]); } else if (arg == "-t") { @@ -567,7 +469,7 @@ int main(int argc, char **argv) print_device_list(); return EXIT_SUCCESS; } - #ifdef WIN32 + #ifdef _WIN32 else if (arg == "-IgSerNum") { return set_ignore_serial_number(); @@ -588,7 +490,13 @@ int main(int argc, char **argv) print_syntax_error("Missing key=value argument"); return EXIT_FAILURE; } - return set_environment_variable(argv[i]); + string key_and_value = argv[i]; + if (os_putenv(key_and_value.c_str())) + { + logger.log_error("Failed to set environment variable with expression '" + key_and_value + "'. Hint: parameter must have the form: key=value"); + return EXIT_FAILURE; + } + return EXIT_SUCCESS; } else if (arg == "-b" || arg == "-brun") { @@ -610,22 +518,23 @@ int main(int argc, char **argv) args.push_back(s); } - string name = argv[i]; - auto script = g_ScriptCatalog.find(name); - if (script == g_ScriptCatalog.end()) { - script_spec = "(custom)"; - if (!g_ScriptCatalog.add_from_file(name)) + string script_spec = argv[i]; + const Script *script = g_ScriptCatalog.find(script_spec); + if (!script) { + script_name_feedback = "(custom)"; + script = g_ScriptCatalog.add_from_file(script_spec); + if (!script) { - logger.log_error("Unable to load script from file: " + name); + logger.log_error("Unable to load script from file: " + script_spec); return EXIT_FAILURE; } } else { - script_spec = "(built-in)"; + script_name_feedback = "(built-in)"; } - script_spec += name; - script_text = g_ScriptCatalog[name].replace_script_args(args); + script_name_feedback += script_spec; + script_text = script->replace_arguments(args); break; } else @@ -709,8 +618,7 @@ int main(int argc, char **argv) signal(SIGINT, interrupt); uuu_set_askpasswd(ask_passwd); - map nt_session; - uuu_register_notify_callback(update_progress, &nt_session); + transfer_feedback.enable(); if (shell) { @@ -723,7 +631,7 @@ int main(int argc, char **argv) int ret = uuu_run_cmd(protocol_cmd.c_str(), dryrun); // what is the purpose of printing blank lines? Don't know about success, but on error, there are several blank lines on screen - for (size_t i = 0; i < g_map_path_nt.size()+3; i++) + for (size_t i = 0; i < g_transfer_context.map_path_nt.size()+3; i++) printf("\n"); if (ret) @@ -740,12 +648,12 @@ int main(int argc, char **argv) } else if (!script_text.empty()) { - if (script_spec.empty()) + if (script_name_feedback.empty()) { logger.log_internal_error("Expected non-empty script_spec"); return EXIT_FAILURE; } - logger.log_verbose("Running command script: " + script_spec); + logger.log_verbose("Running command script: " + script_name_feedback); if (uuu_run_cmd_script(script_text.c_str(), dryrun)) { logger.log_error(uuu_get_last_err_string()); @@ -774,5 +682,5 @@ int main(int argc, char **argv) // move cursor below status area if(!g_verbose) printf("\n\n\n"); - return g_overall_status; // why return this value?? + return g_transfer_context.overall_status; // why return this value?? } From 5dfe40daf8593892d3ff24362aecf0de0fd7c319 Mon Sep 17 00:00:00 2001 From: SteveBroshar Date: Sat, 22 Feb 2025 11:00:48 -0600 Subject: [PATCH 04/90] simplify script --- libuuu/string_man.h | 99 ++++++++++++- msvc/uuu.vcxproj | 2 +- msvc/uuu.vcxproj.filters | 6 +- uuu/Script.cpp | 290 --------------------------------------- uuu/Script.h | 260 ++++++++++++++++++++--------------- uuu/ScriptCatalog.h | 135 ++++++++++++++++++ uuu/autocomplete.cpp | 2 +- uuu/uuu.cpp | 151 +++++++++++++++++--- 8 files changed, 517 insertions(+), 428 deletions(-) delete mode 100644 uuu/Script.cpp create mode 100644 uuu/ScriptCatalog.h diff --git a/libuuu/string_man.h b/libuuu/string_man.h index 447c8c49..b7145dc1 100644 --- a/libuuu/string_man.h +++ b/libuuu/string_man.h @@ -4,6 +4,8 @@ #include #include +#include +#include #include #include @@ -11,6 +13,7 @@ namespace string_man { /** * @brief Formats like printf with output to std::string and minimal size allocation + * @param[out] text Output text */ inline void format(std::string s, const char* fmt, ...) { @@ -28,7 +31,7 @@ namespace string_man { /** * @brief Replaces each occurance of a substring - * @param text Input text + * @param[in,out] text Input/output text * @param from Substring to replace * @param to Text to replace found substring with * @return Reference to text (supports chaining) @@ -46,11 +49,28 @@ namespace string_man { } /** - * @brief Returns the input text as uppercase - * @param text Input text - * @return Uppercase text - */ - static std::string toupper(const std::string& text) + * @brief Replaces each lowercase letter with uppercase + * @param[in,out] text Input/output text + * @return Uppercase text + * @return Reference to text (supports chaining) + */ + inline std::string& uppercase(std::string text) + { + const std::locale loc; + for (size_t i = 0; i < text.size(); ++i) + { + text.push_back(std::toupper(text[i], loc)); + } + return text; + } + + /** + * @brief Returns a copy of the input with lowercase letters replaced with uppercase + * @param[in] text Input text + * @return Uppercase text + * @return Result text + */ + inline std::string uppercase_copy(const std::string& text) { const std::locale loc; std::string upper; @@ -64,4 +84,71 @@ namespace string_man { return upper; } + /** + * @brief Removes whitespace from the beginning + * @param[in,out] text Input/output text + * @return Reference to text (supports chaining) + */ + inline std::string& left_trim(std::string& s) { + s.erase(s.begin(), std::find_if(s.begin(), s.end(), [](unsigned char ch) { + return !std::isspace(ch); + })); + return s; + } + + /** + * @brief Removes whitespace from the end + * @param[in,out] text Input/output text + * @return Reference to text (supports chaining) + */ + inline std::string& right_trim(std::string& s) { + s.erase(std::find_if(s.rbegin(), s.rend(), [](unsigned char ch) { + return !std::isspace(ch); + }).base(), s.end()); + return s; + } + + /** + * @brief Removes whitespace from both ends + * @param[in,out] text Input/output text + * @return Reference to text (supports chaining) + */ + inline std::string& trim(std::string& s) { + right_trim(s); + left_trim(s); + return s; + } + + /** + * @brief Returns the input string but with whitespace removed from the beginning + * @param[in] text Input text + * @return Result text + */ + inline std::string left_trim_copy(const std::string& s) { + std::string copy(s); + left_trim(copy); + return copy; + } + + /** + * @brief Returns the input string but with whitespace removed from the end + * @param[in] text Input text + * @return Result text + */ + inline std::string right_trim_copy(const std::string& s) { + std::string copy(s); + right_trim(copy); + return copy; + } + + /** + * @brief Returns the input string but with whitespace removed from both ends + * @param[in] text Input text + * @return Result text + */ + inline std::string trim_copy(const std::string& s) { + std::string copy(s); + trim(copy); + return copy; + } } \ No newline at end of file diff --git a/msvc/uuu.vcxproj b/msvc/uuu.vcxproj index 0aca674d..266d6ba0 100644 --- a/msvc/uuu.vcxproj +++ b/msvc/uuu.vcxproj @@ -20,13 +20,13 @@ - + diff --git a/msvc/uuu.vcxproj.filters b/msvc/uuu.vcxproj.filters index 42c29452..016884b8 100644 --- a/msvc/uuu.vcxproj.filters +++ b/msvc/uuu.vcxproj.filters @@ -18,9 +18,6 @@ Source Files - - Source Files - Source Files @@ -41,5 +38,8 @@ Header Files + + Header Files + \ No newline at end of file diff --git a/uuu/Script.cpp b/uuu/Script.cpp deleted file mode 100644 index daf32f5f..00000000 --- a/uuu/Script.cpp +++ /dev/null @@ -1,290 +0,0 @@ -/* -* Copyright 2018-2021 NXP. -* -* Redistribution and use in source and binary forms, with or without modification, -* are permitted provided that the following conditions are met: -* -* Redistributions of source code must retain the above copyright notice, this -* list of conditions and the following disclaimer. -* -* Redistributions in binary form must reproduce the above copyright notice, this -* list of conditions and the following disclaimer in the documentation and/or -* other materials provided with the distribution. -* -* Neither the name of the NXP Semiconductor nor the names of its -* contributors may be used to endorse or promote products derived from this -* software without specific prior written permission. -* -* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE -* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -* POSSIBILITY OF SUCH DAMAGE. -* -*/ - -#include "Script.h" -#include "VtEmulation.h" - -#include "../libuuu/string_man.h" - -#include -#include - -/** - * @brief Parses characters between argument name and its description and checks if it's optional - * @param option Text between argument name and description - */ -void Script::Arg::parser(const std::string& option) -{ - const auto pos = option.find('['); - if (pos == std::string::npos) - { - return; - } - m_default_argument_value_name = option.substr(pos + 1, option.find(']') - pos - 1); - optionality = ARG_OPTION | ARG_OPTION_KEY; -} - -/** - * @brief Constructs from a config object; caches the config and parses arguments from the text - * @param config Configuation, name and desc can be null/empty, but text cannot - */ -Script::Script(const ScriptConfig& config) : - m_text{config.m_text}, - m_desc{config.m_desc ? config.m_desc : ""}, - m_name{config.m_name ? config.m_name : ""} -{ - // Regular expression to detect script argument name occurrences - static const std::regex arg_name_regexp{R"####((@| )(_\S+))####"}; - - for (std::sregex_iterator it - = std::sregex_iterator{m_text.cbegin(), m_text.cend(), arg_name_regexp}; - it != std::sregex_iterator{}; ++it) - { - const std::string arg_name{it->str(2)}; - if (!has_arg(arg_name)) - { - Arg arg; - arg.m_name = arg_name; - arg.optionality = Arg::ARG_MUST; - m_args.emplace_back(std::move(arg)); - } - } - - for (size_t i = 0; i < m_args.size(); i++) - { - std::string str; - str += "@"; - str += m_args[i].m_name; - const auto pos = m_text.find(str); - if (pos != std::string::npos) { - const auto start_descript = m_text.find('|', pos); - if (start_descript != std::string::npos) - { - m_args[i].m_desc = m_text.substr(start_descript + 1, - m_text.find('\n', start_descript) - start_descript - 1); - const std::string def{m_text.substr(pos, start_descript - pos)}; - m_args[i].parser(def); - } - } - } -} - -/** - * @brief Indicates whether the script has an argument specified by name - * @param name Argument name - */ -bool Script::has_arg(const std::string &name) const -{ - return std::any_of(m_args.cbegin(), m_args.cend(), - [&name](const Arg &arg){ return arg.m_name == name; }); -} - -/** - * @brief Replaces matching substrings plus unknown logic related to file extensions - * @param[in,out] text Input/output string - * @param[in] match Substring to be replaced - * @param[in,out] replace Text to substitute for match; oddly, this is modified - * @return Modified input string object - */ -static std::string replace_str(std::string text, const std::string& match, std::string replace) -{ - // conform replace text - { - std::string s5, s4; - std::string extensions[] = { ".BZ2", ".ZST" }; - if (replace.size() > 4) - { - if (replace[replace.size() - 1] == '\"') - { - s5 = string_man::toupper(replace.substr(replace.size() - 5)); - for (std::string it : extensions) - { - if (s5 == it) - { - replace = replace.substr(0, replace.size() - 1); - replace += "/*\""; - } - } - } - else - { - s4 = string_man::toupper(replace.substr(replace.size() - 4)); - for (std::string it : extensions) - { - if (it == s4) - { - replace += "/*"; - } - } - } - } - } - - for (size_t j = 0; (j = text.find(match, j)) != std::string::npos;) - { - if (j == 0 || (j != 0 && text[j - 1] == ' ')) - text.replace(j, match.size(), replace); - j += match.size(); - } - - return text; -} - -/** - * @brief Returns a copy of the script content with argument references replaced with values - * @param values Argument values; ordered per the arguments of the script definition - * @return Script text after replacement - * @details - * Ignores values in excess of defined argument count. - * For arguments indexed beyond the last value, if it is configured for replacement with the - * value of another argument (ARG_OPTION_KEY), the other argument occurs _before_ the target - * argument in the script definition, and the argument has a value in values, then argument - * references are replaced with the value of the other argument. - */ -std::string Script::replace_arguments(const std::vector& values) const -{ - std::string text = m_text; - for (size_t i = 0; i < values.size() && i < m_args.size(); i++) - { - text = replace_str(text, m_args[i].m_name, values[i]); - } - - // handle optional args - for (size_t i = values.size(); i < m_args.size(); i++) - { - if (m_args[i].optionality & Arg::ARG_OPTION_KEY) - { - for (size_t j = 0; j < values.size(); j++) - { - if (m_args[j].m_name == m_args[i].m_default_argument_value_name) - { - text = replace_str(text, m_args[i].m_name, values[j]); - break; - } - } - } - } - - return text; -} - -/** - * @brief Writes the script content to stdout - */ -void Script::print_content() const { - std::string text = m_text; - while (text.size() > 0 && (text[0] == '\n' || text[0] == ' ')) - text = text.erase(0, 1); - std::cout << text; -} - -/** - * @brief Writes the name, description and arguments to stdout - */ -void Script::print_definition() const -{ - printf("\t%s%s%s\t%s\n", g_vt->boldwhite, m_name.c_str(), g_vt->default_fg, m_desc.c_str()); - for (auto i = 0u; i < m_args.size(); ++i) - { - std::string desc{m_args[i].m_name}; - if (m_args[i].optionality & Arg::ARG_OPTION) - { - desc += g_vt->boldwhite; - desc += "[Optional]"; - desc += g_vt->default_fg; - } - desc += " "; - desc += m_args[i].m_desc; - printf("\t\targ%u: %s\n", i, desc.c_str()); - } -} - -static constexpr ScriptConfig builtin_script_configs[] = -{ - { - "emmc", -#include "emmc_burn_loader.clst" - ,"burn boot loader to eMMC boot partition" - }, - { - "emmc_all", -#include "emmc_burn_all.clst" - ,"burn whole image to eMMC" - }, - { - "fat_write", -#include "fat_write.clst" - ,"update one file in fat partition, require uboot fastboot running in board" - }, - { - "nand", -#include "nand_burn_loader.clst" - ,"burn boot loader to NAND flash" - }, - { - "qspi", -#include "qspi_burn_loader.clst" - ,"burn boot loader to qspi nor flash" - }, - { - "spi_nand", -#include "fspinand_burn_loader.clst" - ,"burn boot loader to spi nand flash" - }, - { - "sd", -#include "sd_burn_loader.clst" - ,"burn boot loader to sd card" - }, - { - "sd_all", -#include "sd_burn_all.clst" - ,"burn whole image to sd card" - }, - { - "spl", -#include "spl_boot.clst" - ,"boot spl and uboot" - }, - { - "nvme_all", -#include "nvme_burn_all.clst" - ,"burn whole image io nvme storage" - }, - { - nullptr, - nullptr, - nullptr, - } -}; - -//! Script catalog global instance -ScriptCatalog g_ScriptCatalog(builtin_script_configs); diff --git a/uuu/Script.h b/uuu/Script.h index 553f97e6..4476f23b 100644 --- a/uuu/Script.h +++ b/uuu/Script.h @@ -31,8 +31,12 @@ #pragma once +#include "../libuuu/string_man.h" + #include +#include #include +#include #include #include @@ -42,162 +46,196 @@ struct ScriptConfig final { //! Script name - const char *m_name = nullptr; + const char *name = nullptr; //! Script content - const char *m_text = nullptr; + const char *text = nullptr; //! Script description/documentation - const char *m_desc = nullptr; + const char *desc = nullptr; }; /** - * @brief Script definition + * @brief Script argument definition */ -class Script final +class ScriptArg final { - /** - * @brief Argument definition - */ - class Arg final - { - public: - enum - { - //! Required - ARG_MUST = 0x1, - //! Optional; no default - ARG_OPTION = 0x2, - //! Optional with default from another argument - ARG_OPTION_KEY = 0x4, - }; - - void parser(const std::string& option); - - //! Argument name - std::string m_name; - //! Argument description/documentation - std::string m_desc; - //! Optionality - uint32_t optionality = ARG_MUST; - //! Name of argument that has the default value if this is optional - //! and not explicitly specified - std::string m_default_argument_value_name; - }; - public: - Script(const ScriptConfig&); + const std::string name; - std::string replace_arguments(const std::vector& args) const; - void print_definition() const; - void print_content() const; + //! Argument description/documentation + const std::string desc; - //! Script content - const std::string m_text; - //! Script description/documentation - const std::string m_desc; - //! Script name - const std::string m_name; - //! Arguments - std::vector m_args; + //! Name of argument that has the default value if this is optional + //! and not explicitly specified; blank for no default value + const std::string default_value_arg_name; -private: - bool has_arg(const std::string &arg) const; + ScriptArg(std::string name, std::string desc, std::string default_value_arg_name = "") + : + name(name), desc(desc), default_value_arg_name(default_value_arg_name) + { + } }; /** - * @brief Script catalog + * @brief Script definition */ -class ScriptCatalog final +class Script final { - std::map items; - -public: /** - * @brief Constructs from an array of config objects; terminated by an item with a null name - * @param[in] config Pointer to the first object + * @brief Indicates whether the script has an argument specified by name + * @param name Argument name */ - ScriptCatalog(const ScriptConfig configs[]) + bool has_arg(const std::string& name) const { - while (configs->m_name) - { - items.emplace(configs->m_name, *configs); - ++configs; - } + return std::any_of(args.cbegin(), args.cend(), + [&name](const ScriptArg& arg) { return arg.name == name; }); } - /** - * @brief Returns a pointer to the script specified by name or null if not found - */ - const Script* find(const std::string& name) - { - auto item = items.find(name); - return item == items.end() ? nullptr : &item->second; + void parse_arguments() { + static const std::regex arg_name_regexp{ R"####((@| )(_\S+))####" }; + for (std::sregex_iterator i = std::sregex_iterator{ text.cbegin(), text.cend(), arg_name_regexp }; + i != std::sregex_iterator{}; ++i) + { + const std::string arg_name{ i->str(2) }; + if (!has_arg(arg_name)) + { + std::string desc; + std::string default_argument_value_name; + const std::string name_spec = "@" + arg_name; + const auto name_spec_start = text.find(name_spec); + if (name_spec_start != std::string::npos) { + const auto desc_delim_start = text.find('|', name_spec_start); + if (desc_delim_start != std::string::npos) + { + desc = text.substr( + desc_delim_start + 1, + text.find('\n', desc_delim_start) - desc_delim_start - 1); + string_man::trim(desc); + const std::string middle_part{ text.substr(name_spec_start, desc_delim_start - name_spec_start) }; + const auto pos = middle_part.find('['); + if (pos != std::string::npos) + { + default_argument_value_name = middle_part.substr(pos + 1, middle_part.find(']') - pos - 1); + } + } + } + + ScriptArg arg(arg_name, desc, default_argument_value_name); + args.emplace_back(std::move(arg)); + } + } } /** - * @brief Loads a file as a script; adding it to the catalog - * @param path File system path - * @return Pointer to the new script object or null if unable to load + * @brief Replaces matching substrings plus unknown logic related to file extensions + * @param[in,out] text Input/output string + * @param[in] match Substring to be replaced + * @param[in,out] replace Text to substitute for match; oddly, this is modified + * @return Modified input string object */ - const Script* add_from_file(const std::string& path) + static std::string replace_str(std::string text, const std::string& match, std::string replace) { - std::ifstream t(path); - std::string fileContents((std::istreambuf_iterator(t)), - std::istreambuf_iterator()); - - if (fileContents.empty()) { - return nullptr; + // conform replace text + { + std::string s5, s4; + std::string extensions[] = { ".BZ2", ".ZST" }; + if (replace.size() > 4) + { + if (replace[replace.size() - 1] == '\"') + { + s5 = string_man::uppercase_copy(replace.substr(replace.size() - 5)); + for (std::string it : extensions) + { + if (s5 == it) + { + replace = replace.substr(0, replace.size() - 1); + replace += "/*\""; + } + } + } + else + { + s4 = string_man::uppercase_copy(replace.substr(replace.size() - 4)); + for (std::string it : extensions) + { + if (it == s4) + { + replace += "/*"; + } + } + } + } } - ScriptConfig script_definition{ - path.c_str(), - fileContents.c_str(), - "Script loaded from file" - }; + for (size_t j = 0; (j = text.find(match, j)) != std::string::npos;) + { + if (j == 0 || (j != 0 && text[j - 1] == ' ')) + text.replace(j, match.size(), replace); + j += match.size(); + } - auto item = items.emplace(path, script_definition); - return &item.first->second; + return text; } +public: /** - * @brief Gets a string that lists each script name separated by comma + * @brief Constructs from a config object; caches the config and parses argument definitions from the text + * @param config Configuation; name and desc can be null/empty, but text cannot */ - std::string get_names() const + Script(const ScriptConfig& config) : + name{ config.name ? config.name : "" }, + desc{ config.desc ? config.desc : "" }, + text{ config.text } { - std::string text; - for (const auto& item : items) - { - text += item.first + ","; - } - text.pop_back(); - return text; + parse_arguments(); } /** - * @brief Writes (to stdout) usage information about each script + * @brief Returns a copy of the script content with argument references replaced with values + * @param values Argument values; ordered per the arguments of the script definition + * @return Script text after replacement + * @details + * Ignores values in excess of defined argument count. + * For arguments indexed beyond the last value, if it is configured for replacement with the + * value of another argument (ARG_OPTION_KEY), the other argument occurs _before_ the target + * argument in the script definition, and the argument has a value in values, then argument + * references are replaced with the value of the other argument. */ - void print_usage() const + std::string replace_arguments(const std::vector& values) const { - for (const auto& item : items) + std::string text = text; + for (size_t i = 0; i < values.size() && i < args.size(); i++) { - item.second.print_definition(); + auto& arg = args[i]; + text = replace_str(text, arg.name, values[i]); } - } - /** - * @brief Prints the name of each script that matches; for use with auto-complete - * @param match Search text - * @param space Text printed after each script name - */ - void print_auto_complete(const std::string& match, const char* space = " ") const - { - for (const auto& item : items) + // handle optional args + for (size_t i = values.size(); i < args.size(); i++) { - if (item.first.substr(0, match.size()) == match) + auto& arg = args[i]; + if (!arg.default_value_arg_name.empty()) { - printf("%s%s\n", item.first.c_str(), space); + for (size_t j = 0; j < values.size(); j++) + { + if (args[j].name == arg.default_value_arg_name) + { + text = replace_str(text, arg.name, values[j]); + break; + } + } } } + + return text; } -}; -extern ScriptCatalog g_ScriptCatalog; + const std::string name; + + //! Description/documentation + const std::string desc; + + //! Content + const std::string text; + + std::vector args; +}; diff --git a/uuu/ScriptCatalog.h b/uuu/ScriptCatalog.h new file mode 100644 index 00000000..f29a7eb2 --- /dev/null +++ b/uuu/ScriptCatalog.h @@ -0,0 +1,135 @@ +/* +* Copyright 2018-2021 NXP. +* +* Redistribution and use in source and binary forms, with or without modification, +* are permitted provided that the following conditions are met: +* +* Redistributions of source code must retain the above copyright notice, this +* list of conditions and the following disclaimer. +* +* Redistributions in binary form must reproduce the above copyright notice, this +* list of conditions and the following disclaimer in the documentation and/or +* other materials provided with the distribution. +* +* Neither the name of the NXP Semiconductor nor the names of its +* contributors may be used to endorse or promote products derived from this +* software without specific prior written permission. +* +* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +* POSSIBILITY OF SUCH DAMAGE. +* +*/ + +#pragma once + +#include "Script.h" + +#include + +/** + * @brief Script catalog + */ +class ScriptCatalog final +{ + std::map items; + +public: + /** + * @brief Constructs from an array of config objects; terminated by an item with a null name + * @param[in] config Pointer to the first object + */ + ScriptCatalog(const ScriptConfig configs[]) + { + while (configs->name) + { + items.emplace(configs->name, *configs); + ++configs; + } + } + + /** + * @brief Returns a read-only reference to the items + * @details + * By exposing this, some of the other functions become convenience functions since can + * perform their action via the items. + */ + const std::map& get_items() + { + return items; + } + + /** + * @brief Returns a pointer to the script specified by name or null if not found + */ + const Script* find(const std::string& name) + { + auto item = items.find(name); + return item == items.end() ? nullptr : &item->second; + } + + /** + * @brief Loads a file as a script; adding it to the catalog + * @param path File system path + * @return Pointer to the new script object or null if unable to load + */ + const Script* add_from_file(const std::string& path) + { + std::ifstream t(path); + std::string fileContents((std::istreambuf_iterator(t)), + std::istreambuf_iterator()); + + if (fileContents.empty()) { + return nullptr; + } + + ScriptConfig script_definition{ + path.c_str(), + fileContents.c_str(), + "Script loaded from file" + }; + + auto item = items.emplace(path, script_definition); + return &item.first->second; + } + + /** + * @brief Gets a string that lists each script name separated by comma + */ + std::string get_names() const + { + std::string text; + for (const auto& item : items) + { + text += item.first + ","; + } + text.pop_back(); + return text; + } + + /** + * @brief Prints the name of each script that matches; for use with auto-complete + * @param match Search text + * @param space Text printed after each script name + */ + void print_auto_complete(const std::string& match, const char* space = " ") const + { + for (const auto& item : items) + { + if (item.first.substr(0, match.size()) == match) + { + printf("%s%s\n", item.first.c_str(), space); + } + } + } +}; + +extern ScriptCatalog g_ScriptCatalog; diff --git a/uuu/autocomplete.cpp b/uuu/autocomplete.cpp index cf86e419..6af54a46 100644 --- a/uuu/autocomplete.cpp +++ b/uuu/autocomplete.cpp @@ -43,7 +43,7 @@ #include #include #include -#include "Script.h" +#include "ScriptCatalog.h" #include "../libuuu/libuuu.h" diff --git a/uuu/uuu.cpp b/uuu/uuu.cpp index e57953d2..261fe87a 100644 --- a/uuu/uuu.cpp +++ b/uuu/uuu.cpp @@ -31,7 +31,7 @@ #include "environment.h" #include "logger.h" -#include "Script.h" +#include "ScriptCatalog.h" #include "TransferFeedback.h" #include "../libuuu/string_man.h" @@ -121,7 +121,8 @@ static void print_cli_help() "\n" "Special modes:\n" "uuu -ls-devices\tList connected devices\n" - "uuu -ls-builtin\tList built-in scripts\n" + "uuu -ls-builtin [BUILTIN]\n" + " \t\tList built-in script; all if none specified\n" "uuu -cat-builtin BUILTIN\n" " \t\tOutput built-in script\n" "uuu -s\t\tInteractive (shell) mode; records commands in uuu.inputlog\n" @@ -143,9 +144,50 @@ static void print_syntax_error(const string& message) { logger.log_info("Hint: see help output from 'uuu -h'"); } -static void print_script_catalog() { - printf("\nBuilt-in scripts:\n"); - g_ScriptCatalog.print_usage(); +/** + * @brief Writes the name, description and arguments to stdout + */ +static void print_definition(const Script& script) +{ + std::string id_highlight = g_vt->green; + std::string key_highlight = g_vt->kcyn; + std::string no_highlight = g_vt->default_fg; + std::cout << id_highlight << script.name << g_vt->default_fg << ": " << script.desc << std::endl; + for (auto& arg : script.args) + { + std::string desc = id_highlight + arg.name + no_highlight; + if (!arg.default_value_arg_name.empty()) + { + desc += " " + + key_highlight + "[default=" + + id_highlight + arg.default_value_arg_name + + key_highlight + "]" + + no_highlight; + } + std::cout << "\t" << desc << ": " << arg.desc << std::endl; + } +} + +static void print_script_catalog(const std::string& script_name) { + auto& items = g_ScriptCatalog.get_items(); + if (script_name.empty()) + { + std::cout << std::endl << "Built-in scripts:" << std::endl; + for (const auto& item : items) + { + print_definition(item.second); + } + } + else + { + auto item = items.find(script_name); + if (item == items.end()) + { + logger.log_error("Unknown script: " + script_name); + exit(EXIT_FAILURE); + } + print_definition(item->second); + } } static void print_protocol_help() { @@ -201,6 +243,17 @@ static int print_udev_rule(const char * /*pro*/, const char * /*chip*/, const ch return EXIT_SUCCESS; } +static void print_udev_info() +{ + uuu_for_each_cfg(print_udev_rule, NULL); + + cerr << endl << + "Enable udev rules via:" << endl << + "\tsudo sh -c \"uuu -udev >> /etc/udev/rules.d/70-uuu.rules\"" << endl << + "\tsudo udevadm control --reload" << endl << + "Note: These instructions output to standard error so are excluded" << endl << endl; +} + static void print_usb_filter() { if (!g_transfer_context.usb_path_filter.empty()) @@ -270,15 +323,14 @@ static void proces_interactive_commands() } } -static void print_udev_info() -{ - uuu_for_each_cfg(print_udev_rule, NULL); - - cerr << endl << - "Enable udev rules via:" << endl << - "\tsudo sh -c \"uuu -udev >> /etc/udev/rules.d/70-uuu.rules\"" << endl << - "\tsudo udevadm control --reload" << endl << - "Note: These instructions output to standard error so are excluded" << endl << endl; +/** + * @brief Writes the content of a script to stdout + */ +static void print_content(const Script& script) { + std::string text = script.text; + while (text.size() > 0 && (text[0] == '\n' || text[0] == ' ')) + text = text.erase(0, 1); + std::cout << text; } static int print_device_info(const char *path, const char *chip, const char *pro, uint16_t vid, uint16_t pid, uint16_t bcd, const char *serial_no, void * /*p*/) @@ -296,6 +348,68 @@ static void print_device_list() uuu_for_each_devices(print_device_info, NULL); } +static constexpr ScriptConfig builtin_script_configs[] = +{ + { + "emmc", +#include "emmc_burn_loader.clst" + ,"burn boot loader to eMMC boot partition" + }, + { + "emmc_all", +#include "emmc_burn_all.clst" + ,"burn whole image to eMMC" + }, + { + "fat_write", +#include "fat_write.clst" + ,"update one file in fat partition, require uboot fastboot running in board" + }, + { + "nand", +#include "nand_burn_loader.clst" + ,"burn boot loader to NAND flash" + }, + { + "qspi", +#include "qspi_burn_loader.clst" + ,"burn boot loader to qspi nor flash" + }, + { + "spi_nand", +#include "fspinand_burn_loader.clst" + ,"burn boot loader to spi nand flash" + }, + { + "sd", +#include "sd_burn_loader.clst" + ,"burn boot loader to sd card" + }, + { + "sd_all", +#include "sd_burn_all.clst" + ,"burn whole image to sd card" + }, + { + "spl", +#include "spl_boot.clst" + ,"boot spl and uboot" + }, + { + "nvme_all", +#include "nvme_burn_all.clst" + ,"burn whole image io nvme storage" + }, + { + nullptr, + nullptr, + nullptr, + } +}; + +//! Script catalog global instance +ScriptCatalog g_ScriptCatalog(builtin_script_configs); + int main(int argc, char **argv) { // commented out since causes failure when pass script file name/path as first arg plus -v after @@ -337,7 +451,7 @@ int main(int argc, char **argv) print_syntax_error("Unknown built-in script '" + script_name + "'; options: " + g_ScriptCatalog.get_names()); return EXIT_FAILURE; } - script->print_content(); + print_content(*script); return EXIT_SUCCESS; } } @@ -414,7 +528,12 @@ int main(int argc, char **argv) } else if (arg == "-ls-builtin") { - print_script_catalog(); + std::string script_name; + if (++i < argc) + { + script_name = argv[i]; + } + print_script_catalog(script_name); return EXIT_SUCCESS; } else if (arg == "-m") From 2278318088e14b89212f0c49b7afe87bf8f9e35c Mon Sep 17 00:00:00 2001 From: SteveBroshar Date: Sat, 22 Feb 2025 11:30:15 -0600 Subject: [PATCH 05/90] fix bugs --- libuuu/string_man.h | 8 ++++---- uuu/Script.h | 14 +++++--------- uuu/uuu.cpp | 22 ++++++++++++++++------ 3 files changed, 25 insertions(+), 19 deletions(-) diff --git a/libuuu/string_man.h b/libuuu/string_man.h index b7145dc1..027141a1 100644 --- a/libuuu/string_man.h +++ b/libuuu/string_man.h @@ -15,17 +15,17 @@ namespace string_man { * @brief Formats like printf with output to std::string and minimal size allocation * @param[out] text Output text */ - inline void format(std::string s, const char* fmt, ...) + inline void format(std::string& text, const char* fmt, ...) { va_list args; va_start(args, fmt); size_t len = std::vsnprintf(NULL, 0, fmt, args); va_end(args); - s.resize(len); + text.resize(len); va_start(args, fmt); - std::vsnprintf((char*)s.c_str(), len + 1, fmt, args); + std::vsnprintf((char*)text.c_str(), len + 1, fmt, args); va_end(args); } @@ -54,7 +54,7 @@ namespace string_man { * @return Uppercase text * @return Reference to text (supports chaining) */ - inline std::string& uppercase(std::string text) + inline std::string& uppercase(std::string& text) { const std::locale loc; for (size_t i = 0; i < text.size(); ++i) diff --git a/uuu/Script.h b/uuu/Script.h index 4476f23b..2fb42083 100644 --- a/uuu/Script.h +++ b/uuu/Script.h @@ -126,13 +126,12 @@ class Script final } /** - * @brief Replaces matching substrings plus unknown logic related to file extensions + * @brief Replaces matching sub-strings plus unknown logic related to file extensions * @param[in,out] text Input/output string * @param[in] match Substring to be replaced * @param[in,out] replace Text to substitute for match; oddly, this is modified - * @return Modified input string object */ - static std::string replace_str(std::string text, const std::string& match, std::string replace) + static void replace_arg(std::string& text, const std::string& match, std::string replace) { // conform replace text { @@ -172,8 +171,6 @@ class Script final text.replace(j, match.size(), replace); j += match.size(); } - - return text; } public: @@ -202,11 +199,10 @@ class Script final */ std::string replace_arguments(const std::vector& values) const { - std::string text = text; + std::string text = this->text; for (size_t i = 0; i < values.size() && i < args.size(); i++) { - auto& arg = args[i]; - text = replace_str(text, arg.name, values[i]); + replace_arg(text, args[i].name, values[i]); } // handle optional args @@ -219,7 +215,7 @@ class Script final { if (args[j].name == arg.default_value_arg_name) { - text = replace_str(text, arg.name, values[j]); + replace_arg(text, arg.name, values[j]); break; } } diff --git a/uuu/uuu.cpp b/uuu/uuu.cpp index 261fe87a..b7414e32 100644 --- a/uuu/uuu.cpp +++ b/uuu/uuu.cpp @@ -625,8 +625,9 @@ int main(int argc, char **argv) return EXIT_FAILURE; } + string script_spec = argv[i]; vector args; - for (int j = i + 2; j < argc; j++) + for (int j = i + 1; j < argc; j++) { string s = argv[j]; if (s.find(' ') != string::npos) @@ -637,10 +638,10 @@ int main(int argc, char **argv) args.push_back(s); } - string script_spec = argv[i]; + script_name_feedback = script_spec; const Script *script = g_ScriptCatalog.find(script_spec); if (!script) { - script_name_feedback = "(custom)"; + script_name_feedback += " (custom)"; script = g_ScriptCatalog.add_from_file(script_spec); if (!script) { @@ -650,10 +651,15 @@ int main(int argc, char **argv) } else { - script_name_feedback = "(built-in)"; + script_name_feedback += " (built-in)"; } - script_name_feedback += script_spec; script_text = script->replace_arguments(args); + if (g_verbose) + { + std::cout << "" << std::endl; + } break; } else @@ -772,7 +778,7 @@ int main(int argc, char **argv) logger.log_internal_error("Expected non-empty script_spec"); return EXIT_FAILURE; } - logger.log_verbose("Running command script: " + script_name_feedback); + logger.log_verbose("Running script: " + script_name_feedback); if (uuu_run_cmd_script(script_text.c_str(), dryrun)) { logger.log_error(uuu_get_last_err_string()); @@ -788,6 +794,10 @@ int main(int argc, char **argv) return EXIT_FAILURE; } } + else + { + logger.log_info("??"); + } if (uuu_wait_uuu_finish(deamon, dryrun)) { From 084d9c674ed3ae8a0bdaf9a815ab93e481e8c6b3 Mon Sep 17 00:00:00 2001 From: SteveBroshar Date: Sat, 22 Feb 2025 15:23:52 -0600 Subject: [PATCH 06/90] organize --- libuuu/string_man.h | 91 ++++++++++++--------------------- uuu/Script.h | 57 +++++++++++++-------- uuu/ScriptCatalog.h | 1 + uuu/environment.h | 117 ++++++++++++++++++++++-------------------- uuu/logger.h | 43 ++++++++++------ uuu/uuu.cpp | 121 ++++++++++++++++++++++++-------------------- 6 files changed, 225 insertions(+), 205 deletions(-) diff --git a/libuuu/string_man.h b/libuuu/string_man.h index 027141a1..7e8d0a31 100644 --- a/libuuu/string_man.h +++ b/libuuu/string_man.h @@ -2,20 +2,26 @@ #pragma once #include -#include -#include -#include +//#include +//#include #include #include +/** + * @brief String manipulation functions + * @details + * Functions modify the input string and to support chaining return its reference. + * Chaining example: uppercase(trim(s)). + */ namespace string_man { /** - * @brief Formats like printf with output to std::string and minimal size allocation - * @param[out] text Output text + * @brief Formats like printf with output to std::string and automatic and minimal size allocation + * @param[out] text Output text; input value is ignored + * @return Reference to text */ - inline void format(std::string& text, const char* fmt, ...) + inline std::string& format(std::string& text, const char* fmt, ...) { va_list args; va_start(args, fmt); @@ -27,6 +33,8 @@ namespace string_man { va_start(args, fmt); std::vsnprintf((char*)text.c_str(), len + 1, fmt, args); va_end(args); + + return text; } /** @@ -34,7 +42,7 @@ namespace string_man { * @param[in,out] text Input/output text * @param from Substring to replace * @param to Text to replace found substring with - * @return Reference to text (supports chaining) + * @return Reference to text */ inline std::string& replace(std::string& text, const std::string& from, const std::string& to) { if (!from.empty()) @@ -51,43 +59,39 @@ namespace string_man { /** * @brief Replaces each lowercase letter with uppercase * @param[in,out] text Input/output text - * @return Uppercase text - * @return Reference to text (supports chaining) + * @return Reference to text */ - inline std::string& uppercase(std::string& text) + inline std::string& lowercase(std::string& text) { const std::locale loc; - for (size_t i = 0; i < text.size(); ++i) + size_t length = text.size(); + for (size_t i = 0; i < length; ++i) { - text.push_back(std::toupper(text[i], loc)); + text[i] = std::tolower(text[i], loc); } return text; } /** - * @brief Returns a copy of the input with lowercase letters replaced with uppercase - * @param[in] text Input text - * @return Uppercase text - * @return Result text + * @brief Replaces each lowercase letter with uppercase + * @param[in,out] text Input/output text + * @return Reference to text */ - inline std::string uppercase_copy(const std::string& text) + inline std::string& uppercase(std::string& text) { const std::locale loc; - std::string upper; - upper.reserve(text.size()); - - for (size_t i = 0; i < text.size(); ++i) + size_t length = text.size(); + for (size_t i = 0; i < length; ++i) { - upper.push_back(std::toupper(text[i], loc)); + text[i] = std::toupper(text[i], loc); } - - return upper; + return text; } /** * @brief Removes whitespace from the beginning * @param[in,out] text Input/output text - * @return Reference to text (supports chaining) + * @return Reference to text */ inline std::string& left_trim(std::string& s) { s.erase(s.begin(), std::find_if(s.begin(), s.end(), [](unsigned char ch) { @@ -99,7 +103,7 @@ namespace string_man { /** * @brief Removes whitespace from the end * @param[in,out] text Input/output text - * @return Reference to text (supports chaining) + * @return Reference to text */ inline std::string& right_trim(std::string& s) { s.erase(std::find_if(s.rbegin(), s.rend(), [](unsigned char ch) { @@ -111,44 +115,11 @@ namespace string_man { /** * @brief Removes whitespace from both ends * @param[in,out] text Input/output text - * @return Reference to text (supports chaining) + * @return Reference to text */ inline std::string& trim(std::string& s) { right_trim(s); left_trim(s); return s; } - - /** - * @brief Returns the input string but with whitespace removed from the beginning - * @param[in] text Input text - * @return Result text - */ - inline std::string left_trim_copy(const std::string& s) { - std::string copy(s); - left_trim(copy); - return copy; - } - - /** - * @brief Returns the input string but with whitespace removed from the end - * @param[in] text Input text - * @return Result text - */ - inline std::string right_trim_copy(const std::string& s) { - std::string copy(s); - right_trim(copy); - return copy; - } - - /** - * @brief Returns the input string but with whitespace removed from both ends - * @param[in] text Input text - * @return Result text - */ - inline std::string trim_copy(const std::string& s) { - std::string copy(s); - trim(copy); - return copy; - } } \ No newline at end of file diff --git a/uuu/Script.h b/uuu/Script.h index 2fb42083..aa4921fb 100644 --- a/uuu/Script.h +++ b/uuu/Script.h @@ -31,9 +31,10 @@ #pragma once +#include "logger.h" + #include "../libuuu/string_man.h" -#include #include #include #include @@ -128,48 +129,57 @@ class Script final /** * @brief Replaces matching sub-strings plus unknown logic related to file extensions * @param[in,out] text Input/output string - * @param[in] match Substring to be replaced - * @param[in,out] replace Text to substitute for match; oddly, this is modified + * @param[in] arg_name Substring to be replaced + * @param[in,out] arg_value Text to substitute for match; oddly, this is modified */ - static void replace_arg(std::string& text, const std::string& match, std::string replace) + static void replace_arg(std::string& text, const std::string& arg_name, std::string arg_value) { // conform replace text { - std::string s5, s4; std::string extensions[] = { ".BZ2", ".ZST" }; - if (replace.size() > 4) + if (arg_value.size() > 4) { - if (replace[replace.size() - 1] == '\"') + if (arg_value[arg_value.size() - 1] == '\"') { - s5 = string_man::uppercase_copy(replace.substr(replace.size() - 5)); - for (std::string it : extensions) + std::string s5 = arg_value.substr(arg_value.size() - 5); + string_man::uppercase(s5); + for (auto& ext : extensions) { - if (s5 == it) + if (s5 == ext) { - replace = replace.substr(0, replace.size() - 1); - replace += "/*\""; + arg_value = arg_value.substr(0, arg_value.size() - 1); + arg_value += "/*\""; } } } else { - s4 = string_man::uppercase_copy(replace.substr(replace.size() - 4)); - for (std::string it : extensions) + std::string s4 = arg_value.substr(arg_value.size() - 4); + string_man::uppercase(s4); + for (auto& ext : extensions) { - if (it == s4) + if (ext == s4) { - replace += "/*"; + arg_value += "/*"; } } } } } - for (size_t j = 0; (j = text.find(match, j)) != std::string::npos;) + if (arg_value.find(' ') != std::string::npos) + { + arg_value.insert(arg_value.begin(), '"'); + arg_value.insert(arg_value.end(), '"'); + } + + g_logger.log_verbose("Replacing script parameter '" + arg_name + "' with value '" + arg_value + "'"); + + for (size_t j = 0; (j = text.find(arg_name, j)) != std::string::npos;) { if (j == 0 || (j != 0 && text[j - 1] == ' ')) - text.replace(j, match.size(), replace); - j += match.size(); + text.replace(j, arg_name.size(), arg_value); + j += arg_name.size(); } } @@ -202,7 +212,14 @@ class Script final std::string text = this->text; for (size_t i = 0; i < values.size() && i < args.size(); i++) { - replace_arg(text, args[i].name, values[i]); + const std::string before_text = text; + const std::string arg_name = args[i].name; + const std::string arg_value = values[i]; + replace_arg(text, arg_name, arg_value); + if (text == before_text) + { + g_logger.log_warning("Argument '" + arg_name + "' not found/replaced in the script text"); + } } // handle optional args diff --git a/uuu/ScriptCatalog.h b/uuu/ScriptCatalog.h index f29a7eb2..995039d7 100644 --- a/uuu/ScriptCatalog.h +++ b/uuu/ScriptCatalog.h @@ -33,6 +33,7 @@ #include "Script.h" +#include #include /** diff --git a/uuu/environment.h b/uuu/environment.h index 5174b9f0..69828b4a 100644 --- a/uuu/environment.h +++ b/uuu/environment.h @@ -14,79 +14,86 @@ #include #include -static int ask_passwd(char* prompt, char user[MAX_USER_LEN], char passwd[MAX_USER_LEN]) -{ - std::cout << std::endl << prompt << " Required Login" << std::endl; - std::cout << "Username:"; - std::cin.getline(user, 128); - std::cout << "Password:"; - int i = 0; +/** + * @brief System environment functions + */ +namespace environment { + + static int ask_passwd(char* prompt, char user[MAX_USER_LEN], char passwd[MAX_USER_LEN]) + { + std::cout << std::endl << prompt << " Required Login" << std::endl; + std::cout << "Username:"; + std::cin.getline(user, 128); + std::cout << "Password:"; + int i = 0; #ifdef _WIN32 - while ((passwd[i] = _getch()) != '\r') { - if (passwd[i] == '\b') { - if (i != 0) { - std::cout << "\b \b"; - i--; + while ((passwd[i] = _getch()) != '\r') { + if (passwd[i] == '\b') { + if (i != 0) { + std::cout << "\b \b"; + i--; + } + } + else { + std::cout << '*'; + i++; } } - else { - std::cout << '*'; - i++; - } - } #else - struct termios old, tty; - tcgetattr(STDIN_FILENO, &tty); - old = tty; - tty.c_lflag &= ~ECHO; - tcsetattr(STDIN_FILENO, TCSANOW, &tty); + struct termios old, tty; + tcgetattr(STDIN_FILENO, &tty); + old = tty; + tty.c_lflag &= ~ECHO; + tcsetattr(STDIN_FILENO, TCSANOW, &tty); - string pd; - getline(std::cin, pd); + string pd; + getline(std::cin, pd); - tcsetattr(STDIN_FILENO, TCSANOW, &old); - if (pd.size() > MAX_USER_LEN - 1) - return EXIT_FAILURE; - memcpy(passwd, pd.data(), pd.size()); - i = pd.size(); + tcsetattr(STDIN_FILENO, TCSANOW, &old); + if (pd.size() > MAX_USER_LEN - 1) + return EXIT_FAILURE; + memcpy(passwd, pd.data(), pd.size()); + i = pd.size(); #endif - passwd[i] = 0; - std::cout << std::endl; - return EXIT_SUCCESS; -} + passwd[i] = 0; + std::cout << std::endl; + return EXIT_SUCCESS; + } #ifdef _WIN32 -static int ignore_serial_number(const char* pro, const char* chip, const char*/*comp*/, uint16_t vid, uint16_t pid, uint16_t /*bcdlow*/, uint16_t /*bcdhigh*/, void*/*p*/) -{ - printf("\t %s\t %s\t 0x%04X\t0x%04X\n", chip, pro, vid, pid); + static int ignore_serial_number(const char* pro, const char* chip, const char*/*comp*/, uint16_t vid, uint16_t pid, uint16_t /*bcdlow*/, uint16_t /*bcdhigh*/, void*/*p*/) + { + printf("\t %s\t %s\t 0x%04X\t0x%04X\n", chip, pro, vid, pid); - char sub[128]; - snprintf(sub, 128, "IgnoreHWSerNum%04x%04x", vid, pid); - const BYTE value = 1; + char sub[128]; + snprintf(sub, 128, "IgnoreHWSerNum%04x%04x", vid, pid); + const BYTE value = 1; - LSTATUS ret = RegSetKeyValueA(HKEY_LOCAL_MACHINE, - "SYSTEM\\CurrentControlSet\\Control\\UsbFlags", - sub, REG_BINARY, &value, 1); - if (ret == ERROR_SUCCESS) - return EXIT_SUCCESS; + LSTATUS ret = RegSetKeyValueA(HKEY_LOCAL_MACHINE, + "SYSTEM\\CurrentControlSet\\Control\\UsbFlags", + sub, REG_BINARY, &value, 1); + if (ret == ERROR_SUCCESS) + return EXIT_SUCCESS; - printf("Set key failure, try run as administrator permission\n"); - return EXIT_FAILURE; -} + printf("Set key failure, try run as administrator permission\n"); + return EXIT_FAILURE; + } -static int set_ignore_serial_number() -{ - printf("Modifying registry to ignore serial number for finding devices...\n"); - return uuu_for_each_cfg(ignore_serial_number, NULL); -} + static int set_ignore_serial_number() + { + printf("Modifying registry to ignore serial number for finding devices...\n"); + return uuu_for_each_cfg(ignore_serial_number, NULL); + } -#define os_putenv _putenv +#define environment_putenv _putenv #else -#define os_putenv putenv +#define environment_putenv putenv + +#endif -#endif \ No newline at end of file +} \ No newline at end of file diff --git a/uuu/logger.h b/uuu/logger.h index 262ef06f..f4dbc5c7 100644 --- a/uuu/logger.h +++ b/uuu/logger.h @@ -12,36 +12,45 @@ */ class Logger final { -public: - bool is_color_output_enabled = false; - - void log_error(const std::string& message) const + void log(const std::string& label, const std::string& message, const std::string& color) const { if (is_color_output_enabled) { - std::cerr << g_vt->red << "Error: " << g_vt->default_fg << message << std::endl; + std::cerr << color << label << ": " << g_vt->default_fg << message << std::endl; } else { - std::cerr << "Error: " << message << std::endl; + std::cerr << label << ": " << message << std::endl; } } +public: + bool is_color_output_enabled = false; + + bool is_verbose_enabled() + { + extern int g_verbose; + return g_verbose; + } + void log_internal_error(const std::string& message) const { - if (is_color_output_enabled) - { - std::cerr << g_vt->red << "INTERNAL ERROR: " << g_vt->default_fg << message << std::endl; - } - else - { - std::cerr << "Error: " << message << std::endl; - } + log("INTERNAL ERROR", message, g_vt->red); + } + + void log_error(const std::string& message) const + { + log("Error", message, g_vt->red); + } + + void log_warning(const std::string& message) const + { + log("Warning", message, g_vt->yellow); } void log_info(const std::string& message) const { - std::cout << message << std::endl; + log("Info", message, g_vt->green); } void log_verbose(const std::string& message) const @@ -49,7 +58,9 @@ class Logger final extern int g_verbose; if (g_verbose) { - std::cout << "Verbose: " << message << std::endl; + log("Verbose", message, g_vt->kcyn); } } }; + +extern Logger g_logger; diff --git a/uuu/uuu.cpp b/uuu/uuu.cpp index b7414e32..25a90d62 100644 --- a/uuu/uuu.cpp +++ b/uuu/uuu.cpp @@ -57,8 +57,8 @@ int g_verbose = 0; bmap_mode g_bmap_mode = bmap_mode::Default; std::shared_ptr g_vt = std::make_shared(); TransferContext g_transfer_context; +Logger g_logger; -static Logger logger; static TransferFeedback transfer_feedback; static char sample_cmd_list[] = { #include "uuu.clst" @@ -140,8 +140,8 @@ static void print_cli_help() } static void print_syntax_error(const string& message) { - logger.log_error(message); - logger.log_info("Hint: see help output from 'uuu -h'"); + g_logger.log_error(message); + g_logger.log_info("Hint: see help output from 'uuu -h'"); } /** @@ -183,7 +183,7 @@ static void print_script_catalog(const std::string& script_name) { auto item = items.find(script_name); if (item == items.end()) { - logger.log_error("Unknown script: " + script_name); + g_logger.log_error("Unknown script: " + script_name); exit(EXIT_FAILURE); } print_definition(item->second); @@ -410,6 +410,45 @@ static constexpr ScriptConfig builtin_script_configs[] = //! Script catalog global instance ScriptCatalog g_ScriptCatalog(builtin_script_configs); +static std::string load_script_text(const std::string& script_spec, const vector& args, std::string& script_name_feedback) +{ + script_name_feedback = script_spec; + const Script* script = g_ScriptCatalog.find(script_spec); + if (!script) { + script_name_feedback += " (custom)"; + script = g_ScriptCatalog.add_from_file(script_spec); + if (!script) + { + g_logger.log_error("Unable to load script from file: " + script_spec); + exit(EXIT_FAILURE); + } + } + else + { + script_name_feedback += " (built-in)"; + } + + if (args.size() > script->args.size()) + { + g_logger.log_error("Too many parameters for script: " + args[script->args.size()]); + exit(EXIT_FAILURE); + } + + std::string text = script->replace_arguments(args); + + if (g_verbose) + { + g_logger.log_verbose(""); + } + + return text; +} + int main(int argc, char **argv) { // commented out since causes failure when pass script file name/path as first arg plus -v after @@ -418,7 +457,7 @@ int main(int argc, char **argv) std::unique_ptr auto_cursor; if (g_vt->enable()) { - logger.is_color_output_enabled = true; + g_logger.is_color_output_enabled = true; auto_cursor = std::make_unique(); } else @@ -591,7 +630,7 @@ int main(int argc, char **argv) #ifdef _WIN32 else if (arg == "-IgSerNum") { - return set_ignore_serial_number(); + return environment::set_ignore_serial_number(); } #endif else if (arg == "-bmap") @@ -610,9 +649,9 @@ int main(int argc, char **argv) return EXIT_FAILURE; } string key_and_value = argv[i]; - if (os_putenv(key_and_value.c_str())) + if (environment_putenv(key_and_value.c_str())) { - logger.log_error("Failed to set environment variable with expression '" + key_and_value + "'. Hint: parameter must have the form: key=value"); + g_logger.log_error("Failed to set environment variable with expression '" + key_and_value + "'. Hint: parameter must have the form: key=value"); return EXIT_FAILURE; } return EXIT_SUCCESS; @@ -625,41 +664,13 @@ int main(int argc, char **argv) return EXIT_FAILURE; } - string script_spec = argv[i]; vector args; for (int j = i + 1; j < argc; j++) { - string s = argv[j]; - if (s.find(' ') != string::npos) - { - s.insert(s.begin(), '"'); - s.insert(s.end(), '"'); - } - args.push_back(s); + args.push_back(argv[j]); } - script_name_feedback = script_spec; - const Script *script = g_ScriptCatalog.find(script_spec); - if (!script) { - script_name_feedback += " (custom)"; - script = g_ScriptCatalog.add_from_file(script_spec); - if (!script) - { - logger.log_error("Unable to load script from file: " + script_spec); - return EXIT_FAILURE; - } - } - else - { - script_name_feedback += " (built-in)"; - } - script_text = script->replace_arguments(args); - if (g_verbose) - { - std::cout << "" << std::endl; - } + script_text = load_script_text(argv[i], args, script_name_feedback); break; } else @@ -701,19 +712,19 @@ int main(int argc, char **argv) if (deamon && shell) { - logger.log_error("Incompatible options: deamon (-d) and shell (-s)"); + g_logger.log_error("Incompatible options: deamon (-d) and shell (-s)"); return EXIT_FAILURE; } if (deamon && dryrun) { - logger.log_error("Incompatible options: deamon (-d) and dry-run (-dry)"); + g_logger.log_error("Incompatible options: deamon (-d) and dry-run (-dry)"); return EXIT_FAILURE; } if (shell && dryrun) { - logger.log_error("Incompatible options: shell (-s) and dry-run (-dry)"); + g_logger.log_error("Incompatible options: shell (-s) and dry-run (-dry)"); return EXIT_FAILURE; } @@ -742,7 +753,7 @@ int main(int argc, char **argv) signal(SIGINT, interrupt); - uuu_set_askpasswd(ask_passwd); + uuu_set_askpasswd(environment::ask_passwd); transfer_feedback.enable(); if (shell) @@ -752,7 +763,7 @@ int main(int argc, char **argv) } else if (!protocol_cmd.empty()) { - logger.log_verbose("Executing single command: " + protocol_cmd); + g_logger.log_verbose("Executing single command: " + protocol_cmd); int ret = uuu_run_cmd(protocol_cmd.c_str(), dryrun); // what is the purpose of printing blank lines? Don't know about success, but on error, there are several blank lines on screen @@ -761,11 +772,11 @@ int main(int argc, char **argv) if (ret) { - logger.log_error(uuu_get_last_err_string()); + g_logger.log_error(uuu_get_last_err_string()); return EXIT_FAILURE; } - logger.log_info("Command succeeded :)"); + g_logger.log_info("Command succeeded :)"); if (shell) proces_interactive_commands(); @@ -775,41 +786,43 @@ int main(int argc, char **argv) { if (script_name_feedback.empty()) { - logger.log_internal_error("Expected non-empty script_spec"); + g_logger.log_internal_error("Expected non-empty script_spec"); return EXIT_FAILURE; } - logger.log_verbose("Running script: " + script_name_feedback); + g_logger.log_verbose("Running script: " + script_name_feedback); if (uuu_run_cmd_script(script_text.c_str(), dryrun)) { - logger.log_error(uuu_get_last_err_string()); + g_logger.log_error(uuu_get_last_err_string()); return EXIT_FAILURE; } } else if (!input_path.empty()) { - logger.log_verbose("Running as auto detect file: " + input_path); + g_logger.log_verbose("Running as auto detect file: " + input_path); if (uuu_auto_detect_file(input_path.c_str())) { - logger.log_error(uuu_get_last_err_string()); + g_logger.log_error(uuu_get_last_err_string()); return EXIT_FAILURE; } } else { - logger.log_info("??"); + g_logger.log_info("??"); } if (uuu_wait_uuu_finish(deamon, dryrun)) { - logger.log_error(uuu_get_last_err_string()); + g_logger.log_error(uuu_get_last_err_string()); return EXIT_FAILURE; } // wait for the thread exit, after send out CMD_DONE std::this_thread::sleep_for(std::chrono::milliseconds(100)); - // move cursor below status area + // move cursor below status area; [why 3?] if(!g_verbose) printf("\n\n\n"); - return g_transfer_context.overall_status; // why return this value?? + g_logger.log_info(g_transfer_context.overall_status == 0 ? "Success :)" : "Failed :("); + + return g_transfer_context.overall_status; // [why return this value??] } From ca0bd59d83b522109df06a7a8e247d6ca7cd7abd Mon Sep 17 00:00:00 2001 From: SteveBroshar Date: Sat, 22 Feb 2025 15:33:24 -0600 Subject: [PATCH 07/90] support doxygen --- .gitignore | 1 + Doxyfile | 2862 ++++++++++++++++++++++++++++++++++++++ libuuu/string_man.h | 1 + msvc/uuu.vcxproj | 2 +- msvc/uuu.vcxproj.filters | 2 +- uuu/Script.h | 4 +- uuu/ScriptCatalog.h | 31 +- uuu/TransferFeedback.h | 1 + uuu/VtEmulation.h | 1 + uuu/environment.h | 1 + uuu/logger.h | 1 + uuu/uuu.cpp | 3 +- 12 files changed, 2875 insertions(+), 35 deletions(-) create mode 100644 Doxyfile diff --git a/.gitignore b/.gitignore index 6e323bf1..ff6722eb 100644 --- a/.gitignore +++ b/.gitignore @@ -16,3 +16,4 @@ CMakeCache.txt node_modules build bin/ +Docs/ diff --git a/Doxyfile b/Doxyfile new file mode 100644 index 00000000..35db4762 --- /dev/null +++ b/Doxyfile @@ -0,0 +1,2862 @@ +# Doxyfile 1.9.8 + +# This file describes the settings to be used by the documentation system +# doxygen (www.doxygen.org) for a project. +# +# All text after a double hash (##) is considered a comment and is placed in +# front of the TAG it is preceding. +# +# All text after a single hash (#) is considered a comment and will be ignored. +# The format is: +# TAG = value [value, ...] +# For lists, items can also be appended using: +# TAG += value [value, ...] +# Values that contain spaces should be placed between quotes (\" \"). +# +# Note: +# +# Use doxygen to compare the used configuration file with the template +# configuration file: +# doxygen -x [configFile] +# Use doxygen to compare the used configuration file with the template +# configuration file without replacing the environment variables or CMake type +# replacement variables: +# doxygen -x_noenv [configFile] + +#--------------------------------------------------------------------------- +# Project related configuration options +#--------------------------------------------------------------------------- + +# This tag specifies the encoding used for all characters in the configuration +# file that follow. The default is UTF-8 which is also the encoding used for all +# text before the first occurrence of this tag. Doxygen uses libiconv (or the +# iconv built into libc) for the transcoding. See +# https://www.gnu.org/software/libiconv/ for the list of possible encodings. +# The default value is: UTF-8. + +DOXYFILE_ENCODING = UTF-8 + +# The PROJECT_NAME tag is a single word (or a sequence of words surrounded by +# double-quotes, unless you are using Doxywizard) that should identify the +# project for which the documentation is generated. This name is used in the +# title of most generated pages and in a few other places. +# The default value is: My Project. + +PROJECT_NAME = "Universal Update Utility" + +# The PROJECT_NUMBER tag can be used to enter a project or revision number. This +# could be handy for archiving the generated documentation or if some version +# control system is used. + +PROJECT_NUMBER = + +# Using the PROJECT_BRIEF tag one can provide an optional one line description +# for a project that appears at the top of each page and should give viewer a +# quick idea about the purpose of the project. Keep the description short. + +PROJECT_BRIEF = + +# With the PROJECT_LOGO tag one can specify a logo or an icon that is included +# in the documentation. The maximum height of the logo should not exceed 55 +# pixels and the maximum width should not exceed 200 pixels. Doxygen will copy +# the logo to the output directory. + +PROJECT_LOGO = + +# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path +# into which the generated documentation will be written. If a relative path is +# entered, it will be relative to the location where doxygen was started. If +# left blank the current directory will be used. + +OUTPUT_DIRECTORY = Docs + +# If the CREATE_SUBDIRS tag is set to YES then doxygen will create up to 4096 +# sub-directories (in 2 levels) under the output directory of each output format +# and will distribute the generated files over these directories. Enabling this +# option can be useful when feeding doxygen a huge amount of source files, where +# putting all generated files in the same directory would otherwise causes +# performance problems for the file system. Adapt CREATE_SUBDIRS_LEVEL to +# control the number of sub-directories. +# The default value is: NO. + +CREATE_SUBDIRS = NO + +# Controls the number of sub-directories that will be created when +# CREATE_SUBDIRS tag is set to YES. Level 0 represents 16 directories, and every +# level increment doubles the number of directories, resulting in 4096 +# directories at level 8 which is the default and also the maximum value. The +# sub-directories are organized in 2 levels, the first level always has a fixed +# number of 16 directories. +# Minimum value: 0, maximum value: 8, default value: 8. +# This tag requires that the tag CREATE_SUBDIRS is set to YES. + +CREATE_SUBDIRS_LEVEL = 8 + +# If the ALLOW_UNICODE_NAMES tag is set to YES, doxygen will allow non-ASCII +# characters to appear in the names of generated files. If set to NO, non-ASCII +# characters will be escaped, for example _xE3_x81_x84 will be used for Unicode +# U+3044. +# The default value is: NO. + +ALLOW_UNICODE_NAMES = NO + +# The OUTPUT_LANGUAGE tag is used to specify the language in which all +# documentation generated by doxygen is written. Doxygen will use this +# information to generate all constant output in the proper language. +# Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Bulgarian, +# Catalan, Chinese, Chinese-Traditional, Croatian, Czech, Danish, Dutch, English +# (United States), Esperanto, Farsi (Persian), Finnish, French, German, Greek, +# Hindi, Hungarian, Indonesian, Italian, Japanese, Japanese-en (Japanese with +# English messages), Korean, Korean-en (Korean with English messages), Latvian, +# Lithuanian, Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese, +# Romanian, Russian, Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish, +# Swedish, Turkish, Ukrainian and Vietnamese. +# The default value is: English. + +OUTPUT_LANGUAGE = English + +# If the BRIEF_MEMBER_DESC tag is set to YES, doxygen will include brief member +# descriptions after the members that are listed in the file and class +# documentation (similar to Javadoc). Set to NO to disable this. +# The default value is: YES. + +BRIEF_MEMBER_DESC = YES + +# If the REPEAT_BRIEF tag is set to YES, doxygen will prepend the brief +# description of a member or function before the detailed description +# +# Note: If both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the +# brief descriptions will be completely suppressed. +# The default value is: YES. + +REPEAT_BRIEF = YES + +# This tag implements a quasi-intelligent brief description abbreviator that is +# used to form the text in various listings. Each string in this list, if found +# as the leading text of the brief description, will be stripped from the text +# and the result, after processing the whole list, is used as the annotated +# text. Otherwise, the brief description is used as-is. If left blank, the +# following values are used ($name is automatically replaced with the name of +# the entity):The $name class, The $name widget, The $name file, is, provides, +# specifies, contains, represents, a, an and the. + +ABBREVIATE_BRIEF = "The $name class" \ + "The $name widget" \ + "The $name file" \ + is \ + provides \ + specifies \ + contains \ + represents \ + a \ + an \ + the + +# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then +# doxygen will generate a detailed section even if there is only a brief +# description. +# The default value is: NO. + +ALWAYS_DETAILED_SEC = NO + +# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all +# inherited members of a class in the documentation of that class as if those +# members were ordinary class members. Constructors, destructors and assignment +# operators of the base classes will not be shown. +# The default value is: NO. + +INLINE_INHERITED_MEMB = NO + +# If the FULL_PATH_NAMES tag is set to YES, doxygen will prepend the full path +# before files name in the file list and in the header files. If set to NO the +# shortest path that makes the file name unique will be used +# The default value is: YES. + +FULL_PATH_NAMES = YES + +# The STRIP_FROM_PATH tag can be used to strip a user-defined part of the path. +# Stripping is only done if one of the specified strings matches the left-hand +# part of the path. The tag can be used to show relative paths in the file list. +# If left blank the directory from which doxygen is run is used as the path to +# strip. +# +# Note that you can specify absolute paths here, but also relative paths, which +# will be relative from the directory where doxygen is started. +# This tag requires that the tag FULL_PATH_NAMES is set to YES. + +STRIP_FROM_PATH = + +# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the +# path mentioned in the documentation of a class, which tells the reader which +# header file to include in order to use a class. If left blank only the name of +# the header file containing the class definition is used. Otherwise one should +# specify the list of include paths that are normally passed to the compiler +# using the -I flag. + +STRIP_FROM_INC_PATH = + +# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but +# less readable) file names. This can be useful is your file systems doesn't +# support long names like on DOS, Mac, or CD-ROM. +# The default value is: NO. + +SHORT_NAMES = NO + +# If the JAVADOC_AUTOBRIEF tag is set to YES then doxygen will interpret the +# first line (until the first dot) of a Javadoc-style comment as the brief +# description. If set to NO, the Javadoc-style will behave just like regular Qt- +# style comments (thus requiring an explicit @brief command for a brief +# description.) +# The default value is: NO. + +JAVADOC_AUTOBRIEF = NO + +# If the JAVADOC_BANNER tag is set to YES then doxygen will interpret a line +# such as +# /*************** +# as being the beginning of a Javadoc-style comment "banner". If set to NO, the +# Javadoc-style will behave just like regular comments and it will not be +# interpreted by doxygen. +# The default value is: NO. + +JAVADOC_BANNER = NO + +# If the QT_AUTOBRIEF tag is set to YES then doxygen will interpret the first +# line (until the first dot) of a Qt-style comment as the brief description. If +# set to NO, the Qt-style will behave just like regular Qt-style comments (thus +# requiring an explicit \brief command for a brief description.) +# The default value is: NO. + +QT_AUTOBRIEF = NO + +# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make doxygen treat a +# multi-line C++ special comment block (i.e. a block of //! or /// comments) as +# a brief description. This used to be the default behavior. The new default is +# to treat a multi-line C++ comment block as a detailed description. Set this +# tag to YES if you prefer the old behavior instead. +# +# Note that setting this tag to YES also means that rational rose comments are +# not recognized any more. +# The default value is: NO. + +MULTILINE_CPP_IS_BRIEF = NO + +# By default Python docstrings are displayed as preformatted text and doxygen's +# special commands cannot be used. By setting PYTHON_DOCSTRING to NO the +# doxygen's special commands can be used and the contents of the docstring +# documentation blocks is shown as doxygen documentation. +# The default value is: YES. + +PYTHON_DOCSTRING = YES + +# If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the +# documentation from any documented member that it re-implements. +# The default value is: YES. + +INHERIT_DOCS = YES + +# If the SEPARATE_MEMBER_PAGES tag is set to YES then doxygen will produce a new +# page for each member. If set to NO, the documentation of a member will be part +# of the file/class/namespace that contains it. +# The default value is: NO. + +SEPARATE_MEMBER_PAGES = NO + +# The TAB_SIZE tag can be used to set the number of spaces in a tab. Doxygen +# uses this value to replace tabs by spaces in code fragments. +# Minimum value: 1, maximum value: 16, default value: 4. + +TAB_SIZE = 4 + +# This tag can be used to specify a number of aliases that act as commands in +# the documentation. An alias has the form: +# name=value +# For example adding +# "sideeffect=@par Side Effects:^^" +# will allow you to put the command \sideeffect (or @sideeffect) in the +# documentation, which will result in a user-defined paragraph with heading +# "Side Effects:". Note that you cannot put \n's in the value part of an alias +# to insert newlines (in the resulting output). You can put ^^ in the value part +# of an alias to insert a newline as if a physical newline was in the original +# file. When you need a literal { or } or , in the value part of an alias you +# have to escape them by means of a backslash (\), this can lead to conflicts +# with the commands \{ and \} for these it is advised to use the version @{ and +# @} or use a double escape (\\{ and \\}) + +ALIASES = + +# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources +# only. Doxygen will then generate output that is more tailored for C. For +# instance, some of the names that are used will be different. The list of all +# members will be omitted, etc. +# The default value is: NO. + +OPTIMIZE_OUTPUT_FOR_C = NO + +# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java or +# Python sources only. Doxygen will then generate output that is more tailored +# for that language. For instance, namespaces will be presented as packages, +# qualified scopes will look different, etc. +# The default value is: NO. + +OPTIMIZE_OUTPUT_JAVA = NO + +# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran +# sources. Doxygen will then generate output that is tailored for Fortran. +# The default value is: NO. + +OPTIMIZE_FOR_FORTRAN = NO + +# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL +# sources. Doxygen will then generate output that is tailored for VHDL. +# The default value is: NO. + +OPTIMIZE_OUTPUT_VHDL = NO + +# Set the OPTIMIZE_OUTPUT_SLICE tag to YES if your project consists of Slice +# sources only. Doxygen will then generate output that is more tailored for that +# language. For instance, namespaces will be presented as modules, types will be +# separated into more groups, etc. +# The default value is: NO. + +OPTIMIZE_OUTPUT_SLICE = NO + +# Doxygen selects the parser to use depending on the extension of the files it +# parses. With this tag you can assign which parser to use for a given +# extension. Doxygen has a built-in mapping, but you can override or extend it +# using this tag. The format is ext=language, where ext is a file extension, and +# language is one of the parsers supported by doxygen: IDL, Java, JavaScript, +# Csharp (C#), C, C++, Lex, D, PHP, md (Markdown), Objective-C, Python, Slice, +# VHDL, Fortran (fixed format Fortran: FortranFixed, free formatted Fortran: +# FortranFree, unknown formatted Fortran: Fortran. In the later case the parser +# tries to guess whether the code is fixed or free formatted code, this is the +# default for Fortran type files). For instance to make doxygen treat .inc files +# as Fortran files (default is PHP), and .f files as C (default is Fortran), +# use: inc=Fortran f=C. +# +# Note: For files without extension you can use no_extension as a placeholder. +# +# Note that for custom extensions you also need to set FILE_PATTERNS otherwise +# the files are not read by doxygen. When specifying no_extension you should add +# * to the FILE_PATTERNS. +# +# Note see also the list of default file extension mappings. + +EXTENSION_MAPPING = + +# If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments +# according to the Markdown format, which allows for more readable +# documentation. See https://daringfireball.net/projects/markdown/ for details. +# The output of markdown processing is further processed by doxygen, so you can +# mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in +# case of backward compatibilities issues. +# The default value is: YES. + +MARKDOWN_SUPPORT = YES + +# When the TOC_INCLUDE_HEADINGS tag is set to a non-zero value, all headings up +# to that level are automatically included in the table of contents, even if +# they do not have an id attribute. +# Note: This feature currently applies only to Markdown headings. +# Minimum value: 0, maximum value: 99, default value: 5. +# This tag requires that the tag MARKDOWN_SUPPORT is set to YES. + +TOC_INCLUDE_HEADINGS = 5 + +# The MARKDOWN_ID_STYLE tag can be used to specify the algorithm used to +# generate identifiers for the Markdown headings. Note: Every identifier is +# unique. +# Possible values are: DOXYGEN use a fixed 'autotoc_md' string followed by a +# sequence number starting at 0 and GITHUB use the lower case version of title +# with any whitespace replaced by '-' and punctuation characters removed. +# The default value is: DOXYGEN. +# This tag requires that the tag MARKDOWN_SUPPORT is set to YES. + +MARKDOWN_ID_STYLE = DOXYGEN + +# When enabled doxygen tries to link words that correspond to documented +# classes, or namespaces to their corresponding documentation. Such a link can +# be prevented in individual cases by putting a % sign in front of the word or +# globally by setting AUTOLINK_SUPPORT to NO. +# The default value is: YES. + +AUTOLINK_SUPPORT = YES + +# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want +# to include (a tag file for) the STL sources as input, then you should set this +# tag to YES in order to let doxygen match functions declarations and +# definitions whose arguments contain STL classes (e.g. func(std::string); +# versus func(std::string) {}). This also make the inheritance and collaboration +# diagrams that involve STL classes more complete and accurate. +# The default value is: NO. + +BUILTIN_STL_SUPPORT = NO + +# If you use Microsoft's C++/CLI language, you should set this option to YES to +# enable parsing support. +# The default value is: NO. + +CPP_CLI_SUPPORT = NO + +# Set the SIP_SUPPORT tag to YES if your project consists of sip (see: +# https://www.riverbankcomputing.com/software/sip/intro) sources only. Doxygen +# will parse them like normal C++ but will assume all classes use public instead +# of private inheritance when no explicit protection keyword is present. +# The default value is: NO. + +SIP_SUPPORT = NO + +# For Microsoft's IDL there are propget and propput attributes to indicate +# getter and setter methods for a property. Setting this option to YES will make +# doxygen to replace the get and set methods by a property in the documentation. +# This will only work if the methods are indeed getting or setting a simple +# type. If this is not the case, or you want to show the methods anyway, you +# should set this option to NO. +# The default value is: YES. + +IDL_PROPERTY_SUPPORT = YES + +# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC +# tag is set to YES then doxygen will reuse the documentation of the first +# member in the group (if any) for the other members of the group. By default +# all members of a group must be documented explicitly. +# The default value is: NO. + +DISTRIBUTE_GROUP_DOC = NO + +# If one adds a struct or class to a group and this option is enabled, then also +# any nested class or struct is added to the same group. By default this option +# is disabled and one has to add nested compounds explicitly via \ingroup. +# The default value is: NO. + +GROUP_NESTED_COMPOUNDS = NO + +# Set the SUBGROUPING tag to YES to allow class member groups of the same type +# (for instance a group of public functions) to be put as a subgroup of that +# type (e.g. under the Public Functions section). Set it to NO to prevent +# subgrouping. Alternatively, this can be done per class using the +# \nosubgrouping command. +# The default value is: YES. + +SUBGROUPING = YES + +# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and unions +# are shown inside the group in which they are included (e.g. using \ingroup) +# instead of on a separate page (for HTML and Man pages) or section (for LaTeX +# and RTF). +# +# Note that this feature does not work in combination with +# SEPARATE_MEMBER_PAGES. +# The default value is: NO. + +INLINE_GROUPED_CLASSES = NO + +# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and unions +# with only public data fields or simple typedef fields will be shown inline in +# the documentation of the scope in which they are defined (i.e. file, +# namespace, or group documentation), provided this scope is documented. If set +# to NO, structs, classes, and unions are shown on a separate page (for HTML and +# Man pages) or section (for LaTeX and RTF). +# The default value is: NO. + +INLINE_SIMPLE_STRUCTS = NO + +# When TYPEDEF_HIDES_STRUCT tag is enabled, a typedef of a struct, union, or +# enum is documented as struct, union, or enum with the name of the typedef. So +# typedef struct TypeS {} TypeT, will appear in the documentation as a struct +# with name TypeT. When disabled the typedef will appear as a member of a file, +# namespace, or class. And the struct will be named TypeS. This can typically be +# useful for C code in case the coding convention dictates that all compound +# types are typedef'ed and only the typedef is referenced, never the tag name. +# The default value is: NO. + +TYPEDEF_HIDES_STRUCT = NO + +# The size of the symbol lookup cache can be set using LOOKUP_CACHE_SIZE. This +# cache is used to resolve symbols given their name and scope. Since this can be +# an expensive process and often the same symbol appears multiple times in the +# code, doxygen keeps a cache of pre-resolved symbols. If the cache is too small +# doxygen will become slower. If the cache is too large, memory is wasted. The +# cache size is given by this formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range +# is 0..9, the default is 0, corresponding to a cache size of 2^16=65536 +# symbols. At the end of a run doxygen will report the cache usage and suggest +# the optimal cache size from a speed point of view. +# Minimum value: 0, maximum value: 9, default value: 0. + +LOOKUP_CACHE_SIZE = 0 + +# The NUM_PROC_THREADS specifies the number of threads doxygen is allowed to use +# during processing. When set to 0 doxygen will based this on the number of +# cores available in the system. You can set it explicitly to a value larger +# than 0 to get more control over the balance between CPU load and processing +# speed. At this moment only the input processing can be done using multiple +# threads. Since this is still an experimental feature the default is set to 1, +# which effectively disables parallel processing. Please report any issues you +# encounter. Generating dot graphs in parallel is controlled by the +# DOT_NUM_THREADS setting. +# Minimum value: 0, maximum value: 32, default value: 1. + +NUM_PROC_THREADS = 1 + +# If the TIMESTAMP tag is set different from NO then each generated page will +# contain the date or date and time when the page was generated. Setting this to +# NO can help when comparing the output of multiple runs. +# Possible values are: YES, NO, DATETIME and DATE. +# The default value is: NO. + +TIMESTAMP = NO + +#--------------------------------------------------------------------------- +# Build related configuration options +#--------------------------------------------------------------------------- + +# If the EXTRACT_ALL tag is set to YES, doxygen will assume all entities in +# documentation are documented, even if no documentation was available. Private +# class members and static file members will be hidden unless the +# EXTRACT_PRIVATE respectively EXTRACT_STATIC tags are set to YES. +# Note: This will also disable the warnings about undocumented members that are +# normally produced when WARNINGS is set to YES. +# The default value is: NO. + +EXTRACT_ALL = NO + +# If the EXTRACT_PRIVATE tag is set to YES, all private members of a class will +# be included in the documentation. +# The default value is: NO. + +EXTRACT_PRIVATE = NO + +# If the EXTRACT_PRIV_VIRTUAL tag is set to YES, documented private virtual +# methods of a class will be included in the documentation. +# The default value is: NO. + +EXTRACT_PRIV_VIRTUAL = NO + +# If the EXTRACT_PACKAGE tag is set to YES, all members with package or internal +# scope will be included in the documentation. +# The default value is: NO. + +EXTRACT_PACKAGE = NO + +# If the EXTRACT_STATIC tag is set to YES, all static members of a file will be +# included in the documentation. +# The default value is: NO. + +EXTRACT_STATIC = NO + +# If the EXTRACT_LOCAL_CLASSES tag is set to YES, classes (and structs) defined +# locally in source files will be included in the documentation. If set to NO, +# only classes defined in header files are included. Does not have any effect +# for Java sources. +# The default value is: YES. + +EXTRACT_LOCAL_CLASSES = YES + +# This flag is only useful for Objective-C code. If set to YES, local methods, +# which are defined in the implementation section but not in the interface are +# included in the documentation. If set to NO, only methods in the interface are +# included. +# The default value is: NO. + +EXTRACT_LOCAL_METHODS = NO + +# If this flag is set to YES, the members of anonymous namespaces will be +# extracted and appear in the documentation as a namespace called +# 'anonymous_namespace{file}', where file will be replaced with the base name of +# the file that contains the anonymous namespace. By default anonymous namespace +# are hidden. +# The default value is: NO. + +EXTRACT_ANON_NSPACES = NO + +# If this flag is set to YES, the name of an unnamed parameter in a declaration +# will be determined by the corresponding definition. By default unnamed +# parameters remain unnamed in the output. +# The default value is: YES. + +RESOLVE_UNNAMED_PARAMS = YES + +# If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all +# undocumented members inside documented classes or files. If set to NO these +# members will be included in the various overviews, but no documentation +# section is generated. This option has no effect if EXTRACT_ALL is enabled. +# The default value is: NO. + +HIDE_UNDOC_MEMBERS = NO + +# If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all +# undocumented classes that are normally visible in the class hierarchy. If set +# to NO, these classes will be included in the various overviews. This option +# will also hide undocumented C++ concepts if enabled. This option has no effect +# if EXTRACT_ALL is enabled. +# The default value is: NO. + +HIDE_UNDOC_CLASSES = NO + +# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend +# declarations. If set to NO, these declarations will be included in the +# documentation. +# The default value is: NO. + +HIDE_FRIEND_COMPOUNDS = NO + +# If the HIDE_IN_BODY_DOCS tag is set to YES, doxygen will hide any +# documentation blocks found inside the body of a function. If set to NO, these +# blocks will be appended to the function's detailed documentation block. +# The default value is: NO. + +HIDE_IN_BODY_DOCS = NO + +# The INTERNAL_DOCS tag determines if documentation that is typed after a +# \internal command is included. If the tag is set to NO then the documentation +# will be excluded. Set it to YES to include the internal documentation. +# The default value is: NO. + +INTERNAL_DOCS = NO + +# With the correct setting of option CASE_SENSE_NAMES doxygen will better be +# able to match the capabilities of the underlying filesystem. In case the +# filesystem is case sensitive (i.e. it supports files in the same directory +# whose names only differ in casing), the option must be set to YES to properly +# deal with such files in case they appear in the input. For filesystems that +# are not case sensitive the option should be set to NO to properly deal with +# output files written for symbols that only differ in casing, such as for two +# classes, one named CLASS and the other named Class, and to also support +# references to files without having to specify the exact matching casing. On +# Windows (including Cygwin) and MacOS, users should typically set this option +# to NO, whereas on Linux or other Unix flavors it should typically be set to +# YES. +# Possible values are: SYSTEM, NO and YES. +# The default value is: SYSTEM. + +CASE_SENSE_NAMES = SYSTEM + +# If the HIDE_SCOPE_NAMES tag is set to NO then doxygen will show members with +# their full class and namespace scopes in the documentation. If set to YES, the +# scope will be hidden. +# The default value is: NO. + +HIDE_SCOPE_NAMES = NO + +# If the HIDE_COMPOUND_REFERENCE tag is set to NO (default) then doxygen will +# append additional text to a page's title, such as Class Reference. If set to +# YES the compound reference will be hidden. +# The default value is: NO. + +HIDE_COMPOUND_REFERENCE= NO + +# If the SHOW_HEADERFILE tag is set to YES then the documentation for a class +# will show which file needs to be included to use the class. +# The default value is: YES. + +SHOW_HEADERFILE = YES + +# If the SHOW_INCLUDE_FILES tag is set to YES then doxygen will put a list of +# the files that are included by a file in the documentation of that file. +# The default value is: YES. + +SHOW_INCLUDE_FILES = YES + +# If the SHOW_GROUPED_MEMB_INC tag is set to YES then Doxygen will add for each +# grouped member an include statement to the documentation, telling the reader +# which file to include in order to use the member. +# The default value is: NO. + +SHOW_GROUPED_MEMB_INC = NO + +# If the FORCE_LOCAL_INCLUDES tag is set to YES then doxygen will list include +# files with double quotes in the documentation rather than with sharp brackets. +# The default value is: NO. + +FORCE_LOCAL_INCLUDES = NO + +# If the INLINE_INFO tag is set to YES then a tag [inline] is inserted in the +# documentation for inline members. +# The default value is: YES. + +INLINE_INFO = YES + +# If the SORT_MEMBER_DOCS tag is set to YES then doxygen will sort the +# (detailed) documentation of file and class members alphabetically by member +# name. If set to NO, the members will appear in declaration order. +# The default value is: YES. + +SORT_MEMBER_DOCS = YES + +# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the brief +# descriptions of file, namespace and class members alphabetically by member +# name. If set to NO, the members will appear in declaration order. Note that +# this will also influence the order of the classes in the class list. +# The default value is: NO. + +SORT_BRIEF_DOCS = NO + +# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the +# (brief and detailed) documentation of class members so that constructors and +# destructors are listed first. If set to NO the constructors will appear in the +# respective orders defined by SORT_BRIEF_DOCS and SORT_MEMBER_DOCS. +# Note: If SORT_BRIEF_DOCS is set to NO this option is ignored for sorting brief +# member documentation. +# Note: If SORT_MEMBER_DOCS is set to NO this option is ignored for sorting +# detailed member documentation. +# The default value is: NO. + +SORT_MEMBERS_CTORS_1ST = NO + +# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the hierarchy +# of group names into alphabetical order. If set to NO the group names will +# appear in their defined order. +# The default value is: NO. + +SORT_GROUP_NAMES = NO + +# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be sorted by +# fully-qualified names, including namespaces. If set to NO, the class list will +# be sorted only by class name, not including the namespace part. +# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. +# Note: This option applies only to the class list, not to the alphabetical +# list. +# The default value is: NO. + +SORT_BY_SCOPE_NAME = NO + +# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to do proper +# type resolution of all parameters of a function it will reject a match between +# the prototype and the implementation of a member function even if there is +# only one candidate or it is obvious which candidate to choose by doing a +# simple string match. By disabling STRICT_PROTO_MATCHING doxygen will still +# accept a match between prototype and implementation in such cases. +# The default value is: NO. + +STRICT_PROTO_MATCHING = NO + +# The GENERATE_TODOLIST tag can be used to enable (YES) or disable (NO) the todo +# list. This list is created by putting \todo commands in the documentation. +# The default value is: YES. + +GENERATE_TODOLIST = YES + +# The GENERATE_TESTLIST tag can be used to enable (YES) or disable (NO) the test +# list. This list is created by putting \test commands in the documentation. +# The default value is: YES. + +GENERATE_TESTLIST = YES + +# The GENERATE_BUGLIST tag can be used to enable (YES) or disable (NO) the bug +# list. This list is created by putting \bug commands in the documentation. +# The default value is: YES. + +GENERATE_BUGLIST = YES + +# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or disable (NO) +# the deprecated list. This list is created by putting \deprecated commands in +# the documentation. +# The default value is: YES. + +GENERATE_DEPRECATEDLIST= YES + +# The ENABLED_SECTIONS tag can be used to enable conditional documentation +# sections, marked by \if ... \endif and \cond +# ... \endcond blocks. + +ENABLED_SECTIONS = + +# The MAX_INITIALIZER_LINES tag determines the maximum number of lines that the +# initial value of a variable or macro / define can have for it to appear in the +# documentation. If the initializer consists of more lines than specified here +# it will be hidden. Use a value of 0 to hide initializers completely. The +# appearance of the value of individual variables and macros / defines can be +# controlled using \showinitializer or \hideinitializer command in the +# documentation regardless of this setting. +# Minimum value: 0, maximum value: 10000, default value: 30. + +MAX_INITIALIZER_LINES = 30 + +# Set the SHOW_USED_FILES tag to NO to disable the list of files generated at +# the bottom of the documentation of classes and structs. If set to YES, the +# list will mention the files that were used to generate the documentation. +# The default value is: YES. + +SHOW_USED_FILES = YES + +# Set the SHOW_FILES tag to NO to disable the generation of the Files page. This +# will remove the Files entry from the Quick Index and from the Folder Tree View +# (if specified). +# The default value is: YES. + +SHOW_FILES = YES + +# Set the SHOW_NAMESPACES tag to NO to disable the generation of the Namespaces +# page. This will remove the Namespaces entry from the Quick Index and from the +# Folder Tree View (if specified). +# The default value is: YES. + +SHOW_NAMESPACES = YES + +# The FILE_VERSION_FILTER tag can be used to specify a program or script that +# doxygen should invoke to get the current version for each file (typically from +# the version control system). Doxygen will invoke the program by executing (via +# popen()) the command command input-file, where command is the value of the +# FILE_VERSION_FILTER tag, and input-file is the name of an input file provided +# by doxygen. Whatever the program writes to standard output is used as the file +# version. For an example see the documentation. + +FILE_VERSION_FILTER = + +# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed +# by doxygen. The layout file controls the global structure of the generated +# output files in an output format independent way. To create the layout file +# that represents doxygen's defaults, run doxygen with the -l option. You can +# optionally specify a file name after the option, if omitted DoxygenLayout.xml +# will be used as the name of the layout file. See also section "Changing the +# layout of pages" for information. +# +# Note that if you run doxygen from a directory containing a file called +# DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE +# tag is left empty. + +LAYOUT_FILE = + +# The CITE_BIB_FILES tag can be used to specify one or more bib files containing +# the reference definitions. This must be a list of .bib files. The .bib +# extension is automatically appended if omitted. This requires the bibtex tool +# to be installed. See also https://en.wikipedia.org/wiki/BibTeX for more info. +# For LaTeX the style of the bibliography can be controlled using +# LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the +# search path. See also \cite for info how to create references. + +CITE_BIB_FILES = + +#--------------------------------------------------------------------------- +# Configuration options related to warning and progress messages +#--------------------------------------------------------------------------- + +# The QUIET tag can be used to turn on/off the messages that are generated to +# standard output by doxygen. If QUIET is set to YES this implies that the +# messages are off. +# The default value is: NO. + +QUIET = NO + +# The WARNINGS tag can be used to turn on/off the warning messages that are +# generated to standard error (stderr) by doxygen. If WARNINGS is set to YES +# this implies that the warnings are on. +# +# Tip: Turn warnings on while writing the documentation. +# The default value is: YES. + +WARNINGS = YES + +# If the WARN_IF_UNDOCUMENTED tag is set to YES then doxygen will generate +# warnings for undocumented members. If EXTRACT_ALL is set to YES then this flag +# will automatically be disabled. +# The default value is: YES. + +WARN_IF_UNDOCUMENTED = YES + +# If the WARN_IF_DOC_ERROR tag is set to YES, doxygen will generate warnings for +# potential errors in the documentation, such as documenting some parameters in +# a documented function twice, or documenting parameters that don't exist or +# using markup commands wrongly. +# The default value is: YES. + +WARN_IF_DOC_ERROR = YES + +# If WARN_IF_INCOMPLETE_DOC is set to YES, doxygen will warn about incomplete +# function parameter documentation. If set to NO, doxygen will accept that some +# parameters have no documentation without warning. +# The default value is: YES. + +WARN_IF_INCOMPLETE_DOC = YES + +# This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that +# are documented, but have no documentation for their parameters or return +# value. If set to NO, doxygen will only warn about wrong parameter +# documentation, but not about the absence of documentation. If EXTRACT_ALL is +# set to YES then this flag will automatically be disabled. See also +# WARN_IF_INCOMPLETE_DOC +# The default value is: NO. + +WARN_NO_PARAMDOC = NO + +# If WARN_IF_UNDOC_ENUM_VAL option is set to YES, doxygen will warn about +# undocumented enumeration values. If set to NO, doxygen will accept +# undocumented enumeration values. If EXTRACT_ALL is set to YES then this flag +# will automatically be disabled. +# The default value is: NO. + +WARN_IF_UNDOC_ENUM_VAL = NO + +# If the WARN_AS_ERROR tag is set to YES then doxygen will immediately stop when +# a warning is encountered. If the WARN_AS_ERROR tag is set to FAIL_ON_WARNINGS +# then doxygen will continue running as if WARN_AS_ERROR tag is set to NO, but +# at the end of the doxygen process doxygen will return with a non-zero status. +# If the WARN_AS_ERROR tag is set to FAIL_ON_WARNINGS_PRINT then doxygen behaves +# like FAIL_ON_WARNINGS but in case no WARN_LOGFILE is defined doxygen will not +# write the warning messages in between other messages but write them at the end +# of a run, in case a WARN_LOGFILE is defined the warning messages will be +# besides being in the defined file also be shown at the end of a run, unless +# the WARN_LOGFILE is defined as - i.e. standard output (stdout) in that case +# the behavior will remain as with the setting FAIL_ON_WARNINGS. +# Possible values are: NO, YES, FAIL_ON_WARNINGS and FAIL_ON_WARNINGS_PRINT. +# The default value is: NO. + +WARN_AS_ERROR = NO + +# The WARN_FORMAT tag determines the format of the warning messages that doxygen +# can produce. The string should contain the $file, $line, and $text tags, which +# will be replaced by the file and line number from which the warning originated +# and the warning text. Optionally the format may contain $version, which will +# be replaced by the version of the file (if it could be obtained via +# FILE_VERSION_FILTER) +# See also: WARN_LINE_FORMAT +# The default value is: $file:$line: $text. + +WARN_FORMAT = "$file:$line: $text" + +# In the $text part of the WARN_FORMAT command it is possible that a reference +# to a more specific place is given. To make it easier to jump to this place +# (outside of doxygen) the user can define a custom "cut" / "paste" string. +# Example: +# WARN_LINE_FORMAT = "'vi $file +$line'" +# See also: WARN_FORMAT +# The default value is: at line $line of file $file. + +WARN_LINE_FORMAT = "at line $line of file $file" + +# The WARN_LOGFILE tag can be used to specify a file to which warning and error +# messages should be written. If left blank the output is written to standard +# error (stderr). In case the file specified cannot be opened for writing the +# warning and error messages are written to standard error. When as file - is +# specified the warning and error messages are written to standard output +# (stdout). + +WARN_LOGFILE = + +#--------------------------------------------------------------------------- +# Configuration options related to the input files +#--------------------------------------------------------------------------- + +# The INPUT tag is used to specify the files and/or directories that contain +# documented source files. You may enter file names like myfile.cpp or +# directories like /usr/src/myproject. Separate the files or directories with +# spaces. See also FILE_PATTERNS and EXTENSION_MAPPING +# Note: If this tag is empty the current directory is searched. + +INPUT = + +# This tag can be used to specify the character encoding of the source files +# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses +# libiconv (or the iconv built into libc) for the transcoding. See the libiconv +# documentation (see: +# https://www.gnu.org/software/libiconv/) for the list of possible encodings. +# See also: INPUT_FILE_ENCODING +# The default value is: UTF-8. + +INPUT_ENCODING = UTF-8 + +# This tag can be used to specify the character encoding of the source files +# that doxygen parses The INPUT_FILE_ENCODING tag can be used to specify +# character encoding on a per file pattern basis. Doxygen will compare the file +# name with each pattern and apply the encoding instead of the default +# INPUT_ENCODING) if there is a match. The character encodings are a list of the +# form: pattern=encoding (like *.php=ISO-8859-1). See cfg_input_encoding +# "INPUT_ENCODING" for further information on supported encodings. + +INPUT_FILE_ENCODING = + +# If the value of the INPUT tag contains directories, you can use the +# FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and +# *.h) to filter out the source-files in the directories. +# +# Note that for custom extensions or not directly supported extensions you also +# need to set EXTENSION_MAPPING for the extension otherwise the files are not +# read by doxygen. +# +# Note the list of default checked file patterns might differ from the list of +# default file extension mappings. +# +# If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cxxm, +# *.cpp, *.cppm, *.c++, *.c++m, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, *.idl, +# *.ddl, *.odl, *.h, *.hh, *.hxx, *.hpp, *.h++, *.ixx, *.l, *.cs, *.d, *.php, +# *.php4, *.php5, *.phtml, *.inc, *.m, *.markdown, *.md, *.mm, *.dox (to be +# provided as doxygen C comment), *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, +# *.f18, *.f, *.for, *.vhd, *.vhdl, *.ucf, *.qsf and *.ice. + +FILE_PATTERNS = *.c \ + *.cc \ + *.cxx \ + *.cxxm \ + *.cpp \ + *.cppm \ + *.c++ \ + *.c++m \ + *.java \ + *.ii \ + *.ixx \ + *.ipp \ + *.i++ \ + *.inl \ + *.idl \ + *.ddl \ + *.odl \ + *.h \ + *.hh \ + *.hxx \ + *.hpp \ + *.h++ \ + *.ixx \ + *.l \ + *.cs \ + *.d \ + *.php \ + *.php4 \ + *.php5 \ + *.phtml \ + *.inc \ + *.m \ + *.markdown \ + *.md \ + *.mm \ + *.dox \ + *.py \ + *.pyw \ + *.f90 \ + *.f95 \ + *.f03 \ + *.f08 \ + *.f18 \ + *.f \ + *.for \ + *.vhd \ + *.vhdl \ + *.ucf \ + *.qsf \ + *.ice + +# The RECURSIVE tag can be used to specify whether or not subdirectories should +# be searched for input files as well. +# The default value is: NO. + +RECURSIVE = YES + +# The EXCLUDE tag can be used to specify files and/or directories that should be +# excluded from the INPUT source files. This way you can easily exclude a +# subdirectory from a directory tree whose root is specified with the INPUT tag. +# +# Note that relative paths are relative to the directory from which doxygen is +# run. + +EXCLUDE = + +# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or +# directories that are symbolic links (a Unix file system feature) are excluded +# from the input. +# The default value is: NO. + +EXCLUDE_SYMLINKS = NO + +# If the value of the INPUT tag contains directories, you can use the +# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude +# certain files from those directories. +# +# Note that the wildcards are matched against the file with absolute path, so to +# exclude all test directories for example use the pattern */test/* + +EXCLUDE_PATTERNS = + +# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names +# (namespaces, classes, functions, etc.) that should be excluded from the +# output. The symbol name can be a fully qualified name, a word, or if the +# wildcard * is used, a substring. Examples: ANamespace, AClass, +# ANamespace::AClass, ANamespace::*Test + +EXCLUDE_SYMBOLS = + +# The EXAMPLE_PATH tag can be used to specify one or more files or directories +# that contain example code fragments that are included (see the \include +# command). + +EXAMPLE_PATH = + +# If the value of the EXAMPLE_PATH tag contains directories, you can use the +# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and +# *.h) to filter out the source-files in the directories. If left blank all +# files are included. + +EXAMPLE_PATTERNS = * + +# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be +# searched for input files to be used with the \include or \dontinclude commands +# irrespective of the value of the RECURSIVE tag. +# The default value is: NO. + +EXAMPLE_RECURSIVE = NO + +# The IMAGE_PATH tag can be used to specify one or more files or directories +# that contain images that are to be included in the documentation (see the +# \image command). + +IMAGE_PATH = + +# The INPUT_FILTER tag can be used to specify a program that doxygen should +# invoke to filter for each input file. Doxygen will invoke the filter program +# by executing (via popen()) the command: +# +# +# +# where is the value of the INPUT_FILTER tag, and is the +# name of an input file. Doxygen will then use the output that the filter +# program writes to standard output. If FILTER_PATTERNS is specified, this tag +# will be ignored. +# +# Note that the filter must not add or remove lines; it is applied before the +# code is scanned, but not when the output code is generated. If lines are added +# or removed, the anchors will not be placed correctly. +# +# Note that doxygen will use the data processed and written to standard output +# for further processing, therefore nothing else, like debug statements or used +# commands (so in case of a Windows batch file always use @echo OFF), should be +# written to standard output. +# +# Note that for custom extensions or not directly supported extensions you also +# need to set EXTENSION_MAPPING for the extension otherwise the files are not +# properly processed by doxygen. + +INPUT_FILTER = + +# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern +# basis. Doxygen will compare the file name with each pattern and apply the +# filter if there is a match. The filters are a list of the form: pattern=filter +# (like *.cpp=my_cpp_filter). See INPUT_FILTER for further information on how +# filters are used. If the FILTER_PATTERNS tag is empty or if none of the +# patterns match the file name, INPUT_FILTER is applied. +# +# Note that for custom extensions or not directly supported extensions you also +# need to set EXTENSION_MAPPING for the extension otherwise the files are not +# properly processed by doxygen. + +FILTER_PATTERNS = + +# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using +# INPUT_FILTER) will also be used to filter the input files that are used for +# producing the source files to browse (i.e. when SOURCE_BROWSER is set to YES). +# The default value is: NO. + +FILTER_SOURCE_FILES = NO + +# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file +# pattern. A pattern will override the setting for FILTER_PATTERN (if any) and +# it is also possible to disable source filtering for a specific pattern using +# *.ext= (so without naming a filter). +# This tag requires that the tag FILTER_SOURCE_FILES is set to YES. + +FILTER_SOURCE_PATTERNS = + +# If the USE_MDFILE_AS_MAINPAGE tag refers to the name of a markdown file that +# is part of the input, its contents will be placed on the main page +# (index.html). This can be useful if you have a project on for instance GitHub +# and want to reuse the introduction page also for the doxygen output. + +USE_MDFILE_AS_MAINPAGE = + +# The Fortran standard specifies that for fixed formatted Fortran code all +# characters from position 72 are to be considered as comment. A common +# extension is to allow longer lines before the automatic comment starts. The +# setting FORTRAN_COMMENT_AFTER will also make it possible that longer lines can +# be processed before the automatic comment starts. +# Minimum value: 7, maximum value: 10000, default value: 72. + +FORTRAN_COMMENT_AFTER = 72 + +#--------------------------------------------------------------------------- +# Configuration options related to source browsing +#--------------------------------------------------------------------------- + +# If the SOURCE_BROWSER tag is set to YES then a list of source files will be +# generated. Documented entities will be cross-referenced with these sources. +# +# Note: To get rid of all source code in the generated output, make sure that +# also VERBATIM_HEADERS is set to NO. +# The default value is: NO. + +SOURCE_BROWSER = NO + +# Setting the INLINE_SOURCES tag to YES will include the body of functions, +# classes and enums directly into the documentation. +# The default value is: NO. + +INLINE_SOURCES = NO + +# Setting the STRIP_CODE_COMMENTS tag to YES will instruct doxygen to hide any +# special comment blocks from generated source code fragments. Normal C, C++ and +# Fortran comments will always remain visible. +# The default value is: YES. + +STRIP_CODE_COMMENTS = YES + +# If the REFERENCED_BY_RELATION tag is set to YES then for each documented +# entity all documented functions referencing it will be listed. +# The default value is: NO. + +REFERENCED_BY_RELATION = NO + +# If the REFERENCES_RELATION tag is set to YES then for each documented function +# all documented entities called/used by that function will be listed. +# The default value is: NO. + +REFERENCES_RELATION = NO + +# If the REFERENCES_LINK_SOURCE tag is set to YES and SOURCE_BROWSER tag is set +# to YES then the hyperlinks from functions in REFERENCES_RELATION and +# REFERENCED_BY_RELATION lists will link to the source code. Otherwise they will +# link to the documentation. +# The default value is: YES. + +REFERENCES_LINK_SOURCE = YES + +# If SOURCE_TOOLTIPS is enabled (the default) then hovering a hyperlink in the +# source code will show a tooltip with additional information such as prototype, +# brief description and links to the definition and documentation. Since this +# will make the HTML file larger and loading of large files a bit slower, you +# can opt to disable this feature. +# The default value is: YES. +# This tag requires that the tag SOURCE_BROWSER is set to YES. + +SOURCE_TOOLTIPS = YES + +# If the USE_HTAGS tag is set to YES then the references to source code will +# point to the HTML generated by the htags(1) tool instead of doxygen built-in +# source browser. The htags tool is part of GNU's global source tagging system +# (see https://www.gnu.org/software/global/global.html). You will need version +# 4.8.6 or higher. +# +# To use it do the following: +# - Install the latest version of global +# - Enable SOURCE_BROWSER and USE_HTAGS in the configuration file +# - Make sure the INPUT points to the root of the source tree +# - Run doxygen as normal +# +# Doxygen will invoke htags (and that will in turn invoke gtags), so these +# tools must be available from the command line (i.e. in the search path). +# +# The result: instead of the source browser generated by doxygen, the links to +# source code will now point to the output of htags. +# The default value is: NO. +# This tag requires that the tag SOURCE_BROWSER is set to YES. + +USE_HTAGS = NO + +# If the VERBATIM_HEADERS tag is set the YES then doxygen will generate a +# verbatim copy of the header file for each class for which an include is +# specified. Set to NO to disable this. +# See also: Section \class. +# The default value is: YES. + +VERBATIM_HEADERS = NO + +# If the CLANG_ASSISTED_PARSING tag is set to YES then doxygen will use the +# clang parser (see: +# http://clang.llvm.org/) for more accurate parsing at the cost of reduced +# performance. This can be particularly helpful with template rich C++ code for +# which doxygen's built-in parser lacks the necessary type information. +# Note: The availability of this option depends on whether or not doxygen was +# generated with the -Duse_libclang=ON option for CMake. +# The default value is: NO. + +CLANG_ASSISTED_PARSING = NO + +# If the CLANG_ASSISTED_PARSING tag is set to YES and the CLANG_ADD_INC_PATHS +# tag is set to YES then doxygen will add the directory of each input to the +# include path. +# The default value is: YES. +# This tag requires that the tag CLANG_ASSISTED_PARSING is set to YES. + +CLANG_ADD_INC_PATHS = YES + +# If clang assisted parsing is enabled you can provide the compiler with command +# line options that you would normally use when invoking the compiler. Note that +# the include paths will already be set by doxygen for the files and directories +# specified with INPUT and INCLUDE_PATH. +# This tag requires that the tag CLANG_ASSISTED_PARSING is set to YES. + +CLANG_OPTIONS = + +# If clang assisted parsing is enabled you can provide the clang parser with the +# path to the directory containing a file called compile_commands.json. This +# file is the compilation database (see: +# http://clang.llvm.org/docs/HowToSetupToolingForLLVM.html) containing the +# options used when the source files were built. This is equivalent to +# specifying the -p option to a clang tool, such as clang-check. These options +# will then be passed to the parser. Any options specified with CLANG_OPTIONS +# will be added as well. +# Note: The availability of this option depends on whether or not doxygen was +# generated with the -Duse_libclang=ON option for CMake. + +CLANG_DATABASE_PATH = + +#--------------------------------------------------------------------------- +# Configuration options related to the alphabetical class index +#--------------------------------------------------------------------------- + +# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index of all +# compounds will be generated. Enable this if the project contains a lot of +# classes, structs, unions or interfaces. +# The default value is: YES. + +ALPHABETICAL_INDEX = YES + +# The IGNORE_PREFIX tag can be used to specify a prefix (or a list of prefixes) +# that should be ignored while generating the index headers. The IGNORE_PREFIX +# tag works for classes, function and member names. The entity will be placed in +# the alphabetical list under the first letter of the entity name that remains +# after removing the prefix. +# This tag requires that the tag ALPHABETICAL_INDEX is set to YES. + +IGNORE_PREFIX = + +#--------------------------------------------------------------------------- +# Configuration options related to the HTML output +#--------------------------------------------------------------------------- + +# If the GENERATE_HTML tag is set to YES, doxygen will generate HTML output +# The default value is: YES. + +GENERATE_HTML = YES + +# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. If a +# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of +# it. +# The default directory is: html. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_OUTPUT = html + +# The HTML_FILE_EXTENSION tag can be used to specify the file extension for each +# generated HTML page (for example: .htm, .php, .asp). +# The default value is: .html. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_FILE_EXTENSION = .html + +# The HTML_HEADER tag can be used to specify a user-defined HTML header file for +# each generated HTML page. If the tag is left blank doxygen will generate a +# standard header. +# +# To get valid HTML the header file that includes any scripts and style sheets +# that doxygen needs, which is dependent on the configuration options used (e.g. +# the setting GENERATE_TREEVIEW). It is highly recommended to start with a +# default header using +# doxygen -w html new_header.html new_footer.html new_stylesheet.css +# YourConfigFile +# and then modify the file new_header.html. See also section "Doxygen usage" +# for information on how to generate the default header that doxygen normally +# uses. +# Note: The header is subject to change so you typically have to regenerate the +# default header when upgrading to a newer version of doxygen. For a description +# of the possible markers and block names see the documentation. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_HEADER = + +# The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each +# generated HTML page. If the tag is left blank doxygen will generate a standard +# footer. See HTML_HEADER for more information on how to generate a default +# footer and what special commands can be used inside the footer. See also +# section "Doxygen usage" for information on how to generate the default footer +# that doxygen normally uses. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_FOOTER = + +# The HTML_STYLESHEET tag can be used to specify a user-defined cascading style +# sheet that is used by each HTML page. It can be used to fine-tune the look of +# the HTML output. If left blank doxygen will generate a default style sheet. +# See also section "Doxygen usage" for information on how to generate the style +# sheet that doxygen normally uses. +# Note: It is recommended to use HTML_EXTRA_STYLESHEET instead of this tag, as +# it is more robust and this tag (HTML_STYLESHEET) will in the future become +# obsolete. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_STYLESHEET = + +# The HTML_EXTRA_STYLESHEET tag can be used to specify additional user-defined +# cascading style sheets that are included after the standard style sheets +# created by doxygen. Using this option one can overrule certain style aspects. +# This is preferred over using HTML_STYLESHEET since it does not replace the +# standard style sheet and is therefore more robust against future updates. +# Doxygen will copy the style sheet files to the output directory. +# Note: The order of the extra style sheet files is of importance (e.g. the last +# style sheet in the list overrules the setting of the previous ones in the +# list). +# Note: Since the styling of scrollbars can currently not be overruled in +# Webkit/Chromium, the styling will be left out of the default doxygen.css if +# one or more extra stylesheets have been specified. So if scrollbar +# customization is desired it has to be added explicitly. For an example see the +# documentation. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_EXTRA_STYLESHEET = + +# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or +# other source files which should be copied to the HTML output directory. Note +# that these files will be copied to the base HTML output directory. Use the +# $relpath^ marker in the HTML_HEADER and/or HTML_FOOTER files to load these +# files. In the HTML_STYLESHEET file, use the file name only. Also note that the +# files will be copied as-is; there are no commands or markers available. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_EXTRA_FILES = + +# The HTML_COLORSTYLE tag can be used to specify if the generated HTML output +# should be rendered with a dark or light theme. +# Possible values are: LIGHT always generate light mode output, DARK always +# generate dark mode output, AUTO_LIGHT automatically set the mode according to +# the user preference, use light mode if no preference is set (the default), +# AUTO_DARK automatically set the mode according to the user preference, use +# dark mode if no preference is set and TOGGLE allow to user to switch between +# light and dark mode via a button. +# The default value is: AUTO_LIGHT. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE = AUTO_LIGHT + +# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen +# will adjust the colors in the style sheet and background images according to +# this color. Hue is specified as an angle on a color-wheel, see +# https://en.wikipedia.org/wiki/Hue for more information. For instance the value +# 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300 +# purple, and 360 is red again. +# Minimum value: 0, maximum value: 359, default value: 220. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_HUE = 220 + +# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors +# in the HTML output. For a value of 0 the output will use gray-scales only. A +# value of 255 will produce the most vivid colors. +# Minimum value: 0, maximum value: 255, default value: 100. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_SAT = 100 + +# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to the +# luminance component of the colors in the HTML output. Values below 100 +# gradually make the output lighter, whereas values above 100 make the output +# darker. The value divided by 100 is the actual gamma applied, so 80 represents +# a gamma of 0.8, The value 220 represents a gamma of 2.2, and 100 does not +# change the gamma. +# Minimum value: 40, maximum value: 240, default value: 80. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_GAMMA = 80 + +# If the HTML_DYNAMIC_MENUS tag is set to YES then the generated HTML +# documentation will contain a main index with vertical navigation menus that +# are dynamically created via JavaScript. If disabled, the navigation index will +# consists of multiple levels of tabs that are statically embedded in every HTML +# page. Disable this option to support browsers that do not have JavaScript, +# like the Qt help browser. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_DYNAMIC_MENUS = YES + +# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML +# documentation will contain sections that can be hidden and shown after the +# page has loaded. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_DYNAMIC_SECTIONS = NO + +# If the HTML_CODE_FOLDING tag is set to YES then classes and functions can be +# dynamically folded and expanded in the generated HTML source code. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_CODE_FOLDING = YES + +# With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries +# shown in the various tree structured indices initially; the user can expand +# and collapse entries dynamically later on. Doxygen will expand the tree to +# such a level that at most the specified number of entries are visible (unless +# a fully collapsed tree already exceeds this amount). So setting the number of +# entries 1 will produce a full collapsed tree by default. 0 is a special value +# representing an infinite number of entries and will result in a full expanded +# tree by default. +# Minimum value: 0, maximum value: 9999, default value: 100. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_INDEX_NUM_ENTRIES = 100 + +# If the GENERATE_DOCSET tag is set to YES, additional index files will be +# generated that can be used as input for Apple's Xcode 3 integrated development +# environment (see: +# https://developer.apple.com/xcode/), introduced with OSX 10.5 (Leopard). To +# create a documentation set, doxygen will generate a Makefile in the HTML +# output directory. Running make will produce the docset in that directory and +# running make install will install the docset in +# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at +# startup. See https://developer.apple.com/library/archive/featuredarticles/Doxy +# genXcode/_index.html for more information. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_DOCSET = NO + +# This tag determines the name of the docset feed. A documentation feed provides +# an umbrella under which multiple documentation sets from a single provider +# (such as a company or product suite) can be grouped. +# The default value is: Doxygen generated docs. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_FEEDNAME = "Doxygen generated docs" + +# This tag determines the URL of the docset feed. A documentation feed provides +# an umbrella under which multiple documentation sets from a single provider +# (such as a company or product suite) can be grouped. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_FEEDURL = + +# This tag specifies a string that should uniquely identify the documentation +# set bundle. This should be a reverse domain-name style string, e.g. +# com.mycompany.MyDocSet. Doxygen will append .docset to the name. +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_BUNDLE_ID = org.doxygen.Project + +# The DOCSET_PUBLISHER_ID tag specifies a string that should uniquely identify +# the documentation publisher. This should be a reverse domain-name style +# string, e.g. com.mycompany.MyDocSet.documentation. +# The default value is: org.doxygen.Publisher. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_PUBLISHER_ID = org.doxygen.Publisher + +# The DOCSET_PUBLISHER_NAME tag identifies the documentation publisher. +# The default value is: Publisher. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_PUBLISHER_NAME = Publisher + +# If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three +# additional HTML index files: index.hhp, index.hhc, and index.hhk. The +# index.hhp is a project file that can be read by Microsoft's HTML Help Workshop +# on Windows. In the beginning of 2021 Microsoft took the original page, with +# a.o. the download links, offline the HTML help workshop was already many years +# in maintenance mode). You can download the HTML help workshop from the web +# archives at Installation executable (see: +# http://web.archive.org/web/20160201063255/http://download.microsoft.com/downlo +# ad/0/A/9/0A939EF6-E31C-430F-A3DF-DFAE7960D564/htmlhelp.exe). +# +# The HTML Help Workshop contains a compiler that can convert all HTML output +# generated by doxygen into a single compiled HTML file (.chm). Compiled HTML +# files are now used as the Windows 98 help format, and will replace the old +# Windows help format (.hlp) on all Windows platforms in the future. Compressed +# HTML files also contain an index, a table of contents, and you can search for +# words in the documentation. The HTML workshop also contains a viewer for +# compressed HTML files. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_HTMLHELP = NO + +# The CHM_FILE tag can be used to specify the file name of the resulting .chm +# file. You can add a path in front of the file if the result should not be +# written to the html output directory. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +CHM_FILE = + +# The HHC_LOCATION tag can be used to specify the location (absolute path +# including file name) of the HTML help compiler (hhc.exe). If non-empty, +# doxygen will try to run the HTML help compiler on the generated index.hhp. +# The file has to be specified with full path. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +HHC_LOCATION = + +# The GENERATE_CHI flag controls if a separate .chi index file is generated +# (YES) or that it should be included in the main .chm file (NO). +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +GENERATE_CHI = NO + +# The CHM_INDEX_ENCODING is used to encode HtmlHelp index (hhk), content (hhc) +# and project file content. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +CHM_INDEX_ENCODING = + +# The BINARY_TOC flag controls whether a binary table of contents is generated +# (YES) or a normal table of contents (NO) in the .chm file. Furthermore it +# enables the Previous and Next buttons. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +BINARY_TOC = NO + +# The TOC_EXPAND flag can be set to YES to add extra items for group members to +# the table of contents of the HTML help documentation and to the tree view. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +TOC_EXPAND = NO + +# The SITEMAP_URL tag is used to specify the full URL of the place where the +# generated documentation will be placed on the server by the user during the +# deployment of the documentation. The generated sitemap is called sitemap.xml +# and placed on the directory specified by HTML_OUTPUT. In case no SITEMAP_URL +# is specified no sitemap is generated. For information about the sitemap +# protocol see https://www.sitemaps.org +# This tag requires that the tag GENERATE_HTML is set to YES. + +SITEMAP_URL = + +# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and +# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that +# can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help +# (.qch) of the generated HTML documentation. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_QHP = NO + +# If the QHG_LOCATION tag is specified, the QCH_FILE tag can be used to specify +# the file name of the resulting .qch file. The path specified is relative to +# the HTML output folder. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QCH_FILE = + +# The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help +# Project output. For more information please see Qt Help Project / Namespace +# (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#namespace). +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_NAMESPACE = org.doxygen.Project + +# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt +# Help Project output. For more information please see Qt Help Project / Virtual +# Folders (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#virtual-folders). +# The default value is: doc. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_VIRTUAL_FOLDER = doc + +# If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom +# filter to add. For more information please see Qt Help Project / Custom +# Filters (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-filters). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_CUST_FILTER_NAME = + +# The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the +# custom filter to add. For more information please see Qt Help Project / Custom +# Filters (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-filters). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_CUST_FILTER_ATTRS = + +# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this +# project's filter section matches. Qt Help Project / Filter Attributes (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#filter-attributes). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_SECT_FILTER_ATTRS = + +# The QHG_LOCATION tag can be used to specify the location (absolute path +# including file name) of Qt's qhelpgenerator. If non-empty doxygen will try to +# run qhelpgenerator on the generated .qhp file. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHG_LOCATION = + +# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files will be +# generated, together with the HTML files, they form an Eclipse help plugin. To +# install this plugin and make it available under the help contents menu in +# Eclipse, the contents of the directory containing the HTML and XML files needs +# to be copied into the plugins directory of eclipse. The name of the directory +# within the plugins directory should be the same as the ECLIPSE_DOC_ID value. +# After copying Eclipse needs to be restarted before the help appears. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_ECLIPSEHELP = NO + +# A unique identifier for the Eclipse help plugin. When installing the plugin +# the directory name containing the HTML and XML files should also have this +# name. Each documentation set should have its own identifier. +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_ECLIPSEHELP is set to YES. + +ECLIPSE_DOC_ID = org.doxygen.Project + +# If you want full control over the layout of the generated HTML pages it might +# be necessary to disable the index and replace it with your own. The +# DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) at top +# of each HTML page. A value of NO enables the index and the value YES disables +# it. Since the tabs in the index contain the same information as the navigation +# tree, you can set this option to YES if you also set GENERATE_TREEVIEW to YES. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +DISABLE_INDEX = NO + +# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index +# structure should be generated to display hierarchical information. If the tag +# value is set to YES, a side panel will be generated containing a tree-like +# index structure (just like the one that is generated for HTML Help). For this +# to work a browser that supports JavaScript, DHTML, CSS and frames is required +# (i.e. any modern browser). Windows users are probably better off using the +# HTML help feature. Via custom style sheets (see HTML_EXTRA_STYLESHEET) one can +# further fine tune the look of the index (see "Fine-tuning the output"). As an +# example, the default style sheet generated by doxygen has an example that +# shows how to put an image at the root of the tree instead of the PROJECT_NAME. +# Since the tree basically has the same information as the tab index, you could +# consider setting DISABLE_INDEX to YES when enabling this option. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_TREEVIEW = NO + +# When both GENERATE_TREEVIEW and DISABLE_INDEX are set to YES, then the +# FULL_SIDEBAR option determines if the side bar is limited to only the treeview +# area (value NO) or if it should extend to the full height of the window (value +# YES). Setting this to YES gives a layout similar to +# https://docs.readthedocs.io with more room for contents, but less room for the +# project logo, title, and description. If either GENERATE_TREEVIEW or +# DISABLE_INDEX is set to NO, this option has no effect. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +FULL_SIDEBAR = NO + +# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that +# doxygen will group on one line in the generated HTML documentation. +# +# Note that a value of 0 will completely suppress the enum values from appearing +# in the overview section. +# Minimum value: 0, maximum value: 20, default value: 4. +# This tag requires that the tag GENERATE_HTML is set to YES. + +ENUM_VALUES_PER_LINE = 4 + +# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be used +# to set the initial width (in pixels) of the frame in which the tree is shown. +# Minimum value: 0, maximum value: 1500, default value: 250. +# This tag requires that the tag GENERATE_HTML is set to YES. + +TREEVIEW_WIDTH = 250 + +# If the EXT_LINKS_IN_WINDOW option is set to YES, doxygen will open links to +# external symbols imported via tag files in a separate window. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +EXT_LINKS_IN_WINDOW = NO + +# If the OBFUSCATE_EMAILS tag is set to YES, doxygen will obfuscate email +# addresses. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +OBFUSCATE_EMAILS = YES + +# If the HTML_FORMULA_FORMAT option is set to svg, doxygen will use the pdf2svg +# tool (see https://github.com/dawbarton/pdf2svg) or inkscape (see +# https://inkscape.org) to generate formulas as SVG images instead of PNGs for +# the HTML output. These images will generally look nicer at scaled resolutions. +# Possible values are: png (the default) and svg (looks nicer but requires the +# pdf2svg or inkscape tool). +# The default value is: png. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_FORMULA_FORMAT = png + +# Use this tag to change the font size of LaTeX formulas included as images in +# the HTML documentation. When you change the font size after a successful +# doxygen run you need to manually remove any form_*.png images from the HTML +# output directory to force them to be regenerated. +# Minimum value: 8, maximum value: 50, default value: 10. +# This tag requires that the tag GENERATE_HTML is set to YES. + +FORMULA_FONTSIZE = 10 + +# The FORMULA_MACROFILE can contain LaTeX \newcommand and \renewcommand commands +# to create new LaTeX commands to be used in formulas as building blocks. See +# the section "Including formulas" for details. + +FORMULA_MACROFILE = + +# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see +# https://www.mathjax.org) which uses client side JavaScript for the rendering +# instead of using pre-rendered bitmaps. Use this if you do not have LaTeX +# installed or if you want to formulas look prettier in the HTML output. When +# enabled you may also need to install MathJax separately and configure the path +# to it using the MATHJAX_RELPATH option. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +USE_MATHJAX = NO + +# With MATHJAX_VERSION it is possible to specify the MathJax version to be used. +# Note that the different versions of MathJax have different requirements with +# regards to the different settings, so it is possible that also other MathJax +# settings have to be changed when switching between the different MathJax +# versions. +# Possible values are: MathJax_2 and MathJax_3. +# The default value is: MathJax_2. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_VERSION = MathJax_2 + +# When MathJax is enabled you can set the default output format to be used for +# the MathJax output. For more details about the output format see MathJax +# version 2 (see: +# http://docs.mathjax.org/en/v2.7-latest/output.html) and MathJax version 3 +# (see: +# http://docs.mathjax.org/en/latest/web/components/output.html). +# Possible values are: HTML-CSS (which is slower, but has the best +# compatibility. This is the name for Mathjax version 2, for MathJax version 3 +# this will be translated into chtml), NativeMML (i.e. MathML. Only supported +# for NathJax 2. For MathJax version 3 chtml will be used instead.), chtml (This +# is the name for Mathjax version 3, for MathJax version 2 this will be +# translated into HTML-CSS) and SVG. +# The default value is: HTML-CSS. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_FORMAT = HTML-CSS + +# When MathJax is enabled you need to specify the location relative to the HTML +# output directory using the MATHJAX_RELPATH option. The destination directory +# should contain the MathJax.js script. For instance, if the mathjax directory +# is located at the same level as the HTML output directory, then +# MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax +# Content Delivery Network so you can quickly see the result without installing +# MathJax. However, it is strongly recommended to install a local copy of +# MathJax from https://www.mathjax.org before deployment. The default value is: +# - in case of MathJax version 2: https://cdn.jsdelivr.net/npm/mathjax@2 +# - in case of MathJax version 3: https://cdn.jsdelivr.net/npm/mathjax@3 +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_RELPATH = + +# The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax +# extension names that should be enabled during MathJax rendering. For example +# for MathJax version 2 (see +# https://docs.mathjax.org/en/v2.7-latest/tex.html#tex-and-latex-extensions): +# MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols +# For example for MathJax version 3 (see +# http://docs.mathjax.org/en/latest/input/tex/extensions/index.html): +# MATHJAX_EXTENSIONS = ams +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_EXTENSIONS = + +# The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces +# of code that will be used on startup of the MathJax code. See the MathJax site +# (see: +# http://docs.mathjax.org/en/v2.7-latest/output.html) for more details. For an +# example see the documentation. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_CODEFILE = + +# When the SEARCHENGINE tag is enabled doxygen will generate a search box for +# the HTML output. The underlying search engine uses javascript and DHTML and +# should work on any modern browser. Note that when using HTML help +# (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets (GENERATE_DOCSET) +# there is already a search function so this one should typically be disabled. +# For large projects the javascript based search engine can be slow, then +# enabling SERVER_BASED_SEARCH may provide a better solution. It is possible to +# search using the keyboard; to jump to the search box use + S +# (what the is depends on the OS and browser, but it is typically +# , /