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

Бабинцев Григорий #244

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions cs/Markdown.Tests/Markdown.Tests.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="NUnit" Version="3.12.0" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\Markdown\Markdown.csproj" />
</ItemGroup>

</Project>
16 changes: 16 additions & 0 deletions cs/Markdown.Tests/Markdown.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using NUnit.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Markdown.Builder;
using Markdown.Checker;
using Markdown.Config;
using Markdown.Parser;
using FluentAssertions;

namespace Markdown.Tests

Choose a reason for hiding this comment

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

Этот файл не подключен к проекту. Скорее всего ты нажал удалить в райдере. Но проекты (csproj) лишь исключаются из решения (sln), а файлы физически остаются. Но это была правильная идея для расположения тестов. Тогда здесь были бы файлы MdTetsts, HtmlBuilderTests, MarkdownParserTests

{

}
2 changes: 2 additions & 0 deletions cs/Markdown.Tests/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// See https://aka.ms/new-console-template for more information
Console.WriteLine("Hello, World!");

Choose a reason for hiding this comment

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

У тестов не делают Program файл и в целом Main метод, так как точка входа генерируется при сборке фреймворком для тестов

80 changes: 80 additions & 0 deletions cs/Markdown/Builder/HtmlBuilder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
using Markdown.Domain.Tags;
using Markdown.Domain;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Markdown.Extensions;

