1+ using System ;
2+ using System . Collections . Generic ;
3+ using System . Collections . Immutable ;
4+ using System . IO . Pipes ;
5+ using System . Linq ;
6+ using System . Text . RegularExpressions ;
7+ using System . Threading . Tasks ;
8+ using Microsoft . AspNetCore . Html ;
9+ using Microsoft . AspNetCore . Razor . TagHelpers ;
10+ using Microsoft . CodeAnalysis ;
11+
12+ namespace OpenTabletDriver . Web . TagHelpers
13+ {
14+ [ HtmlTargetElement ( "codeblock" ) ]
15+ public class CodeBlockTagHelper : TagHelper
16+ {
17+ public string Language { set ; get ; }
18+
19+ public override async Task ProcessAsync ( TagHelperContext context , TagHelperOutput output )
20+ {
21+ output . TagName = "pre" ;
22+
23+ if ( output . Attributes . TryGetAttribute ( "class" , out var classAttr ) )
24+ output . Attributes . SetAttribute ( "class" , $ "{ classAttr . Value } card card-body") ;
25+ else
26+ output . Attributes . Add ( "class" , $ "card card-body") ;
27+
28+
29+ var content = await output . GetChildContentAsync ( ) ;
30+ var innerHtml = content . GetContent ( ) . Trim ( '\n ' ) ;
31+
32+ var body = TrimPreceding ( innerHtml , ' ' ) ;
33+ output . Content . SetHtmlContent ( body ) ;
34+ }
35+
36+ private string TrimPreceding ( string value , char character )
37+ {
38+ var lines = value . Split ( Environment . NewLine ) ;
39+ int preceding = CountPreceding ( lines , character ) ;
40+ var trimmedLines = from line in lines
41+ select TrimPrecedingLine ( line , character , preceding ) ;
42+
43+ var formattedLines = LanguageFormat ( trimmedLines . ToArray ( ) , Language ) ;
44+
45+ return string . Join ( Environment . NewLine , formattedLines ) ;
46+ }
47+
48+ private IEnumerable < string > LanguageFormat ( IList < string > lines , string language )
49+ {
50+ switch ( language )
51+ {
52+ case "sh" :
53+ case "bash" :
54+ case "nix" :
55+ {
56+ for ( int i = 0 ; i < lines . Count ; i ++ )
57+ {
58+ var line = lines [ i ] ;
59+ if ( line . TrimStart ( ) . StartsWith ( "#" ) )
60+ {
61+ var nextLine = lines [ i + 1 ] ;
62+ const string tag = "span" ;
63+ yield return $ "<{ tag } class=\" text-muted\" >{ line } </{ tag } >{ nextLine } ";
64+ i ++ ;
65+ }
66+ else
67+ {
68+ yield return line ;
69+ }
70+ }
71+
72+ break ;
73+ }
74+ case "ini" :
75+ {
76+ for ( int i = 0 ; i < lines . Count ; i ++ )
77+ {
78+ var line = lines [ i ] ;
79+ if ( Regex . IsMatch ( line , @"^\[.+?\]$" ) )
80+ {
81+ var nextLine = lines [ i + 1 ] ;
82+ const string tag = "span" ;
83+ yield return $ "<{ tag } class=\" text-info\" >{ line } </{ tag } >{ nextLine } ";
84+ i ++ ;
85+ }
86+ else
87+ {
88+ yield return line ;
89+ }
90+ }
91+
92+ break ;
93+ }
94+ default :
95+ {
96+ foreach ( var line in lines )
97+ yield return line ;
98+ break ;
99+ }
100+ }
101+ }
102+
103+ private int CountPreceding ( IEnumerable < string > lines , char leadingCharacter )
104+ {
105+ foreach ( var line in lines )
106+ {
107+ // Make sure that the line actually starts with the leading character
108+ if ( line . StartsWith ( leadingCharacter ) == false )
109+ continue ;
110+
111+ // Determine last index of leading character, return if something else is found
112+ for ( var i = 0 ; i < line . Length ; i ++ )
113+ {
114+ var character = line [ i ] ;
115+ if ( character != leadingCharacter )
116+ return i ;
117+ }
118+
119+ // Assume that this line is the template for trimming
120+ return line . Length ;
121+ }
122+
123+ throw new ArgumentException ( "No lines match the target leading character." , nameof ( lines ) ) ;
124+ }
125+
126+ private string TrimPrecedingLine ( string line , char character , int amount )
127+ {
128+ return line . StartsWith ( character ) ? new string ( line . Skip ( amount ) . ToArray ( ) ) : line ;
129+ }
130+ }
131+ }
0 commit comments