diff --git a/.travis.yml b/.travis.yml index b41a0f5..ed2d4bf 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,6 @@ language: cpp -dist: trusty +dist: xenial sudo: required group: edge @@ -51,7 +51,7 @@ matrix: - TEST_LANGUAGE_STANDARD=14 addons: apt: - sources: ['ubuntu-toolchain-r-test', 'llvm-toolchain-trusty-5.0'] + sources: ['ubuntu-toolchain-r-test', 'llvm-toolchain-xenial-5.0'] packages: ['g++-7', 'clang-5.0', 'ninja-build'] - os: linux @@ -61,9 +61,30 @@ matrix: - TEST_LANGUAGE_STANDARD=17 addons: apt: - sources: ['ubuntu-toolchain-r-test', 'llvm-toolchain-trusty-6.0'] + sources: ['ubuntu-toolchain-r-test', 'llvm-toolchain-xenial-6.0'] packages: ['g++-7', 'clang-6.0', 'ninja-build'] + - os: linux + compiler: clang + env: + - COMPILER=clang++-7 + - TEST_LANGUAGE_STANDARD=17 + addons: + apt: + sources: ['ubuntu-toolchain-r-test', 'llvm-toolchain-xenial-7'] + packages: ['g++-8', 'clang-7', 'ninja-build'] + + - os: linux + compiler: clang + env: + - COMPILER=clang++-8 + - TEST_LANGUAGE_STANDARD=17 + addons: + apt: + sources: ['ubuntu-toolchain-r-test', 'llvm-toolchain-xenial-8'] + packages: ['g++-8', 'clang-8', 'ninja-build'] + + ################ # build script # ################ diff --git a/README.md b/README.md index dc3759a..b7fa19f 100644 --- a/README.md +++ b/README.md @@ -91,7 +91,7 @@ See the DirectX 12 enabled [viewer](examples/viewer) example for a demonstration | ------------| -------------------------- | | .gltf files w/external resources | 100% complete and passing | | .gltf files w/embedded resources | 100% complete and passing (2 models excluded due to out-of-spec mimetypes) | -| .gltf files w/pbrSpecularGlossiness extension | 100% complete and passing | +| .gltf files w/Draco extension | 100% complete and passing | | .glb files | 100% complete and passing | * Safety diff --git a/examples/thirdparty/CLI11/CLI11.hpp b/examples/thirdparty/CLI11/CLI11.hpp index bf3f813..ae58c81 100644 --- a/examples/thirdparty/CLI11/CLI11.hpp +++ b/examples/thirdparty/CLI11/CLI11.hpp @@ -1,15 +1,15 @@ #pragma once -// CLI11: Version 1.6.1 +// CLI11: Version 1.7.1 // Originally designed by Henry Schreiner // https://github.com/CLIUtils/CLI11 // // This is a standalone header file generated by MakeSingleHeader.py in CLI11/scripts -// from: v1.6.1 +// from: v1.7.1 // // From LICENSE: // -// CLI11 1.6 Copyright (c) 2017-2018 University of Cincinnati, developed by Henry +// CLI11 1.7 Copyright (c) 2017-2019 University of Cincinnati, developed by Henry // Schreiner under NSF AWARD 1414736. All rights reserved. // // Redistribution and use in source and binary forms of CLI11, with or without @@ -67,9 +67,9 @@ #define CLI11_VERSION_MAJOR 1 -#define CLI11_VERSION_MINOR 6 +#define CLI11_VERSION_MINOR 7 #define CLI11_VERSION_PATCH 1 -#define CLI11_VERSION "1.6.1" +#define CLI11_VERSION "1.7.1" @@ -117,14 +117,21 @@ #ifdef __has_include +// You can explicitly enable or disable support +// by defining these to 1 or 0. #if defined(CLI11_CPP17) && __has_include() && \ !defined(CLI11_STD_OPTIONAL) #define CLI11_STD_OPTIONAL 1 +#elif !defined(CLI11_STD_OPTIONAL) +#define CLI11_STD_OPTIONAL 0 #endif #if defined(CLI11_CPP14) && __has_include() && \ - !defined(CLI11_EXPERIMENTAL_OPTIONAL) + !defined(CLI11_EXPERIMENTAL_OPTIONAL) \ + && (!defined(CLI11_STD_OPTIONAL) || CLI11_STD_OPTIONAL == 0) #define CLI11_EXPERIMENTAL_OPTIONAL 1 +#elif !defined(CLI11_EXPERIMENTAL_OPTIONAL) +#define CLI11_EXPERIMENTAL_OPTIONAL 0 #endif #if __has_include() && !defined(CLI11_BOOST_OPTIONAL) @@ -132,6 +139,8 @@ #if BOOST_VERSION >= 105800 #define CLI11_BOOST_OPTIONAL 1 #endif +#elif !defined(CLI11_BOOST_OPTIONAL) +#define CLI11_BOOST_OPTIONAL 0 #endif #endif @@ -335,27 +344,54 @@ inline std::string to_lower(std::string str) { return str; } +/// remove underscores from a string +inline std::string remove_underscore(std::string str) { + str.erase(std::remove(std::begin(str), std::end(str), '_'), std::end(str)); + return str; +} + +/// Find and replace a substring with another substring +inline std::string find_and_replace(std::string str, std::string from, std::string to) { + + size_t start_pos = 0; + + while((start_pos = str.find(from, start_pos)) != std::string::npos) { + str.replace(start_pos, from.length(), to); + start_pos += to.length(); + } + + return str; +} + +/// Find a trigger string and call a modify callable function that takes the current string and starting position of the +/// trigger and returns the position in the string to search for the next trigger string +template inline std::string find_and_modify(std::string str, std::string trigger, Callable modify) { + size_t start_pos = 0; + while((start_pos = str.find(trigger, start_pos)) != std::string::npos) { + start_pos = modify(str, start_pos); + } + return str; +} + /// Split a string '"one two" "three"' into 'one two', 'three' +/// Quote characters can be ` ' or " inline std::vector split_up(std::string str) { - std::vector delims = {'\'', '\"'}; + const std::string delims("\'\"`"); auto find_ws = [](char ch) { return std::isspace(ch, std::locale()); }; trim(str); std::vector output; - + bool embeddedQuote = false; + char keyChar = ' '; while(!str.empty()) { - if(str[0] == '\'') { - auto end = str.find('\'', 1); - if(end != std::string::npos) { - output.push_back(str.substr(1, end - 1)); - str = str.substr(end + 1); - } else { - output.push_back(str.substr(1)); - str = ""; + if(delims.find_first_of(str[0]) != std::string::npos) { + keyChar = str[0]; + auto end = str.find_first_of(keyChar, 1); + while((end != std::string::npos) && (str[end - 1] == '\\')) { // deal with escaped quotes + end = str.find_first_of(keyChar, end + 1); + embeddedQuote = true; } - } else if(str[0] == '\"') { - auto end = str.find('\"', 1); if(end != std::string::npos) { output.push_back(str.substr(1, end - 1)); str = str.substr(end + 1); @@ -363,7 +399,6 @@ inline std::vector split_up(std::string str) { output.push_back(str.substr(1)); str = ""; } - } else { auto it = std::find_if(std::begin(str), std::end(str), find_ws); if(it != std::end(str)) { @@ -375,9 +410,13 @@ inline std::vector split_up(std::string str) { str = ""; } } + // transform any embedded quotes into the regular character + if(embeddedQuote) { + output.back() = find_and_replace(output.back(), std::string("\\") + keyChar, std::string(1, keyChar)); + embeddedQuote = false; + } trim(str); } - return output; } @@ -397,6 +436,34 @@ inline std::string fix_newlines(std::string leader, std::string input) { return input; } +/// This function detects an equal or colon followed by an escaped quote after an argument +/// then modifies the string to replace the equality with a space. This is needed +/// to allow the split up function to work properly and is intended to be used with the find_and_modify function +/// the return value is the offset+1 which is required by the find_and_modify function. +inline size_t escape_detect(std::string &str, size_t offset) { + auto next = str[offset + 1]; + if((next == '\"') || (next == '\'') || (next == '`')) { + auto astart = str.find_last_of("-/ \"\'`", offset - 1); + if(astart != std::string::npos) { + if(str[astart] == ((str[offset] == '=') ? '-' : '/')) + str[offset] = ' '; // interpret this as a space so the split_up works properly + } + } + return offset + 1; +} + +/// Add quotes if the string contains spaces +inline std::string &add_quotes_if_needed(std::string &str) { + if((str.front() != '"' && str.front() != '\'') || str.front() != str.back()) { + char quote = str.find('"') < str.find('\'') ? '\'' : '"'; + if(str.find(' ') != std::string::npos) { + str.insert(0, 1, quote); + str.append(1, quote); + } + } + return str; +} + } // namespace detail } // namespace CLI @@ -408,9 +475,9 @@ namespace CLI { // These are temporary and are undef'd at the end of this file. #define CLI11_ERROR_DEF(parent, name) \ protected: \ - name(std::string name, std::string msg, int exit_code) : parent(std::move(name), std::move(msg), exit_code) {} \ - name(std::string name, std::string msg, ExitCodes exit_code) \ - : parent(std::move(name), std::move(msg), exit_code) {} \ + name(std::string ename, std::string msg, int exit_code) : parent(std::move(ename), std::move(msg), exit_code) {} \ + name(std::string ename, std::string msg, ExitCodes exit_code) \ + : parent(std::move(ename), std::move(msg), exit_code) {} \ \ public: \ name(std::string msg, ExitCodes exit_code) : parent(#name, std::move(msg), exit_code) {} \ @@ -452,16 +519,16 @@ enum class ExitCodes { /// All errors derive from this one class Error : public std::runtime_error { - int exit_code; - std::string name{"Error"}; + int actual_exit_code; + std::string error_name{"Error"}; public: - int get_exit_code() const { return exit_code; } + int get_exit_code() const { return actual_exit_code; } - std::string get_name() const { return name; } + std::string get_name() const { return error_name; } Error(std::string name, std::string msg, int exit_code = static_cast(ExitCodes::BaseClass)) - : runtime_error(msg), exit_code(exit_code), name(std::move(name)) {} + : runtime_error(msg), actual_exit_code(exit_code), error_name(std::move(name)) {} Error(std::string name, std::string msg, ExitCodes exit_code) : Error(name, msg, static_cast(exit_code)) {} }; @@ -549,7 +616,7 @@ class CallForHelp : public ParseError { CallForHelp() : CallForHelp("This should be caught in your main function, see examples", ExitCodes::Success) {} }; -/// Usually somethign like --help-all on command line +/// Usually something like --help-all on command line class CallForAllHelp : public ParseError { CLI11_ERROR_DEF(ParseError, CallForAllHelp) CallForAllHelp() @@ -853,7 +920,7 @@ inline bool split_short(const std::string ¤t, std::string &name, std::stri // Returns false if not a long option. Otherwise, sets opt name and other side of = and returns true inline bool split_long(const std::string ¤t, std::string &name, std::string &value) { if(current.size() > 2 && current.substr(0, 2) == "--" && valid_first_char(current[2])) { - auto loc = current.find("="); + auto loc = current.find_first_of('='); if(loc != std::string::npos) { name = current.substr(2, loc - 2); value = current.substr(loc + 1); @@ -866,6 +933,22 @@ inline bool split_long(const std::string ¤t, std::string &name, std::strin return false; } +// Returns false if not a windows style option. Otherwise, sets opt name and value and returns true +inline bool split_windows(const std::string ¤t, std::string &name, std::string &value) { + if(current.size() > 1 && current[0] == '/' && valid_first_char(current[1])) { + auto loc = current.find_first_of(':'); + if(loc != std::string::npos) { + name = current.substr(1, loc - 1); + value = current.substr(loc + 1); + } else { + name = current.substr(1); + value = ""; + } + return true; + } else + return false; +} + // Splits a string into multiple long and short names inline std::vector split_names(std::string current) { std::vector output; @@ -1025,7 +1108,7 @@ class ConfigINI : public Config { std::vector output; while(getline(input, line)) { - std::vector items; + std::vector items_buffer; detail::trim(line); size_t len = line.length(); @@ -1040,10 +1123,10 @@ class ConfigINI : public Config { if(pos != std::string::npos) { out.name = detail::trim_copy(line.substr(0, pos)); std::string item = detail::trim_copy(line.substr(pos + 1)); - items = detail::split_up(item); + items_buffer = detail::split_up(item); } else { out.name = detail::trim_copy(line); - items = {"ON"}; + items_buffer = {"ON"}; } if(detail::to_lower(section) != "default") { @@ -1057,7 +1140,7 @@ class ConfigINI : public Config { out.parents.insert(out.parents.end(), plist.begin(), plist.end()); } - out.inputs.insert(std::end(out.inputs), std::begin(items), std::end(items)); + out.inputs.insert(std::end(out.inputs), std::begin(items_buffer), std::end(items_buffer)); } } return output; @@ -1244,6 +1327,33 @@ struct Range : public Validator { template explicit Range(T max) : Range(static_cast(0), max) {} }; +namespace detail { +/// split a string into a program name and command line arguments +/// the string is assumed to contain a file name followed by other arguments +/// the return value contains is a pair with the first argument containing the program name and the second everything +/// else +inline std::pair split_program_name(std::string commandline) { + // try to determine the programName + std::pair vals; + trim(commandline); + auto esp = commandline.find_first_of(' ', 1); + while(!ExistingFile(commandline.substr(0, esp)).empty()) { + esp = commandline.find_first_of(' ', esp + 1); + if(esp == std::string::npos) { + // if we have reached the end and haven't found a valid file just assume the first argument is the + // program name + esp = commandline.find_first_of(' ', 1); + break; + } + } + vals.first = commandline.substr(0, esp); + rtrim(vals.first); + // strip the program name + vals.second = (esp != std::string::npos) ? commandline.substr(esp + 1) : std::string{}; + ltrim(vals.second); + return vals; +} +} // namespace detail /// @} } // namespace CLI @@ -1440,6 +1550,9 @@ template class OptionBase { /// Ignore the case when matching (option, not value) bool ignore_case_{false}; + /// Ignore underscores when matching (option, not value) + bool ignore_underscore_{false}; + /// Allow this option to be given in a configuration file bool configurable_{true}; @@ -1451,6 +1564,7 @@ template class OptionBase { other->group(group_); other->required(required_); other->ignore_case(ignore_case_); + other->ignore_underscore(ignore_underscore_); other->configurable(configurable_); other->multi_option_policy(multi_option_policy_); } @@ -1485,6 +1599,9 @@ template class OptionBase { /// The status of ignore case bool get_ignore_case() const { return ignore_case_; } + /// The status of ignore_underscore + bool get_ignore_underscore() const { return ignore_underscore_; } + /// The status of configurable bool get_configurable() const { return configurable_; } @@ -1540,6 +1657,12 @@ class OptionDefaults : public OptionBase { ignore_case_ = value; return this; } + + /// Ignore underscores in the option name + OptionDefaults *ignore_underscore(bool value = true) { + ignore_underscore_ = value; + return this; + } }; class Option : public OptionBase