#pragma once #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include class TSectionDesc; class TYandexConfig: public TSimpleRefCount, TNonCopyable { public: class Directives; typedef TMap SectionAttrs; struct Section; typedef TMultiMap 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 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 CurSections; TVector AllSections; TVector Errors; TVector EndLines; TString ConfigPath; }; class TYandexConfig::Directives: public TMap> { 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 inline bool GetValue(TStringBuf key, T& value) const { TString tmp; if (GetValue(key, tmp)) { value = FromString(tmp); return true; } return false; } template inline T Value(TStringBuf key, T def = T()) const { GetValue(key, def); return def; } template bool TryFillArray(TStringBuf key, TVector& result, const TDelim delim = ',') const { auto it = find(key); if (it != end() && (*it).second != nullptr) { TVector 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& 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; \ } \ }