/* * This file is a part of PikoTools * and is distributed under the (new) BSD licence. * Author: Tomasz Sowa */ /* * Copyright (c) 2016-2021, Tomasz Sowa * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * * Neither the name Tomasz Sowa nor the names of contributors to this * project may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF * THE POSSIBILITY OF SUCH DAMAGE. */ #include "mainoptionsparser.h" #include "utf8/utf8.h" #include namespace pt { MainOptionsParser::MainOptionsParser() { space = 0; arguments_required_space = 0; should_use_utf8 = true; last_status = status_ok; non_option_arguments_name = L"args"; } MainOptionsParser::~MainOptionsParser() { } void MainOptionsParser::use_utf8(bool utf8) { should_use_utf8 = utf8; } void MainOptionsParser::set_non_options_arguments_name(const wchar_t * name) { non_option_arguments_name = name; } void MainOptionsParser::set_non_options_arguments_name(const std::wstring & name) { non_option_arguments_name = name; } std::wstring & MainOptionsParser::get_wrong_option() { return last_error_option; } MainOptionsParser::Status MainOptionsParser::parse(int argc, const char ** argv, Space & out_space) { space = &out_space; arguments_required_space = nullptr; return parse(argc, argv); } MainOptionsParser::Status MainOptionsParser::parse(int argc, const char ** argv, Space & out_space, const Space & arguments) { space = &out_space; arguments_required_space = &arguments;; return parse(argc, argv); } MainOptionsParser::Status MainOptionsParser::parse(int argc, const char ** argv) { last_status = status_ok; last_error_option.clear(); space->set_empty_object(); for(size_t i=1 ; i < (size_t)argc && last_status == status_ok ; ) { parse((size_t)argc, argv, i); } options.clear(); option.clear(); argument.clear(); arguments.clear(); return last_status; } void MainOptionsParser::parse(size_t argc, const char ** argv, size_t & argv_index) { const char * pchar = argv[argv_index]; if( *pchar == '-' ) { if( *(pchar+1) == '-' && *(pchar+2) == 0 ) { // two hyphens only "--" argv_index += 1; parse_non_option_arguments(argc, argv, argv_index); } else if( *(pchar+1) == '-' ) { // two hyphens and a string, such as "--abc" parse_long_option(argc, argv, argv_index); } else if( *(pchar+1) != 0 ) { // one hyphen and a string, such as "-abc" parse_short_option(argc, argv, argv_index); } else { parse_non_option_arguments(argc, argv, argv_index); } } else { parse_non_option_arguments(argc, argv, argv_index); } } void MainOptionsParser::convert_str(const char * src, std::wstring & dst) { if( should_use_utf8 ) { UTF8ToWide(src, dst); } else { dst.clear(); for( ; *src ; ++src ) dst += (wchar_t)(unsigned char)*src; } } void MainOptionsParser::convert_str(const char * src, size_t len, std::wstring & dst) { if( should_use_utf8 ) { UTF8ToWide(src, len, dst); } else { dst.clear(); for(size_t i=0 ; i < len ; ++i) dst += (wchar_t)(unsigned char)src[i]; } } void MainOptionsParser::convert_str(const std::wstring & src, Space & space) { if( should_use_utf8 ) { space.set_empty_wstring(); space.value.value_wstring = src; } else { space.set_empty_string(); std::string & dst = space.value.value_string; dst.clear(); for(size_t i=0 ; i < src.size() ; ++i) dst += (char)src[i]; } } void MainOptionsParser::parse_short_option(size_t argc, const char ** argv, size_t & argv_index) { convert_str(argv[argv_index] + 1, options); const wchar_t * options_pchar = options.c_str(); arguments.clear(); bool was_argument = false; argv_index += 1; for( ; *options_pchar && !was_argument && last_status == status_ok ; ++options_pchar ) { option = *options_pchar; size_t args_len = how_many_arguments_required(option); if( args_len > 0 ) { was_argument = true; if( *(options_pchar+1) ) { // first argument is directly behind the option argument = options_pchar + 1; arguments.push_back(argument); args_len -= 1; } parse_arguments(argc, argv, argv_index, args_len); } add_option_to_space(option, arguments); } } void MainOptionsParser::parse_long_option(size_t argc, const char ** argv, size_t & argv_index) { const char * option_begin = argv[argv_index] + 2; // skip first two hyphens -- const char * option_end = option_begin; bool is_equal_form = false; // is the option in the form with equal sign, such as: option=argument while( *option_end != 0 && *option_end != '=' ) { option_end += 1; } if( *option_end == '=' ) { is_equal_form = true; convert_str(option_begin, option_end - option_begin, option); convert_str(option_end + 1, argument); } else { convert_str(option_begin, option); } argv_index += 1; size_t args_len = how_many_arguments_required(option); arguments.clear(); if( is_equal_form ) { if( args_len == 0 ) { if( !argument.empty() ) { // report an error last_status = status_argument_provided; last_error_option = option; } } else if( args_len == 1 ) { // argument can be empty in such a case: option= // we treat it as if the argument would not be provided if( !argument.empty() ) { arguments.push_back(argument); args_len -= 1; } } else { // args_len is > 1 but when using option=argument form // we can provide only one argument last_status = status_argument_not_provided; last_error_option = option; } } if( last_status == status_ok ) { parse_arguments(argc, argv, argv_index, args_len); add_option_to_space(option, arguments); } } void MainOptionsParser::parse_arguments(size_t argc, const char ** argv, size_t & argv_index, size_t args_len) { for( ; args_len > 0 && argv_index < argc ; --args_len, ++argv_index) { convert_str(argv[argv_index], argument); arguments.push_back(argument); } if( args_len > 0 ) { last_status = status_argument_not_provided; last_error_option = option; } } void MainOptionsParser::parse_non_option_arguments(size_t argc, const char ** argv, size_t & argv_index) { Space * table_with_args = new Space(); table_with_args->set_empty_table(); for( ; argv_index < argc ; ++argv_index) { convert_str(argv[argv_index], argument); table_with_args->add(argument); } space->add(non_option_arguments_name, table_with_args); } void MainOptionsParser::add_option_to_space(const std::wstring & option, const std::vector & arguments) { Space * option_table = space->get_object_field(option); if( !option_table ) { option_table = &space->add_empty_space(option); } if( !option_table->is_table()) { option_table->set_empty_table(); } Space * arguments_table = new Space(); arguments_table->set_empty_table(); for(const std::wstring & arg : arguments) { Space & space_arg = arguments_table->add_empty_space(); convert_str(arg, space_arg); } option_table->add(arguments_table); } size_t MainOptionsParser::how_many_arguments_required(const std::wstring & arg) { size_t res = 0; if( arguments_required_space && arguments_required_space->is_object() ) { long res_long = arguments_required_space->to_llong(arg, 0); if( res_long < 0 ) res_long = 0; res = (size_t)res_long; // argument 'arg' needs 'res' options } return res; } } // namespace