aboutsummaryrefslogtreecommitdiffstats
path: root/yql/essentials/utils/docs/markdown.cpp
blob: eb584c6b47ad44ff68e5dc81a47de4e33acd9a05 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
#include "markdown.h"

#include <yql/essentials/utils/yql_panic.h>

#include <contrib/libs/re2/re2/re2.h>

#include <util/generic/yexception.h>

namespace NSQLComplete {

    class TMarkdownParser {
    public:
        void Parse(IInputStream& markdown, TMarkdownCallback&& onSection) {
            for (TString line; markdown.ReadLine(line) != 0;) {
                if (IsSkipping_) {
                    if (IsSectionHeader(line)) {
                        ResetSection(std::move(line));
                        IsSkipping_ = false;
                    } else {
                        // Skip
                    }
                } else {
                    if (IsSectionHeader(line)) {
                        onSection(std::move(Section_));
                        ResetSection(std::move(line));
                    } else {
                        line.append('\n');
                        Section_.Body.append(std::move(line));
                    }
                }
            }

            if (!IsSkipping_) {
                onSection(std::move(Section_));
            }
        }

    private:
        void ResetSection(TString&& line) {
            Section_ = TMarkdownSection();

            TString content;
            std::optional<TString> dummy;
            std::optional<TString> anchor;
            YQL_ENSURE(
                RE2::FullMatch(line, SectionHeaderRegex_, &content, &dummy, &anchor),
                "line '" << line << "' does not match regex '"
                         << SectionHeaderRegex_.pattern() << "'");

            Section_.Header.Content = std::move(content);
            if (anchor) {
                Section_.Header.Anchor = std::move(*anchor);
            }
        }

        bool IsSectionHeader(TStringBuf line) const {
            return HeaderDepth(line) == 2;
        }

        size_t HeaderDepth(TStringBuf line) const {
            size_t pos = line.find_first_not_of('#');
            return pos != TStringBuf::npos ? pos : 0;
        }

        RE2 SectionHeaderRegex_{R"re(## ([^#]+)(\s+{(#[a-z0-9\-_]+)})?)re"};
        bool IsSkipping_ = true;
        TMarkdownSection Section_;
    };

    void ParseMarkdown(IInputStream& markdown, TMarkdownCallback&& onSection) {
        TMarkdownParser parser;
        parser.Parse(markdown, std::forward<TMarkdownCallback>(onSection));
    }

} // namespace NSQLComplete