From 70fc23278e45788bf4812cb22d888ea024dc9c65 Mon Sep 17 00:00:00 2001 From: Jochen Topf Date: Wed, 21 Feb 2018 17:09:11 +0100 Subject: [PATCH] Use clara.hpp for command line parsing instead of getopt. This also works on Windows. Clara is from https://github.com/catchorg/Clara . Fixes #20. --- EXTERNAL_LICENSES.txt | 28 + examples/CMakeLists.txt | 96 ++- examples/clara.hpp | 1239 ++++++++++++++++++++++++++++++++++++ examples/vtzero-filter.cpp | 77 ++- examples/vtzero-show.cpp | 86 +-- 5 files changed, 1385 insertions(+), 141 deletions(-) create mode 100644 EXTERNAL_LICENSES.txt create mode 100644 examples/clara.hpp diff --git a/EXTERNAL_LICENSES.txt b/EXTERNAL_LICENSES.txt new file mode 100644 index 0000000..0f06c09 --- /dev/null +++ b/EXTERNAL_LICENSES.txt @@ -0,0 +1,28 @@ + +==== clara.hpp + +Copyright 2017 Two Blue Cubes Ltd. All rights reserved. + +Boost Software License - Version 1.0 - August 17th, 2003 + +Permission is hereby granted, free of charge, to any person or organization +obtaining a copy of the software and accompanying documentation covered by +this license (the "Software") to use, reproduce, display, distribute, +execute, and transmit the Software, and to prepare derivative works of the +Software, and to permit third-parties to whom the Software is furnished to +do so, all subject to the following: + +The copyright notices in the Software and this entire statement, including +the above license grant, this restriction and the following disclaimer, +must be included in all copies of the Software, in whole or in part, and +all derivative works of the Software, unless such copies or derivative +works are solely in the form of machine-executable object code generated by +a source language processor. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT +SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE +FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 2a561aa..588be59 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -16,71 +16,69 @@ add_executable(vtzero-stats vtzero-stats.cpp utils.cpp) add_executable(vtzero-streets vtzero-streets.cpp utils.cpp) -# disable building of examples that need getopt lib on MSVC -if(NOT MSVC) - add_executable(vtzero-filter vtzero-filter.cpp utils.cpp) +#------------------------------------------------------------- - add_test(NAME vtzero-filter-empty - COMMAND vtzero-filter) - set_tests_properties(vtzero-filter-empty PROPERTIES - PASS_REGULAR_EXPRESSION "^Usage: ") +add_executable(vtzero-filter vtzero-filter.cpp utils.cpp) - add_test(NAME vtzero-filter-help - COMMAND vtzero-filter -h) - set_tests_properties(vtzero-filter-help PROPERTIES - PASS_REGULAR_EXPRESSION "^vtzero-filter ") +add_test(NAME vtzero-filter-empty + COMMAND vtzero-filter) +set_tests_properties(vtzero-filter-empty PROPERTIES + PASS_REGULAR_EXPRESSION "^Error in command line: Missing file name of vector tile to read") - add_test(NAME vtzero-filter-layer - COMMAND vtzero-filter -o ${CMAKE_CURRENT_BINARY_DIR}/bridges.mvt ${TEST_FILE} bridge) +add_test(NAME vtzero-filter-help + COMMAND vtzero-filter -h) +set_tests_properties(vtzero-filter-help PROPERTIES + PASS_REGULAR_EXPRESSION "^usage:\n vtzero-filter ") - add_test(NAME vtzero-filter-feature - COMMAND vtzero-filter -o ${CMAKE_CURRENT_BINARY_DIR}/bridges.mvt ${TEST_FILE} waterway_label 221925711) +add_test(NAME vtzero-filter-layer + COMMAND vtzero-filter -o ${CMAKE_CURRENT_BINARY_DIR}/bridges.mvt ${TEST_FILE} bridge) - add_test(NAME vtzero-filter-invalid-id - COMMAND vtzero-filter -o ${CMAKE_CURRENT_BINARY_DIR}/bridges.mvt ${TEST_FILE} waterway_label abc) - set_tests_properties(vtzero-filter-invalid-id PROPERTIES - WILL_FAIL true) +add_test(NAME vtzero-filter-feature + COMMAND vtzero-filter -o ${CMAKE_CURRENT_BINARY_DIR}/bridges.mvt ${TEST_FILE} waterway_label 221925711) - #------------------------------------------------------------- +add_test(NAME vtzero-filter-invalid-id + COMMAND vtzero-filter -o ${CMAKE_CURRENT_BINARY_DIR}/bridges.mvt ${TEST_FILE} waterway_label abc) +set_tests_properties(vtzero-filter-invalid-id PROPERTIES + WILL_FAIL true) - add_executable(vtzero-show vtzero-show.cpp utils.cpp) +#------------------------------------------------------------- - add_test(NAME vtzero-show-empty - COMMAND vtzero-show) - set_tests_properties(vtzero-show-empty PROPERTIES - PASS_REGULAR_EXPRESSION "^Usage: ") +add_executable(vtzero-show vtzero-show.cpp utils.cpp) - add_test(NAME vtzero-show-help - COMMAND vtzero-show -h) - set_tests_properties(vtzero-show-help PROPERTIES - PASS_REGULAR_EXPRESSION "^vtzero-show ") +add_test(NAME vtzero-show-empty + COMMAND vtzero-show) +set_tests_properties(vtzero-show-empty PROPERTIES + PASS_REGULAR_EXPRESSION "^Error in command line: Missing file name of vector tile to read") - add_test(NAME vtzero-show-layers - COMMAND vtzero-show -l ${TEST_FILE}) - set_tests_properties(vtzero-show-layers PROPERTIES - PASS_REGULAR_EXPRESSION "^landuse 78\nwaterway 327\n.*\nwaterway_label 4\n$") +add_test(NAME vtzero-show-help + COMMAND vtzero-show -h) +set_tests_properties(vtzero-show-help PROPERTIES + PASS_REGULAR_EXPRESSION "^usage:\n vtzero-show ") - add_test(NAME vtzero-show-layer-num - COMMAND vtzero-show ${TEST_FILE} 2) - set_tests_properties(vtzero-show-layer-num PROPERTIES - PASS_REGULAR_EXPRESSION "layer: [0-9]+\n name: water\n") +add_test(NAME vtzero-show-layers + COMMAND vtzero-show -l ${TEST_FILE}) +set_tests_properties(vtzero-show-layers PROPERTIES + PASS_REGULAR_EXPRESSION "^landuse 78\nwaterway 327\n.*\nwaterway_label 4\n$") - add_test(NAME vtzero-show-layer-name - COMMAND vtzero-show ${CMAKE_SOURCE_DIR}/test/data/mapbox-streets-v6-14-8714-8017.mvt water) - set_tests_properties(vtzero-show-layer-name PROPERTIES - PASS_REGULAR_EXPRESSION "layer: [0-9]+\n name: water\n") +add_test(NAME vtzero-show-layer-num + COMMAND vtzero-show ${TEST_FILE} 2) +set_tests_properties(vtzero-show-layer-num PROPERTIES + PASS_REGULAR_EXPRESSION "layer: [0-9]+\n name: water\n") - #------------------------------------------------------------- +add_test(NAME vtzero-show-layer-name + COMMAND vtzero-show ${CMAKE_SOURCE_DIR}/test/data/mapbox-streets-v6-14-8714-8017.mvt water) +set_tests_properties(vtzero-show-layer-name PROPERTIES + PASS_REGULAR_EXPRESSION "layer: [0-9]+\n name: water\n") - file(GLOB ext_tests RELATIVE ${CMAKE_SOURCE_DIR}/test/data/ ${CMAKE_SOURCE_DIR}/test/data/ext*) +#------------------------------------------------------------- - foreach(_test IN LISTS ext_tests) - message(STATUS "Adding ext test: ${_test}") - add_test(NAME ext-tests-${_test} - COMMAND vtzero-show ${CMAKE_SOURCE_DIR}/test/data/${_test}) - endforeach() +file(GLOB ext_tests RELATIVE ${CMAKE_SOURCE_DIR}/test/data/ ${CMAKE_SOURCE_DIR}/test/data/ext*) -endif() +foreach(_test IN LISTS ext_tests) + message(STATUS "Adding ext test: ${_test}") + add_test(NAME ext-tests-${_test} + COMMAND vtzero-show ${CMAKE_SOURCE_DIR}/test/data/${_test}) +endforeach() #----------------------------------------------------------------------------- diff --git a/examples/clara.hpp b/examples/clara.hpp new file mode 100644 index 0000000..9a60c17 --- /dev/null +++ b/examples/clara.hpp @@ -0,0 +1,1239 @@ +// Copyright 2017 Two Blue Cubes Ltd. All rights reserved. +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// See https://github.com/philsquared/Clara for more details + +// Clara v1.1.2 + +#ifndef CLARA_HPP_INCLUDED +#define CLARA_HPP_INCLUDED + +#ifndef CLARA_CONFIG_CONSOLE_WIDTH +#define CLARA_CONFIG_CONSOLE_WIDTH 80 +#endif + +#ifndef CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH +#define CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH CLARA_CONFIG_CONSOLE_WIDTH +#endif + +// ----------- #included from clara_textflow.hpp ----------- + +// TextFlowCpp +// +// A single-header library for wrapping and laying out basic text, by Phil Nash +// +// This work is licensed under the BSD 2-Clause license. +// See the accompanying LICENSE file, or the one at https://opensource.org/licenses/BSD-2-Clause +// +// This project is hosted at https://github.com/philsquared/textflowcpp + +#ifndef CLARA_TEXTFLOW_HPP_INCLUDED +#define CLARA_TEXTFLOW_HPP_INCLUDED + +#include +#include +#include +#include + +#ifndef CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH +#define CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH 80 +#endif + + +namespace clara { namespace TextFlow { + + inline auto isWhitespace( char c ) -> bool { + static std::string chars = " \t\n\r"; + return chars.find( c ) != std::string::npos; + } + inline auto isBreakableBefore( char c ) -> bool { + static std::string chars = "[({<|"; + return chars.find( c ) != std::string::npos; + } + inline auto isBreakableAfter( char c ) -> bool { + static std::string chars = "])}>.,:;*+-=&/\\"; + return chars.find( c ) != std::string::npos; + } + + class Columns; + + class Column { + std::vector m_strings; + size_t m_width = CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH; + size_t m_indent = 0; + size_t m_initialIndent = std::string::npos; + + public: + class iterator { + friend Column; + + Column const& m_column; + size_t m_stringIndex = 0; + size_t m_pos = 0; + + size_t m_len = 0; + size_t m_end = 0; + bool m_suffix = false; + + iterator( Column const& column, size_t stringIndex ) + : m_column( column ), + m_stringIndex( stringIndex ) + {} + + auto line() const -> std::string const& { return m_column.m_strings[m_stringIndex]; } + + auto isBoundary( size_t at ) const -> bool { + assert( at > 0 ); + assert( at <= line().size() ); + + return at == line().size() || + ( isWhitespace( line()[at] ) && !isWhitespace( line()[at-1] ) ) || + isBreakableBefore( line()[at] ) || + isBreakableAfter( line()[at-1] ); + } + + void calcLength() { + assert( m_stringIndex < m_column.m_strings.size() ); + + m_suffix = false; + auto width = m_column.m_width-indent(); + m_end = m_pos; + while( m_end < line().size() && line()[m_end] != '\n' ) + ++m_end; + + if( m_end < m_pos + width ) { + m_len = m_end - m_pos; + } + else { + size_t len = width; + while (len > 0 && !isBoundary(m_pos + len)) + --len; + while (len > 0 && isWhitespace( line()[m_pos + len - 1] )) + --len; + + if (len > 0) { + m_len = len; + } else { + m_suffix = true; + m_len = width - 1; + } + } + } + + auto indent() const -> size_t { + auto initial = m_pos == 0 && m_stringIndex == 0 ? m_column.m_initialIndent : std::string::npos; + return initial == std::string::npos ? m_column.m_indent : initial; + } + + auto addIndentAndSuffix(std::string const &plain) const -> std::string { + return std::string( indent(), ' ' ) + (m_suffix ? plain + "-" : plain); + } + + public: + explicit iterator( Column const& column ) : m_column( column ) { + assert( m_column.m_width > m_column.m_indent ); + assert( m_column.m_initialIndent == std::string::npos || m_column.m_width > m_column.m_initialIndent ); + calcLength(); + if( m_len == 0 ) + m_stringIndex++; // Empty string + } + + auto operator *() const -> std::string { + assert( m_stringIndex < m_column.m_strings.size() ); + assert( m_pos <= m_end ); + if( m_pos + m_column.m_width < m_end ) + return addIndentAndSuffix(line().substr(m_pos, m_len)); + else + return addIndentAndSuffix(line().substr(m_pos, m_end - m_pos)); + } + + auto operator ++() -> iterator& { + m_pos += m_len; + if( m_pos < line().size() && line()[m_pos] == '\n' ) + m_pos += 1; + else + while( m_pos < line().size() && isWhitespace( line()[m_pos] ) ) + ++m_pos; + + if( m_pos == line().size() ) { + m_pos = 0; + ++m_stringIndex; + } + if( m_stringIndex < m_column.m_strings.size() ) + calcLength(); + return *this; + } + auto operator ++(int) -> iterator { + iterator prev( *this ); + operator++(); + return prev; + } + + auto operator ==( iterator const& other ) const -> bool { + return + m_pos == other.m_pos && + m_stringIndex == other.m_stringIndex && + &m_column == &other.m_column; + } + auto operator !=( iterator const& other ) const -> bool { + return !operator==( other ); + } + }; + using const_iterator = iterator; + + explicit Column( std::string const& text ) { m_strings.push_back( text ); } + + auto width( size_t newWidth ) -> Column& { + assert( newWidth > 0 ); + m_width = newWidth; + return *this; + } + auto indent( size_t newIndent ) -> Column& { + m_indent = newIndent; + return *this; + } + auto initialIndent( size_t newIndent ) -> Column& { + m_initialIndent = newIndent; + return *this; + } + + auto width() const -> size_t { return m_width; } + auto begin() const -> iterator { return iterator( *this ); } + auto end() const -> iterator { return { *this, m_strings.size() }; } + + inline friend std::ostream& operator << ( std::ostream& os, Column const& col ) { + bool first = true; + for( auto line : col ) { + if( first ) + first = false; + else + os << "\n"; + os << line; + } + return os; + } + + auto operator + ( Column const& other ) -> Columns; + + auto toString() const -> std::string { + std::ostringstream oss; + oss << *this; + return oss.str(); + } + }; + + class Spacer : public Column { + + public: + explicit Spacer( size_t spaceWidth ) : Column( "" ) { + width( spaceWidth ); + } + }; + + class Columns { + std::vector m_columns; + + public: + + class iterator { + friend Columns; + struct EndTag {}; + + std::vector const& m_columns; + std::vector m_iterators; + size_t m_activeIterators; + + iterator( Columns const& columns, EndTag ) + : m_columns( columns.m_columns ), + m_activeIterators( 0 ) + { + m_iterators.reserve( m_columns.size() ); + + for( auto const& col : m_columns ) + m_iterators.push_back( col.end() ); + } + + public: + explicit iterator( Columns const& columns ) + : m_columns( columns.m_columns ), + m_activeIterators( m_columns.size() ) + { + m_iterators.reserve( m_columns.size() ); + + for( auto const& col : m_columns ) + m_iterators.push_back( col.begin() ); + } + + auto operator ==( iterator const& other ) const -> bool { + return m_iterators == other.m_iterators; + } + auto operator !=( iterator const& other ) const -> bool { + return m_iterators != other.m_iterators; + } + auto operator *() const -> std::string { + std::string row, padding; + + for( size_t i = 0; i < m_columns.size(); ++i ) { + auto width = m_columns[i].width(); + if( m_iterators[i] != m_columns[i].end() ) { + std::string col = *m_iterators[i]; + row += padding + col; + if( col.size() < width ) + padding = std::string( width - col.size(), ' ' ); + else + padding = ""; + } + else { + padding += std::string( width, ' ' ); + } + } + return row; + } + auto operator ++() -> iterator& { + for( size_t i = 0; i < m_columns.size(); ++i ) { + if (m_iterators[i] != m_columns[i].end()) + ++m_iterators[i]; + } + return *this; + } + auto operator ++(int) -> iterator { + iterator prev( *this ); + operator++(); + return prev; + } + }; + using const_iterator = iterator; + + auto begin() const -> iterator { return iterator( *this ); } + auto end() const -> iterator { return { *this, iterator::EndTag() }; } + + auto operator += ( Column const& col ) -> Columns& { + m_columns.push_back( col ); + return *this; + } + auto operator + ( Column const& col ) -> Columns { + Columns combined = *this; + combined += col; + return combined; + } + + inline friend std::ostream& operator << ( std::ostream& os, Columns const& cols ) { + + bool first = true; + for( auto line : cols ) { + if( first ) + first = false; + else + os << "\n"; + os << line; + } + return os; + } + + auto toString() const -> std::string { + std::ostringstream oss; + oss << *this; + return oss.str(); + } + }; + + inline auto Column::operator + ( Column const& other ) -> Columns { + Columns cols; + cols += *this; + cols += other; + return cols; + } +}} // namespace clara::TextFlow + +#endif // CLARA_TEXTFLOW_HPP_INCLUDED + +// ----------- end of #include from clara_textflow.hpp ----------- +// ........... back in clara.hpp + + +#include +#include +#include + +#if !defined(CLARA_PLATFORM_WINDOWS) && ( defined(WIN32) || defined(__WIN32__) || defined(_WIN32) || defined(_MSC_VER) ) +#define CLARA_PLATFORM_WINDOWS +#endif + +namespace clara { +namespace detail { + + // Traits for extracting arg and return type of lambdas (for single argument lambdas) + template + struct UnaryLambdaTraits : UnaryLambdaTraits {}; + + template + struct UnaryLambdaTraits { + static const bool isValid = false; + }; + + template + struct UnaryLambdaTraits { + static const bool isValid = true; + using ArgType = typename std::remove_const::type>::type; + using ReturnType = ReturnT; + }; + + class TokenStream; + + // Transport for raw args (copied from main args, or supplied via init list for testing) + class Args { + friend TokenStream; + std::string m_exeName; + std::vector m_args; + + public: + Args( int argc, char *argv[] ) { + m_exeName = argv[0]; + for( int i = 1; i < argc; ++i ) + m_args.push_back( argv[i] ); + } + + Args( std::initializer_list args ) + : m_exeName( *args.begin() ), + m_args( args.begin()+1, args.end() ) + {} + + auto exeName() const -> std::string { + return m_exeName; + } + }; + + // Wraps a token coming from a token stream. These may not directly correspond to strings as a single string + // may encode an option + its argument if the : or = form is used + enum class TokenType { + Option, Argument + }; + struct Token { + TokenType type; + std::string token; + }; + + inline auto isOptPrefix( char c ) -> bool { + return c == '-' +#ifdef CLARA_PLATFORM_WINDOWS + || c == '/' +#endif + ; + } + + // Abstracts iterators into args as a stream of tokens, with option arguments uniformly handled + class TokenStream { + using Iterator = std::vector::const_iterator; + Iterator it; + Iterator itEnd; + std::vector m_tokenBuffer; + + void loadBuffer() { + m_tokenBuffer.resize( 0 ); + + // Skip any empty strings + while( it != itEnd && it->empty() ) + ++it; + + if( it != itEnd ) { + auto const &next = *it; + if( isOptPrefix( next[0] ) ) { + auto delimiterPos = next.find_first_of( " :=" ); + if( delimiterPos != std::string::npos ) { + m_tokenBuffer.push_back( { TokenType::Option, next.substr( 0, delimiterPos ) } ); + m_tokenBuffer.push_back( { TokenType::Argument, next.substr( delimiterPos + 1 ) } ); + } else { + if( next[1] != '-' && next.size() > 2 ) { + std::string opt = "- "; + for( size_t i = 1; i < next.size(); ++i ) { + opt[1] = next[i]; + m_tokenBuffer.push_back( { TokenType::Option, opt } ); + } + } else { + m_tokenBuffer.push_back( { TokenType::Option, next } ); + } + } + } else { + m_tokenBuffer.push_back( { TokenType::Argument, next } ); + } + } + } + + public: + explicit TokenStream( Args const &args ) : TokenStream( args.m_args.begin(), args.m_args.end() ) {} + + TokenStream( Iterator it, Iterator itEnd ) : it( it ), itEnd( itEnd ) { + loadBuffer(); + } + + explicit operator bool() const { + return !m_tokenBuffer.empty() || it != itEnd; + } + + auto count() const -> size_t { return m_tokenBuffer.size() + (itEnd - it); } + + auto operator*() const -> Token { + assert( !m_tokenBuffer.empty() ); + return m_tokenBuffer.front(); + } + + auto operator->() const -> Token const * { + assert( !m_tokenBuffer.empty() ); + return &m_tokenBuffer.front(); + } + + auto operator++() -> TokenStream & { + if( m_tokenBuffer.size() >= 2 ) { + m_tokenBuffer.erase( m_tokenBuffer.begin() ); + } else { + if( it != itEnd ) + ++it; + loadBuffer(); + } + return *this; + } + }; + + + class ResultBase { + public: + enum Type { + Ok, LogicError, RuntimeError + }; + + protected: + ResultBase( Type type ) : m_type( type ) {} + virtual ~ResultBase() = default; + + virtual void enforceOk() const = 0; + + Type m_type; + }; + + template + class ResultValueBase : public ResultBase { + public: + auto value() const -> T const & { + enforceOk(); + return m_value; + } + + protected: + ResultValueBase( Type type ) : ResultBase( type ) {} + + ResultValueBase( ResultValueBase const &other ) : ResultBase( other ) { + if( m_type == ResultBase::Ok ) + new( &m_value ) T( other.m_value ); + } + + ResultValueBase( Type, T const &value ) : ResultBase( Ok ) { + new( &m_value ) T( value ); + } + + auto operator=( ResultValueBase const &other ) -> ResultValueBase & { + if( m_type == ResultBase::Ok ) + m_value.~T(); + ResultBase::operator=(other); + if( m_type == ResultBase::Ok ) + new( &m_value ) T( other.m_value ); + return *this; + } + + ~ResultValueBase() override { + if( m_type == Ok ) + m_value.~T(); + } + + union { + T m_value; + }; + }; + + template<> + class ResultValueBase : public ResultBase { + protected: + using ResultBase::ResultBase; + }; + + template + class BasicResult : public ResultValueBase { + public: + template + explicit BasicResult( BasicResult const &other ) + : ResultValueBase( other.type() ), + m_errorMessage( other.errorMessage() ) + { + assert( type() != ResultBase::Ok ); + } + + template + static auto ok( U const &value ) -> BasicResult { return { ResultBase::Ok, value }; } + static auto ok() -> BasicResult { return { ResultBase::Ok }; } + static auto logicError( std::string const &message ) -> BasicResult { return { ResultBase::LogicError, message }; } + static auto runtimeError( std::string const &message ) -> BasicResult { return { ResultBase::RuntimeError, message }; } + + explicit operator bool() const { return m_type == ResultBase::Ok; } + auto type() const -> ResultBase::Type { return m_type; } + auto errorMessage() const -> std::string { return m_errorMessage; } + + protected: + void enforceOk() const override { + // !TBD: If no exceptions, std::terminate here or something + switch( m_type ) { + case ResultBase::LogicError: + throw std::logic_error( m_errorMessage ); + case ResultBase::RuntimeError: + throw std::runtime_error( m_errorMessage ); + case ResultBase::Ok: + break; + } + } + + std::string m_errorMessage; // Only populated if resultType is an error + + BasicResult( ResultBase::Type type, std::string const &message ) + : ResultValueBase(type), + m_errorMessage(message) + { + assert( m_type != ResultBase::Ok ); + } + + using ResultValueBase::ResultValueBase; + using ResultBase::m_type; + }; + + enum class ParseResultType { + Matched, NoMatch, ShortCircuitAll, ShortCircuitSame + }; + + class ParseState { + public: + + ParseState( ParseResultType type, TokenStream const &remainingTokens ) + : m_type(type), + m_remainingTokens( remainingTokens ) + {} + + auto type() const -> ParseResultType { return m_type; } + auto remainingTokens() const -> TokenStream { return m_remainingTokens; } + + private: + ParseResultType m_type; + TokenStream m_remainingTokens; + }; + + using Result = BasicResult; + using ParserResult = BasicResult; + using InternalParseResult = BasicResult; + + struct HelpColumns { + std::string left; + std::string right; + }; + + template + inline auto convertInto( std::string const &source, T& target ) -> ParserResult { + std::stringstream ss; + ss << source; + ss >> target; + if( ss.fail() ) + return ParserResult::runtimeError( "Unable to convert '" + source + "' to destination type" ); + else + return ParserResult::ok( ParseResultType::Matched ); + } + inline auto convertInto( std::string const &source, std::string& target ) -> ParserResult { + target = source; + return ParserResult::ok( ParseResultType::Matched ); + } + inline auto convertInto( std::string const &source, bool &target ) -> ParserResult { + std::string srcLC = source; + std::transform( srcLC.begin(), srcLC.end(), srcLC.begin(), []( char c ) { return static_cast( ::tolower(c) ); } ); + if (srcLC == "y" || srcLC == "1" || srcLC == "true" || srcLC == "yes" || srcLC == "on") + target = true; + else if (srcLC == "n" || srcLC == "0" || srcLC == "false" || srcLC == "no" || srcLC == "off") + target = false; + else + return ParserResult::runtimeError( "Expected a boolean value but did not recognise: '" + source + "'" ); + return ParserResult::ok( ParseResultType::Matched ); + } + + struct NonCopyable { + NonCopyable() = default; + NonCopyable( NonCopyable const & ) = delete; + NonCopyable( NonCopyable && ) = delete; + NonCopyable &operator=( NonCopyable const & ) = delete; + NonCopyable &operator=( NonCopyable && ) = delete; + }; + + struct BoundRef : NonCopyable { + virtual ~BoundRef() = default; + virtual auto isContainer() const -> bool { return false; } + virtual auto isFlag() const -> bool { return false; } + }; + struct BoundValueRefBase : BoundRef { + virtual auto setValue( std::string const &arg ) -> ParserResult = 0; + }; + struct BoundFlagRefBase : BoundRef { + virtual auto setFlag( bool flag ) -> ParserResult = 0; + virtual auto isFlag() const -> bool { return true; } + }; + + template + struct BoundValueRef : BoundValueRefBase { + T &m_ref; + + explicit BoundValueRef( T &ref ) : m_ref( ref ) {} + + auto setValue( std::string const &arg ) -> ParserResult override { + return convertInto( arg, m_ref ); + } + }; + + template + struct BoundValueRef> : BoundValueRefBase { + std::vector &m_ref; + + explicit BoundValueRef( std::vector &ref ) : m_ref( ref ) {} + + auto isContainer() const -> bool override { return true; } + + auto setValue( std::string const &arg ) -> ParserResult override { + T temp; + auto result = convertInto( arg, temp ); + if( result ) + m_ref.push_back( temp ); + return result; + } + }; + + struct BoundFlagRef : BoundFlagRefBase { + bool &m_ref; + + explicit BoundFlagRef( bool &ref ) : m_ref( ref ) {} + + auto setFlag( bool flag ) -> ParserResult override { + m_ref = flag; + return ParserResult::ok( ParseResultType::Matched ); + } + }; + + template + struct LambdaInvoker { + static_assert( std::is_same::value, "Lambda must return void or clara::ParserResult" ); + + template + static auto invoke( L const &lambda, ArgType const &arg ) -> ParserResult { + return lambda( arg ); + } + }; + + template<> + struct LambdaInvoker { + template + static auto invoke( L const &lambda, ArgType const &arg ) -> ParserResult { + lambda( arg ); + return ParserResult::ok( ParseResultType::Matched ); + } + }; + + template + inline auto invokeLambda( L const &lambda, std::string const &arg ) -> ParserResult { + ArgType temp{}; + auto result = convertInto( arg, temp ); + return !result + ? result + : LambdaInvoker::ReturnType>::invoke( lambda, temp ); + } + + + template + struct BoundLambda : BoundValueRefBase { + L m_lambda; + + static_assert( UnaryLambdaTraits::isValid, "Supplied lambda must take exactly one argument" ); + explicit BoundLambda( L const &lambda ) : m_lambda( lambda ) {} + + auto setValue( std::string const &arg ) -> ParserResult override { + return invokeLambda::ArgType>( m_lambda, arg ); + } + }; + + template + struct BoundFlagLambda : BoundFlagRefBase { + L m_lambda; + + static_assert( UnaryLambdaTraits::isValid, "Supplied lambda must take exactly one argument" ); + static_assert( std::is_same::ArgType, bool>::value, "flags must be boolean" ); + + explicit BoundFlagLambda( L const &lambda ) : m_lambda( lambda ) {} + + auto setFlag( bool flag ) -> ParserResult override { + return LambdaInvoker::ReturnType>::invoke( m_lambda, flag ); + } + }; + + enum class Optionality { Optional, Required }; + + struct Parser; + + class ParserBase { + public: + virtual ~ParserBase() = default; + virtual auto validate() const -> Result { return Result::ok(); } + virtual auto parse( std::string const& exeName, TokenStream const &tokens) const -> InternalParseResult = 0; + virtual auto cardinality() const -> size_t { return 1; } + + auto parse( Args const &args ) const -> InternalParseResult { + return parse( args.exeName(), TokenStream( args ) ); + } + }; + + template + class ComposableParserImpl : public ParserBase { + public: + template + auto operator|( T const &other ) const -> Parser; + + template + auto operator+( T const &other ) const -> Parser; + }; + + // Common code and state for Args and Opts + template + class ParserRefImpl : public ComposableParserImpl { + protected: + Optionality m_optionality = Optionality::Optional; + std::shared_ptr m_ref; + std::string m_hint; + std::string m_description; + + explicit ParserRefImpl( std::shared_ptr const &ref ) : m_ref( ref ) {} + + public: + template + ParserRefImpl( T &ref, std::string const &hint ) + : m_ref( std::make_shared>( ref ) ), + m_hint( hint ) + {} + + template + ParserRefImpl( LambdaT const &ref, std::string const &hint ) + : m_ref( std::make_shared>( ref ) ), + m_hint(hint) + {} + + auto operator()( std::string const &description ) -> DerivedT & { + m_description = description; + return static_cast( *this ); + } + + auto optional() -> DerivedT & { + m_optionality = Optionality::Optional; + return static_cast( *this ); + }; + + auto required() -> DerivedT & { + m_optionality = Optionality::Required; + return static_cast( *this ); + }; + + auto isOptional() const -> bool { + return m_optionality == Optionality::Optional; + } + + auto cardinality() const -> size_t override { + if( m_ref->isContainer() ) + return 0; + else + return 1; + } + + auto hint() const -> std::string { return m_hint; } + }; + + class ExeName : public ComposableParserImpl { + std::shared_ptr m_name; + std::shared_ptr m_ref; + + template + static auto makeRef(LambdaT const &lambda) -> std::shared_ptr { + return std::make_shared>( lambda) ; + } + + public: + ExeName() : m_name( std::make_shared( "" ) ) {} + + explicit ExeName( std::string &ref ) : ExeName() { + m_ref = std::make_shared>( ref ); + } + + template + explicit ExeName( LambdaT const& lambda ) : ExeName() { + m_ref = std::make_shared>( lambda ); + } + + // The exe name is not parsed out of the normal tokens, but is handled specially + auto parse( std::string const&, TokenStream const &tokens ) const -> InternalParseResult override { + return InternalParseResult::ok( ParseState( ParseResultType::NoMatch, tokens ) ); + } + + auto name() const -> std::string { return *m_name; } + auto set( std::string const& newName ) -> ParserResult { + + auto lastSlash = newName.find_last_of( "\\/" ); + auto filename = ( lastSlash == std::string::npos ) + ? newName + : newName.substr( lastSlash+1 ); + + *m_name = filename; + if( m_ref ) + return m_ref->setValue( filename ); + else + return ParserResult::ok( ParseResultType::Matched ); + } + }; + + class Arg : public ParserRefImpl { + public: + using ParserRefImpl::ParserRefImpl; + + auto parse( std::string const &, TokenStream const &tokens ) const -> InternalParseResult override { + auto validationResult = validate(); + if( !validationResult ) + return InternalParseResult( validationResult ); + + auto remainingTokens = tokens; + auto const &token = *remainingTokens; + if( token.type != TokenType::Argument ) + return InternalParseResult::ok( ParseState( ParseResultType::NoMatch, remainingTokens ) ); + + assert( !m_ref->isFlag() ); + auto valueRef = static_cast( m_ref.get() ); + + auto result = valueRef->setValue( remainingTokens->token ); + if( !result ) + return InternalParseResult( result ); + else + return InternalParseResult::ok( ParseState( ParseResultType::Matched, ++remainingTokens ) ); + } + }; + + inline auto normaliseOpt( std::string const &optName ) -> std::string { +#ifdef CLARA_PLATFORM_WINDOWS + if( optName[0] == '/' ) + return "-" + optName.substr( 1 ); + else +#endif + return optName; + } + + class Opt : public ParserRefImpl { + protected: + std::vector m_optNames; + + public: + template + explicit Opt( LambdaT const &ref ) : ParserRefImpl( std::make_shared>( ref ) ) {} + + explicit Opt( bool &ref ) : ParserRefImpl( std::make_shared( ref ) ) {} + + template + Opt( LambdaT const &ref, std::string const &hint ) : ParserRefImpl( ref, hint ) {} + + template + Opt( T &ref, std::string const &hint ) : ParserRefImpl( ref, hint ) {} + + auto operator[]( std::string const &optName ) -> Opt & { + m_optNames.push_back( optName ); + return *this; + } + + auto getHelpColumns() const -> std::vector { + std::ostringstream oss; + bool first = true; + for( auto const &opt : m_optNames ) { + if (first) + first = false; + else + oss << ", "; + oss << opt; + } + if( !m_hint.empty() ) + oss << " <" << m_hint << ">"; + return { { oss.str(), m_description } }; + } + + auto isMatch( std::string const &optToken ) const -> bool { + auto normalisedToken = normaliseOpt( optToken ); + for( auto const &name : m_optNames ) { + if( normaliseOpt( name ) == normalisedToken ) + return true; + } + return false; + } + + using ParserBase::parse; + + auto parse( std::string const&, TokenStream const &tokens ) const -> InternalParseResult override { + auto validationResult = validate(); + if( !validationResult ) + return InternalParseResult( validationResult ); + + auto remainingTokens = tokens; + if( remainingTokens && remainingTokens->type == TokenType::Option ) { + auto const &token = *remainingTokens; + if( isMatch(token.token ) ) { + if( m_ref->isFlag() ) { + auto flagRef = static_cast( m_ref.get() ); + auto result = flagRef->setFlag( true ); + if( !result ) + return InternalParseResult( result ); + if( result.value() == ParseResultType::ShortCircuitAll ) + return InternalParseResult::ok( ParseState( result.value(), remainingTokens ) ); + } else { + auto valueRef = static_cast( m_ref.get() ); + ++remainingTokens; + if( !remainingTokens ) + return InternalParseResult::runtimeError( "Expected argument following " + token.token ); + auto const &argToken = *remainingTokens; + if( argToken.type != TokenType::Argument ) + return InternalParseResult::runtimeError( "Expected argument following " + token.token ); + auto result = valueRef->setValue( argToken.token ); + if( !result ) + return InternalParseResult( result ); + if( result.value() == ParseResultType::ShortCircuitAll ) + return InternalParseResult::ok( ParseState( result.value(), remainingTokens ) ); + } + return InternalParseResult::ok( ParseState( ParseResultType::Matched, ++remainingTokens ) ); + } + } + return InternalParseResult::ok( ParseState( ParseResultType::NoMatch, remainingTokens ) ); + } + + auto validate() const -> Result override { + if( m_optNames.empty() ) + return Result::logicError( "No options supplied to Opt" ); + for( auto const &name : m_optNames ) { + if( name.empty() ) + return Result::logicError( "Option name cannot be empty" ); +#ifdef CLARA_PLATFORM_WINDOWS + if( name[0] != '-' && name[0] != '/' ) + return Result::logicError( "Option name must begin with '-' or '/'" ); +#else + if( name[0] != '-' ) + return Result::logicError( "Option name must begin with '-'" ); +#endif + } + return ParserRefImpl::validate(); + } + }; + + struct Help : Opt { + Help( bool &showHelpFlag ) + : Opt([&]( bool flag ) { + showHelpFlag = flag; + return ParserResult::ok( ParseResultType::ShortCircuitAll ); + }) + { + static_cast( *this ) + ("display usage information") + ["-?"]["-h"]["--help"] + .optional(); + } + }; + + + struct Parser : ParserBase { + + mutable ExeName m_exeName; + std::vector m_options; + std::vector m_args; + + auto operator|=( ExeName const &exeName ) -> Parser & { + m_exeName = exeName; + return *this; + } + + auto operator|=( Arg const &arg ) -> Parser & { + m_args.push_back(arg); + return *this; + } + + auto operator|=( Opt const &opt ) -> Parser & { + m_options.push_back(opt); + return *this; + } + + auto operator|=( Parser const &other ) -> Parser & { + m_options.insert(m_options.end(), other.m_options.begin(), other.m_options.end()); + m_args.insert(m_args.end(), other.m_args.begin(), other.m_args.end()); + return *this; + } + + template + auto operator|( T const &other ) const -> Parser { + return Parser( *this ) |= other; + } + + // Forward deprecated interface with '+' instead of '|' + template + auto operator+=( T const &other ) -> Parser & { return operator|=( other ); } + template + auto operator+( T const &other ) const -> Parser { return operator|( other ); } + + auto getHelpColumns() const -> std::vector { + std::vector cols; + for (auto const &o : m_options) { + auto childCols = o.getHelpColumns(); + cols.insert( cols.end(), childCols.begin(), childCols.end() ); + } + return cols; + } + + void writeToStream( std::ostream &os ) const { + if (!m_exeName.name().empty()) { + os << "usage:\n" << " " << m_exeName.name() << " "; + bool required = true, first = true; + for( auto const &arg : m_args ) { + if (first) + first = false; + else + os << " "; + if( arg.isOptional() && required ) { + os << "["; + required = false; + } + os << "<" << arg.hint() << ">"; + if( arg.cardinality() == 0 ) + os << " ... "; + } + if( !required ) + os << "]"; + if( !m_options.empty() ) + os << " options"; + os << "\n\nwhere options are:" << std::endl; + } + + auto rows = getHelpColumns(); + size_t consoleWidth = CLARA_CONFIG_CONSOLE_WIDTH; + size_t optWidth = 0; + for( auto const &cols : rows ) + optWidth = (std::max)(optWidth, cols.left.size() + 2); + + optWidth = (std::min)(optWidth, consoleWidth/2); + + for( auto const &cols : rows ) { + auto row = + TextFlow::Column( cols.left ).width( optWidth ).indent( 2 ) + + TextFlow::Spacer(4) + + TextFlow::Column( cols.right ).width( consoleWidth - 7 - optWidth ); + os << row << std::endl; + } + } + + friend auto operator<<( std::ostream &os, Parser const &parser ) -> std::ostream& { + parser.writeToStream( os ); + return os; + } + + auto validate() const -> Result override { + for( auto const &opt : m_options ) { + auto result = opt.validate(); + if( !result ) + return result; + } + for( auto const &arg : m_args ) { + auto result = arg.validate(); + if( !result ) + return result; + } + return Result::ok(); + } + + using ParserBase::parse; + + auto parse( std::string const& exeName, TokenStream const &tokens ) const -> InternalParseResult override { + + struct ParserInfo { + ParserBase const* parser = nullptr; + size_t count = 0; + }; + const size_t totalParsers = m_options.size() + m_args.size(); + assert( totalParsers < 512 ); + // ParserInfo parseInfos[totalParsers]; // <-- this is what we really want to do + ParserInfo parseInfos[512]; + + { + size_t i = 0; + for (auto const &opt : m_options) parseInfos[i++].parser = &opt; + for (auto const &arg : m_args) parseInfos[i++].parser = &arg; + } + + m_exeName.set( exeName ); + + auto result = InternalParseResult::ok( ParseState( ParseResultType::NoMatch, tokens ) ); + while( result.value().remainingTokens() ) { + bool tokenParsed = false; + + for( size_t i = 0; i < totalParsers; ++i ) { + auto& parseInfo = parseInfos[i]; + if( parseInfo.parser->cardinality() == 0 || parseInfo.count < parseInfo.parser->cardinality() ) { + result = parseInfo.parser->parse(exeName, result.value().remainingTokens()); + if (!result) + return result; + if (result.value().type() != ParseResultType::NoMatch) { + tokenParsed = true; + ++parseInfo.count; + break; + } + } + } + + if( result.value().type() == ParseResultType::ShortCircuitAll ) + return result; + if( !tokenParsed ) + return InternalParseResult::runtimeError( "Unrecognised token: " + result.value().remainingTokens()->token ); + } + // !TBD Check missing required options + return result; + } + }; + + template + template + auto ComposableParserImpl::operator|( T const &other ) const -> Parser { + return Parser() | static_cast( *this ) | other; + } +} // namespace detail + + +// A Combined parser +using detail::Parser; + +// A parser for options +using detail::Opt; + +// A parser for arguments +using detail::Arg; + +// Wrapper for argc, argv from main() +using detail::Args; + +// Specifies the name of the executable +using detail::ExeName; + +// Convenience wrapper for option parser that specifies the help option +using detail::Help; + +// enum of result types from a parse +using detail::ParseResultType; + +// Result type for parser operation +using detail::ParserResult; + + +} // namespace clara + +#endif // CLARA_HPP_INCLUDED diff --git a/examples/vtzero-filter.cpp b/examples/vtzero-filter.cpp index 4955c95..4698250 100644 --- a/examples/vtzero-filter.cpp +++ b/examples/vtzero-filter.cpp @@ -6,6 +6,7 @@ *****************************************************************************/ +#include "clara.hpp" #include "utils.hpp" #include @@ -14,69 +15,63 @@ #include #include #include -#include #include #include #include -static void print_help() { - std::cout << "vtzero-filter [OPTIONS] VECTOR-TILE LAYER-NUM|LAYER-NAME [ID]\n\n" - << "Filter contents of vector tile.\n" - << "Options:\n" - << " -h, --help This help message\n" - << " -o, --output=FILE Write output to file FILE\n"; -} - -static void print_usage(const char* command) { - std::cerr << "Usage: " << command << " [OPTIONS] VECTOR-TILE LAYER-NUM|LAYER-NAME [ID]\n"; -} - int main(int argc, char* argv[]) { + std::string filename; + std::string layer_num_or_name; + std::string idstr; std::string output_file{"filtered.mvt"}; - static struct option long_options[] = { - {"help", no_argument, nullptr, 'h'}, - {"output", required_argument, nullptr, 'o'}, - {nullptr, 0, nullptr, 0} - }; - - while (true) { - const int c = getopt_long(argc, argv, "ho:", long_options, nullptr); - if (c == -1) { - break; - } + bool help = false; + + const auto cli + = clara::Opt(output_file, "FILE") + ["-o"]["--output"] + ("write output to FILE") + | clara::Help(help) + | clara::Arg(filename, "FILENAME").required() + ("vector tile") + | clara::Arg(layer_num_or_name, "LAYER-NUM|LAYER-NAME").required() + ("layer") + | clara::Arg(idstr, "ID") + ("feature_id"); + + const auto result = cli.parse(clara::Args(argc, argv)); + if (!result) { + std::cerr << "Error in command line: " << result.errorMessage() << '\n'; + return 1; + } - switch (c) { - case 'h': - print_help(); - return 0; - case 'o': - output_file = optarg; - break; - default: - return 1; - } + if (help) { + std::cout << cli + << "\nFilter contents of vector tile.\n"; + return 0; } - const int remaining_args = argc - optind; - if (remaining_args < 2 || remaining_args > 3) { - print_usage(argv[0]); + if (filename.empty()) { + std::cerr << "Error in command line: Missing file name of vector tile to read\n"; return 1; } - const auto data = read_file(argv[optind]); + if (layer_num_or_name.empty()) { + std::cerr << "Error in command line: Missing layer number or name\n"; + return 1; + } + const auto data = read_file(filename); vtzero::vector_tile tile{data}; - auto layer = get_layer(tile, argv[optind + 1]); + auto layer = get_layer(tile, layer_num_or_name); std::cerr << "Found layer: " << std::string(layer.name()) << "\n"; vtzero::tile_builder tb; - if (remaining_args == 2) { + if (idstr.empty()) { tb.add_existing_layer(layer); } else { - std::string idstr{argv[optind + 2]}; char* str_end = nullptr; const int64_t id = std::strtoll(idstr.c_str(), &str_end, 10); if (str_end != idstr.c_str() + idstr.size()) { diff --git a/examples/vtzero-show.cpp b/examples/vtzero-show.cpp index ba6b1ae..cf11d2f 100644 --- a/examples/vtzero-show.cpp +++ b/examples/vtzero-show.cpp @@ -6,30 +6,16 @@ *****************************************************************************/ +#include "clara.hpp" #include "utils.hpp" #include #include #include -#include #include #include -static void print_help() { - std::cout << "vtzero-show [OPTIONS] VECTOR-TILE [LAYER-NUM|LAYER-NAME]\n\n" - << "Show contents of vector tile.\n\n" - << "Options:\n" - << " -h, --help This help message\n" - << " -l, --layers Show layer overview with feature count\n" - << " -t, --tables Also print key/value tables\n" - << " -T, --value-types Also show value types\n"; -} - -static void print_usage(const char* command) { - std::cerr << "Usage: " << command << " [OPTIONS] VECTOR-TILE [LAYER-NUM|LAYER-NAME]\n"; -} - class geom_handler { std::string output{}; @@ -181,56 +167,54 @@ static void print_layer_overview(const vtzero::layer& layer) { } int main(int argc, char* argv[]) { + std::string filename; + std::string layer_num_or_name; bool layer_overview = false; bool print_tables = false; bool print_value_types = false; + bool help = false; + + const auto cli + = clara::Opt(layer_overview) + ["-l"]["--layers"] + ("show layer overview with feature count") + | clara::Opt(print_tables) + ["-t"]["--tables"] + ("also print key/value tables") + | clara::Opt(print_value_types) + ["-T"]["--value-types"] + ("also show value types") + | clara::Help(help) + | clara::Arg(filename, "FILENAME").required() + ("vector tile") + | clara::Arg(layer_num_or_name, "LAYER-NUM|LAYER-NAME") + ("layer"); + + const auto result = cli.parse(clara::Args(argc, argv)); + if (!result) { + std::cerr << "Error in command line: " << result.errorMessage() << '\n'; + return 1; + } - static struct option long_options[] = { - {"help", no_argument, nullptr, 'h'}, - {"layers", no_argument, nullptr, 'l'}, - {"tables", no_argument, nullptr, 't'}, - {"value-types", no_argument, nullptr, 'T'}, - {nullptr, 0, nullptr, 0} - }; - - while (true) { - const int c = getopt_long(argc, argv, "hltT", long_options, nullptr); - if (c == -1) { - break; - } - - switch (c) { - case 'h': - print_help(); - return 0; - case 'l': - layer_overview = true; - break; - case 't': - print_tables = true; - break; - case 'T': - print_value_types = true; - break; - default: - return 1; - } + if (help) { + std::cout << cli + << "\nShow contents of vector tile FILENAME.\n"; + return 0; } - const int remaining_args = argc - optind; - if (remaining_args < 1 || remaining_args > 2) { - print_usage(argv[0]); + if (filename.empty()) { + std::cerr << "Error in command line: Missing file name of vector tile to read\n"; return 1; } int layer_num = 0; int feature_num = 0; try { - const auto data = read_file(argv[optind]); + const auto data = read_file(filename); vtzero::vector_tile tile{data}; - if (remaining_args == 1) { + if (layer_num_or_name.empty()) { while (auto layer = tile.next_layer()) { if (layer_overview) { print_layer_overview(layer); @@ -240,7 +224,7 @@ int main(int argc, char* argv[]) { ++layer_num; } } else { - auto layer = get_layer(tile, argv[optind + 1]); + auto layer = get_layer(tile, layer_num_or_name); if (layer_overview) { print_layer_overview(layer); } else {