mirror of
https://github.com/RGBCube/serenity
synced 2025-07-26 00:17:46 +00:00
Libraries: Add LibMarkdown
This commit is contained in:
parent
dd5541fefc
commit
2e80b2b32f
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/LibM"
|
||||||
build_targets="$build_targets ../Libraries/LibPCIDB"
|
build_targets="$build_targets ../Libraries/LibPCIDB"
|
||||||
build_targets="$build_targets ../Libraries/LibVT"
|
build_targets="$build_targets ../Libraries/LibVT"
|
||||||
|
build_targets="$build_targets ../Libraries/LibMarkdown"
|
||||||
|
|
||||||
build_targets="$build_targets ../Applications/About"
|
build_targets="$build_targets ../Applications/About"
|
||||||
build_targets="$build_targets ../Applications/Calculator"
|
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)
|
$(APPS) : % : %.o $(OBJS)
|
||||||
@echo "LD $@"
|
@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
|
%.o: %.cpp
|
||||||
@echo "CXX $<"
|
@echo "CXX $<"
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue