1313$ timeStart = microtime (true );
1414
1515$ filesChanged = 0 ;
16-
1716$ linesCounted = 0 ;
18-
1917$ links = [];
20-
2118$ warnings = [];
2219
2320// Buffer headings so we can check for style
2421$ headings = []; // [filename => [line => heading]]
2522$ checksHeadings = false ;
2623
27- function find_markdown_files ( $ dir ): array
24+ class MarkdownFormatter
2825{
29- $ markdown_files = [];
26+ protected string $ input ;
27+ protected string $ output ;
28+ protected string $ filename ;
3029
31- $ iterator = new RecursiveIteratorIterator (new RecursiveDirectoryIterator ($ dir ));
32- foreach ($ iterator as $ file ) {
33- // Skip _data directory
34- if (str_contains ($ file ->getPathname (), '_data ' )) {
35- continue ;
30+ public function __construct (string $ input , string $ filename = 'Input ' )
31+ {
32+ $ this ->input = $ input ;
33+ $ this ->filename = $ filename ;
34+
35+ $ this ->run ();
36+ }
37+
38+ protected function run (): void
39+ {
40+ $ stream = $ this ->input ;
41+ $ filename = $ this ->filename ;
42+
43+ $ text = $ stream ;
44+ $ text = str_replace ("\r\n" , "\n" , $ text );
45+ $ text = str_replace ("\t" , ' ' , $ text );
46+
47+ if (empty (trim ($ text ))) {
48+ // Warn
49+ global $ warnings ;
50+ $ warnings [] = "File $ filename is empty " ;
51+
52+ return ;
3653 }
3754
38- if ($ file ->isFile () && strtolower ($ file ->getExtension ()) == 'md ' ) {
39- $ markdown_files [] = realpath ($ file ->getPathname ());
55+ $ lines = explode ("\n" , $ text );
56+ $ new_lines = [];
57+
58+ $ last_line = '' ;
59+ $ was_last_line_heading = false ;
60+ $ is_inside_fenced_code_block = false ;
61+ $ is_inside_fenced_fenced_code_block = false ;
62+ $ firstHeadingLevel = null ;
63+ foreach ($ lines as $ index => $ line ) {
64+ global $ linesCounted ;
65+ $ linesCounted ++;
66+
67+ /** Normalization */
68+
69+ // Remove multiple empty lines
70+ if (trim ($ line ) == '' && trim ($ last_line ) == '' ) {
71+ continue ;
72+ }
73+
74+ // Make sure there is a space after headings
75+ if ($ was_last_line_heading && trim ($ line ) != '' ) {
76+ $ new_lines [] = '' ;
77+ }
78+
79+ // Make sure there are two empty lines before level 2 headings (but not if it's the first l2 heading)
80+ if ($ is_inside_fenced_code_block !== true && str_starts_with ($ line , '## ' ) && $ index > $ firstHeadingLevel + 3 ) {
81+ $ new_lines [] = '' ;
82+ }
83+
84+ if ($ firstHeadingLevel === null && str_starts_with ($ line , '# ' )) {
85+ $ firstHeadingLevel = $ index ;
86+ }
87+
88+ // Check if line is a heading
89+ if (str_starts_with ($ line , '## ' )) {
90+ $ was_last_line_heading = true ;
91+ global $ headings ;
92+ $ headings [$ filename ][$ index + 1 ] = $ line ;
93+ } else {
94+ $ was_last_line_heading = false ;
95+ }
96+
97+ // Make sure there is a space before opening a fenced code block (search for ```language)
98+ if (str_starts_with ($ line , '``` ' ) && $ line !== '``` ' && trim ($ last_line ) != '' ) {
99+ if (! $ is_inside_fenced_fenced_code_block ) {
100+ $ new_lines [] = '' ;
101+ }
102+ }
103+
104+ // Check if line is a fenced code block
105+ if (str_starts_with ($ line , '`` ' )) {
106+ $ is_inside_fenced_code_block = ! $ is_inside_fenced_code_block ;
107+ }
108+
109+ // Check if line is an escaped fenced code block
110+ if (str_starts_with ($ line , '```` ' )) {
111+ $ is_inside_fenced_fenced_code_block = ! $ is_inside_fenced_fenced_code_block ;
112+ }
113+
114+ // Remove trailing spaces
115+ $ line = rtrim ($ line );
116+
117+ $ new_lines [] = $ line ;
118+ $ last_line = $ line ;
40119 }
41- }
42120
43- return $ markdown_files ;
44- }
121+ $ new_content = implode ( "\n" , $ new_lines ) ;
122+ $ new_content = trim ( $ new_content ). "\n" ;
45123
46- function handle_file (string $ file ): void
47- {
48- echo 'Handling ' .$ file ."\n" ;
124+ $ this ->output = $ new_content ;
125+ }
49126
50- normalize_lines ($ file );
127+ public function getOutput (): string
128+ {
129+ return $ this ->output ;
130+ }
51131}
52132
53- function normalize_lines ( $ filename ): void
133+ function lint ( string $ filename ): void
54134{
55- $ stream = file_get_contents ($ filename );
56-
57- $ text = $ stream ;
58- $ text = str_replace ("\r\n" , "\n" , $ text );
59- $ text = str_replace ("\t" , ' ' , $ text );
135+ /** Linting */
136+ $ text = file_get_contents ($ filename );
60137
61138 if (empty (trim ($ text ))) {
62139 // Warn
@@ -67,72 +144,13 @@ function normalize_lines($filename): void
67144 }
68145
69146 $ lines = explode ("\n" , $ text );
70- $ new_lines = [];
71-
72- $ last_line = '' ;
73- $ was_last_line_heading = false ;
74147 $ is_inside_fenced_code_block = false ;
75- $ is_inside_fenced_fenced_code_block = false ;
76- $ firstHeadingLevel = null ;
77148 foreach ($ lines as $ index => $ line ) {
78- global $ linesCounted ;
79- $ linesCounted ++;
80-
81- /** Normalization */
82-
83- // Remove multiple empty lines
84- if (trim ($ line ) == '' && trim ($ last_line ) == '' ) {
85- continue ;
86- }
87-
88- // Make sure there is a space after headings
89- if ($ was_last_line_heading && trim ($ line ) != '' ) {
90- $ new_lines [] = '' ;
91- }
92-
93- // Make sure there are two empty lines before level 2 headings (but not if it's the first l2 heading)
94- if ($ is_inside_fenced_code_block !== true && str_starts_with ($ line , '## ' ) && $ index > $ firstHeadingLevel + 3 ) {
95- $ new_lines [] = '' ;
96- }
97-
98- if ($ firstHeadingLevel === null && str_starts_with ($ line , '# ' )) {
99- $ firstHeadingLevel = $ index ;
100- }
101-
102- // Check if line is a heading
103- if (str_starts_with ($ line , '## ' )) {
104- $ was_last_line_heading = true ;
105- global $ headings ;
106- $ headings [$ filename ][$ index + 1 ] = $ line ;
107- } else {
108- $ was_last_line_heading = false ;
109- }
110-
111- // Make sure there is a space before opening a fenced code block (search for ```language)
112- if (str_starts_with ($ line , '``` ' ) && $ line !== '``` ' && trim ($ last_line ) != '' ) {
113- if (! $ is_inside_fenced_fenced_code_block ) {
114- $ new_lines [] = '' ;
115- }
116- }
117-
118149 // Check if line is a fenced code block
119150 if (str_starts_with ($ line , '`` ' )) {
120151 $ is_inside_fenced_code_block = ! $ is_inside_fenced_code_block ;
121152 }
122153
123- // Check if line is an escaped fenced code block
124- if (str_starts_with ($ line , '```` ' )) {
125- $ is_inside_fenced_fenced_code_block = ! $ is_inside_fenced_fenced_code_block ;
126- }
127-
128- // Remove trailing spaces
129- $ line = rtrim ($ line );
130-
131- $ new_lines [] = $ line ;
132- $ last_line = $ line ;
133-
134- /** Linting */
135-
136154 // if not inside fenced code block
137155 if (! $ is_inside_fenced_code_block ) {
138156 // Add any links to buffer, so we can check them later
@@ -267,9 +285,42 @@ function normalize_lines($filename): void
267285 }
268286 }
269287 }
288+ }
289+
290+ function find_markdown_files ($ dir ): array
291+ {
292+ $ markdown_files = [];
293+
294+ $ iterator = new RecursiveIteratorIterator (new RecursiveDirectoryIterator ($ dir ));
295+ foreach ($ iterator as $ file ) {
296+ // Skip _data directory
297+ if (str_contains ($ file ->getPathname (), '_data ' )) {
298+ continue ;
299+ }
300+
301+ if ($ file ->isFile () && strtolower ($ file ->getExtension ()) == 'md ' ) {
302+ $ markdown_files [] = realpath ($ file ->getPathname ());
303+ }
304+ }
305+
306+ return $ markdown_files ;
307+ }
308+
309+ function handle_file (string $ file ): void
310+ {
311+ echo 'Handling ' .$ file ."\n" ;
312+
313+ normalize_lines ($ file );
314+ lint ($ file );
315+ }
316+
317+ function normalize_lines ($ filename ): void
318+ {
319+ $ stream = file_get_contents ($ filename );
320+
321+ $ formatter = new MarkdownFormatter ($ stream , $ filename );
322+ $ new_content = $ formatter ->getOutput ();
270323
271- $ new_content = implode ("\n" , $ new_lines );
272- $ new_content = trim ($ new_content )."\n" ;
273324 file_put_contents ($ filename , $ new_content );
274325
275326 if ($ new_content !== $ stream ) {
0 commit comments