namespace Markdown.Builder
{
public class HtmlBuilder : IHtmlBuilder
{
private readonly Dictionary<Tag, string> _htmlTagsMarkupDict;

private int shift;

public HtmlBuilder(Dictionary<Tag, string> htmlTagsMarkupDict)

Choose a reason for hiding this comment

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

Предлагаю его спрятать внутрь реализации. В конструктор выносятся внешние зависимости (Singleton) и реализации интерфейсов. В данном случае словарь мапинга больше похож на внутрянку билдера. Но, можно передавать файл настроек и из него подтягивать, если необходимо

{
_htmlTagsMarkupDict = htmlTagsMarkupDict;
}

public string BuildHtmlFromMarkdown(string markdownText, List<Token> tokens)
{
var htmlResultText = new StringBuilder(markdownText);
var htmlTags = ConvertToHtmlTags(tokens);
shift = 0;

Choose a reason for hiding this comment

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

В задаче это не требовалось, но такой подход не потокобезопасен. Вся обработка текста происходит в методах, а сдвиг хранится один на весь класс. Лучше его хранить локально и передавать как аргумент


foreach (var tag in htmlTags)
{
ReplaceMarkdownWithHtml(htmlResultText, tag);
shift = htmlResultText.Length - markdownText.Length;
}

return htmlResultText.ToString();
}

private void ReplaceMarkdownWithHtml(StringBuilder htmlResultText, HtmlTag tag)
{
var mdTagLength = GetMdTagLength(tag);

htmlResultText.Remove(tag.Index + shift, mdTagLength);

htmlResultText.Insert(tag.Index + shift, tag.GetMarkup());
}

private int GetMdTagLength(HtmlTag tag)

Choose a reason for hiding this comment

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

Ой какая страшная логика. Сюда будет сложно добавить обработку нового типа. Может добавим в тэг информацию о длине? Тогда сможем сделать маркдаун независимую реализацию генератора.

{
if (tag.Tag == Tag.Bold)
{
shift--;
return 2;
}

if (tag.IsClosing && (tag.Tag == Tag.Header || tag.Tag == Tag.EscapedSymbol))
{
shift++;
return 0;
}

return 1;
}

private List<HtmlTag> ConvertToHtmlTags(List<Token> tokens)
{
var htmlTags = new List<HtmlTag>();

foreach (var token in tokens)
{
var htmlMarkup = _htmlTagsMarkupDict[token.TagType];
var tag = token.TagType;

Choose a reason for hiding this comment

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

Всё-таки это tagType ну или textType


htmlTags.Add(new HtmlTag(tag, token.StartIndex, false, htmlMarkup));

Choose a reason for hiding this comment

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

У нас уже имеется htmlMarkup, точно нужно сохранять tag?

htmlTags.Add(new HtmlTag(tag, token.EndIndex, true, htmlMarkup));
}

return htmlTags.OrderBy(tag => tag.Index).ToList();
}
}
}
85 changes: 85 additions & 0 deletions cs/Markdown/Checker/TagChecker.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
using Markdown.Domain.Tags;
using Markdown.Domain;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Markdown.Checker
{
public class TagChecker : ITagChecker

Choose a reason for hiding this comment

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

Скорее MdTagResolver

{
private int currentIndex;

Choose a reason for hiding this comment

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

Точно хотим его выносить в общедоступные поля и терять потокобезопасность?


public const char HASH_SYMBOL = '#';
public const char UNDERSCORE_SYMBOL = '_';
public const char SLASH_SYMBOL = '\\';
Comment on lines +15 to +17

Choose a reason for hiding this comment

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

Можно сделать private, так как снаружи нет смысла их использовать


public Tuple<Tag, int> GetTagType(string line, int index)

Choose a reason for hiding this comment

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

В явном виде создавать Tupl не нужно. Можно просто указывать в скобках типы, а под капотом оно само скомпилируется. Зато получим возможность указать имена возвращаемым переменным. А то совсем не понятно, что значит этот int

(Tag tagType, int endIndex) GetTagType(string line, int index);

Choose a reason for hiding this comment

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

Может быть DefineTagType?

{
currentIndex = index;
var tag = Tag.None;

switch (line[index])
{
case UNDERSCORE_SYMBOL:
tag = GetTagForUnderscore(line);
break;
case SLASH_SYMBOL:
tag = GetTagForSlash(line);
break;
case HASH_SYMBOL:
if (index == 0)
tag = Tag.Header;
break;
}

return Tuple.Create(tag, currentIndex);
}

private Tag GetTagForSlash(string line)
{
if (currentIndex < line.Length - 1
&& (line[currentIndex + 1] == SLASH_SYMBOL
|| line[currentIndex + 1] == UNDERSCORE_SYMBOL
|| line[currentIndex + 1] == HASH_SYMBOL))
{
return Tag.EscapedSymbol;
}

return Tag.None;
}

private Tag GetTagForUnderscore(string line)
{
if (currentIndex < line.Length - 1 && line[currentIndex + 1] == UNDERSCORE_SYMBOL)
return GetTagWithMultipleUnderscores(line);

return Tag.Italic;
}

private Tag GetTagWithMultipleUnderscores(string line)
{
if (currentIndex < line.Length - 2 && line[currentIndex + 2] == UNDERSCORE_SYMBOL)
{
currentIndex = FindEndOfInvalidTag(line);
return Tag.None;
}

currentIndex++;

return Tag.Bold;
}

private int FindEndOfInvalidTag(string line)
{
var endIndex = currentIndex;

while (endIndex < line.Length && line[endIndex] == UNDERSCORE_SYMBOL)
endIndex++;

return endIndex;
}
}
}
30 changes: 30 additions & 0 deletions cs/Markdown/Config/MarkdownConfig.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
using Markdown.Domain.Tags;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Markdown.Config
{
public class MarkdownConfig
{
public static Dictionary<Tag, string> HtmlTags => new Dictionary<Tag, string>()

Choose a reason for hiding this comment

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

Важно ещё помнить, что стрелочный оператор генерирует новый объект на каждый вызов. Здесь это уместно, но чтобы предотвратить создание можно использовать {get;} =

{
{ Tag.Bold, "strong" }, { Tag.Italic, "em" },
{ Tag.Header, "h1" }, { Tag.EscapedSymbol, "" }
Comment on lines +14 to +15

Choose a reason for hiding this comment

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

Либо все в строку, либо каждый элемент в новой. Смешивать вот так не стоит. В данном случае лучше каждую запись делать в новой строке

public static Dictionary<Tag, string> HtmlTags => new()
        {
            { Tag.Bold, "strong" },
            { Tag.Italic, "em" },
            { Tag.Header, "h1" },
            { Tag.EscapedSymbol, "" }
        };

И тип у словаря при создании можно явно не определять, компилятор сам его вычислит по типу свойства Dictionary<Tag, string> HtmlTags

};

public static Dictionary<Tag, string> MdTags => new Dictionary<Tag, string>()
{
{ Tag.Bold, "__" }, { Tag.Italic, "_" },
{ Tag.Header, "# " }, { Tag.EscapedSymbol, ""}
};

public static Dictionary<Tag, Tag> DifferentTags => new Dictionary<Tag, Tag>()
{
{Tag.Bold, Tag.Italic},
{Tag.Italic, Tag.Bold}
};
}
}
13 changes: 13 additions & 0 deletions cs/Markdown/Domain/IHtmlBuilder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Markdown.Domain
{
public interface IHtmlBuilder
{
string BuildHtmlFromMarkdown(string markdownText, List<Token> tokens);

Choose a reason for hiding this comment

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

Хотим привязываться именно к MD и HTML или видим интерфейс, который умеет генерировать что-то из списка токенов и исходного текста?

}
}
13 changes: 13 additions & 0 deletions cs/Markdown/Domain/IMarkdownParser.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Markdown.Domain
{
public interface IMarkdownParser
{
List<Token> ParseMarkdown(string line);

Choose a reason for hiding this comment

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

Это интерфейс, который умеет преобразовывать какой-то текст в набор токенов, или жёсткая привязка в MD?

}
}
14 changes: 14 additions & 0 deletions cs/Markdown/Domain/ITagChecker.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Markdown.Domain.Tags;

