pikotools/src/mainoptions/mainoptionsparser.cpp

398 lines
8.5 KiB
C++

/*
* This file is a part of PikoTools
* and is distributed under the (new) BSD licence.
* Author: Tomasz Sowa <t.sowa@ttmath.org>
*/
/*
* 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 <string.h>
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<std::wstring> & 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