blob: 645bf512a86f8c17c3727edd4510d41cc08a7f4f [file] [log] [blame]
#ifndef MAPOPTIONS_HXX
#define MAPOPTIONS_HXX
/// \file MapOptions.hxx
/// Defines namespace Convert (to convert between some atomic types and strings)
/// and class MapOptions (to define and access a set of named parameters).
///
/// <b>namespace Convert</b> defines conversion functions between atomic <b>int/float/double</b> types and <b>strings</b>.
/// <h2>Example:</h2>
/// \code
/// string pis = "pi is " + Convert::to_string(4 * atan2(1.0, 1.0f));
/// float pif = Convert::to<float>(pis.c_str() + 6);
/// int pii = Convert::to<int> (pis.c_str() + 6);
/// std::cout << "pi is " << pif << "; rounded to " << pii << endl;
/// \endcode
#include "RTTL/common/RTVec.hxx"
#include <cstring>
#include <vector>
#include <map>
#include <unistd.h>
using namespace std;
namespace RTTL {
/// Defines <b>string <=> atomic types</b> conversions.
namespace Convert
{
_INLINE static string to_string(int v)
{
char s[12]; sprintf(s, "%-10i " , v); *strchr(s, ' ') = 0; return string(s);
}
_INLINE static string to_string(float v)
{
char s[16]; sprintf(s, "%-14.7g " , v); *strchr(s, ' ') = 0; return string(s);
}
_INLINE static string to_string(double v)
{
char s[24]; sprintf(s, "%-22.14g ", v); *strchr(s, ' ') = 0; return string(s);
}
_INLINE static string to_string(const string& s)
{
return s;
}
template<typename DataType> _INLINE static DataType to(const string& s)
{
cerr << "No default converter...\n"
<< "This is most likely happen when char* values\n"
<< "are assigned to (should be const char*).\n\n";
exit(1);
return DataType(0);
}
template<> _INLINE int to<int >(const string& s)
{
return atoi(s.c_str());
}
template<> _INLINE float to<float >(const string& s)
{
return float(atof(s.c_str()));
}
template<> _INLINE double to<double>(const string& s)
{
return atof(s.c_str());
}
template<> _INLINE const char* to<const char*>(const string& s)
{
return s.c_str();
}
template<> _INLINE string to<string>(const string& s)
{
return s;
}
};
/// \class MapOptions
/// Defines a set of the named vectors of strings (parameters) and
/// methods to access them.
///
/// Implementation details (see <em><b>example below</b></em>):
/// <ul>
/// <li> In order to access parameters, they must be parsed (with parse()) or added (with add()) first.
/// <li> parse() processes the second argument argv as a list of the named tokens.
/// <li> "name" of the parameter is defined by the first character(s) which is either
/// <ol>
/// <li> '-' or '--'. In this case, the new parameter replaces the old one with the same name (if it exists).
/// <li> '+'. In this case, the new parameter is appended to the vector of parameters with the same name.
/// </ol>
/// <li> Any token, which does not represent a name (with prefix), is considered to be an entry
/// and appended to the previously named parameter, thus creating potentially infinite vector of
/// parameters with the same name (different entries are accessed using index argument in get() function).
/// <li> Different tokens are either specified as separate entries in argv array or separated with ',' or ';' inside
/// one argv[i] entry.
/// <li> Extra brackets ('[' and ']') could be specified for readability, which are ignored by the parser.
/// <li> <b>There is one exception to this scheme. All <i>independent</i> tokens with '.' are considered to be filenames
/// and added to the "files" group. If the specified file does not exist, parse() returns false immediately.
/// (unless keep_all_filenames parameter is true) </b>
/// <li> <b>Additionally, all files with *.ini extension are assumed to include parameters and loaded recursively.</b>
/// <li> <b>Only those parameters are considered to define independent filenames,
/// which do not have the preceding parameter name
/// (that is, "-save debug.txt" defines token "save" with value "debug.txt" and not a filename).</b>
/// </ul>
///
/// <b>*.ini</b> file format allows to specify different parameters on different lines.
/// Additionally, nested parameter files could be loaded with the 'include' directive.
///
/// It is allowed to specify component name using [prefixed] notation.
/// Accordingly, the following fragment
/// <pre>
///[camera]
/// pos = -2.9 1.5 -2.2
/// dir = 2.9,-0.5,2.2
/// up = 0; 1; 0
/// </pre>
/// defines "camera.pos", "camera.dir", and "camera.up" parameters
/// (note different ways of defining the vectors).
///
/// The parsed parameters could be retrieved with the templated get() function,
/// which defines the return data type by the default parameter.
/// \note If the named parameter does not exist, attempt is made to
/// retrieve this parameter from the environment.
///
/// \note MT support exists insofar STL supports it. Accessing/updating vectors simultaneously
/// may crush the system (read-only multiple accesses are safe, I think :).
///
/// <h2>Example for the following command line parameters:</h2>
/// <pre>
/// echo > head.obj; echo > body.obj
/// bin/TestOptions -pbo=1 -pos [1, 2, 3] -up 0,0,1 +dir 5 +dir 6 +dir 7 -nthreads 3 -verb head.obj body.obj
/// </pre>
/// \code
/// #include "RTTL/common/MapOptions.hxx"
/// int main(int argc, const char* argv[]) {
/// // Read parameters from the command line and print them out.
/// options.parse(argc-1, argv+1);
/// cout << options << endl << endl;
///
/// // Get environment value (defined in Windows and Linux as well).
/// // Note: Under IDE with icc, it will not be defined (as a matter of fact),
/// // but it will be defined if compiled using mvsc.
/// cout << "USERNAME = " << options.get("USERNAME") << endl;
/// // Try to get the value from the environment. get<> type is defined by defvalue.
/// cout << "PROCESSOR_LEVEL = " << options.get("PROCESSOR_LEVEL", -1) << endl;
///
/// cout << "PBO = " << options.get("pbo", 0) << endl;
/// cout << "output is " << (options.defined("statistics, stat, all")? "report stat":"compact") << endl;
/// cout << "pos is ["
/// << options.get("pos", 0.0f, 0) << ","
/// << options.get("pos", 0.0f, 1) << ","
/// << options.get("pos", 0.0f, 2) << "]" << endl;
/// (*options["up"])[1] = Convert::to_string(-1); // change 2nd entry
/// cout << "up is " << options.getVec3f("up") << endl;
/// cout << "dir is " << options.getVec3f("dir") << endl;
/// cout << options.get("nthreads") << endl; // returns string("3")
/// options.remove("nthreads");
/// cout << options.get("nthreads") << endl; // returns the name
/// int nfiles = options.vector_size("files");
/// for (int fi = 0; fi < nfiles; fi++) {
/// const string& fn = (*options["files"])[fi];
/// cout << "Adding obj file: " << fn << endl;
/// }
/// }
/// \endcode
/// <h2>Output (on Linux machine):</h2>
/// <pre>
/// dir = [5, 6, 7];
/// files = [head.obj, body.obj];
/// nthreads = 3;
/// pbo = 1;
/// pos = [1, 2, 3];
/// up = [0, 0, 1];
///
/// USERNAME = aresheto
/// PROCESSOR_LEVEL = -1
/// PBO = 1
/// output is report stat
/// pos is [1,2,3]
/// up is [0,-1,1]
/// dir is [5,6,7]
/// 3
/// nthreads
/// Adding obj file: head.obj
/// Adding obj file: body.obj
/// </pre>
///
/// <h2>Q&A</h2>
/// <ul>
/// <li> Is -pbo the same as --pbo ?
/// - Yes.
/// <li> what's the syntax of specifying an options to a parameter:
/// --pbo=on", or "--pbo=true", or "--pbo=1", or no "=" sign at all, or
/// "--pbo means true, --pbo=0 means off" or "--pbo/--no-pbo" (icc-style)
/// <ol>
/// - Sign ('=') is optional.
/// - Any "pbo" definition will result in defined("pbo") returning true, but its value could be different, depending on comand line parameter.
/// - --pbo=0 will just mean that "pbo" is defined and its value is "0".
/// Then query options.get("pbo", 1) will return integer 0, that's all.</ol>
/// <li>what's the preference of arguments: if two instances of same
/// parameter are specified on cmd line, I assume it's the one the appears
/// last that counts ... is that so ? what about env-vars -- do they
/// _override_ cmd line vars, or is it the other way around ?
/// - Last parameter (either on command line or in ini file overrides previous one with the same name,
/// unless it is prefixed with '+'. In that case the new value will be added to the named vector.
/// Environment has the lowest priority; it is querried only if the value was not specified on command line
/// or in included file(s).
///
/// <li> Can we add a specific file name extension that's being parsed as if all it's lines are specified in the command line ? i.e., if we create a file default.rtopt (or whatever extension we pick...) with content
/// <pre>
/// res X Y
/// pbo off
/// </pre>
/// then that's the same as having specified "--res X Y --pbo=off" on the command line ?
/// - For whom do you think documentation is written for? Just go and read it :)
/// </ul>
typedef vector<string> vector_of_strings;
typedef map<string, vector_of_strings*> map_strings_to_vector_of_strings;
/// Declares mapping of strings to vector of strings, what did you expect?
class MapOptions: public map_strings_to_vector_of_strings
{
public:
typedef map_strings_to_vector_of_strings::iterator iterator;
typedef map_strings_to_vector_of_strings::const_iterator const_iterator;
// Default ctor.
/// Dtor.
~MapOptions();
/// Clean everything (it is not required if dtor is called implicitly).
void clear();
/// Add converted value to the named parameter.
template<typename DataType>
void add(const string& name, const DataType value)
{
add_string(name, Convert::to_string(value));
}
/// Add value string to the named parameter.
void add_string(const string& name, const string& value);
/// Return 1st entry (with index 0) of the named parameter as a string
/// or return name as a string if it does not exist (even in the environment).
string get(string name) const;
/// Return the named vector using defvalue for missing components.
template<int N, typename DataType>
RTVec_t<N, DataType> getVector(string name, DataType defvalue = 0) const {
RTVec_t<N, DataType> v;
getArray<N, DataType>(name, v, defvalue);
return v;
}
/// Specialization <3, float>.
RTVec3f getVec3f(string name, float defvalue = 0) const;
/// Specialization <3, int>.
RTVec3f getVec3i(string name, int defvalue = 0) const;
/// Specialization <2, float>.
RTVec2f getVec2f(string name, float defvalue = 0) const;
/// Specialization <2, int>.
/// X windows-like settings of n1Xn2 are allowed.
RTVec2i getVec2i(string name, int defvalue = 0) const;
/// Write the named vector to vtgt (no more than N entries) or use defvalue
/// if it is smaller than N.
template<int N, typename DataType>
void getArray(string name, DataType* vtgt, DataType defvalue = 0) const {
int n = vector_size(name);
if (N > 0) n = min(n, N);
int verboselevel = verbose();
if (n == 0 && verboselevel >= 1) {
std::cout << "MapOptions: parameter " << name << " is undefined; using default value of " << defvalue << std::endl;
}
int i = 0;
if (n) {
if (verboselevel >= 2) {
std::cout << "MapOptions: using " << name << " = [ ";
}
const vector_of_strings& vs = *(*this)[name];
for (vector_of_strings::const_iterator it = vs.begin(); i < n; it++) {
vtgt[i++] = Convert::to<DataType>(*it);
if (verboselevel >= 2) {
std::cout << vtgt[i-1] << " ";
}
}
if (verboselevel >= 2) {
std::cout << "]" << std::endl;
}
}
for (; i < N; i++) {
vtgt[i] = defvalue;
}
}
/// Indicate what MapOptions should report during calls to get() functions.
/// It is based on (user supplied) integer value of 'verbose' keyword and
/// is choosen as follows:
/// <ul>
/// <li> 0 - do nothing except reporting unused parameters at the exit (default value)
/// <li> 1 - report undefined parameters (ones for which default values were used)
/// <li> 2 - report all requested parameters.
/// </ul>
int verbose() const;
/// return index entry in the named parameter if it exists or
/// defvalue otherwise.
template<typename DataType>
DataType get(const string& name, DataType defvalue, unsigned int index = 0) const {
const_iterator it = find(name);
int verboselevel = verbose();
if (it == end()) {
// See if it is defined in the environment...
const char* parenv = getenv(name.c_str());
if (parenv) {
return Convert::to<DataType>(parenv);
}
// Nope, return caller-supplied default value.
if (verboselevel >= 1) {
std::cout << "MapOptions: parameter " << name << "[" << index << "]"
<< " is undefined; using default value of " << defvalue << std::endl;
}
return defvalue;
} else {
const vector_of_strings& entry = *it->second;
if (index >= entry.size()) return defvalue;
if (entry[index].size() == 0) return defvalue;
DataType value = Convert::to<DataType>(entry[index]);
if (verboselevel >= 2) {
std::cout << "MapOptions: using " << name << "[" << index << "] = " << value << std::endl;
}
return value;
}
}
/// Return # of components in the named vector or 0.
unsigned int vector_size(const string& name) const;
/// Return true iff the parameter defined by len characters of name exists either in this or in the environment.
bool defined(const char* name, int len) const;
/// Return true iff the named parameter exists either in this or in the environment.
bool defined(const string& name) const;
/// Remove the named parameter.
void remove(const string& name);
/// Parse named tokens defined by argv (see the detailed class description).
/// If keep_all_filenames is false (default value), non-existing files will be deleted from 'files' group.
bool parse(int argc, char* argv[], bool keep_all_filenames = false);
/// Parse named tokens defined by a.
bool parse(const char* a);
/// Return true iff all characters of s before separator (" \t,;])" or 0) represents a number
/// (using integer, float or exponential notation).
bool isNumber(const char* s);
/// Parse named tokens defined by argv (see the detailed class description).
/// If keep_all_filenames is false (default value), non-existing files will be deleted from 'files' group.
bool parse(int argc, const char* argv[], bool keep_all_filenames = false);
/// Parse all named tokens in file filename (see the detailed class description).
bool parse_file(const char* filename);
/// const version.
const vector_of_strings* operator[](const string& name) const;
/// Return either pointer to the vector_of_strings defined by the name or pointer to the newly created empty vector.
vector_of_strings* operator[](const string& name);
protected:
void remove_all_entries();
/// Overload find operation to allow component names (like "nt; nthread; nthreads").
/// This function will also check m_used_entries.
/// \note Order of items in component name is important (first one is checked first, etc).
iterator find(const string& name);
const_iterator find(const string& name) const;
iterator find(const char* name, int len);
const_iterator find(const char* name, int len) const;
protected:
std::map<void*, bool> m_used_entries;
};
//ostream& operator<<(ostream& out, const MapOptions& mo);
//ostream& operator<<(ostream& out, MapOptions::const_iterator it);
//ostream& operator<<(ostream& out, MapOptions::iterator it);
_INLINE ostream& operator<<(ostream& out, MapOptions::const_iterator it)
{
const vector_of_strings& vec = *it->second;
unsigned int sz = vec.size();
out << it->first << " = ";
if (sz > 1) out << "[";
unsigned int i = 0;
while (true) {
out << vec[i];
if (++i == sz) break;
out << ", ";
}
if (sz > 1) out << "]";
out << ";" << endl;
return out;
}
_INLINE ostream& operator<<(ostream& out, MapOptions::iterator it)
{
out << (MapOptions::const_iterator&)it;
return out;
}
_INLINE ostream& operator<<(ostream& out, const MapOptions& mo)
{
MapOptions::const_iterator it;
unsigned int maxwidth = 0;
for (it = mo.begin(); it != mo.end(); it++) {
maxwidth = max(maxwidth, (unsigned int)it->first.length());
}
maxwidth++;
for (it = mo.begin(); it != mo.end(); it++) {
cout.width(maxwidth);
cout << it;
}
return out;
}
/// Allows access to this variable defined in Global.cxx.
extern MapOptions options;
}
#endif