commit 63bc8016e1deb186c0d757ad8f3787459ed09fb2 Author: Daniel Tam Date: Sat Nov 19 02:24:32 2022 -0600 initial program diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f6734c4 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +fix_subs \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..b07c695 --- /dev/null +++ b/Makefile @@ -0,0 +1,2 @@ +fix_subs: fix_subs.cpp ezOptionParser.hpp + g++ -std=c++20 -Wall -o fix_subs fix_subs.cpp ezOptionParser.hpp \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..4b50d1c --- /dev/null +++ b/README.md @@ -0,0 +1,55 @@ +# fix-subs-in-nested-folders + +Some shows will have their subtitles nested in a `Subs/` folder that is within the season itself. This causes some issues +with Plex as it will not find the subtitles if they are nested. An example of this nested structure is: +``` +/Show Name + |- Season 01 + |- S01E01.mp4 + |- S01E02.mp4 + |- S01E03.mp4 + |- Subs/ + |- S01E01/ + |- Subtitle 1.srt + |- Subtitle 2.srt + |- S01E02/ + |- Subtitle 1.srt + |- Subtitle 2.srt + |- S01E03/ + |- Subtitle 1.srt + |- Subtitle 2.srt + |- Season 02 + |- S02E01.mp4 + |- S02E02.mp4 + |- S02E03.mp4 + |- Subs/ + |- S02E01/ + |- Subtitle 1.srt + |- Subtitle 2.srt + |- S02E02/ + |- Subtitle 1.srt + |- Subtitle 2.srt + |- S02E03/ + |- Subtitle 1.srt + |- Subtitle 2.srt +``` + +This short program will go through the folders and grab the subtitles files (.srt) and place them in the appropriate season folder. +Plex should then be able to search for these subtitle files and load them correctly. + +## Building +Compile with C++20 using gcc: + +``` +g++ -std=c++20 -o fix_subs fix_subs ezOptionParser.hpp +``` +Or using the Makefile: +``` +make +``` + +## Running +You can simply run once it's compile using the flag `-f` to specify the top directory of the show. +``` +./fix_subs -f "/home/daniel/Videos/Show Name/" +``` diff --git a/ezOptionParser.hpp b/ezOptionParser.hpp new file mode 100644 index 0000000..3dfbd39 --- /dev/null +++ b/ezOptionParser.hpp @@ -0,0 +1,2158 @@ +/* +This file is part of ezOptionParser. See MIT-LICENSE. + +Copyright (C) 2011,2012,2014 Remik Ziemlinski + +CHANGELOG + +v0.0.0 20110505 rsz Created. +v0.1.0 20111006 rsz Added validator. +v0.1.1 20111012 rsz Fixed validation of ulonglong. +v0.1.2 20111126 rsz Allow flag names start with alphanumeric (previously, flag had to start with alpha). +v0.1.3 20120108 rsz Created work-around for unique id generation with IDGenerator that avoids retarded c++ translation unit linker errors with single-header static variables. Forced inline on all methods to please retard compiler and avoid multiple def errors. +v0.1.4 20120629 Enforced MIT license on all files. +v0.2.0 20121120 Added parseIndex to OptionGroup. +v0.2.1 20130506 Allow disabling doublespace of OPTIONS usage descriptions. +v0.2.2 20140504 Jose Santiago added compiler warning fixes. + Bruce Shankle added a crash fix in description printing. +*/ +#ifndef EZ_OPTION_PARSER_H +#define EZ_OPTION_PARSER_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace ez { +#define DEBUGLINE() printf("%s:%d\n", __FILE__, __LINE__); + +/* ################################################################### */ +template +static T fromString(const std::string* s) { + std::istringstream stream (s->c_str()); + T t; + stream >> t; + return t; +}; +template +static T fromString(const char* s) { + std::istringstream stream (s); + T t; + stream >> t; + return t; +}; +/* ################################################################### */ +static inline bool isdigit(const std::string & s, int i=0) { + int n = s.length(); + for(; i < n; ++i) + switch(s[i]) { + case '0': case '1': case '2': + case '3': case '4': case '5': + case '6': case '7': case '8': case '9': break; + default: return false; + } + + return true; +}; +/* ################################################################### */ +static bool isdigit(const std::string * s, int i=0) { + int n = s->length(); + for(; i < n; ++i) + switch(s->at(i)) { + case '0': case '1': case '2': + case '3': case '4': case '5': + case '6': case '7': case '8': case '9': break; + default: return false; + } + + return true; +}; +/* ################################################################### */ +/* +Compare strings for opts, so short opt flags come before long format flags. +For example, -d < --dimension < --dmn, and also lower come before upper. The default STL std::string compare doesn't do that. +*/ +static bool CmpOptStringPtr(std::string * s1, std::string * s2) { + int c1,c2; + const char *s=s1->c_str(); + for(c1=0; c1 < (long int)s1->size(); ++c1) + if (isalnum(s[c1])) // locale sensitive. + break; + + s=s2->c_str(); + for(c2=0; c2 < (long int)s2->size(); ++c2) + if (isalnum(s[c2])) + break; + + // Test which has more symbols before its name. + if (c1 > c2) + return false; + else if (c1 < c2) + return true; + + // Both have same number of symbols, so compare first letter. + char char1 = s1->at(c1); + char char2 = s2->at(c2); + char lo1 = tolower(char1); + char lo2 = tolower(char2); + + if (lo1 != lo2) + return lo1 < lo2; + + // Their case doesn't match, so find which is lower. + char up1 = isupper(char1); + char up2 = isupper(char2); + + if (up1 && !up2) + return false; + else if (!up1 && up2) + return true; + + return (s1->compare(*s2)<0); +}; +/* ################################################################### */ +/* +Makes a vector of strings from one string, +splitting at (and excluding) delimiter "token". +*/ +static void SplitDelim( const std::string& s, const char token, std::vector * result) { + std::string::const_iterator i = s.begin(); + std::string::const_iterator j = s.begin(); + const std::string::const_iterator e = s.end(); + + while(i!=e) { + while(i!=e && *i++!=token); + std::string *newstr = new std::string(j, i); + if (newstr->at(newstr->size()-1) == token) newstr->erase(newstr->size()-1); + result->push_back(newstr); + j = i; + } +}; +/* ################################################################### */ +// Variant that uses deep copies and references instead of pointers (less efficient). +static void SplitDelim( const std::string& s, const char token, std::vector & result) { + std::string::const_iterator i = s.begin(); + std::string::const_iterator j = s.begin(); + const std::string::const_iterator e = s.end(); + + while(i!=e) { + while(i!=e && *i++!=token); + std::string newstr(j, i); + if (newstr.at(newstr.size()-1) == token) newstr.erase(newstr.size()-1); + result.push_back(newstr); + j = i; + } +}; +/* ################################################################### */ +// Variant that uses list instead of vector for efficient insertion, etc. +static void SplitDelim( const std::string& s, const char token, std::list & result) { + std::string::const_iterator i = s.begin(); + std::string::const_iterator j = s.begin(); + const std::string::const_iterator e = s.end(); + + while(i!=e) { + while(i!=e && *i++!=token); + std::string *newstr = new std::string(j, i); + if (newstr->at(newstr->size()-1) == token) newstr->erase(newstr->size()-1); + result.push_back(newstr); + j = i; + } +}; +/* ################################################################### */ +static void ToU1(std::string ** strings, unsigned char * out, int n) { + for(int i=0; i < n; ++i) { + out[i] = (unsigned char)atoi(strings[i]->c_str()); + } +}; +/* ################################################################### */ +static void ToS1(std::string ** strings, char * out, int n) { + for(int i=0; i < n; ++i) { + out[i] = (char)atoi(strings[i]->c_str()); + } +}; +/* ################################################################### */ +static void ToU2(std::string ** strings, unsigned short * out, int n) { + for(int i=0; i < n; ++i) { + out[i] = (unsigned short)atoi(strings[i]->c_str()); + } +}; +/* ################################################################### */ +static void ToS2(std::string ** strings, short * out, int n) { + for(int i=0; i < n; ++i) { + out[i] = (short)atoi(strings[i]->c_str()); + } +}; +/* ################################################################### */ +static void ToS4(std::string ** strings, int * out, int n) { + for(int i=0; i < n; ++i) { + out[i] = atoi(strings[i]->c_str()); + } +}; +/* ################################################################### */ +static void ToU4(std::string ** strings, unsigned int * out, int n) { + for(int i=0; i < n; ++i) { + out[i] = (unsigned int)strtoul(strings[i]->c_str(), NULL, 0); + } +}; +/* ################################################################### */ +static void ToS8(std::string ** strings, long long * out, int n) { + for(int i=0; i < n; ++i) { + std::stringstream ss(strings[i]->c_str()); + ss >> out[i]; + } +}; +/* ################################################################### */ +static void ToU8(std::string ** strings, unsigned long long * out, int n) { + for(int i=0; i < n; ++i) { + std::stringstream ss(strings[i]->c_str()); + ss >> out[i]; + } +}; +/* ################################################################### */ +static void ToF(std::string ** strings, float * out, int n) { + for(int i=0; i < n; ++i) { + out[i] = (float)atof(strings[i]->c_str()); + } +}; +/* ################################################################### */ +static void ToD(std::string ** strings, double * out, int n) { + for(int i=0; i < n; ++i) { + out[i] = (double)atof(strings[i]->c_str()); + } +}; +/* ################################################################### */ +static void StringsToInts(std::vector & strings, std::vector & out) { + for(int i=0; i < (long int)strings.size(); ++i) { + out.push_back(atoi(strings[i].c_str())); + } +}; +/* ################################################################### */ +static void StringsToInts(std::vector * strings, std::vector * out) { + for(int i=0; i < (long int)strings->size(); ++i) { + out->push_back(atoi(strings->at(i)->c_str())); + } +}; +/* ################################################################### */ +static void StringsToLongs(std::vector & strings, std::vector & out) { + for(int i=0; i < (long int)strings.size(); ++i) { + out.push_back(atol(strings[i].c_str())); + } +}; +/* ################################################################### */ +static void StringsToLongs(std::vector * strings, std::vector * out) { + for(int i=0; i < (long int)strings->size(); ++i) { + out->push_back(atol(strings->at(i)->c_str())); + } +}; +/* ################################################################### */ +static void StringsToULongs(std::vector & strings, std::vector & out) { + for(int i=0; i < (long int)strings.size(); ++i) { + out.push_back(strtoul(strings[i].c_str(),0,0)); + } +}; +/* ################################################################### */ +static void StringsToULongs(std::vector * strings, std::vector * out) { + for(int i=0; i < (long int)strings->size(); ++i) { + out->push_back(strtoul(strings->at(i)->c_str(),0,0)); + } +}; +/* ################################################################### */ +static void StringsToFloats(std::vector & strings, std::vector & out) { + for(int i=0; i < (long int)strings.size(); ++i) { + out.push_back(atof(strings[i].c_str())); + } +}; +/* ################################################################### */ +static void StringsToFloats(std::vector * strings, std::vector * out) { + for(int i=0; i < (long int)strings->size(); ++i) { + out->push_back(atof(strings->at(i)->c_str())); + } +}; +/* ################################################################### */ +static void StringsToDoubles(std::vector & strings, std::vector & out) { + for(int i=0; i < (long int)strings.size(); ++i) { + out.push_back(atof(strings[i].c_str())); + } +}; +/* ################################################################### */ +static void StringsToDoubles(std::vector * strings, std::vector * out) { + for(int i=0; i < (long int)strings->size(); ++i) { + out->push_back(atof(strings->at(i)->c_str())); + } +}; +/* ################################################################### */ +static void StringsToStrings(std::vector * strings, std::vector * out) { + for(int i=0; i < (long int)strings->size(); ++i) { + out->push_back( *strings->at(i) ); + } +}; +/* ################################################################### */ +static void ToLowerASCII(std::string & s) { + int n = s.size(); + int i=0; + char c; + for(; i < n; ++i) { + c = s[i]; + if(c<='Z' && c>='A') + s[i] = c+32; + } +} +/* ################################################################### */ +static char** CommandLineToArgvA(char* CmdLine, int* _argc) { + char** argv; + char* _argv; + unsigned long len; + unsigned long argc; + char a; + unsigned long i, j; + + bool in_QM; + bool in_TEXT; + bool in_SPACE; + + len = strlen(CmdLine); + i = ((len+2)/2)*sizeof(void*) + sizeof(void*); + + argv = (char**)malloc(i + (len+2)*sizeof(char)); + + _argv = (char*)(((unsigned char*)argv)+i); + + argc = 0; + argv[argc] = _argv; + in_QM = false; + in_TEXT = false; + in_SPACE = true; + i = 0; + j = 0; + + while( (a = CmdLine[i]) ) { + if(in_QM) { + if( (a == '\"') || + (a == '\'')) // rsz. Added single quote. + { + in_QM = false; + } else { + _argv[j] = a; + j++; + } + } else { + switch(a) { + case '\"': + case '\'': // rsz. Added single quote. + in_QM = true; + in_TEXT = true; + if(in_SPACE) { + argv[argc] = _argv+j; + argc++; + } + in_SPACE = false; + break; + case ' ': + case '\t': + case '\n': + case '\r': + if(in_TEXT) { + _argv[j] = '\0'; + j++; + } + in_TEXT = false; + in_SPACE = true; + break; + default: + in_TEXT = true; + if(in_SPACE) { + argv[argc] = _argv+j; + argc++; + } + _argv[j] = a; + j++; + in_SPACE = false; + break; + } + } + i++; + } + _argv[j] = '\0'; + argv[argc] = NULL; + + (*_argc) = argc; + return argv; +}; +/* ################################################################### */ +// Create unique ids with static and still allow single header that avoids multiple definitions linker error. +class ezOptionParserIDGenerator { +public: + static ezOptionParserIDGenerator& instance () { static ezOptionParserIDGenerator Generator; return Generator; } + short next () { return ++_id; } +private: + ezOptionParserIDGenerator() : _id(-1) {} + short _id; +}; +/* ################################################################### */ +/* Validate a value by checking: +- if as string, see if converted value is within datatype's limits, +- and see if falls within a desired range, +- or see if within set of given list of values. + +If comparing with a range, the values list must contain one or two values. One value is required when comparing with <, <=, >, >=. Use two values when requiring a test such as list[0] */ + GE, /* value >= list[0] */ + GTLT, /* list[0] < value < list[1] */ + GELT, /* list[0] <= value < list[1] */ + GELE, /* list[0] <= value <= list[1] */ + GTLE, /* list[0] < value <= list[1] */ + IN /* if value is in list */ + }; + + enum TYPE { NOTYPE=0, S1, U1, S2, U2, S4, U4, S8, U8, F, D, T }; + enum TYPE2 { NOTYPE2=0, INT8, UINT8, INT16, UINT16, INT32, UINT32, INT64, UINT64, FLOAT, DOUBLE, TEXT }; + + union { + unsigned char *u1; + char *s1; + unsigned short *u2; + short *s2; + unsigned int *u4; + int *s4; + unsigned long long *u8; + long long *s8; + float *f; + double *d; + std::string** t; + }; + + char op; + bool quiet; + short id; + char type; + int size; + bool insensitive; +}; +/* ------------------------------------------------------------------- */ +ezOptionValidator::~ezOptionValidator() { + reset(); +}; +/* ------------------------------------------------------------------- */ +void ezOptionValidator::reset() { + #define CLEAR(TYPE,P) case TYPE: if (P) delete [] P; P = 0; break; + switch(type) { + CLEAR(S1,s1); + CLEAR(U1,u1); + CLEAR(S2,s2); + CLEAR(U2,u2); + CLEAR(S4,s4); + CLEAR(U4,u4); + CLEAR(S8,s8); + CLEAR(U8,u8); + CLEAR(F,f); + CLEAR(D,d); + case T: + for(int i=0; i < size; ++i) + delete t[i]; + + delete [] t; + t = 0; + break; + default: break; + } + + size = 0; + op = NOOP; + type = NOTYPE; +}; +/* ------------------------------------------------------------------- */ +ezOptionValidator::ezOptionValidator(char _type) : s1(0), op(0), quiet(0), type(_type), size(0), insensitive(0) { + id = ezOptionParserIDGenerator::instance().next(); +}; +/* ------------------------------------------------------------------- */ +ezOptionValidator::ezOptionValidator(char _type, char _op, const char* list, int _size) : s1(0), op(_op), quiet(0), type(_type), size(_size), insensitive(0) { + id = ezOptionParserIDGenerator::instance().next(); + s1 = new char[size]; + memcpy(s1, list, size); +}; +/* ------------------------------------------------------------------- */ +ezOptionValidator::ezOptionValidator(char _type, char _op, const unsigned char* list, int _size) : u1(0), op(_op), quiet(0), type(_type), size(_size), insensitive(0) { + id = ezOptionParserIDGenerator::instance().next(); + u1 = new unsigned char[size]; + memcpy(u1, list, size); +}; +/* ------------------------------------------------------------------- */ +ezOptionValidator::ezOptionValidator(char _type, char _op, const short* list, int _size) : s2(0), op(_op), quiet(0), type(_type), size(_size), insensitive(0) { + id = ezOptionParserIDGenerator::instance().next(); + s2 = new short[size]; + memcpy(s2, list, size*sizeof(short)); +}; +/* ------------------------------------------------------------------- */ +ezOptionValidator::ezOptionValidator(char _type, char _op, const unsigned short* list, int _size) : u2(0), op(_op), quiet(0), type(_type), size(_size), insensitive(0) { + id = ezOptionParserIDGenerator::instance().next(); + u2 = new unsigned short[size]; + memcpy(u2, list, size*sizeof(unsigned short)); +}; +/* ------------------------------------------------------------------- */ +ezOptionValidator::ezOptionValidator(char _type, char _op, const int* list, int _size) : s4(0), op(_op), quiet(0), type(_type), size(_size), insensitive(0) { + id = ezOptionParserIDGenerator::instance().next(); + s4 = new int[size]; + memcpy(s4, list, size*sizeof(int)); +}; +/* ------------------------------------------------------------------- */ +ezOptionValidator::ezOptionValidator(char _type, char _op, const unsigned int* list, int _size) : u4(0), op(_op), quiet(0), type(_type), size(_size), insensitive(0) { + id = ezOptionParserIDGenerator::instance().next(); + u4 = new unsigned int[size]; + memcpy(u4, list, size*sizeof(unsigned int)); +}; +/* ------------------------------------------------------------------- */ +ezOptionValidator::ezOptionValidator(char _type, char _op, const long long* list, int _size) : s8(0), op(_op), quiet(0), type(_type), size(_size), insensitive(0) { + id = ezOptionParserIDGenerator::instance().next(); + s8 = new long long[size]; + memcpy(s8, list, size*sizeof(long long)); +}; +/* ------------------------------------------------------------------- */ +ezOptionValidator::ezOptionValidator(char _type, char _op, const unsigned long long* list, int _size) : u8(0), op(_op), quiet(0), type(_type), size(_size), insensitive(0) { + id = ezOptionParserIDGenerator::instance().next(); + u8 = new unsigned long long[size]; + memcpy(u8, list, size*sizeof(unsigned long long)); +}; +/* ------------------------------------------------------------------- */ +ezOptionValidator::ezOptionValidator(char _type, char _op, const float* list, int _size) : f(0), op(_op), quiet(0), type(_type), size(_size), insensitive(0) { + id = ezOptionParserIDGenerator::instance().next(); + f = new float[size]; + memcpy(f, list, size*sizeof(float)); +}; +/* ------------------------------------------------------------------- */ +ezOptionValidator::ezOptionValidator(char _type, char _op, const double* list, int _size) : d(0), op(_op), quiet(0), type(_type), size(_size), insensitive(0) { + id = ezOptionParserIDGenerator::instance().next(); + d = new double[size]; + memcpy(d, list, size*sizeof(double)); +}; +/* ------------------------------------------------------------------- */ +ezOptionValidator::ezOptionValidator(char _type, char _op, const char** list, int _size, bool _insensitive) : t(0), op(_op), quiet(0), type(_type), size(_size), insensitive(_insensitive) { + id = ezOptionParserIDGenerator::instance().next(); + t = new std::string*[size]; + int i=0; + + for(; i < size; ++i) { + t[i] = new std::string(list[i]); + } +}; +/* ------------------------------------------------------------------- */ +/* Less efficient but convenient ctor that parses strings to setup validator. +_type: s1, u1, s2, u2, ..., f, d, t +_op: lt, gt, ..., in +_list: comma-delimited string +*/ +ezOptionValidator::ezOptionValidator(const char* _type, const char* _op, const char* _list, bool _insensitive) : t(0), quiet(0), type(0), size(0), insensitive(_insensitive) { + id = ezOptionParserIDGenerator::instance().next(); + + switch(_type[0]) { + case 'u': + switch(_type[1]) { + case '1': type = U1; break; + case '2': type = U2; break; + case '4': type = U4; break; + case '8': type = U8; break; + default: break; + } + break; + case 's': + switch(_type[1]) { + case '1': type = S1; + break; + case '2': type = S2; break; + case '4': type = S4; break; + case '8': type = S8; break; + default: break; + } + break; + case 'f': type = F; break; + case 'd': type = D; break; + case 't': type = T; break; + default: + if (!quiet) + std::cerr << "ERROR: Unknown validator datatype \"" << _type << "\".\n"; + break; + } + + int nop = 0; + if (_op != 0) + nop = strlen(_op); + + switch(nop) { + case 0: op = NOOP; break; + case 2: + switch(_op[0]) { + case 'g': + switch(_op[1]) { + case 'e': op = GE; break; + default: op = GT; break; + } + break; + case 'i': op = IN; + break; + default: + switch(_op[1]) { + case 'e': op = LE; break; + default: op = LT; break; + } + break; + } + break; + case 4: + switch(_op[1]) { + case 'e': + switch(_op[3]) { + case 'e': op = GELE; break; + default: op = GELT; break; + } + break; + default: + switch(_op[3]) { + case 'e': op = GTLE; break; + default: op = GTLT; break; + } + break; + } + break; + default: + if (!quiet) + std::cerr << "ERROR: Unknown validator operation \"" << _op << "\".\n"; + break; + } + + if (_list == 0) return; + // Create list of strings and then cast to native datatypes. + std::string unsplit(_list); + std::list split; + std::list::iterator it; + SplitDelim(unsplit, ',', split); + size = split.size(); + std::string **strings = new std::string*[size]; + + int i = 0; + for(it = split.begin(); it != split.end(); ++it) + strings[i++] = *it; + + if (insensitive) + for(i=0; i < size; ++i) + ToLowerASCII(*strings[i]); + + #define FreeStrings() { \ + for(i=0; i < size; ++i)\ + delete strings[i];\ + delete [] strings;\ + } + + #define ToArray(T,P,Y) case T: P = new Y[size]; To##T(strings, P, size); FreeStrings(); break; + switch(type) { + ToArray(S1,s1,char); + ToArray(U1,u1,unsigned char); + ToArray(S2,s2,short); + ToArray(U2,u2,unsigned short); + ToArray(S4,s4,int); + ToArray(U4,u4,unsigned int); + ToArray(S8,s8,long long); + ToArray(U8,u8,unsigned long long); + ToArray(F,f,float); + ToArray(D,d,double); + case T: t = strings; break; /* Don't erase strings array. */ + default: break; + } +}; +/* ------------------------------------------------------------------- */ +void ezOptionValidator::print() { + printf("id=%d, op=%d, type=%d, size=%d, insensitive=%d\n", id, op, type, size, insensitive); +}; +/* ------------------------------------------------------------------- */ +bool ezOptionValidator::isValid(const std::string * valueAsString) { + if (valueAsString == 0) return false; + +#define CHECKRANGE(E,T) {\ + std::stringstream ss(valueAsString->c_str()); \ + long long E##value; \ + ss >> E##value; \ + long long E##min = static_cast(std::numeric_limits::min()); \ + if (E##value < E##min) { \ + if (!quiet) \ + std::cerr << "ERROR: Invalid value " << E##value << " is less than datatype min " << E##min << ".\n"; \ + return false; \ + } \ + \ + long long E##max = static_cast(std::numeric_limits::max()); \ + if (E##value > E##max) { \ + if (!quiet) \ + std::cerr << "ERROR: Invalid value " << E##value << " is greater than datatype max " << E##max << ".\n"; \ + return false; \ + } \ +} + // Check if within datatype limits. + if (type != T) { + switch(type) { + case S1: CHECKRANGE(S1,char); break; + case U1: CHECKRANGE(U1,unsigned char); break; + case S2: CHECKRANGE(S2,short); break; + case U2: CHECKRANGE(U2,unsigned short); break; + case S4: CHECKRANGE(S4,int); break; + case U4: CHECKRANGE(U4,unsigned int); break; + case S8: { + if ( (valueAsString->at(0) == '-') && + isdigit(valueAsString,1) && + (valueAsString->size() > 19) && + (valueAsString->compare(1, 19, "9223372036854775808") > 0) ) { + if (!quiet) + std::cerr << "ERROR: Invalid value " << *valueAsString << " is less than datatype min -9223372036854775808.\n"; + return false; + } + + if (isdigit(valueAsString) && + (valueAsString->size() > 18) && + valueAsString->compare("9223372036854775807") > 0) { + if (!quiet) + std::cerr << "ERROR: Invalid value " << *valueAsString << " is greater than datatype max 9223372036854775807.\n"; + return false; + } + } break; + case U8: { + if (valueAsString->compare("0") < 0) { + if (!quiet) + std::cerr << "ERROR: Invalid value " << *valueAsString << " is less than datatype min 0.\n"; + return false; + } + + if (isdigit(valueAsString) && + (valueAsString->size() > 19) && + valueAsString->compare("18446744073709551615") > 0) { + if (!quiet) + std::cerr << "ERROR: Invalid value " << *valueAsString << " is greater than datatype max 18446744073709551615.\n"; + return false; + } + } break; + case F: { + double dmax = static_cast(std::numeric_limits::max()); + double dvalue = atof(valueAsString->c_str()); + double dmin = -dmax; + if (dvalue < dmin) { + if (!quiet) { + fprintf(stderr, "ERROR: Invalid value %g is less than datatype min %g.\n", dvalue, dmin); + } + return false; + } + + if (dvalue > dmax) { + if (!quiet) + std::cerr << "ERROR: Invalid value " << dvalue << " is greater than datatype max " << dmax << ".\n"; + return false; + } + } break; + case D: { + long double ldmax = static_cast(std::numeric_limits::max()); + std::stringstream ss(valueAsString->c_str()); + long double ldvalue; + ss >> ldvalue; + long double ldmin = -ldmax; + + if (ldvalue < ldmin) { + if (!quiet) + std::cerr << "ERROR: Invalid value " << ldvalue << " is less than datatype min " << ldmin << ".\n"; + return false; + } + + if (ldvalue > ldmax) { + if (!quiet) + std::cerr << "ERROR: Invalid value " << ldvalue << " is greater than datatype max " << ldmax << ".\n"; + return false; + } + } break; + case NOTYPE: default: break; + } + } else { + if (op == IN) { + int i=0; + if (insensitive) { + std::string valueAsStringLower(*valueAsString); + ToLowerASCII(valueAsStringLower); + for(; i < size; ++i) { + if (valueAsStringLower.compare(t[i]->c_str()) == 0) + return true; + } + } else { + for(; i < size; ++i) { + if (valueAsString->compare(t[i]->c_str()) == 0) + return true; + } + } + return false; + } + } + + // Only check datatype limits, and return; + if (op == NOOP) return true; + +#define VALIDATE(T, U, LIST) { \ + /* Value string converted to true native type. */ \ + std::stringstream ss(valueAsString->c_str());\ + U v;\ + ss >> v;\ + /* Check if within list. */ \ + if (op == IN) { \ + T * last = LIST + size;\ + return (last != std::find(LIST, last, v)); \ + } \ + \ + /* Check if within user's custom range. */ \ + T v0, v1; \ + if (size > 0) { \ + v0 = LIST[0]; \ + } \ + \ + if (size > 1) { \ + v1 = LIST[1]; \ + } \ + \ + switch (op) {\ + case LT:\ + if (size > 0) {\ + return v < v0;\ + } else {\ + std::cerr << "ERROR: No value given to validate if " << v << " < X.\n";\ + return false;\ + }\ + break;\ + case LE:\ + if (size > 0) {\ + return v <= v0;\ + } else {\ + std::cerr << "ERROR: No value given to validate if " << v << " <= X.\n";\ + return false;\ + }\ + break;\ + case GT:\ + if (size > 0) {\ + return v > v0;\ + } else {\ + std::cerr << "ERROR: No value given to validate if " << v << " > X.\n";\ + return false;\ + }\ + break;\ + case GE:\ + if (size > 0) {\ + return v >= v0;\ + } else {\ + std::cerr << "ERROR: No value given to validate if " << v << " >= X.\n";\ + return false;\ + }\ + break;\ + case GTLT:\ + if (size > 1) {\ + return (v0 < v) && (v < v1);\ + } else {\ + std::cerr << "ERROR: Missing values to validate if X1 < " << v << " < X2.\n";\ + return false;\ + }\ + break;\ + case GELT:\ + if (size > 1) {\ + return (v0 <= v) && (v < v1);\ + } else {\ + std::cerr << "ERROR: Missing values to validate if X1 <= " << v << " < X2.\n";\ + return false;\ + }\ + break;\ + case GELE:\ + if (size > 1) {\ + return (v0 <= v) && (v <= v1);\ + } else {\ + std::cerr << "ERROR: Missing values to validate if X1 <= " << v << " <= X2.\n";\ + return false;\ + }\ + break;\ + case GTLE:\ + if (size > 1) {\ + return (v0 < v) && (v <= v1);\ + } else {\ + std::cerr << "ERROR: Missing values to validate if X1 < " << v << " <= X2.\n";\ + return false;\ + }\ + break;\ + case NOOP: case IN: default: break;\ + } \ + } + + switch(type) { + case U1: VALIDATE(unsigned char, int, u1); break; + case S1: VALIDATE(char, int, s1); break; + case U2: VALIDATE(unsigned short, int, u2); break; + case S2: VALIDATE(short, int, s2); break; + case U4: VALIDATE(unsigned int, unsigned int, u4); break; + case S4: VALIDATE(int, int, s4); break; + case U8: VALIDATE(unsigned long long, unsigned long long, u8); break; + case S8: VALIDATE(long long, long long, s8); break; + case F: VALIDATE(float, float, f); break; + case D: VALIDATE(double, double, d); break; + default: break; + } + + return true; +}; +/* ################################################################### */ +class OptionGroup { +public: + OptionGroup() : delim(0), expectArgs(0), isRequired(false), isSet(false) { } + + ~OptionGroup() { + int i; + for(i=0; i < (long int)flags.size(); ++i) + delete flags[i]; + + flags.clear(); + parseIndex.clear(); + clearArgs(); + }; + + inline void clearArgs(); + inline void getInt(int&); + inline void getLong(long&); + inline void getLongLong(long long&); + inline void getULong(unsigned long&); + inline void getULongLong(unsigned long long&); + inline void getFloat(float&); + inline void getDouble(double&); + inline void getString(std::string&); + inline void getInts(std::vector&); + inline void getLongs(std::vector&); + inline void getULongs(std::vector&); + inline void getFloats(std::vector&); + inline void getDoubles(std::vector&); + inline void getStrings(std::vector&); + inline void getMultiInts(std::vector< std::vector >&); + inline void getMultiLongs(std::vector< std::vector >&); + inline void getMultiULongs(std::vector< std::vector >&); + inline void getMultiFloats(std::vector< std::vector >&); + inline void getMultiDoubles(std::vector< std::vector >&); + inline void getMultiStrings(std::vector< std::vector >&); + + // defaults value regardless of being set by user. + std::string defaults; + // If expects arguments, this will delimit arg list. + char delim; + // If not 0, then number of delimited args. -1 for arbitrary number. + int expectArgs; + // Descriptive help message shown in usage instructions for option. + std::string help; + // 0 or 1. + bool isRequired; + // A list of flags that denote this option, i.e. -d, --dimension. + std::vector< std::string* > flags; + // If was set (or found). + bool isSet; + // Lists of arguments, per flag instance, after splitting by delimiter. + std::vector< std::vector< std::string* > * > args; + // Index where each group was parsed from input stream to track order. + std::vector parseIndex; +}; +/* ################################################################### */ +void OptionGroup::clearArgs() { + int i,j; + for(i=0; i < (long int)args.size(); ++i) { + for(j=0; j < (long int)args[i]->size(); ++j) + delete args[i]->at(j); + + delete args[i]; + } + + args.clear(); + isSet = false; +}; +/* ################################################################### */ +void OptionGroup::getInt(int & out) { + if (!isSet) { + if (defaults.empty()) + out = 0; + else + out = atoi(defaults.c_str()); + } else { + if (args.empty() || args[0]->empty()) + out = 0; + else { + out = atoi(args[0]->at(0)->c_str()); + } + } +}; +/* ################################################################### */ +void OptionGroup::getLong(long & out) { + if (!isSet) { + if (defaults.empty()) + out = 0; + else + out = atoi(defaults.c_str()); + } else { + if (args.empty() || args[0]->empty()) + out = 0; + else { + out = atol(args[0]->at(0)->c_str()); + } + } +}; +/* ################################################################### */ +void OptionGroup::getLongLong(long long & out) { + if (!isSet) { + if (defaults.empty()) + out = 0; + else { + std::stringstream ss(defaults.c_str()); + ss >> out; + } + } else { + if (args.empty() || args[0]->empty()) + out = 0; + else { + std::stringstream ss(args[0]->at(0)->c_str()); + ss >> out; + } + } +}; +/* ################################################################### */ +void OptionGroup::getULong(unsigned long & out) { + if (!isSet) { + if (defaults.empty()) + out = 0; + else + out = atoi(defaults.c_str()); + } else { + if (args.empty() || args[0]->empty()) + out = 0; + else { + out = strtoul(args[0]->at(0)->c_str(),0,0); + } + } +}; +/* ################################################################### */ +void OptionGroup::getULongLong(unsigned long long & out) { + if (!isSet) { + if (defaults.empty()) + out = 0; + else { + std::stringstream ss(defaults.c_str()); + ss >> out; + } + } else { + if (args.empty() || args[0]->empty()) + out = 0; + else { + std::stringstream ss(args[0]->at(0)->c_str()); + ss >> out; + } + } +}; +/* ################################################################### */ +void OptionGroup::getFloat(float & out) { + if (!isSet) { + if (defaults.empty()) + out = 0.0; + else + out = (float)atof(defaults.c_str()); + } else { + if (args.empty() || args[0]->empty()) + out = 0.0; + else { + out = (float)atof(args[0]->at(0)->c_str()); + } + } +}; +/* ################################################################### */ +void OptionGroup::getDouble(double & out) { + if (!isSet) { + if (defaults.empty()) + out = 0.0; + else + out = atof(defaults.c_str()); + } else { + if (args.empty() || args[0]->empty()) + out = 0.0; + else { + out = atof(args[0]->at(0)->c_str()); + } + } +}; +/* ################################################################### */ +void OptionGroup::getString(std::string & out) { + if (!isSet) { + out = defaults; + } else { + if (args.empty() || args[0]->empty()) + out = ""; + else { + out = *args[0]->at(0); + } + } +}; +/* ################################################################### */ +void OptionGroup::getInts(std::vector & out) { + if (!isSet) { + if (!defaults.empty()) { + std::vector< std::string > strings; + SplitDelim(defaults, delim, strings); + StringsToInts(strings, out); + } + } else { + if (!(args.empty() || args[0]->empty())) + StringsToInts(args[0], &out); + } +}; +/* ################################################################### */ +void OptionGroup::getLongs(std::vector & out) { + if (!isSet) { + if (!defaults.empty()) { + std::vector< std::string > strings; + SplitDelim(defaults, delim, strings); + StringsToLongs(strings, out); + } + } else { + if (!(args.empty() || args[0]->empty())) + StringsToLongs(args[0], &out); + } +}; +/* ################################################################### */ +void OptionGroup::getULongs(std::vector & out) { + if (!isSet) { + if (!defaults.empty()) { + std::vector< std::string > strings; + SplitDelim(defaults, delim, strings); + StringsToULongs(strings, out); + } + } else { + if (!(args.empty() || args[0]->empty())) + StringsToULongs(args[0], &out); + } +}; +/* ################################################################### */ +void OptionGroup::getFloats(std::vector & out) { + if (!isSet) { + if (!defaults.empty()) { + std::vector< std::string > strings; + SplitDelim(defaults, delim, strings); + StringsToFloats(strings, out); + } + } else { + if (!(args.empty() || args[0]->empty())) + StringsToFloats(args[0], &out); + } +}; +/* ################################################################### */ +void OptionGroup::getDoubles(std::vector & out) { + if (!isSet) { + if (!defaults.empty()) { + std::vector< std::string > strings; + SplitDelim(defaults, delim, strings); + StringsToDoubles(strings, out); + } + } else { + if (!(args.empty() || args[0]->empty())) + StringsToDoubles(args[0], &out); + } +}; +/* ################################################################### */ +void OptionGroup::getStrings(std::vector& out) { + if (!isSet) { + if (!defaults.empty()) { + SplitDelim(defaults, delim, out); + } + } else { + if (!(args.empty() || args[0]->empty())) + StringsToStrings(args[0], &out); + } +}; +/* ################################################################### */ +void OptionGroup::getMultiInts(std::vector< std::vector >& out) { + if (!isSet) { + if (!defaults.empty()) { + std::vector< std::string > strings; + SplitDelim(defaults, delim, strings); + if (out.size() < 1) out.resize(1); + StringsToInts(strings, out[0]); + } + } else { + if (!args.empty()) { + int n = args.size(); + if ((long int)out.size() < n) out.resize(n); + for(int i=0; i < n; ++i) { + StringsToInts(args[i], &out[i]); + } + } + } +}; +/* ################################################################### */ +void OptionGroup::getMultiLongs(std::vector< std::vector >& out) { + if (!isSet) { + if (!defaults.empty()) { + std::vector< std::string > strings; + SplitDelim(defaults, delim, strings); + if (out.size() < 1) out.resize(1); + StringsToLongs(strings, out[0]); + } + } else { + if (!args.empty()) { + int n = args.size(); + if ((long int)out.size() < n) out.resize(n); + for(int i=0; i < n; ++i) { + StringsToLongs(args[i], &out[i]); + } + } + } +}; +/* ################################################################### */ +void OptionGroup::getMultiULongs(std::vector< std::vector >& out) { + if (!isSet) { + if (!defaults.empty()) { + std::vector< std::string > strings; + SplitDelim(defaults, delim, strings); + if (out.size() < 1) out.resize(1); + StringsToULongs(strings, out[0]); + } + } else { + if (!args.empty()) { + int n = args.size(); + if ((long int)out.size() < n) out.resize(n); + for(int i=0; i < n; ++i) { + StringsToULongs(args[i], &out[i]); + } + } + } +}; +/* ################################################################### */ +void OptionGroup::getMultiFloats(std::vector< std::vector >& out) { + if (!isSet) { + if (!defaults.empty()) { + std::vector< std::string > strings; + SplitDelim(defaults, delim, strings); + if (out.size() < 1) out.resize(1); + StringsToFloats(strings, out[0]); + } + } else { + if (!args.empty()) { + int n = args.size(); + if ((long int)out.size() < n) out.resize(n); + for(int i=0; i < n; ++i) { + StringsToFloats(args[i], &out[i]); + } + } + } +}; +/* ################################################################### */ +void OptionGroup::getMultiDoubles(std::vector< std::vector >& out) { + if (!isSet) { + if (!defaults.empty()) { + std::vector< std::string > strings; + SplitDelim(defaults, delim, strings); + if (out.size() < 1) out.resize(1); + StringsToDoubles(strings, out[0]); + } + } else { + if (!args.empty()) { + int n = args.size(); + if ((long int)out.size() < n) out.resize(n); + for(int i=0; i < n; ++i) { + StringsToDoubles(args[i], &out[i]); + } + } + } +}; +/* ################################################################### */ +void OptionGroup::getMultiStrings(std::vector< std::vector >& out) { + if (!isSet) { + if (!defaults.empty()) { + std::vector< std::string > strings; + SplitDelim(defaults, delim, strings); + if (out.size() < 1) out.resize(1); + out[0] = strings; + } + } else { + if (!args.empty()) { + int n = args.size(); + if ((long int)out.size() < n) out.resize(n); + + for(int i=0; i < n; ++i) { + for(int j=0; j < (long int)args[i]->size(); ++j) + out[i].push_back( *args[i]->at(j) ); + } + } + } +}; +/* ################################################################### */ +typedef std::map< int, ezOptionValidator* > ValidatorMap; + +class ezOptionParser { +public: + // How to layout usage descriptions with the option flags. + enum Layout { ALIGN, INTERLEAVE, STAGGER }; + + inline ~ezOptionParser(); + + inline void add(const char * defaults, bool required, int expectArgs, char delim, const char * help, const char * flag1, ezOptionValidator* validator=0); + inline void add(const char * defaults, bool required, int expectArgs, char delim, const char * help, const char * flag1, const char * flag2, ezOptionValidator* validator=0); + inline void add(const char * defaults, bool required, int expectArgs, char delim, const char * help, const char * flag1, const char * flag2, const char * flag3, ezOptionValidator* validator=0); + inline void add(const char * defaults, bool required, int expectArgs, char delim, const char * help, const char * flag1, const char * flag2, const char * flag3, const char * flag4, ezOptionValidator* validator=0); + inline bool exportFile(const char * filename, bool all=false); + inline OptionGroup * get(const char * name); + inline void getUsage(std::string & usage, int width=80, Layout layout=ALIGN); + inline void getUsageDescriptions(std::string & usage, int width=80, Layout layout=STAGGER); + inline bool gotExpected(std::vector & badOptions); + inline bool gotRequired(std::vector & badOptions); + inline bool gotValid(std::vector & badOptions, std::vector & badArgs); + inline bool importFile(const char * filename, char comment='#'); + inline int isSet(const char * name); + inline int isSet(std::string & name); + inline void parse(int argc, const char * argv[]); + inline void prettyPrint(std::string & out); + inline void reset(); + inline void resetArgs(); + + // Insert extra empty line betwee each option's usage description. + char doublespace; + // General description in human language on what the user's tool does. + // It's the first section to get printed in the full usage message. + std::string overview; + // A synopsis of command and options usage to show expected order of input arguments. + // It's the second section to get printed in the full usage message. + std::string syntax; + // Example (third) section in usage message. + std::string example; + // Final section printed in usage message. For contact, copyrights, version info. + std::string footer; + // Map from an option to an Id of its parent group. + std::map< std::string, int > optionGroupIds; + // Unordered collection of the option groups. + std::vector< OptionGroup* > groups; + // Store unexpected args in input. + std::vector< std::string* > unknownArgs; + // List of args that occur left-most before first option flag. + std::vector< std::string* > firstArgs; + // List of args that occur after last right-most option flag and its args. + std::vector< std::string* > lastArgs; + // List of validators. + ValidatorMap validators; + // Maps group id to a validator index into vector of validators. Validator index is -1 if there is no validator for group. + std::map< int, int > groupValidators; +}; +/* ################################################################### */ +ezOptionParser::~ezOptionParser() { + reset(); +} +/* ################################################################### */ +void ezOptionParser::reset() { + this->doublespace = 1; + + int i; + for(i=0; i < (long int)groups.size(); ++i) + delete groups[i]; + groups.clear(); + + for(i=0; i < (long int)unknownArgs.size(); ++i) + delete unknownArgs[i]; + unknownArgs.clear(); + + for(i=0; i < (long int)firstArgs.size(); ++i) + delete firstArgs[i]; + firstArgs.clear(); + + for(i=0; i < (long int)lastArgs.size(); ++i) + delete lastArgs[i]; + lastArgs.clear(); + + ValidatorMap::iterator it; + for(it = validators.begin(); it != validators.end(); ++it) + delete it->second; + + validators.clear(); + optionGroupIds.clear(); + groupValidators.clear(); +}; +/* ################################################################### */ +void ezOptionParser::resetArgs() { + int i; + for(i=0; i < (long int)groups.size(); ++i) + groups[i]->clearArgs(); + + for(i=0; i < (long int)unknownArgs.size(); ++i) + delete unknownArgs[i]; + unknownArgs.clear(); + + for(i=0; i < (long int)firstArgs.size(); ++i) + delete firstArgs[i]; + firstArgs.clear(); + + for(i=0; i < (long int)lastArgs.size(); ++i) + delete lastArgs[i]; + lastArgs.clear(); +}; +/* ################################################################### */ +void ezOptionParser::add(const char * defaults, bool required, int expectArgs, char delim, const char * help, const char * flag1, ezOptionValidator* validator) { + int id = this->groups.size(); + OptionGroup * g = new OptionGroup; + g->defaults = defaults; + g->isRequired = required; + g->expectArgs = expectArgs; + g->delim = delim; + g->isSet = 0; + g->help = help; + std::string *f1 = new std::string(flag1); + g->flags.push_back( f1 ); + this->optionGroupIds[flag1] = id; + this->groups.push_back(g); + + if (validator) { + int vid = validator->id; + validators[vid] = validator; + groupValidators[id] = vid; + } else { + groupValidators[id] = -1; + } +}; +/* ################################################################### */ +void ezOptionParser::add(const char * defaults, bool required, int expectArgs, char delim, const char * help, const char * flag1, const char * flag2, ezOptionValidator* validator) { + int id = this->groups.size(); + OptionGroup * g = new OptionGroup; + g->defaults = defaults; + g->isRequired = required; + g->expectArgs = expectArgs; + g->delim = delim; + g->isSet = 0; + g->help = help; + std::string *f1 = new std::string(flag1); + g->flags.push_back( f1 ); + std::string *f2 = new std::string(flag2); + g->flags.push_back( f2 ); + this->optionGroupIds[flag1] = id; + this->optionGroupIds[flag2] = id; + + this->groups.push_back(g); + + if (validator) { + int vid = validator->id; + validators[vid] = validator; + groupValidators[id] = vid; + } else { + groupValidators[id] = -1; + } +}; +/* ################################################################### */ +void ezOptionParser::add(const char * defaults, bool required, int expectArgs, char delim, const char * help, const char * flag1, const char * flag2, const char * flag3, ezOptionValidator* validator) { + int id = this->groups.size(); + OptionGroup * g = new OptionGroup; + g->defaults = defaults; + g->isRequired = required; + g->expectArgs = expectArgs; + g->delim = delim; + g->isSet = 0; + g->help = help; + std::string *f1 = new std::string(flag1); + g->flags.push_back( f1 ); + std::string *f2 = new std::string(flag2); + g->flags.push_back( f2 ); + std::string *f3 = new std::string(flag3); + g->flags.push_back( f3 ); + this->optionGroupIds[flag1] = id; + this->optionGroupIds[flag2] = id; + this->optionGroupIds[flag3] = id; + + this->groups.push_back(g); + + if (validator) { + int vid = validator->id; + validators[vid] = validator; + groupValidators[id] = vid; + } else { + groupValidators[id] = -1; + } +}; +/* ################################################################### */ +void ezOptionParser::add(const char * defaults, bool required, int expectArgs, char delim, const char * help, const char * flag1, const char * flag2, const char * flag3, const char * flag4, ezOptionValidator* validator) { + int id = this->groups.size(); + OptionGroup * g = new OptionGroup; + g->defaults = defaults; + g->isRequired = required; + g->expectArgs = expectArgs; + g->delim = delim; + g->isSet = 0; + g->help = help; + std::string *f1 = new std::string(flag1); + g->flags.push_back( f1 ); + std::string *f2 = new std::string(flag2); + g->flags.push_back( f2 ); + std::string *f3 = new std::string(flag3); + g->flags.push_back( f3 ); + std::string *f4 = new std::string(flag4); + g->flags.push_back( f4 ); + this->optionGroupIds[flag1] = id; + this->optionGroupIds[flag2] = id; + this->optionGroupIds[flag3] = id; + this->optionGroupIds[flag4] = id; + + this->groups.push_back(g); + + if (validator) { + int vid = validator->id; + validators[vid] = validator; + groupValidators[id] = vid; + } else { + groupValidators[id] = -1; + } +}; +/* ################################################################### */ +bool ezOptionParser::exportFile(const char * filename, bool all) { + int i; + std::string out; + bool quote; + + // Export the first args, except the program name, so start from 1. + for(i=1; i < (long int)firstArgs.size(); ++i) { + quote = ((firstArgs[i]->find_first_of(" \t") != std::string::npos) && (firstArgs[i]->find_first_of("\'\"") == std::string::npos)); + + if (quote) + out.append("\""); + + out.append(*firstArgs[i]); + if (quote) + out.append("\""); + + out.append(" "); + } + + if (firstArgs.size() > 1) + out.append("\n"); + + std::vector stringPtrs(groups.size()); + int m; + int n = groups.size(); + for(i=0; i < n; ++i) { + stringPtrs[i] = groups[i]->flags[0]; + } + + OptionGroup *g; + // Sort first flag of each group with other groups. + std::sort(stringPtrs.begin(), stringPtrs.end(), CmpOptStringPtr); + for(i=0; i < n; ++i) { + g = get(stringPtrs[i]->c_str()); + if (g->isSet || all) { + if (!g->isSet || g->args.empty()) { + if (!g->defaults.empty()) { + out.append(*stringPtrs[i]); + out.append(" "); + quote = ((g->defaults.find_first_of(" \t") != std::string::npos) && (g->defaults.find_first_of("\'\"") == std::string::npos)); + if (quote) + out.append("\""); + + out.append(g->defaults); + if (quote) + out.append("\""); + + out.append("\n"); + } + } else { + int n = g->args.size(); + for(int j=0; j < n; ++j) { + out.append(*stringPtrs[i]); + out.append(" "); + m = g->args[j]->size(); + + for(int k=0; k < m; ++k) { + quote = ( (*g->args[j]->at(k)).find_first_of(" \t") != std::string::npos ); + if (quote) + out.append("\""); + + out.append(*g->args[j]->at(k)); + if (quote) + out.append("\""); + + if ((g->delim) && ((k+1) != m)) + out.append(1,g->delim); + } + out.append("\n"); + } + } + } + } + + // Export the last args. + for(i=0; i < (long int)lastArgs.size(); ++i) { + quote = ( lastArgs[i]->find_first_of(" \t") != std::string::npos ); + if (quote) + out.append("\""); + + out.append(*lastArgs[i]); + if (quote) + out.append("\""); + + out.append(" "); + } + + std::ofstream file(filename); + if (!file.is_open()) + return false; + + file << out; + file.close(); + + return true; +}; +/* ################################################################### */ +// Does not overwrite current options. +// Returns true if file was read successfully. +// So if this is used before parsing CLI, then option values will reflect +// this file, but if used after parsing CLI, then values will contain +// both CLI values and file's values. +// +// Comment lines are allowed if prefixed with #. +// Strings should be quoted as usual. +bool ezOptionParser::importFile(const char * filename, char comment) { + std::ifstream file (filename, std::ios::in | std::ios::ate); + if (!file.is_open()) + return false; + + // Read entire file contents. + std::ifstream::pos_type size = file.tellg(); + char * memblock = new char[(int)size+1]; // Add one for end of string. + file.seekg (0, std::ios::beg); + file.read (memblock, size); + memblock[size] = '\0'; + file.close(); + + // Find comment lines. + std::list lines; + std::string memblockstring(memblock); + delete[] memblock; + SplitDelim(memblockstring, '\n', lines); + int i,j,n; + std::list::iterator iter; + std::vector sq, dq; // Single and double quote indices. + std::vector::iterator lo; // For searching quote indices. + size_t pos; + const char *str; + std::string *line; + // Find all single and double quotes to correctly handle comment tokens. + for(iter=lines.begin(); iter != lines.end(); ++iter) { + line = *iter; + str = line->c_str(); + n = line->size(); + sq.clear(); + dq.clear(); + if (n) { + // If first char is comment, then erase line and continue. + pos = line->find_first_not_of(" \t\r"); + if ((pos==std::string::npos) || (line->at(pos)==comment)) { + line->erase(); + continue; + } else { + // Erase whitespace prefix. + line->erase(0,pos); + n = line->size(); + } + + if (line->at(0)=='"') + dq.push_back(0); + + if (line->at(0)=='\'') + sq.push_back(0); + } else { // Empty line. + continue; + } + + for(i=1; i < n; ++i) { + if ( (str[i]=='"') && (str[i-1]!='\\') ) + dq.push_back(i); + else if ( (str[i]=='\'') && (str[i-1]!='\\') ) + sq.push_back(i); + } + // Scan for comments, and when found, check bounds of quotes. + // Start with second char because already checked first char. + for(i=1; i < n; ++i) { + if ( (line->at(i)==comment) && (line->at(i-1)!='\\') ) { + // If within open/close quote pair, then not real comment. + if (sq.size()) { + lo = std::lower_bound(sq.begin(), sq.end(), i); + // All start of strings will be even indices, closing quotes is odd indices. + j = (int)(lo-sq.begin()); + if ( (j % 2) == 0) { // Even implies comment char not in quote pair. + // Erase from comment char to end of line. + line->erase(i); + break; + } + } else if (dq.size()) { + // Repeat tests for double quotes. + lo = std::lower_bound(dq.begin(), dq.end(), i); + j = (int)(lo-dq.begin()); + if ( (j % 2) == 0) { + line->erase(i); + break; + } + } else { + // Not in quotes. + line->erase(i); + break; + } + } + } + } + + std::string cmd; + // Convert list to string without newlines to simulate commandline. + for(iter=lines.begin(); iter != lines.end(); ++iter) { + if (! (*iter)->empty()) { + cmd.append(**iter); + cmd.append(" "); + } + } + + // Now parse as if from command line. + int argc=0; + char** argv = CommandLineToArgvA((char*)cmd.c_str(), &argc); + + // Parse. + parse(argc, (const char**)argv); + if (argv) free(argv); + for(iter=lines.begin(); iter != lines.end(); ++iter) + delete *iter; + + return true; +}; +/* ################################################################### */ +int ezOptionParser::isSet(const char * name) { + std::string sname(name); + + if (this->optionGroupIds.count(sname)) { + return this->groups[this->optionGroupIds[sname]]->isSet; + } + + return 0; +}; +/* ################################################################### */ +int ezOptionParser::isSet(std::string & name) { + if (this->optionGroupIds.count(name)) { + return this->groups[this->optionGroupIds[name]]->isSet; + } + + return 0; +}; +/* ################################################################### */ +OptionGroup * ezOptionParser::get(const char * name) { + if (optionGroupIds.count(name)) { + return groups[optionGroupIds[name]]; + } + + return 0; +}; +/* ################################################################### */ +void ezOptionParser::getUsage(std::string & usage, int width, Layout layout) { + + usage.append(overview); + usage.append("\n\n"); + usage.append("USAGE: "); + usage.append(syntax); + usage.append("\n\nOPTIONS:\n\n"); + getUsageDescriptions(usage, width, layout); + + if (!example.empty()) { + usage.append("EXAMPLES:\n\n"); + usage.append(example); + } + + if (!footer.empty()) { + usage.append(footer); + } +}; +/* ################################################################### */ +// Creates 2 column formatted help descriptions for each option flag. +void ezOptionParser::getUsageDescriptions(std::string & usage, int width, Layout layout) { + // Sort each flag list amongst each group. + int i; + // Store index of flag groups before sort for easy lookup later. + std::map stringPtrToIndexMap; + std::vector stringPtrs(groups.size()); + + for(i=0; i < (long int)groups.size(); ++i) { + std::sort(groups[i]->flags.begin(), groups[i]->flags.end(), CmpOptStringPtr); + stringPtrToIndexMap[groups[i]->flags[0]] = i; + stringPtrs[i] = groups[i]->flags[0]; + } + + size_t j, k; + std::string opts; + std::vector sortedOpts; + // Sort first flag of each group with other groups. + std::sort(stringPtrs.begin(), stringPtrs.end(), CmpOptStringPtr); + for(i=0; i < (long int)groups.size(); ++i) { + //printf("DEBUG:%d: %d %d %s\n", __LINE__, i, stringPtrToIndexMap[stringPtrs[i]], stringPtrs[i]->c_str()); + k = stringPtrToIndexMap[stringPtrs[i]]; + opts.clear(); + for(j=0; j < groups[k]->flags.size()-1; ++j) { + opts.append(*groups[k]->flags[j]); + opts.append(", "); + + if ((long int)opts.size() > width) + opts.append("\n"); + } + // The last flag. No need to append comma anymore. + opts.append( *groups[k]->flags[j] ); + + if (groups[k]->expectArgs) { + opts.append(" ARG"); + + if (groups[k]->delim) { + opts.append("1["); + opts.append(1, groups[k]->delim); + opts.append("ARGn]"); + } + } + + sortedOpts.push_back(opts); + } + + // Each option group will use this to build multiline help description. + std::list desc; + // Number of whitespaces from start of line to description (interleave layout) or + // gap between flag names and description (align, stagger layouts). + int gutter = 3; + + // Find longest opt flag string to set column start for help usage descriptions. + int maxlen=0; + if (layout == ALIGN) { + for(i=0; i < (long int)groups.size(); ++i) { + if (maxlen < (long int)sortedOpts[i].size()) + maxlen = sortedOpts[i].size(); + } + } + + // The amount of space remaining on a line for help text after flags. + int helpwidth; + std::list::iterator cIter, insertionIter; + size_t pos; + for(i=0; i < (long int)groups.size(); ++i) { + k = stringPtrToIndexMap[stringPtrs[i]]; + + if (layout == STAGGER) + maxlen = sortedOpts[i].size(); + + int pad = gutter + maxlen; + helpwidth = width - pad; + + // All the following split-fu could be optimized by just using substring (offset, length) tuples, but just to get it done, we'll do some not-too expensive string copying. + SplitDelim(groups[k]->help, '\n', desc); + // Split lines longer than allowable help width. + for(insertionIter=desc.begin(), cIter=insertionIter++; + cIter != desc.end(); + cIter=insertionIter++) { + if ((long int)((*cIter)->size()) > helpwidth) { + // Get pointer to next string to insert new strings before it. + std::string *rem = *cIter; + // Remove this line and add back in pieces. + desc.erase(cIter); + // Loop until remaining string is short enough. + while ((long int)rem->size() > helpwidth) { + // Find whitespace to split before helpwidth. + if (rem->at(helpwidth) == ' ') { + // If word ends exactly at helpwidth, then split after it. + pos = helpwidth; + } else { + // Otherwise, split occurs midword, so find whitespace before this word. + pos = rem->rfind(" ", helpwidth); + } + // Insert split string. + desc.insert(insertionIter, new std::string(*rem, 0, pos)); + // Now skip any whitespace to start new line. + pos = rem->find_first_not_of(' ', pos); + rem->erase(0, pos); + } + + if (rem->size()) + desc.insert(insertionIter, rem); + else + delete rem; + } + } + + usage.append(sortedOpts[i]); + if (layout != INTERLEAVE) + // Add whitespace between option names and description. + usage.append(pad - sortedOpts[i].size(), ' '); + else { + usage.append("\n"); + usage.append(gutter, ' '); + } + + if (desc.size() > 0) { // Crash fix by Bruce Shankle. + // First line already padded above (before calling SplitDelim) after option flag names. + cIter = desc.begin(); + usage.append(**cIter); + usage.append("\n"); + // Now inject the pad for each line. + for(++cIter; cIter != desc.end(); ++cIter) { + usage.append(pad, ' '); + usage.append(**cIter); + usage.append("\n"); + } + + if (this->doublespace) usage.append("\n"); + + for(cIter=desc.begin(); cIter != desc.end(); ++cIter) + delete *cIter; + + desc.clear(); + } + + } +}; +/* ################################################################### */ +bool ezOptionParser::gotExpected(std::vector & badOptions) { + int i,j; + + for(i=0; i < (long int)groups.size(); ++i) { + OptionGroup *g = groups[i]; + // If was set, ensure number of args is correct. + if (g->isSet) { + if ((g->expectArgs != 0) && g->args.empty()) { + badOptions.push_back(*g->flags[0]); + continue; + } + + for(j=0; j < (long int)g->args.size(); ++j) { + if ((g->expectArgs != -1) && (g->expectArgs != (long int)g->args[j]->size())) + badOptions.push_back(*g->flags[0]); + } + } + } + + return badOptions.empty(); +}; +/* ################################################################### */ +bool ezOptionParser::gotRequired(std::vector & badOptions) { + int i; + + for(i=0; i < (long int)groups.size(); ++i) { + OptionGroup *g = groups[i]; + // Simple case when required but user never set it. + if (g->isRequired && (!g->isSet)) { + badOptions.push_back(*g->flags[0]); + continue; + } + } + + return badOptions.empty(); +}; +/* ################################################################### */ +bool ezOptionParser::gotValid(std::vector & badOptions, std::vector & badArgs) { + int groupid, validatorid; + std::map< int, int >::iterator it; + + for(it = groupValidators.begin(); it != groupValidators.end(); ++it) { + groupid = it->first; + validatorid = it->second; + if (validatorid < 0) continue; + + OptionGroup *g = groups[groupid]; + ezOptionValidator *v = validators[validatorid]; + bool nextgroup = false; + + for (int i = 0; i < (long int)g->args.size(); ++i) { + if (nextgroup) break; + std::vector< std::string* > * args = g->args[i]; + for (int j = 0; j < (long int)args->size(); ++j) { + if (!v->isValid(args->at(j))) { + badOptions.push_back(*g->flags[0]); + badArgs.push_back(*args->at(j)); + nextgroup = true; + break; + } + } + } + } + + return badOptions.empty(); +}; +/* ################################################################### */ +void ezOptionParser::parse(int argc, const char * argv[]) { + if (argc < 1) return; + + /* + std::map::iterator it; + for ( it=optionGroupIds.begin() ; it != optionGroupIds.end(); it++ ) + std::cout << (*it).first << " => " << (*it).second << std::endl; + */ + + int i, k, firstOptIndex=0, lastOptIndex=0; + std::string s; + OptionGroup *g; + + for(i=0; i < argc; ++i) { + s = argv[i]; + + if (optionGroupIds.count(s)) + break; + } + + firstOptIndex = i; + + if (firstOptIndex == argc) { + // No flags encountered, so set last args. + this->firstArgs.push_back(new std::string(argv[0])); + + for(k=1; k < argc; ++k) + this->lastArgs.push_back(new std::string(argv[k])); + + return; + } + + // Store initial args before opts appear. + for(k=0; k < i; ++k) { + this->firstArgs.push_back(new std::string(argv[k])); + } + + for(; i < argc; ++i) { + s = argv[i]; + + if (optionGroupIds.count(s)) { + k = optionGroupIds[s]; + g = groups[k]; + g->isSet = 1; + g->parseIndex.push_back(i); + + if (g->expectArgs) { + // Read ahead to get args. + ++i; + if (i >= argc) return; + g->args.push_back(new std::vector); + SplitDelim(argv[i], g->delim, g->args.back()); + } + lastOptIndex = i; + } + } + + // Scan for unknown opts/arguments. + for(i=firstOptIndex; i <= lastOptIndex; ++i) { + s = argv[i]; + + if (optionGroupIds.count(s)) { + k = optionGroupIds[s]; + g = groups[k]; + if (g->expectArgs) { + // Read ahead for args and skip them. + ++i; + } + } else { + unknownArgs.push_back(new std::string(argv[i])); + } + } + + if ( lastOptIndex >= (argc-1) ) return; + + // Store final args without flags. + for(k=lastOptIndex + 1; k < argc; ++k) { + this->lastArgs.push_back(new std::string(argv[k])); + } +}; +/* ################################################################### */ +void ezOptionParser::prettyPrint(std::string & out) { + char tmp[256]; + int i,j,k; + + out += "First Args:\n"; + for(i=0; i < (long int)firstArgs.size(); ++i) { + sprintf(tmp, "%d: %s\n", i+1, firstArgs[i]->c_str()); + out += tmp; + } + + // Sort the option flag names. + int n = groups.size(); + std::vector stringPtrs(n); + for(i=0; i < n; ++i) { + stringPtrs[i] = groups[i]->flags[0]; + } + + // Sort first flag of each group with other groups. + std::sort(stringPtrs.begin(), stringPtrs.end(), CmpOptStringPtr); + + out += "\nOptions:\n"; + OptionGroup *g; + for(i=0; i < n; ++i) { + g = get(stringPtrs[i]->c_str()); + out += "\n"; + // The flag names: + for(j=0; j < (long int)g->flags.size()-1; ++j) { + sprintf(tmp, "%s, ", g->flags[j]->c_str()); + out += tmp; + } + sprintf(tmp, "%s:\n", g->flags.back()->c_str()); + out += tmp; + + if (g->isSet) { + if (g->expectArgs) { + if (g->args.empty()) { + sprintf(tmp, "%s (default)\n", g->defaults.c_str()); + out += tmp; + } else { + for(k=0; k < (long int)g->args.size(); ++k) { + for(j=0; j < (long int)g->args[k]->size()-1; ++j) { + sprintf(tmp, "%s%c", g->args[k]->at(j)->c_str(), g->delim); + out += tmp; + } + sprintf(tmp, "%s\n", g->args[k]->back()->c_str()); + out += tmp; + } + } + } else { // Set but no args expected. + sprintf(tmp, "Set\n"); + out += tmp; + } + } else { + sprintf(tmp, "Not set\n"); + out += tmp; + } + } + + out += "\nLast Args:\n"; + for(i=0; i < (long int)lastArgs.size(); ++i) { + sprintf(tmp, "%d: %s\n", i+1, lastArgs[i]->c_str()); + out += tmp; + } + + out += "\nUnknown Args:\n"; + for(i=0; i < (long int)unknownArgs.size(); ++i) { + sprintf(tmp, "%d: %s\n", i+1, unknownArgs[i]->c_str()); + out += tmp; + } +}; +} +/* ################################################################### */ +#endif /* EZ_OPTION_PARSER_H */ diff --git a/fix_subs.cpp b/fix_subs.cpp new file mode 100644 index 0000000..a7f4631 --- /dev/null +++ b/fix_subs.cpp @@ -0,0 +1,119 @@ +#include +#include +#include +#include "ezOptionParser.hpp" + +int main(int argc, const char * argv[]) +{ + //std::string path = "/mnt/z/Marvel's Daredevil (2015) [tvdb-281662]"; + + // args parser + ez::ezOptionParser opt; + opt.overview = "Fix subs in nested folders for TV show for Plex"; + opt.syntax = "fix_subs -f "; + opt.add ( + "", + 1, + 0, + 0, + "PATH of the folder to be fixed.", + "-f" + ); + opt.parse(argc, argv); + + if (opt.lastArgs.size() != 1) + { + std::cerr << "ERROR: Expected 1 argument, but got " << opt.lastArgs.size() << std::endl << std::endl; + std::string usage; + opt.getUsage(usage); + std::cout << usage; + exit(1); + } + + std::string path = *opt.lastArgs[0]; + + // remove trailing slash if there is one + while (path.back() == '/') + { + path = path.substr(0, path.length()-1); + } + std::cout << "Path set to: " << path << std::endl << "Press ENTER to continue" << std::endl; + std::cin.ignore(); + std::cout << "Beginning to fix subs folders..." << std::endl; + // iterate through the top directory + for (const auto &top_dir : std::filesystem::directory_iterator(path)) + { + // if there are files in the top directory, ignore them + if (std::filesystem::is_regular_file(top_dir.path()) == true) + continue; + + // only care for directories starting with 'Season' + std::string folder_name = top_dir.path().filename(); + if (folder_name.starts_with("Season") == true) + { + std::string top_seasons_dir = top_dir.path(); + + // ensure "Subs" directory exists + std::string seasons_dir_subs = top_seasons_dir + "/Subs"; + if (std::filesystem::exists(seasons_dir_subs) == true) + { + // interate through the seasons directories + for (const auto &seasons_dir : std::filesystem::directory_iterator(top_dir.path())) + { + std::string item = seasons_dir.path(); + + // ignore txt files + if (item.ends_with(".txt") == true || item.ends_with(".srt")) + continue; + + // only care about the files + if (std::filesystem::is_regular_file(item) == true) + { + std::string full_episode_filename = seasons_dir.path().filename(); + + // strip extension from filename + std::string episode_filename = full_episode_filename.substr(0, full_episode_filename.find_last_of(".")); + + // iterate through the season's subs folder + std::string episode_sub_dir = seasons_dir_subs + "/" + episode_filename; + + for (const auto &subs_dir : std::filesystem::directory_iterator(episode_sub_dir)) + { + // only care about English (lol) + std::string sub_filename = subs_dir.path().filename(); + if (sub_filename.ends_with("English.srt")) + { + // fix filename + std::string sub_new_filename = episode_filename + "_" + sub_filename; + + // move to season directory + std::string new_sub_dir = top_seasons_dir + "/" + sub_new_filename; + + try + { + std::filesystem::rename(subs_dir.path(), new_sub_dir); + } + catch (std::filesystem::filesystem_error &e) + { + std::cout << "Error: " << e.what() << std::endl; + } + } + } + } + } + + //delete "Subs" directory + try + { + std::filesystem::remove_all(seasons_dir_subs); + } + catch (std::filesystem::filesystem_error &e) + { + std::cout << "Error: " << e.what() << std::endl; + } + } + } + } + + return 0; +} \ No newline at end of file