#pragma once
#include <library/cpp/logger/all.h>
#include <util/str_stl.h>
#include <library/cpp/charset/ci_string.h>
#include <util/generic/map.h>
#include <util/generic/ptr.h>
#include <util/generic/stack.h>
#include <util/generic/string.h>
#include <util/generic/vector.h>
#include <library/cpp/string_utils/parse_vector/vector_parser.h>
#include <util/generic/yexception.h>
#include <util/stream/output.h>
#include <util/string/cast.h>
#include <util/system/defaults.h>
#include <util/system/yassert.h>
#include <util/generic/noncopyable.h>
class TSectionDesc;
class TYandexConfig: public TSimpleRefCount<TYandexConfig>, TNonCopyable {
public:
class Directives;
typedef TMap<const char*, const char*, ci_less> SectionAttrs;
struct Section;
typedef TMultiMap<TCiString, Section*> TSectionsMap;
struct Section {
const char* Name;
SectionAttrs Attrs;
Directives* Cookie;
Section* Parent;
Section* Next;
Section* Child;
bool Owner;
TSectionDesc* Desc;
Section()
: Name("")
, Cookie(nullptr)
, Parent(nullptr)
, Next(nullptr)
, Child(nullptr)
, Owner(false)
, Desc(nullptr)
{
}
Directives& GetDirectives() {
Y_ASSERT(Cookie);
return *Cookie;
}
const Directives& GetDirectives() const {
Y_ASSERT(Cookie);
return *Cookie;
}
bool Parsed() const {
return Cookie != nullptr;
}
TSectionsMap GetAllChildren() const;
};
public:
TYandexConfig()
: FileData(nullptr)
{
Clear();
}
virtual ~TYandexConfig() {
Clear();
}
[[nodiscard]] bool Read(const TString& path);
[[nodiscard]] bool ReadMemory(const char* buffer, const char* configPath = nullptr);
[[nodiscard]] bool ReadMemory(const TStringBuf& buffer, const char* configPath = nullptr);
[[nodiscard]] bool Parse(const TString& path, bool process_directives = true);
[[nodiscard]] bool ParseMemory(const char* buffer, bool process_directives = true, const char* configPath = nullptr);
[[nodiscard]] bool ParseMemory(const TStringBuf& buffer, bool processDirectives = true, const char* configPath = nullptr);
[[nodiscard]] bool ParseSection(const char* SecName, const char* idname = nullptr, const char* idvalue = nullptr);
void AddSection(Section* sec);
void Clear();
void ReportError(const char* ptr, const char* err, bool warning = false);
void ReportError(const char* ptr, bool warning, const char* format, ...) Y_PRINTF_FORMAT(4, 5);
void PrintErrors(TLog* Log);
void PrintErrors(TString& Err);
template <typename TLogWriter>
void PrintErrors(TLogWriter& writer) {
for (const auto& s : Errors) {
writer() << "In '" << ConfigPath << "': " << s << '\n';
}
Errors.clear();
}
Section* GetFirstChild(const char* Name, Section* CurSection = nullptr);
const char* GetConfigPath() const {
return ConfigPath.data();
}
Section* GetRootSection() {
Y_ASSERT(!AllSections.empty());
return AllSections[0];
}
const Section* GetRootSection() const {
Y_ASSERT(!AllSections.empty());
return AllSections[0];
}
void PrintConfig(IOutputStream& os) const;
static void PrintSectionConfig(const TYandexConfig::Section* section, IOutputStream& os, bool printNextSection = true);
protected:
//the followind three functions return 'false' only for fatal errors to break the parsing
virtual bool AddKeyValue(Section& sec, const char* key, const char* value);
virtual bool OnBeginSection(Section& sec); //keep sec.Cookie==0 to skip the section
virtual bool OnEndSection(Section& sec);
private:
bool PrepareLines();
void ProcessComments();
bool ProcessRoot(bool process_directives);
bool ProcessAll(bool process_directives);
bool ProcessBeginSection();
bool ProcessEndSection();
bool ProcessDirective();
void ProcessLineBreak(char*& LineBreak, char toChange);
bool FindEndOfSection(const char* SecName, const char* begin, char*& endsec, char*& endptr);
private:
char* FileData;
ui32 Len;
char* CurrentMemoryPtr;
TStack<Section*> CurSections;
TVector<Section*> AllSections;
TVector<TString> Errors;
TVector<const char*> EndLines;
TString ConfigPath;
};
class TYandexConfig::Directives: public TMap<TCiString, const char*, std::less<>> {
public:
Directives(bool isStrict)
: strict(isStrict)
{
}
Directives()
: strict(true)
{
}
virtual ~Directives() = default;
bool IsStrict() const {
return strict;
}
bool AddKeyValue(const TString& key, const char* value);
bool GetValue(TStringBuf key, TString& value) const;
bool GetNonEmptyValue(TStringBuf key, TString& value) const;
bool GetValue(TStringBuf key, bool& value) const;
template <class T>
inline bool GetValue(TStringBuf key, T& value) const {
TString tmp;
if (GetValue(key, tmp)) {
value = FromString<T>(tmp);
return true;
}
return false;
}
template <class T>
inline T Value(TStringBuf key, T def = T()) const {
GetValue(key, def);
return def;
}
template <class T, class TDelim = char, bool emptyOK = true>
bool TryFillArray(TStringBuf key, TVector<T>& result, const TDelim delim = ',') const {
auto it = find(key);
if (it != end() && (*it).second != nullptr) {
TVector<T> localResult;
if (!TryParseStringToVector((*it).second, localResult, delim)) {
return false;
} else {
std::swap(localResult, result);
return true;
}
} else {
if (emptyOK) {
result.clear();
}
}
return emptyOK;
}
bool FillArray(TStringBuf key, TVector<TString>& values) const;
void Clear();
void declare(const char* directive_name) {
insert(value_type(directive_name, nullptr));
}
virtual bool CheckOnEnd(TYandexConfig& yc, TYandexConfig::Section& sec);
protected:
bool strict;
};
#define DECLARE_CONFIG(ConfigClass) \
class ConfigClass: public TYandexConfig { \
public: \
ConfigClass() \
: TYandexConfig() { \
} \
\
protected: \
virtual bool OnBeginSection(Section& sec); \
\
private: \
ConfigClass(const ConfigClass&); \
ConfigClass& operator=(const ConfigClass&); \
};
#define DECLARE_SECTION(SectionClass) \
class SectionClass: public TYandexConfig::Directives { \
public: \
SectionClass(); \
};
#define DECLARE_SECTION_CHECK(SectionClass) \
class SectionClass: public TYandexConfig::Directives { \
public: \
SectionClass(); \
bool CheckOnEnd(TYandexConfig& yc, TYandexConfig::Section& sec); \
};
#define BEGIN_CONFIG(ConfigClass) \
bool ConfigClass::OnBeginSection(Section& sec) { \
if (sec.Parent == &sec) /* it's root */ { \
assert(*sec.Name == 0); \
/* do not allow any directives at root */ \
sec.Cookie = new TYandexConfig::Directives; \
sec.Owner = true; \
return true; \
}
#define BEGIN_TOPSECTION2(SectionName, DirectivesClass) \
if (*sec.Parent->Name == 0) { /* it's placed at root */ \
if (stricmp(sec.Name, #SectionName) == 0) { \
sec.Cookie = new DirectivesClass; \
sec.Owner = true; \
return true; \
} \
} else if (stricmp(sec.Parent->Name, #SectionName) == 0) {
#define BEGIN_SUBSECTION(SectionName, SubSectionName) \
if (stricmp(sec.Parent->Name, #SubSectionName) == 0 && stricmp(sec.Parent->Parent->Name, #SectionName) == 0) {
#define SUBSECTION2(SubSectionName, DirectivesClass) \
if (stricmp(sec.Name, #SubSectionName) == 0) { \
sec.Cookie = new DirectivesClass; \
sec.Owner = true; \
return true; \
}
#define FAKESECTION(SubSectionName) \
if (stricmp(sec.Name, #SubSectionName) == 0) { \
Y_ASSERT(sec.Cookie == 0); \
return true; \
}
#define END_SECTION() \
}
#define END_CONFIG() \
if (!sec.Parent->Parsed()) \
return true; \
ReportError(sec.Name, true, "section \'%s\' not allowed here and will be ignored", sec.Name); \
return true; \
}
#define SUBSECTION(SectionName) SUBSECTION2(SectionName, SectionName)
#define BEGIN_TOPSECTION(SectionName) BEGIN_TOPSECTION2(SectionName, SectionName)
#define BEGIN_SECTION(SectionClass) \
SectionClass::SectionClass() {
#define DEFINE_SECTION(SectionClass) \
class SectionClass: public TYandexConfig::Directives { \
public: \
SectionClass() {
#define DIRECTIVE(DirectiveName) declare(#DirectiveName);
#define END_DEFINE_SECTION \
} \
} \
;
#define END_DEFINE_SECTION_CHECK \
} \
bool CheckOnEnd(TYandexConfig& yc, TYandexConfig::Section& sec); \
} \
;
#define DEFINE_INDEFINITE_SECTION(SectionClass) \
class SectionClass: public TYandexConfig::Directives { \
public: \
SectionClass() { \
strict = false; \
} \
};
#define BEGIN_SECTION_CHECK(SectionClass) \
bool SectionClass::CheckOnEnd(TYandexConfig& yc, TYandexConfig::Section& sec) { \
(void)yc; \
(void)sec; \
SectionClass& type = *this; \
(void)type;
#define DIR_ABSENT(DirectiveName) (type[#DirectiveName] == 0)
#define DIR_ARG_ABSENT(DirectiveName) (type[#DirectiveName] == 0 || *(type[#DirectiveName]) == 0)
#define DIR_PRESENT(DirectiveName) (type[#DirectiveName] != 0)
#define DIR_ARG_PRESENT(DirectiveName) (type[#DirectiveName] != 0 && *(type[#DirectiveName]) != 0)
#define DIRECTIVE_MUSTBE(DirectiveName) \
if (DIR_ARG_ABSENT(DirectiveName)) { \
yc.ReportError(sec.Name, true, \
"Section \'%s\' must include directive \'%s\'. Section will be ignored", \
sec.Name, #DirectiveName); \
return false; \
}
#define DIRECTIVE_ATLEAST(DirectiveName1, DirectiveName2) \
if (DIR_ARG_ABSENT(DirectiveName1) && DIR_ARG_ABSENT(DirectiveName2)) { \
yc.ReportError(sec.Name, true, \
"Section \'%s\' must include directives \'%s\' or \'%s\'. Section will be ignored", \
sec.Name, #DirectiveName1, #DirectiveName2); \
return false; \
}
#define DIRECTIVE_BOTH(DirectiveName1, DirectiveName2) \
if (DIR_ARG_ABSENT(DirectiveName1) && DIR_ARG_PRESENT(DirectiveName2) || DIR_ARG_ABSENT(DirectiveName2) && DIR_ARG_PRESENT(DirectiveName1)) { \
yc.ReportError(sec.Name, true, \
"Section \'%s\' must include directives \'%s\' and \'%s\' simultaneously. Section will be ignored", \
sec.Name, #DirectiveName1, #DirectiveName2); \
return false; \
}
#define END_SECTION_CHECK() \
return true; \
}
class config_exception: public yexception {
public:
config_exception(const char* fp) {
filepoint = fp;
}
const char* where() const noexcept {
return filepoint;
}
private:
const char* filepoint;
};
#define DEFINE_UNSTRICT_SECTION(SectionClasse) \
class SectionClasse \
: public TYandexConfig::Directives { \
public: \
SectionClasse(const TYandexConfig::Directives& obj) \
: TYandexConfig::Directives(obj) { \
strict = false; \
} \
SectionClasse() { \
strict = false;
DEFINE_UNSTRICT_SECTION(AnyDirectives)
END_DEFINE_SECTION;
#define EMBEDDED_CONFIG(SectionName) \
if (sec.Parent != &sec) /* not root not placed at root */ { \
Section* parent = sec.Parent; \
while (*parent->Name != 0) { /* any child of SectionName */ \
if (stricmp(parent->Name, #SectionName) == 0) { \
sec.Cookie = new AnyDirectives(); \
sec.Owner = true; \
return true; \
} \
parent = parent->Parent; \
} \
}
#define ONE_EMBEDDED_CONFIG(SectionName) \
if (sec.Parent != &sec) /* not root not placed at root */ { \
Section* parent = sec.Parent; \
while (*parent->Name != 0) { /* any child of SectionName */ \
if (stricmp(parent->Name, #SectionName) == 0) { \
if (!parent->Child->Next) { \
sec.Cookie = new AnyDirectives(); \
sec.Owner = true; \
return true; \
} else { \
break; \
} \
} \
parent = parent->Parent; \
} \
}