Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Мажирин Александр #237

Open
wants to merge 31 commits into
base: master
Choose a base branch
from

Conversation

Kexogg
Copy link

@Kexogg Kexogg commented Nov 24, 2024

No description provided.

{
public string Render(string md)
{
var tokenizer = new Tokenizer();

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Выглядит как что-то, что можно вынести в приватное поле и генерировать только при создании класса Md

{
var tokenizer = new Tokenizer();
var tokens = tokenizer.Tokenize(md);
var renderer = new HtmlRenderer();

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

аналогично предыдущему. Не требует специфичных данных метода и не похоже, чтобы сохранял во внутреннюю структуру что-то


<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net9.0</TargetFramework>

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

net9 только вышел из беты, поэтому активно пока не применяется. Лучше по началу использовать стабильные версии с долгосрочной поддержкой. Такие как 8, например

static void Main(string[] args)
{
var mdFile = File.ReadAllText("Markdown.md");
var md = new Markdown.Md();

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Точно нужно указывать полное имя класса с namespace?

{
var mdFile = File.ReadAllText("Markdown.md");
var md = new Markdown.Md();
Console.WriteLine(md.Render(mdFile));

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Для тестов вполне достаточно выводить в консоль. Но если делать какой-то cli, то лучше сохранять в файлик html рядом с входным

@@ -0,0 +1,6 @@
namespace Markdown.Token;

public interface IToken

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

токен больше похож на data class, нежели что-то с различными реализациями. Расскажи, что вкладываешь в него и как планируешь использовать при парсинге?

[Test]
public void Render_ShouldReturnSimpleText()
{
md.Render("Hello, world!").Should().Be("Hello, world!");

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Вынесем текст в константу метода? Чтобы точно не менялась )


namespace MarkdownTests;

public class MarkdownTests

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Класс всё-таки Md, хоть под ним и подразумевается Markdown

[Test]
public void Render_ShouldReturnImage()
{
md.Render("(Hello, image!)[img]").Should().Be("<img alt=\"Hello, image!\" src=\"img\"/>");

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Этот тег получается ты придумал сам? В md для картинок применяется ![alt text](http://url/to/img.png)


namespace MarkdownTests;

public class MarkdownTests

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Весь этот класс в целом тестирует Md как чёрный ящик, что довольно сложно станет при разработке. Есть предложение сократить здесь количество тестов, а сделать отдельные классы тестов для токенайзера и генератора. Таким образом сразу будет понятно, где ошибка: при парсинге или при рендеринге

<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

А в тестах всё ещё net9 прячется

[TestFixture]
public class MarkdownTokenizerTests
{
private MarkdownTokenizer _tokenizer;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Не стоит смешивать правила нейминга в рамках одного проекта. Ранее для приватных полей использовал уже просто с маленькой буквы. Нет единого правильного варианта - в каждом проекте решают по своему, как писать. Главное одинаково

}

[Test]
public void Render_ShouldRenderTagTokenWithAttributes()

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Больше похоже на рендер картинки

}
}

_tokenizer = null;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

В этом нет особого смысла, так как с ним больше ничего нигде не происходит, а в SetUp ты его назначаешь. В основном в TearDown производят сохранение ошибок, восстановление общих настроек. В OneTimeTearDown могут выполнять dispose каких-то переменных.

tokens.Should().HaveCount(1);
tokens[0].Should().BeOfType<TagToken>();
tokens[0].Children![0].TextContent.Should().Be("strong text");
((TagToken)tokens[0]).Tag.Should().BeOfType<StrongTag>();

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Получится сделать с использованием .Which?

return tree;
}

foreach (var tag in tagTokens)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Очень длинный и нечитаемый код. Нужно либо разбивать на методы с понятными названиями, либо переосмысливать происходящее

{
private readonly MarkdownRules markdownRules = new();

private readonly List<ITag> tags = new()

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Скорее supportedTags

for (var i = 0; i < content.Length; i++)
foreach (var tag in tags)
{
var tagToken = new TagToken(tag) { Position = i };

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Он создаёт новый токен на КАЖДЫЙ символ на КАЖДЫЙ поддерживаемый тэг?!

{
IsTag = (tagToken, content) =>
(content.ContainsSubstringOnIndex(tagToken.Tag.MdClosingTag, tagToken.Position)
&& content[..tagToken.Position].LastIndexOf('#') > content[..tagToken.Position].LastIndexOf('\n')) ||

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Для каждого вызова создаём новую строку content[..tagToken.Position] (дважды), за ещё одну линейную сложность .LastIndexOf('#') (дважды) не находим нужный символ. Потратили кучу памяти и много времени!


namespace MarkdownTests;

public class MdTests

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Вот такой тест к сожалению не пройдёт. Теряются переносы строк (учитываю, что они заменяются в твоей реализации на
, но так лучше не делать) , дублируется текст в strong и не совсем корректно обрабатывается курсив в жирном и пересечение выделений.

private const string Text = "#заголовок __с жирным текстом__\n" +
                                    "Просто текст, в котором _курсивные_ выделения\n" +
                                    "__Есть жирный текст__\n" +
                                    "__А вот жирный текст _с курсивом_ внутри _и ещё курсив_ в жирном__\n" +
                                    "_Вот __это_ не__ сработает\n" +
                                    "_И вот так __тоже__ нет_\n" +
                                    "Это - _ - просто подчёркивание\n" +
                                    "Так_ не работает_\n" +
                                    "И _ вот так _ тоже\n" +
                                    "В с_лов_е можно выделять, а в цифрах 1_23_ нет\n" +
                                    "Ещу можно сделать просто _курсив_\n";

        private const string ExpectedResult = "<h1>заголовок <strong>с жирным текстом</strong></h1><br />" +
                                              "Просто текст, в котором <em>курсивные</em> выделения<br />" +
                                              "<strong>Есть жирный текст</strong><br />" +
                                              "<strong>А вот жирный текст <em>с курсивом</em> внутри <em>и ещё курсив</em> в жирном</strong><br />" +
                                              "_Вот __это_ не__ сработает<br />" +
                                              "_И вот так __тоже__ нет_<br />" +
                                              "Это - _ - просто подчёркивание<br />" +
                                              "Так_ не работает_<br />" +
                                              "И _ вот так _ тоже<br />" +
                                              "В с<em>лов</em>е можно выделять, а в цифрах 1_23_ нет<br />" +
                                              "Ещу можно сделать просто <em>курсив</em><br />";
        
        [Test]
        public void Render_ShouldReturn_HtmlString()
        {
            var result = new Md().Render(Text);
            result.Should().Be(ExpectedResult);
        }

/// </summary>
/// <param name="content">Содержание</param>
/// <param name="tag">Название тега</param>
public class TagToken(ITag tag) : IToken

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/// Текстовый токен. Содержит чистый текст
/// </summary>
/// <param name="content">Текст</param>
public class TextToken(string content) : IToken

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.


private void TokenizePerformanceTest(string filePath, int maxMilliseconds)
{
var content = File.ReadAllText(filePath);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Это ведь шаблонный файл. Не хочешь его генерировать на лету в память, а не сохранять отдельно?

Comment on lines +35 to +37
var memoryBefore = GC.GetTotalMemory(true);
var result = tokenizer.Tokenize(content);
var memoryAfter = GC.GetTotalMemory(false);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Не очень честный метод измерения, так как за время теста GC может всё почистить и размер получится вообще отрицательным. Но хорошо, что знаешь про API для обращения к нему

public class Md : IMd
{
private readonly ITokenizer tokenizer = new MarkdownTokenizer();

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

А теперь токенайзер не хранит никакое состояние и может стать полноценным приватным полем )

[Test]
public void Tokenize_HeaderPerformanceTest()
{
TokenizePerformanceTest("TestCases/MarkdownTokenizer/" + TestContext.CurrentContext.Test.MethodName + ".md", 200);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

У меня этот тест не проходит периодически, занимает 230ms. Думаю, можно ему увеличить порог немного

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants