// File: crn_command_line_params.cpp // See Copyright Notice and license at the end of inc/crnlib.h #include "crn_core.h" #include "crn_command_line_params.h" #include "crn_console.h" #include "crn_cfile_stream.h" #ifdef WIN32 #define CRNLIB_CMD_LINE_ALLOW_SLASH_PARAMS 1 #endif #if CRNLIB_USE_WIN32_API #include "crn_winhdr.h" #endif namespace crnlib { void get_command_line_as_single_string(dynamic_string& cmd_line, int argc, char *argv[]) { argc, argv; #if CRNLIB_USE_WIN32_API cmd_line.set(GetCommandLineA()); #else cmd_line.clear(); for (int i = 0; i < argc; i++) { dynamic_string tmp(argv[i]); if ((tmp.front() != '"') && (tmp.front() != '-') && (tmp.front() != '@')) tmp = "\"" + tmp + "\""; if (cmd_line.get_len()) cmd_line += " "; cmd_line += tmp; } #endif } command_line_params::command_line_params() { } void command_line_params::clear() { m_params.clear(); m_param_map.clear(); } bool command_line_params::split_params(const char* p, dynamic_string_array& params) { bool within_param = false; bool within_quote = false; uint ofs = 0; dynamic_string str; while (p[ofs]) { const char c = p[ofs]; if (within_param) { if (within_quote) { if (c == '"') within_quote = false; str.append_char(c); } else if ((c == ' ') || (c == '\t')) { if (!str.is_empty()) { params.push_back(str); str.clear(); } within_param = false; } else { if (c == '"') within_quote = true; str.append_char(c); } } else if ((c != ' ') && (c != '\t')) { within_param = true; if (c == '"') within_quote = true; str.append_char(c); } ofs++; } if (within_quote) { console::error("Unmatched quote in command line \"%s\"", p); return false; } if (!str.is_empty()) params.push_back(str); return true; } bool command_line_params::load_string_file(const char* pFilename, dynamic_string_array& strings) { cfile_stream in_stream; if (!in_stream.open(pFilename, cDataStreamReadable | cDataStreamSeekable)) { console::error("Unable to open file \"%s\" for reading!", pFilename); return false; } dynamic_string ansi_str; for ( ; ; ) { if (!in_stream.read_line(ansi_str)) break; ansi_str.trim(); if (ansi_str.is_empty()) continue; strings.push_back(dynamic_string(ansi_str.get_ptr())); } return true; } bool command_line_params::parse(const dynamic_string_array& params, uint n, const param_desc* pParam_desc) { CRNLIB_ASSERT(n && pParam_desc); m_params = params; uint arg_index = 0; while (arg_index < params.size()) { const uint cur_arg_index = arg_index; const dynamic_string& src_param = params[arg_index++]; if (src_param.is_empty()) continue; #if CRNLIB_CMD_LINE_ALLOW_SLASH_PARAMS if ((src_param[0] == '/') || (src_param[0] == '-')) #else if (src_param[0] == '-') #endif { if (src_param.get_len() < 2) { console::error("Invalid command line parameter: \"%s\"", src_param.get_ptr()); return false; } dynamic_string key_str(src_param); key_str.right(1); int modifier = 0; char c = key_str[key_str.get_len() - 1]; if (c == '+') modifier = 1; else if (c == '-') modifier = -1; if (modifier) key_str.left(key_str.get_len() - 1); uint param_index; for (param_index = 0; param_index < n; param_index++) if (key_str == pParam_desc[param_index].m_pName) break; if (param_index == n) { console::error("Unrecognized command line parameter: \"%s\"", src_param.get_ptr()); return false; } const param_desc& desc = pParam_desc[param_index]; const uint cMaxValues = 16; dynamic_string val_str[cMaxValues]; uint num_val_strs = 0; if (desc.m_num_values) { CRNLIB_ASSERT(desc.m_num_values <= cMaxValues); if ((arg_index + desc.m_num_values) > params.size()) { console::error("Expected %u value(s) after command line parameter: \"%s\"", desc.m_num_values, src_param.get_ptr()); return false; } for (uint v = 0; v < desc.m_num_values; v++) val_str[num_val_strs++] = params[arg_index++]; } dynamic_string_array strings; if ((desc.m_support_listing_file) && (val_str[0].get_len() >= 2) && (val_str[0][0] == '@')) { dynamic_string filename(val_str[0]); filename.right(1); filename.unquote(); if (!load_string_file(filename.get_ptr(), strings)) { console::error("Failed loading listing file \"%s\"!", filename.get_ptr()); return false; } } else { for (uint v = 0; v < num_val_strs; v++) { val_str[v].unquote(); strings.push_back(val_str[v]); } } param_value pv; pv.m_values.swap(strings); pv.m_index = cur_arg_index; pv.m_modifier = (int8)modifier; m_param_map.insert(std::make_pair(key_str, pv)); } else { param_value pv; pv.m_values.push_back(src_param); pv.m_values.back().unquote(); pv.m_index = cur_arg_index; m_param_map.insert(std::make_pair(g_empty_dynamic_string, pv)); } } return true; } bool command_line_params::parse(const char* pCmd_line, uint n, const param_desc* pParam_desc, bool skip_first_param) { CRNLIB_ASSERT(n && pParam_desc); dynamic_string_array p; if (!split_params(pCmd_line, p)) return 0; if (p.empty()) return 0; if (skip_first_param) p.erase(0U); return parse(p, n, pParam_desc); } bool command_line_params::is_param(uint index) const { CRNLIB_ASSERT(index < m_params.size()); if (index >= m_params.size()) return false; const dynamic_string& w = m_params[index]; if (w.is_empty()) return false; #if CRNLIB_CMD_LINE_ALLOW_SLASH_PARAMS return (w.get_len() >= 2) && ((w[0] == '-') || (w[0] == '/')); #else return (w.get_len() >= 2) && (w[0] == '-'); #endif } uint command_line_params::find(uint num_keys, const char** ppKeys, crnlib::vector* pIterators, crnlib::vector* pUnmatched_indices) const { CRNLIB_ASSERT(ppKeys); if (pUnmatched_indices) { pUnmatched_indices->resize(m_params.size()); for (uint i = 0; i < m_params.size(); i++) (*pUnmatched_indices)[i] = i; } uint n = 0; for (uint i = 0; i < num_keys; i++) { const char* pKey = ppKeys[i]; param_map_const_iterator begin, end; find(pKey, begin, end); while (begin != end) { if (pIterators) pIterators->push_back(begin); if (pUnmatched_indices) { int k = pUnmatched_indices->find(begin->second.m_index); if (k >= 0) pUnmatched_indices->erase_unordered(k); } n++; begin++; } } return n; } void command_line_params::find(const char* pKey, param_map_const_iterator& begin, param_map_const_iterator& end) const { dynamic_string key(pKey); begin = m_param_map.lower_bound(key); end = m_param_map.upper_bound(key); } uint command_line_params::get_count(const char* pKey) const { param_map_const_iterator begin, end; find(pKey, begin, end); uint n = 0; while (begin != end) { n++; begin++; } return n; } command_line_params::param_map_const_iterator command_line_params::get_param(const char* pKey, uint index) const { param_map_const_iterator begin, end; find(pKey, begin, end); if (begin == end) return m_param_map.end(); uint n = 0; while ((begin != end) && (n != index)) { n++; begin++; } if (begin == end) return m_param_map.end(); return begin; } bool command_line_params::has_value(const char* pKey, uint index) const { return get_num_values(pKey, index) != 0; } uint command_line_params::get_num_values(const char* pKey, uint index) const { param_map_const_iterator it = get_param(pKey, index); if (it == end()) return 0; return it->second.m_values.size(); } bool command_line_params::get_value_as_bool(const char* pKey, uint index, bool def) const { param_map_const_iterator it = get_param(pKey, index); if (it == end()) return def; if (it->second.m_modifier) return it->second.m_modifier > 0; else return true; } int command_line_params::get_value_as_int(const char* pKey, uint index, int def, int l, int h, uint value_index) const { param_map_const_iterator it = get_param(pKey, index); if ((it == end()) || (value_index >= it->second.m_values.size())) return def; int val; const char* p = it->second.m_values[value_index].get_ptr(); if (!string_to_int(p, val)) { crnlib::console::warning("Invalid value specified for parameter \"%s\", using default value of %i", pKey, def); return def; } if (val < l) { crnlib::console::warning("Value %i for parameter \"%s\" is out of range, clamping to %i", val, pKey, l); val = l; } else if (val > h) { crnlib::console::warning("Value %i for parameter \"%s\" is out of range, clamping to %i", val, pKey, h); val = h; } return val; } float command_line_params::get_value_as_float(const char* pKey, uint index, float def, float l, float h, uint value_index) const { param_map_const_iterator it = get_param(pKey, index); if ((it == end()) || (value_index >= it->second.m_values.size())) return def; float val; const char* p = it->second.m_values[value_index].get_ptr(); if (!string_to_float(p, val)) { crnlib::console::warning("Invalid value specified for float parameter \"%s\", using default value of %f", pKey, def); return def; } if (val < l) { crnlib::console::warning("Value %f for parameter \"%s\" is out of range, clamping to %f", val, pKey, l); val = l; } else if (val > h) { crnlib::console::warning("Value %f for parameter \"%s\" is out of range, clamping to %f", val, pKey, h); val = h; } return val; } bool command_line_params::get_value_as_string(const char* pKey, uint index, dynamic_string& value, uint value_index) const { param_map_const_iterator it = get_param(pKey, index); if ((it == end()) || (value_index >= it->second.m_values.size())) { value.empty(); return false; } value = it->second.m_values[value_index]; return true; } const dynamic_string& command_line_params::get_value_as_string_or_empty(const char* pKey, uint index, uint value_index) const { param_map_const_iterator it = get_param(pKey, index); if ((it == end()) || (value_index >= it->second.m_values.size())) return g_empty_dynamic_string; return it->second.m_values[value_index]; } } // namespace crnlib