namespace Markdown.Domain
{
public interface ITagChecker
{
Tuple<Tag, int> GetTagType(string line, int index);

Choose a reason for hiding this comment

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

Для читаемости рекомендую использовать имена для переменных внутри tuple. Тогда запись примет вид

 (Tag tagType, int endPosition) GetTagType(string line, int index)

}
}
24 changes: 24 additions & 0 deletions cs/Markdown/Domain/Tags/HtmlTag.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Markdown.Domain.Tags
{
public class HtmlTag

Choose a reason for hiding this comment

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

Можно заменить на record, так как по сути является дата классом

public record HtmlTag(Tag Tag, int Index, bool IsClosing, string Markup);

{
public Tag Tag { get; }

Choose a reason for hiding this comment

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

Точно нужно сохранять тип тега, если уже известен способ оформления? Предлагаю сделать ему размер, который нужно пропускать, а не сам md тэг и потом при рендеринге вычислять этот отступ

public int Index { get; }
public bool IsClosing { get; }
public string Markup { get; }

public HtmlTag(Tag tag, int index, bool isClosing, string htmlMarkup)
{
Tag = tag;
Index = index;
IsClosing = isClosing;
Markup = htmlMarkup;
}
}
}
20 changes: 20 additions & 0 deletions cs/Markdown/Domain/Tags/MdTag.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Markdown.Domain.Tags
{
public class MdTag
{
public Tag Tag { get; }
public int Index { get; }

public MdTag(Tag tag, int index)
{
Tag = tag;
Index = index;
}
}
}
17 changes: 17 additions & 0 deletions cs/Markdown/Domain/Tags/Tag.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Markdown.Domain.Tags
{
public enum Tag

Choose a reason for hiding this comment

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

Так как есть MdTag и HtmlTag, это логичнее назвать TagType. Tag подошло бы для абстрактного класса, от которого наследовались бы каждый из вышеуказанных. Но здесь такое не нужно

{
None,
Bold,
Italic,
Header,
EscapedSymbol,
}
}
25 changes: 25 additions & 0 deletions cs/Markdown/Domain/Token.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Markdown.Domain.Tags;

namespace Markdown.Domain
{
public class Token
{
public Tag TagType { get; }
public int StartIndex { get; }
public int EndIndex { get; }
public bool IsSingleTag { get; }

public Token(Tag type, int startIndex, int endIndex, bool isSingleTag = false)
{
TagType = type;
IsSingleTag = isSingleTag;
StartIndex = startIndex;
EndIndex = endIndex;
}
}
}
23 changes: 23 additions & 0 deletions cs/Markdown/Extensions/HtmlTagExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
using Markdown.Domain.Tags;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;

namespace Markdown.Extensions
{
public static class HtmlTagExtensions
{
public static string GetMarkup(this HtmlTag htmlTag)
{
if (htmlTag.Tag == Tag.EscapedSymbol)
return "";

var format = htmlTag.IsClosing ? "</{0}>" : "<{0}>";

return string.Format(format, htmlTag.Markup);
}
}
}
Loading