Implemented automatic parameter documentation

This commit is contained in:
Leonetienne 2021-06-04 01:50:21 +02:00
parent d36c7ff072
commit 359463682f
5 changed files with 234 additions and 7 deletions

Binary file not shown.

View File

@ -1,4 +1,5 @@
#pragma once #pragma once
#include <string>
namespace Hazelnp namespace Hazelnp
{ {
@ -12,4 +13,27 @@ namespace Hazelnp
STRING, STRING,
LIST LIST
}; };
static inline std::string DataTypeToString(DATA_TYPE type)
{
switch (type)
{
case DATA_TYPE::VOID:
return "VOID";
case DATA_TYPE::INT:
return "INT";
case DATA_TYPE::FLOAT:
return "FLOAT";
case DATA_TYPE::STRING:
return "STRING";
case DATA_TYPE::LIST:
return "LIST";
}
return "";
}
} }

View File

@ -60,12 +60,15 @@ void Hazelnupp::Parse(const int argc, const char* const* argv)
// Apply constraints such as default values, and required parameters. // Apply constraints such as default values, and required parameters.
// Types have already been enforced. // Types have already been enforced.
// Dont apply constraints when we are just printind the param docs
if ((!catchHelp) || (!HasParam("--help")))
ApplyConstraints(); ApplyConstraints();
} }
catch (const HazelnuppConstraintTypeMissmatch& hctm) catch (const HazelnuppConstraintTypeMissmatch& hctm)
{ {
if (crashOnFail) if (crashOnFail)
{ {
std::cout << GenerateDocumentation() << std::endl;
std::cerr << "Fatal error: Command-line parameter value-type mismatch at \"" << hctm.What() << "\"!"; std::cerr << "Fatal error: Command-line parameter value-type mismatch at \"" << hctm.What() << "\"!";
quick_exit(-1009); quick_exit(-1009);
} }
@ -76,6 +79,7 @@ void Hazelnupp::Parse(const int argc, const char* const* argv)
{ {
if (crashOnFail) if (crashOnFail)
{ {
std::cout << GenerateDocumentation() << std::endl;
std::cerr << "Fatal error: Missing required command-line parameter \"" << hctm.What() << "\"!"; std::cerr << "Fatal error: Missing required command-line parameter \"" << hctm.What() << "\"!";
quick_exit(-1010); quick_exit(-1010);
} }
@ -83,6 +87,13 @@ void Hazelnupp::Parse(const int argc, const char* const* argv)
throw hctm; // yeet throw hctm; // yeet
} }
// Catch --help parameter
if ((catchHelp) && (HasParam("--help")))
{
std::cout << GenerateDocumentation() << std::endl;
quick_exit(0);
}
return; return;
} }
@ -281,6 +292,162 @@ bool Hazelnupp::GetCrashOnFail() const
return crashOnFail; return crashOnFail;
} }
void Hazelnupp::SetCatchHelp(bool catchHelp)
{
this->catchHelp = catchHelp;
return;
}
bool Hazelnupp::GetCatchHelp() const
{
return catchHelp;
}
void Hazelnupp::SetBriefDescription(const std::string& description)
{
briefDescription = description;
return;
}
const std::string& Hazelnupp::GetBriefDescription()
{
return briefDescription;
}
void Hazelnp::Hazelnupp::RegisterDescription(const std::string& parameter, const std::string& description)
{
parameterDescriptions[parameter] = description;
return;
}
const std::string Hazelnp::Hazelnupp::GetDescription(const std::string& parameter) const
{
// Do we already have a description for this parameter?
const auto par = parameterDescriptions.find(parameter);
if (par == parameterDescriptions.end())
// No? Then return ""
return "";
// We do? Then return it
return par->second;
}
void Hazelnp::Hazelnupp::ClearDescription(const std::string& parameter)
{
// This will just do nothing if the entry does not exist
parameterDescriptions.erase(parameter);
return;
}
std::string Hazelnupp::GenerateDocumentation() const
{
std::stringstream ss;
// Add brief, if available
if (briefDescription.length() > 0)
ss << briefDescription << std::endl;
// Collect parameter information
struct ParamDocEntry
{
std::string abbreviation;
std::string description;
std::string type;
bool required = false;
bool typeIsForced = false;
std::string defaultVal;
};
std::unordered_map<std::string, ParamDocEntry> paramInfos;
// Collect descriptions
for (const auto& it : parameterDescriptions)
{
// Do we already have that param in the paramInfo set?
if (paramInfos.find(it.first) == paramInfos.end())
// No? Create it.
paramInfos[it.first] = ParamDocEntry();
paramInfos[it.first].description = it.second;
}
// Collect abbreviations
// first value is abbreviation, second is long form
for (const auto& it : abbreviations)
{
// Do we already have that param in the paramInfo set?
if (paramInfos.find(it.second) == paramInfos.end())
// No? Create it.
paramInfos[it.second] = ParamDocEntry();
paramInfos[it.second].abbreviation = it.first;
}
// Collect constraints
for (const auto& it : constraints)
{
// Do we already have that param in the paramInfo set?
if (paramInfos.find(it.first) == paramInfos.end())
// No? Create it.
paramInfos[it.first] = ParamDocEntry();
ParamDocEntry& cached = paramInfos[it.first];
cached.required = it.second.required;
cached.typeIsForced = it.second.constrainType;
cached.type = DataTypeToString(it.second.wantedType);
std::stringstream defaultValueSs;
for (const std::string& s : it.second.defaultValue)
{
defaultValueSs << '\'' << s << '\'';
// Add a space if we are not at the last entry
if ((void*)&s != (void*)&it.second.defaultValue.back())
defaultValueSs << " ";
}
cached.defaultVal = defaultValueSs.str();
}
// Now generate the documentatino body
if (paramInfos.size() > 0)
{
ss << std::endl
<< "==== AVAILABLE PARAMETERS ===="
<< std::endl << std::endl;
for (const auto& it : paramInfos)
{
const ParamDocEntry& pde = it.second;
// Put name
ss << it.first << " ";
// Put abbreviation
if (pde.abbreviation.length() > 0)
ss << pde.abbreviation << " ";
// Put type
if (pde.typeIsForced)
ss << pde.type << " ";
// Put default value
if (pde.defaultVal.length() > 0)
ss << "default=[" << pde.defaultVal << "] ";
// Put required tag, but only if no default value
if ((pde.required) && (pde.defaultVal.length() == 0))
ss << "[[REQUIRED]] ";
// Put brief description
if (pde.description.length() > 0)
ss << pde.description;
ss << std::endl << std::endl;
}
}
return ss.str();
}
void Hazelnupp::ApplyConstraints() void Hazelnupp::ApplyConstraints()
{ {
// Enforce required parameters / default values // Enforce required parameters / default values

View File

@ -54,6 +54,32 @@ namespace Hazelnp
//! Gets whether the application crashes on an exception whilst parsing, and prints to stderr. //! Gets whether the application crashes on an exception whilst parsing, and prints to stderr.
bool GetCrashOnFail() const; bool GetCrashOnFail() const;
//! Sets whether the Hazelnupp should automatically catch the --help parameter, print the parameter documentation to stdout, and exit or not.
void SetCatchHelp(bool catchHelp);
//! Retruns whether the Hazelnupp should automatically catch the --help parameter, print the parameter documentation to stdout, and exit or not.
bool GetCatchHelp() const;
//! Sets a brief description of the application to be automatically added to the documentation.
void SetBriefDescription(const std::string& description);
//! Returns the brief description of the application to be automatically added to the documentation.
const std::string& GetBriefDescription();
//! Willl register a short description for a parameter.
//! Will overwrite existing descriptions for that parameter.
void RegisterDescription(const std::string& parameter, const std::string& description);
//! Will return a short description for a parameter, if it exists.
//! Empty string if it does not exist.
const std::string GetDescription(const std::string& parameter) const;
//! Will delete the description of a parameter if it exists.
void ClearDescription(const std::string& parameter);
//! Will generate a text-based documentation suited to show the user, for example on --help.
std::string GenerateDocumentation() const;
private: private:
//! Will translate the c-like args to an std::vector //! Will translate the c-like args to an std::vector
void PopulateRawArgs(const int argc, const char* const* argv); void PopulateRawArgs(const int argc, const char* const* argv);
@ -76,14 +102,24 @@ namespace Hazelnp
std::string executableName; //! The path of the executable. Always argv[0] std::string executableName; //! The path of the executable. Always argv[0]
std::unordered_map<std::string, Parameter*> parameters; std::unordered_map<std::string, Parameter*> parameters;
// These are abbreviations. Like, -f for --force. //! These are abbreviations. Like, -f for --force.
std::unordered_map<std::string, std::string> abbreviations; std::unordered_map<std::string, std::string> abbreviations;
// Parameter constraints, mapped to keys //! Parameter constraints, mapped to keys
std::unordered_map<std::string, ParamConstraint> constraints; std::unordered_map<std::string, ParamConstraint> constraints;
//! Raw argv
std::vector<std::string> rawArgs; std::vector<std::string> rawArgs;
//! Short descriptions for parameters
std::unordered_map<std::string, std::string> parameterDescriptions;
//! A brief description of the application to be added to the generated documentation. Optional.
std::string briefDescription;
//! If set to true, Hazelnupp will automatically catch the --help parameter, print the parameter documentation to stdout and exit.
bool catchHelp = true;
//! If set to true, Hazelnupp will crash the application with output to stderr when an exception is thrown whilst parsing. //! If set to true, Hazelnupp will crash the application with output to stderr when an exception is thrown whilst parsing.
bool crashOnFail = true; bool crashOnFail = true;
}; };

View File

@ -27,26 +27,26 @@
</PropertyGroup> </PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" /> <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration"> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
<ConfigurationType>StaticLibrary</ConfigurationType> <ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries> <UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v142</PlatformToolset> <PlatformToolset>v142</PlatformToolset>
<CharacterSet>Unicode</CharacterSet> <CharacterSet>Unicode</CharacterSet>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration"> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
<ConfigurationType>StaticLibrary</ConfigurationType> <ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries> <UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v142</PlatformToolset> <PlatformToolset>v142</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization> <WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>Unicode</CharacterSet> <CharacterSet>Unicode</CharacterSet>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration"> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
<ConfigurationType>StaticLibrary</ConfigurationType> <ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries> <UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v142</PlatformToolset> <PlatformToolset>v142</PlatformToolset>
<CharacterSet>Unicode</CharacterSet> <CharacterSet>Unicode</CharacterSet>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration"> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
<ConfigurationType>StaticLibrary</ConfigurationType> <ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries> <UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v142</PlatformToolset> <PlatformToolset>v142</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization> <WholeProgramOptimization>true</WholeProgramOptimization>