Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions source/Makefile.Objects
Original file line number Diff line number Diff line change
Expand Up @@ -717,6 +717,7 @@ OBJS_IO=module_parameter/input_conv.o\
init_info.o\
output_info.o\
parse_args.o\
parse_command_line.o\
module_parameter/availability.o\
module_parameter/availability_validator.o\
module_parameter/read_input.o\
Expand Down
1 change: 1 addition & 0 deletions source/source_io/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ list(APPEND objects
module_json/para_json.cpp
parse_args.cpp
input_help.cpp
parse_command_line.cpp
module_chgpot/get_pchg_pw.cpp
module_wf/get_wf_pw.cpp
)
Expand Down
5 changes: 5 additions & 0 deletions source/source_io/input_help.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -452,6 +452,11 @@ void ParameterHelp::show_general_help(std::ostream& os) {
os << " -i, -I, --info Display detailed build information\n";
os << " -h, --help [param] Display help for parameter (or this message)\n";
os << " -s, --search <query> Search for parameters matching query\n";
os << " -p, --parameter <name> <value> (Not taking effects yet)\n";
os << " Set an INPUT variable from the command line.\n";
// os << " usable in INPUT as ${name} or $name.\n";
// os << " Repeatable; overrides INPUT 'variable'.\n";
os << " -in, --input <file> Path to INPUT (default: INPUT)\n";
os << " --check-input Check input file syntax and exit\n";
os << " --generate-parameters-yaml\n";
os << " Dump all parameter metadata as YAML\n";
Expand Down
34 changes: 34 additions & 0 deletions source/source_io/parse_args.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,40 @@ void parse_args(int argc, char** argv)
{
ModuleIO::ReadInput::check_mode = true;
}
// ---- NEW: pass-through branches for run-control options.
// These flags are owned by ModuleIO::parse_command_line, which is
// called right after this function in main(). Here we only validate
// the token count and skip the tokens, so they survive the
// unknown-argument fallback below. No state is stored here
// (governance rule 1: no new globals).
else if (arg == "-p" || arg == "--parameter")
{
// Needs two following tokens: <name> <value>
const bool has_pair = (i + 2 < argc);
if (!has_pair)
{
const std::string msg = "Error: Option " + arg
+ " requires <name> <value>";
std::cerr << msg << std::endl;
std::exit(1);
}
// Skip "<name> <value>"; full validation (empty name,
// digit-leading name) is done in parse_command_line.
i += 2;
}
else if (arg == "-in" || arg == "--input")
{
// Needs one following token: <file>
const bool has_file = (i + 1 < argc);
if (!has_file)
{
const std::string msg = "Error: Option " + arg
+ " requires <file>";
std::cerr << msg << std::endl;
std::exit(1);
}
++i;
}
else
{
// Error message goes to stderr
Expand Down
81 changes: 81 additions & 0 deletions source/source_io/parse_command_line.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
#include "source_io/parse_command_line.h"

#include <cctype>
#include <cstdio>
#include <stdexcept>

namespace ModuleIO
{

CommandLineArgs parse_command_line(int argc, char* argv[])
{
CommandLineArgs args;
for (int i = 1; i < argc; ++i)
{
const std::string arg = argv[i];

if (arg == "-p" || arg == "--parameter")
{
if (i + 2 >= argc)
{
const std::string msg = "Option " + arg
+ " requires <name> <value>";
throw std::runtime_error(msg);
}
++i;
const std::string name = argv[i];
if (name.empty())
{
const std::string msg = "Invalid variable name: (empty)";
throw std::runtime_error(msg);
}
const unsigned char first_char
= static_cast<unsigned char>(name[0]);
const bool starts_with_digit = (std::isdigit(first_char) != 0);
if (starts_with_digit)
{
const std::string msg = "Invalid variable name: " + name;
throw std::runtime_error(msg);
}
++i;
const std::string value = argv[i];
args.vars[name] = value; // later duplicates override earlier ones
}
else if (arg == "-in" || arg == "--input")
{
if (i + 1 >= argc)
{
const std::string msg = "Option " + arg + " requires <file>";
throw std::runtime_error(msg);
}
++i;
args.input_file = argv[i];
}
else
{
// Unreachable in normal flow: parse_args rejects unknown
// flags before this function runs. Kept as a defensive check
// so parse_command_line stays self-contained for tests.
const std::string msg = "Unknown option: " + arg;
throw std::runtime_error(msg);
}
}
return args;
}

void print_help(const std::string& bin)
{
// Run-control options only; the general help is owned by
// ParameterHelp::show_general_help (via parse_args).
std::printf(
"Run-control options of %s:\n"
" -p, --parameter <name> <value>\n"
" Set an INPUT variable from the command line,\n"
" usable in INPUT as ${name} or $name.\n"
" Repeatable; overrides INPUT 'variable'.\n"
" -in, --input <file> Path to INPUT (default: INPUT)\n",
bin.c_str());
}

} // namespace ModuleIO

39 changes: 39 additions & 0 deletions source/source_io/parse_command_line.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
#ifndef ModuleIO_PARSE_COMMAND_LINE_H
#define ModuleIO_PARSE_COMMAND_LINE_H

#include <map>
#include <string>

namespace ModuleIO
{

/// Run-control options parsed after ModuleIO::parse_args.
///
/// Flag ownership:
/// - Informational flags (-v/-i/-h/-s/--generate-parameters-yaml) and
/// --check-input are owned by ModuleIO::parse_args and never reach
/// this function.
/// - -p/--parameter and -in/--input are validated (token count only)
/// and skipped by parse_args, then fully parsed here.
struct CommandLineArgs
{
// -p / --parameter <name> <value> : INPUT variable injection (repeatable)
// These override INPUT 'variable' definitions.
std::map<std::string, std::string> vars;

// -in / --input <file> : explicit INPUT path, default "INPUT"
std::string input_file = "INPUT";
};

/// Parses -p/--parameter and -in/--input options.
/// Throws std::runtime_error on malformed arguments (parse_args has
/// already filtered unknown flags before this function is called).
CommandLineArgs parse_command_line(int argc, char* argv[]);

/// Prints usage for the run-control options handled here.
void print_help(const std::string& bin_name);

} // namespace ModuleIO

#endif // ModuleIO_PARSE_COMMAND_LINE_H

6 changes: 6 additions & 0 deletions source/source_io/test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,12 @@ AddTest(
SOURCES input_help_test.cpp ../input_help.cpp
)

AddTest(
TARGET MODULE_IO_parse_command_line_test
LIBS parameter base device io_input
SOURCES parse_command_line_test.cpp ../parse_command_line.cpp
)

AddTest(
TARGET MODULE_IO_bessel_basis_test
LIBS parameter base device
Expand Down
116 changes: 116 additions & 0 deletions source/source_io/test/parse_command_line_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
#include "source_io/parse_command_line.h"

#include <gtest/gtest.h>
#include <stdexcept>

namespace
{
/// Builds an argv array from strings and returns the token count
/// (excluding the terminating nullptr) via the out-parameter.
/// Lifetime note: `storage` and `strings` must outlive the parse call.
int make_argv(std::vector<std::string>& strings,
std::vector<char*>& storage)
{
strings.insert(strings.begin(), "abacus");
storage.clear();
storage.reserve(strings.size());
for (size_t i = 0; i < strings.size(); ++i)
{
storage.push_back(&strings[i][0]);
}
storage.push_back(nullptr);
return static_cast<int>(strings.size());
}
} // namespace

TEST(ParseCommandLineTest, ShortFormSingleVar)
{
char* argv[] = {(char*)"abacus",
(char*)"-p", (char*)"a", (char*)"1", nullptr};
const ModuleIO::CommandLineArgs args
= ModuleIO::parse_command_line(4, argv);
ASSERT_EQ(args.vars.size(), 1u);
EXPECT_EQ(args.vars.at("a"), "1");
}

TEST(ParseCommandLineTest, LongFormSingleVar)
{
char* argv[] = {(char*)"abacus",
(char*)"--parameter", (char*)"ecutwfc", (char*)"120",
nullptr};
const ModuleIO::CommandLineArgs args
= ModuleIO::parse_command_line(4, argv);
EXPECT_EQ(args.vars.at("ecutwfc"), "120");
}

TEST(ParseCommandLineTest, MultipleVarsLastWins)
{
std::vector<std::string> tokens
= {"-p", "a", "1", "-p", "a", "2", "-p", "b", "x"};
std::vector<char*> storage;
const int argc = make_argv(tokens, storage);
const ModuleIO::CommandLineArgs args
= ModuleIO::parse_command_line(argc, storage.data());
EXPECT_EQ(args.vars.at("a"), "2");
EXPECT_EQ(args.vars.at("b"), "x");
}

TEST(ParseCommandLineTest, VarPlusCustomInput)
{
char* argv[] = {(char*)"abacus",
(char*)"-p", (char*)"suffix", (char*)"TestRun",
(char*)"-in", (char*)"myINPUT",
nullptr};
const ModuleIO::CommandLineArgs args
= ModuleIO::parse_command_line(6, argv);
EXPECT_EQ(args.vars.at("suffix"), "TestRun");
EXPECT_EQ(args.input_file, "myINPUT");
}

TEST(ParseCommandLineTest, DefaultInputPath)
{
char* argv[] = {(char*)"abacus", nullptr};
const ModuleIO::CommandLineArgs args
= ModuleIO::parse_command_line(1, argv);
EXPECT_EQ(args.input_file, "INPUT");
EXPECT_TRUE(args.vars.empty());
}

TEST(ParseCommandLineTest, MissingValueThrows)
{
char* argv[] = {(char*)"abacus", (char*)"-p", (char*)"a", nullptr};
EXPECT_THROW(ModuleIO::parse_command_line(3, argv), std::runtime_error);
}

TEST(ParseCommandLineTest, EmptyNameThrows)
{
char* argv[] = {(char*)"abacus",
(char*)"-p", (char*)"", (char*)"x", nullptr};
EXPECT_THROW(ModuleIO::parse_command_line(4, argv), std::runtime_error);
}

TEST(ParseCommandLineTest, DigitLeadingNameThrows)
{
char* argv[] = {(char*)"abacus",
(char*)"-p", (char*)"1abc", (char*)"x", nullptr};
EXPECT_THROW(ModuleIO::parse_command_line(4, argv), std::runtime_error);
}

TEST(ParseCommandLineTest, UnknownOptionThrows)
{
char* argv[] = {(char*)"abacus", (char*)"--bogus", nullptr};
EXPECT_THROW(ModuleIO::parse_command_line(2, argv), std::runtime_error);
}

// The exact-argc contract: with argc == 9 the last pair is truncated
// and must throw (documents that argc counts tokens, not nullptr).
TEST(ParseCommandLineTest, TruncatedArgcThrows)
{
char* argv[] = {(char*)"abacus",
(char*)"-p", (char*)"a", (char*)"1",
(char*)"-p", (char*)"a", (char*)"2",
(char*)"-p", (char*)"b", (char*)"x",
nullptr};
EXPECT_THROW(ModuleIO::parse_command_line(9, argv), std::runtime_error);
}

Loading
Loading