mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2024-11-22 23:50:19 +00:00
Libraries: Add LibMarkdown
This commit is contained in:
parent
dd5541fefc
commit
2e80b2b32f
Notes:
sideshowbarker
2024-07-19 11:57:43 +09:00
Author: https://github.com/bugaevc Commit: https://github.com/SerenityOS/serenity/commit/2e80b2b32f2 Pull-request: https://github.com/SerenityOS/serenity/pull/609 Reviewed-by: https://github.com/awesomekling ✅
17 changed files with 711 additions and 1 deletions
|
@ -38,6 +38,7 @@ build_targets="$build_targets ../Libraries/LibHTML"
|
|||
build_targets="$build_targets ../Libraries/LibM"
|
||||
build_targets="$build_targets ../Libraries/LibPCIDB"
|
||||
build_targets="$build_targets ../Libraries/LibVT"
|
||||
build_targets="$build_targets ../Libraries/LibMarkdown"
|
||||
|
||||
build_targets="$build_targets ../Applications/About"
|
||||
build_targets="$build_targets ../Applications/Calculator"
|
||||
|
|
12
Libraries/LibMarkdown/MDBlock.h
Normal file
12
Libraries/LibMarkdown/MDBlock.h
Normal file
|
@ -0,0 +1,12 @@
|
|||
#pragma once
|
||||
|
||||
#include <AK/String.h>
|
||||
|
||||
class MDBlock {
|
||||
public:
|
||||
virtual ~MDBlock() {}
|
||||
|
||||
virtual String render_to_html() const = 0;
|
||||
virtual String render_for_terminal() const = 0;
|
||||
virtual bool parse(Vector<StringView>::ConstIterator& lines) = 0;
|
||||
};
|
137
Libraries/LibMarkdown/MDCodeBlock.cpp
Normal file
137
Libraries/LibMarkdown/MDCodeBlock.cpp
Normal file
|
@ -0,0 +1,137 @@
|
|||
#include <AK/StringBuilder.h>
|
||||
#include <LibMarkdown/MDCodeBlock.h>
|
||||
|
||||
MDText::Style MDCodeBlock::style() const
|
||||
{
|
||||
if (m_style_spec.spans().is_empty())
|
||||
return {};
|
||||
return m_style_spec.spans()[0].style;
|
||||
}
|
||||
|
||||
String MDCodeBlock::style_language() const
|
||||
{
|
||||
if (m_style_spec.spans().is_empty())
|
||||
return {};
|
||||
return m_style_spec.spans()[0].text;
|
||||
}
|
||||
|
||||
String MDCodeBlock::render_to_html() const
|
||||
{
|
||||
StringBuilder builder;
|
||||
|
||||
String style_language = this->style_language();
|
||||
MDText::Style style = this->style();
|
||||
|
||||
if (style.strong)
|
||||
builder.append("<b>");
|
||||
if (style.emph)
|
||||
builder.append("<i>");
|
||||
|
||||
builder.append("<pre>");
|
||||
|
||||
if (style_language.is_null())
|
||||
builder.append("<code>");
|
||||
else
|
||||
builder.appendf("<code class=\"%s\">", style_language.characters());
|
||||
|
||||
// TODO: This should also be done in other places.
|
||||
for (int i = 0; i < m_code.length(); i++)
|
||||
if (m_code[i] == '<')
|
||||
builder.append("<");
|
||||
else if (m_code[i] == '>')
|
||||
builder.append(">");
|
||||
else if (m_code[i] == '&')
|
||||
builder.append("&");
|
||||
else
|
||||
builder.append(m_code[i]);
|
||||
|
||||
builder.append("</code></pre>");
|
||||
|
||||
if (style.emph)
|
||||
builder.append("</i>");
|
||||
if (style.strong)
|
||||
builder.append("</b>");
|
||||
|
||||
builder.append('\n');
|
||||
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
String MDCodeBlock::render_for_terminal() const
|
||||
{
|
||||
StringBuilder builder;
|
||||
|
||||
MDText::Style style = this->style();
|
||||
bool needs_styling = style.strong || style.emph;
|
||||
if (needs_styling) {
|
||||
builder.append("\033[");
|
||||
bool first = true;
|
||||
if (style.strong) {
|
||||
builder.append('1');
|
||||
first = false;
|
||||
}
|
||||
if (style.emph) {
|
||||
if (!first)
|
||||
builder.append(';');
|
||||
builder.append('4');
|
||||
}
|
||||
builder.append('m');
|
||||
}
|
||||
|
||||
builder.append(m_code);
|
||||
|
||||
if (needs_styling)
|
||||
builder.append("\033[0m");
|
||||
|
||||
builder.append("\n\n");
|
||||
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
bool MDCodeBlock::parse(Vector<StringView>::ConstIterator& lines)
|
||||
{
|
||||
if (lines.is_end())
|
||||
return false;
|
||||
|
||||
constexpr auto tick_tick_tick = "```";
|
||||
|
||||
StringView line = *lines;
|
||||
if (!line.starts_with(tick_tick_tick))
|
||||
return false;
|
||||
|
||||
// Our Markdown extension: we allow
|
||||
// specifying a style and a language
|
||||
// for a code block, like so:
|
||||
//
|
||||
// ```**sh**
|
||||
// $ echo hello friends!
|
||||
// ````
|
||||
//
|
||||
// The code block will be made bold,
|
||||
// and if possible syntax-highlighted
|
||||
// as appropriate for a shell script.
|
||||
StringView style_spec = line.substring_view(3, line.length() - 3);
|
||||
bool success = m_style_spec.parse(style_spec);
|
||||
ASSERT(success);
|
||||
|
||||
++lines;
|
||||
|
||||
bool first = true;
|
||||
StringBuilder builder;
|
||||
|
||||
while (true) {
|
||||
if (lines.is_end())
|
||||
break;
|
||||
line = *lines;
|
||||
++lines;
|
||||
if (line == tick_tick_tick)
|
||||
break;
|
||||
if (!first)
|
||||
builder.append('\n');
|
||||
builder.append(line);
|
||||
first = false;
|
||||
}
|
||||
|
||||
m_code = builder.build();
|
||||
return true;
|
||||
}
|
20
Libraries/LibMarkdown/MDCodeBlock.h
Normal file
20
Libraries/LibMarkdown/MDCodeBlock.h
Normal file
|
@ -0,0 +1,20 @@
|
|||
#pragma once
|
||||
|
||||
#include <LibMarkdown/MDBlock.h>
|
||||
#include <LibMarkdown/MDText.h>
|
||||
|
||||
class MDCodeBlock final : public MDBlock {
|
||||
public:
|
||||
virtual ~MDCodeBlock() override {}
|
||||
|
||||
virtual String render_to_html() const override;
|
||||
virtual String render_for_terminal() const override;
|
||||
virtual bool parse(Vector<StringView>::ConstIterator& lines) override;
|
||||
|
||||
private:
|
||||
String style_language() const;
|
||||
MDText::Style style() const;
|
||||
|
||||
String m_code;
|
||||
MDText m_style_spec;
|
||||
};
|
62
Libraries/LibMarkdown/MDDocument.cpp
Normal file
62
Libraries/LibMarkdown/MDDocument.cpp
Normal file
|
@ -0,0 +1,62 @@
|
|||
#include <AK/StringBuilder.h>
|
||||
#include <LibMarkdown/MDCodeBlock.h>
|
||||
#include <LibMarkdown/MDDocument.h>
|
||||
#include <LibMarkdown/MDHeading.h>
|
||||
#include <LibMarkdown/MDList.h>
|
||||
#include <LibMarkdown/MDParagraph.h>
|
||||
|
||||
String MDDocument::render_to_html() const
|
||||
{
|
||||
StringBuilder builder;
|
||||
|
||||
for (auto& block : m_blocks) {
|
||||
auto s = block.render_to_html();
|
||||
builder.append(s);
|
||||
}
|
||||
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
String MDDocument::render_for_terminal() const
|
||||
{
|
||||
StringBuilder builder;
|
||||
|
||||
for (auto& block : m_blocks) {
|
||||
auto s = block.render_for_terminal();
|
||||
builder.append(s);
|
||||
}
|
||||
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
template<typename Block>
|
||||
static bool helper(Vector<StringView>::ConstIterator& lines, NonnullOwnPtrVector<MDBlock>& blocks)
|
||||
{
|
||||
NonnullOwnPtr<Block> block = make<Block>();
|
||||
bool success = block->parse(lines);
|
||||
if (!success)
|
||||
return false;
|
||||
blocks.append(move(block));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MDDocument::parse(const StringView& str)
|
||||
{
|
||||
const Vector<StringView> lines_vec = str.split_view('\n', true);
|
||||
auto lines = lines_vec.begin();
|
||||
|
||||
while (true) {
|
||||
if (lines.is_end())
|
||||
return true;
|
||||
|
||||
if ((*lines).is_empty()) {
|
||||
++lines;
|
||||
continue;
|
||||
}
|
||||
|
||||
bool any = helper<MDList>(lines, m_blocks) || helper<MDParagraph>(lines, m_blocks) || helper<MDCodeBlock>(lines, m_blocks) || helper<MDHeading>(lines, m_blocks);
|
||||
|
||||
if (!any)
|
||||
return false;
|
||||
}
|
||||
}
|
16
Libraries/LibMarkdown/MDDocument.h
Normal file
16
Libraries/LibMarkdown/MDDocument.h
Normal file
|
@ -0,0 +1,16 @@
|
|||
#pragma once
|
||||
|
||||
#include <AK/NonnullOwnPtrVector.h>
|
||||
#include <AK/String.h>
|
||||
#include <LibMarkdown/MDBlock.h>
|
||||
|
||||
class MDDocument final {
|
||||
public:
|
||||
String render_to_html() const;
|
||||
String render_for_terminal() const;
|
||||
|
||||
bool parse(const StringView&);
|
||||
|
||||
private:
|
||||
NonnullOwnPtrVector<MDBlock> m_blocks;
|
||||
};
|
54
Libraries/LibMarkdown/MDHeading.cpp
Normal file
54
Libraries/LibMarkdown/MDHeading.cpp
Normal file
|
@ -0,0 +1,54 @@
|
|||
#include <AK/StringBuilder.h>
|
||||
#include <LibMarkdown/MDHeading.h>
|
||||
|
||||
String MDHeading::render_to_html() const
|
||||
{
|
||||
StringBuilder builder;
|
||||
builder.appendf("<h%d>", m_level);
|
||||
builder.append(m_text.render_to_html());
|
||||
builder.appendf("</h%d>\n", m_level);
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
String MDHeading::render_for_terminal() const
|
||||
{
|
||||
StringBuilder builder;
|
||||
|
||||
switch (m_level) {
|
||||
case 1:
|
||||
case 2:
|
||||
builder.append("\n\033[1m");
|
||||
builder.append(m_text.render_for_terminal().to_uppercase());
|
||||
builder.append("\033[0m\n");
|
||||
break;
|
||||
default:
|
||||
builder.append("\n\033[1m");
|
||||
builder.append(m_text.render_for_terminal());
|
||||
builder.append("\033[0m\n");
|
||||
break;
|
||||
}
|
||||
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
bool MDHeading::parse(Vector<StringView>::ConstIterator& lines)
|
||||
{
|
||||
if (lines.is_end())
|
||||
return false;
|
||||
|
||||
const StringView& line = *lines;
|
||||
|
||||
for (m_level = 0; m_level < line.length(); m_level++)
|
||||
if (line[m_level] != '#')
|
||||
break;
|
||||
|
||||
if (m_level >= line.length() || line[m_level] != ' ')
|
||||
return false;
|
||||
|
||||
StringView title_view = line.substring_view(m_level + 1, line.length() - m_level - 1);
|
||||
bool success = m_text.parse(title_view);
|
||||
ASSERT(success);
|
||||
|
||||
++lines;
|
||||
return true;
|
||||
}
|
19
Libraries/LibMarkdown/MDHeading.h
Normal file
19
Libraries/LibMarkdown/MDHeading.h
Normal file
|
@ -0,0 +1,19 @@
|
|||
#pragma once
|
||||
|
||||
#include <AK/StringView.h>
|
||||
#include <AK/Vector.h>
|
||||
#include <LibMarkdown/MDBlock.h>
|
||||
#include <LibMarkdown/MDText.h>
|
||||
|
||||
class MDHeading final : public MDBlock {
|
||||
public:
|
||||
virtual ~MDHeading() override {}
|
||||
|
||||
virtual String render_to_html() const override;
|
||||
virtual String render_for_terminal() const override;
|
||||
virtual bool parse(Vector<StringView>::ConstIterator& lines) override;
|
||||
|
||||
private:
|
||||
MDText m_text;
|
||||
int m_level { -1 };
|
||||
};
|
89
Libraries/LibMarkdown/MDList.cpp
Normal file
89
Libraries/LibMarkdown/MDList.cpp
Normal file
|
@ -0,0 +1,89 @@
|
|||
#include <AK/StringBuilder.h>
|
||||
#include <LibMarkdown/MDList.h>
|
||||
|
||||
String MDList::render_to_html() const
|
||||
{
|
||||
StringBuilder builder;
|
||||
|
||||
const char* tag = m_is_ordered ? "ol" : "ul";
|
||||
builder.appendf("<%s>", tag);
|
||||
|
||||
for (auto& item : m_items) {
|
||||
builder.append("<li>");
|
||||
builder.append(item.render_to_html());
|
||||
builder.append("</li>\n");
|
||||
}
|
||||
|
||||
builder.appendf("</%s>\n", tag);
|
||||
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
String MDList::render_for_terminal() const
|
||||
{
|
||||
StringBuilder builder;
|
||||
|
||||
int i = 0;
|
||||
for (auto& item : m_items) {
|
||||
builder.append(" ");
|
||||
if (m_is_ordered)
|
||||
builder.appendf("%d. ", ++i);
|
||||
else
|
||||
builder.append("* ");
|
||||
builder.append(item.render_for_terminal());
|
||||
builder.append("\n");
|
||||
}
|
||||
builder.append("\n");
|
||||
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
bool MDList::parse(Vector<StringView>::ConstIterator& lines)
|
||||
{
|
||||
bool first = true;
|
||||
while (true) {
|
||||
if (lines.is_end())
|
||||
break;
|
||||
const StringView& line = *lines;
|
||||
if (line.is_empty())
|
||||
break;
|
||||
|
||||
bool appears_unordered = false;
|
||||
int offset = 0;
|
||||
if (line.length() > 2)
|
||||
if (line[1] == ' ' && (line[0] == '*' || line[0] == '-')) {
|
||||
appears_unordered = true;
|
||||
offset = 2;
|
||||
}
|
||||
|
||||
bool appears_ordered = false;
|
||||
for (int i = 0; i < 10 && i < line.length(); i++) {
|
||||
char ch = line[i];
|
||||
if ('0' <= ch && ch <= '9')
|
||||
continue;
|
||||
if (ch == '.' || ch == ')')
|
||||
if (i + 1 < line.length() && line[i + 1] == ' ') {
|
||||
appears_ordered = true;
|
||||
offset = i + 1;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
ASSERT(!(appears_unordered && appears_ordered));
|
||||
if (!appears_unordered && !appears_ordered)
|
||||
return false;
|
||||
if (first)
|
||||
m_is_ordered = appears_ordered;
|
||||
else if (m_is_ordered != appears_ordered)
|
||||
return false;
|
||||
|
||||
first = false;
|
||||
MDText text;
|
||||
bool success = text.parse(line.substring_view(offset, line.length() - offset));
|
||||
ASSERT(success);
|
||||
m_items.append(move(text));
|
||||
++lines;
|
||||
}
|
||||
|
||||
return !first;
|
||||
}
|
19
Libraries/LibMarkdown/MDList.h
Normal file
19
Libraries/LibMarkdown/MDList.h
Normal file
|
@ -0,0 +1,19 @@
|
|||
#pragma once
|
||||
|
||||
#include <AK/Vector.h>
|
||||
#include <LibMarkdown/MDBlock.h>
|
||||
#include <LibMarkdown/MDText.h>
|
||||
|
||||
class MDList final : public MDBlock {
|
||||
public:
|
||||
virtual ~MDList() override {}
|
||||
|
||||
virtual String render_to_html() const override;
|
||||
virtual String render_for_terminal() const override;
|
||||
virtual bool parse(Vector<StringView>::ConstIterator& lines) override;
|
||||
|
||||
private:
|
||||
// TODO: List items should be considered blocks of their own kind.
|
||||
Vector<MDText> m_items;
|
||||
bool m_is_ordered { false };
|
||||
};
|
66
Libraries/LibMarkdown/MDParagraph.cpp
Normal file
66
Libraries/LibMarkdown/MDParagraph.cpp
Normal file
|
@ -0,0 +1,66 @@
|
|||
#include <AK/StringBuilder.h>
|
||||
#include <LibMarkdown/MDParagraph.h>
|
||||
|
||||
String MDParagraph::render_to_html() const
|
||||
{
|
||||
StringBuilder builder;
|
||||
builder.appendf("<p>");
|
||||
builder.append(m_text.render_to_html());
|
||||
builder.appendf("</p>\n");
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
String MDParagraph::render_for_terminal() const
|
||||
{
|
||||
StringBuilder builder;
|
||||
builder.append(m_text.render_for_terminal());
|
||||
builder.appendf("\n\n");
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
bool MDParagraph::parse(Vector<StringView>::ConstIterator& lines)
|
||||
{
|
||||
if (lines.is_end())
|
||||
return false;
|
||||
|
||||
bool first = true;
|
||||
StringBuilder builder;
|
||||
|
||||
while (true) {
|
||||
if (lines.is_end())
|
||||
break;
|
||||
StringView line = *lines;
|
||||
if (line.is_empty())
|
||||
break;
|
||||
char ch = line[0];
|
||||
// See if it looks like a blockquote
|
||||
// or like an indented block.
|
||||
if (ch == '>' || ch == ' ')
|
||||
break;
|
||||
if (line.length() > 1) {
|
||||
// See if it looks like a heading.
|
||||
if (ch == '#' && (line[1] == '#' || line[1] == ' '))
|
||||
break;
|
||||
// See if it looks like a code block.
|
||||
if (ch == '`' && line[1] == '`')
|
||||
break;
|
||||
// See if it looks like a list.
|
||||
if (ch == '*' || ch == '-')
|
||||
if (line[1] == ' ')
|
||||
break;
|
||||
}
|
||||
|
||||
if (!first)
|
||||
builder.append(' ');
|
||||
builder.append(line);
|
||||
first = false;
|
||||
++lines;
|
||||
}
|
||||
|
||||
if (first)
|
||||
return false;
|
||||
|
||||
bool success = m_text.parse(builder.build());
|
||||
ASSERT(success);
|
||||
return true;
|
||||
}
|
16
Libraries/LibMarkdown/MDParagraph.h
Normal file
16
Libraries/LibMarkdown/MDParagraph.h
Normal file
|
@ -0,0 +1,16 @@
|
|||
#pragma once
|
||||
|
||||
#include <LibMarkdown/MDBlock.h>
|
||||
#include <LibMarkdown/MDText.h>
|
||||
|
||||
class MDParagraph final : public MDBlock {
|
||||
public:
|
||||
virtual ~MDParagraph() override {}
|
||||
|
||||
virtual String render_to_html() const override;
|
||||
virtual String render_for_terminal() const override;
|
||||
virtual bool parse(Vector<StringView>::ConstIterator& lines) override;
|
||||
|
||||
private:
|
||||
MDText m_text;
|
||||
};
|
138
Libraries/LibMarkdown/MDText.cpp
Normal file
138
Libraries/LibMarkdown/MDText.cpp
Normal file
|
@ -0,0 +1,138 @@
|
|||
#include <AK/StringBuilder.h>
|
||||
#include <LibMarkdown/MDText.h>
|
||||
|
||||
String MDText::render_to_html() const
|
||||
{
|
||||
StringBuilder builder;
|
||||
|
||||
Vector<String> open_tags;
|
||||
Style current_style;
|
||||
|
||||
for (auto& span : m_spans) {
|
||||
struct TagAndFlag {
|
||||
String tag;
|
||||
bool Style::*flag;
|
||||
};
|
||||
TagAndFlag tags_and_flags[] = {
|
||||
{ "i", &Style::emph },
|
||||
{ "b", &Style::strong },
|
||||
{ "code", &Style::code }
|
||||
};
|
||||
auto it = open_tags.find([&](const String& open_tag) {
|
||||
for (auto& tag_and_flag : tags_and_flags) {
|
||||
if (open_tag == tag_and_flag.tag && !(span.style.*tag_and_flag.flag))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
if (!it.is_end()) {
|
||||
// We found an open tag that should
|
||||
// not be open for the new span. Close
|
||||
// it and all the open tags that follow
|
||||
// it.
|
||||
for (auto it2 = --open_tags.end(); it2 >= it; --it2) {
|
||||
const String& tag = *it2;
|
||||
builder.appendf("</%s>", tag.characters());
|
||||
for (auto& tag_and_flag : tags_and_flags)
|
||||
if (tag == tag_and_flag.tag)
|
||||
current_style.*tag_and_flag.flag = false;
|
||||
}
|
||||
open_tags.shrink(it.index());
|
||||
}
|
||||
for (auto& tag_and_flag : tags_and_flags) {
|
||||
if (current_style.*tag_and_flag.flag != span.style.*tag_and_flag.flag) {
|
||||
open_tags.append(tag_and_flag.tag);
|
||||
builder.appendf("<%s>", tag_and_flag.tag.characters());
|
||||
}
|
||||
}
|
||||
|
||||
current_style = span.style;
|
||||
builder.append(span.text);
|
||||
}
|
||||
|
||||
for (auto it = --open_tags.end(); it >= open_tags.begin(); --it) {
|
||||
const String& tag = *it;
|
||||
builder.appendf("</%s>", tag.characters());
|
||||
}
|
||||
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
String MDText::render_for_terminal() const
|
||||
{
|
||||
StringBuilder builder;
|
||||
|
||||
for (auto& span : m_spans) {
|
||||
bool needs_styling = span.style.strong || span.style.emph || span.style.code;
|
||||
if (needs_styling) {
|
||||
builder.append("\033[");
|
||||
bool first = true;
|
||||
if (span.style.strong || span.style.code) {
|
||||
builder.append('1');
|
||||
first = false;
|
||||
}
|
||||
if (span.style.emph) {
|
||||
if (!first)
|
||||
builder.append(';');
|
||||
builder.append('4');
|
||||
}
|
||||
builder.append('m');
|
||||
}
|
||||
|
||||
builder.append(span.text.characters());
|
||||
|
||||
if (needs_styling)
|
||||
builder.append("\033[0m");
|
||||
}
|
||||
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
bool MDText::parse(const StringView& str)
|
||||
{
|
||||
Style current_style;
|
||||
int current_span_start = 0;
|
||||
|
||||
for (int offset = 0; offset < str.length(); offset++) {
|
||||
char ch = str[offset];
|
||||
|
||||
bool is_special_character = false;
|
||||
is_special_character |= ch == '`';
|
||||
if (!current_style.code)
|
||||
is_special_character |= ch == '*' || ch == '_';
|
||||
if (!is_special_character)
|
||||
continue;
|
||||
|
||||
if (current_span_start != offset) {
|
||||
Span span {
|
||||
str.substring_view(current_span_start, offset - current_span_start),
|
||||
current_style
|
||||
};
|
||||
m_spans.append(move(span));
|
||||
}
|
||||
|
||||
if (ch == '`') {
|
||||
current_style.code = !current_style.code;
|
||||
} else {
|
||||
if (offset + 1 < str.length() && str[offset + 1] == ch) {
|
||||
offset++;
|
||||
current_style.strong = !current_style.strong;
|
||||
} else {
|
||||
current_style.emph = !current_style.emph;
|
||||
}
|
||||
}
|
||||
|
||||
current_span_start = offset + 1;
|
||||
}
|
||||
|
||||
if (current_span_start < str.length()) {
|
||||
Span span {
|
||||
str.substring_view(current_span_start, str.length() - current_span_start),
|
||||
current_style
|
||||
};
|
||||
m_spans.append(move(span));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
28
Libraries/LibMarkdown/MDText.h
Normal file
28
Libraries/LibMarkdown/MDText.h
Normal file
|
@ -0,0 +1,28 @@
|
|||
#pragma once
|
||||
|
||||
#include <AK/String.h>
|
||||
#include <AK/Vector.h>
|
||||
|
||||
class MDText final {
|
||||
public:
|
||||
struct Style {
|
||||
bool emph { false };
|
||||
bool strong { false };
|
||||
bool code { false };
|
||||
};
|
||||
|
||||
struct Span {
|
||||
String text;
|
||||
Style style;
|
||||
};
|
||||
|
||||
const Vector<Span>& spans() const { return m_spans; }
|
||||
|
||||
String render_to_html() const;
|
||||
String render_for_terminal() const;
|
||||
|
||||
bool parse(const StringView&);
|
||||
|
||||
private:
|
||||
Vector<Span> m_spans;
|
||||
};
|
25
Libraries/LibMarkdown/Makefile
Normal file
25
Libraries/LibMarkdown/Makefile
Normal file
|
@ -0,0 +1,25 @@
|
|||
include ../../Makefile.common
|
||||
|
||||
OBJS = \
|
||||
MDDocument.o \
|
||||
MDParagraph.o \
|
||||
MDHeading.o \
|
||||
MDCodeBlock.o \
|
||||
MDList.o \
|
||||
MDText.o
|
||||
|
||||
LIBRARY = libmarkdown.a
|
||||
DEFINES += -DUSERLAND
|
||||
|
||||
all: $(LIBRARY)
|
||||
|
||||
$(LIBRARY): $(OBJS)
|
||||
@echo "LIB $@"; $(AR) rcs $@ $(OBJS) $(LIBS)
|
||||
|
||||
.cpp.o:
|
||||
@echo "CXX $<"; $(CXX) $(CXXFLAGS) -o $@ -c $<
|
||||
|
||||
-include $(OBJS:%.o=%.d)
|
||||
|
||||
clean:
|
||||
@echo "CLEAN"; rm -f $(LIBRARY) $(OBJS) *.d
|
8
Libraries/LibMarkdown/install.sh
Executable file
8
Libraries/LibMarkdown/install.sh
Executable file
|
@ -0,0 +1,8 @@
|
|||
#!/bin/sh
|
||||
|
||||
set -e
|
||||
SERENITY_ROOT=../../
|
||||
|
||||
mkdir -p $SERENITY_ROOT/Root/usr/include/LibMarkdown/
|
||||
cp *.h $SERENITY_ROOT/Root/usr/include/LibMarkdown/
|
||||
cp libmarkdown.a $SERENITY_ROOT/Root/usr/lib/
|
|
@ -19,7 +19,7 @@ clean:
|
|||
|
||||
$(APPS) : % : %.o $(OBJS)
|
||||
@echo "LD $@"
|
||||
@$(LD) -o $@ $(LDFLAGS) $< -lc -lgui -ldraw -laudio -lipc -lthread -lcore -lpcidb
|
||||
@$(LD) -o $@ $(LDFLAGS) $< -lc -lgui -ldraw -laudio -lipc -lthread -lcore -lpcidb -lmarkdown
|
||||
|
||||
%.o: %.cpp
|
||||
@echo "CXX $<"
|
||||
|
|
Loading…
Reference in a new issue