Skip to content

Commit aeb4cbd

Browse files
committed
#34: Remove blank vertical spaces in Markdown format
1 parent 40f5a0e commit aeb4cbd

File tree

3 files changed

+181
-1
lines changed

3 files changed

+181
-1
lines changed

formatter/formatter_md.go

Lines changed: 79 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
_ "embed"
66
"fmt"
77
"html/template"
8+
"io"
89
"strings"
910
)
1011

@@ -17,6 +18,77 @@ type MarkdownFormatter struct {
1718
// MarkdownTemplate variable is used to store markdown.tmpl embed file contents
1819
var MarkdownTemplate string
1920

21+
type markdownOutputFilter struct {
22+
writer io.Writer
23+
content []byte
24+
}
25+
26+
func (m *markdownOutputFilter) Write(p []byte) (n int, err error) {
27+
m.content = append(m.content, p...)
28+
return len(p), nil
29+
}
30+
31+
// split is used to split markdown content with new line delimiters
32+
func (m *markdownOutputFilter) split() [][]byte {
33+
lines := [][]byte{}
34+
line := []byte{}
35+
for _, b := range m.content {
36+
if b == '\n' {
37+
// No need to add new line if previous one was defined as new line
38+
if len(line) != 0 {
39+
lines = append(lines, line)
40+
}
41+
lines = append(lines, []byte{})
42+
line = []byte{}
43+
continue
44+
}
45+
line = append(line, b)
46+
}
47+
if len(line) != 0 {
48+
lines = append(lines, line)
49+
}
50+
return lines
51+
}
52+
53+
func (m *markdownOutputFilter) filter() []byte {
54+
content := []byte{}
55+
contentLines := m.split()
56+
newLines := 0
57+
isCodeBlock := false
58+
for _, l := range contentLines {
59+
if len(l) >= 3 &&
60+
l[0] == '`' &&
61+
l[1] == '`' &&
62+
l[2] == '`' {
63+
isCodeBlock = !isCodeBlock
64+
}
65+
66+
if !isCodeBlock {
67+
if len(l) == 0 {
68+
newLines++
69+
} else {
70+
newLines = 0
71+
}
72+
73+
// Skip other new lines
74+
if newLines > 2 {
75+
continue
76+
}
77+
78+
if newLines > 0 {
79+
l = append(l, '\n')
80+
}
81+
} else {
82+
// Simply add newline if it's empty slice
83+
if len(l) == 0 {
84+
l = append(l, '\n')
85+
}
86+
}
87+
content = append(content, l...)
88+
}
89+
return content
90+
}
91+
2092
// Format the data and output it to appropriate io.Writer
2193
func (f *MarkdownFormatter) Format(td *TemplateData) (err error) {
2294
tmpl := template.New("markdown")
@@ -25,7 +97,13 @@ func (f *MarkdownFormatter) Format(td *TemplateData) (err error) {
2597
if err != nil {
2698
return
2799
}
28-
return tmpl.Execute(f.config.Writer, td)
100+
markdownOutput := &markdownOutputFilter{writer: f.config.Writer, content: []byte{}}
101+
err = tmpl.Execute(markdownOutput, td)
102+
if err != nil {
103+
return err
104+
}
105+
_, err = f.config.Writer.Write(markdownOutput.filter())
106+
return err
29107
}
30108

31109
func (f *MarkdownFormatter) defineTemplateFunctions(tmpl *template.Template) {

formatter/formatter_md_test.go

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -541,3 +541,104 @@ func Test_markdownHostAnchorTitle(t *testing.T) {
541541
})
542542
}
543543
}
544+
545+
func Test_markdownOutputFilter_filter(t *testing.T) {
546+
tests := []struct {
547+
name string
548+
m *markdownOutputFilter
549+
want []byte
550+
}{
551+
{
552+
name: "Double new lines",
553+
m: &markdownOutputFilter{
554+
content: []byte("# Header\n## Header 2\n### Header 3\n\nNew sentence\n\nAnother new sentence"),
555+
},
556+
want: []byte("# Header\n## Header 2\n### Header 3\n\nNew sentence\n\nAnother new sentence"),
557+
},
558+
{
559+
name: "Simple codeblock",
560+
m: &markdownOutputFilter{
561+
content: []byte("# Header\nSome code:\n```\n\nSome newlines should be ignored\nhere\n\n```\n\n# Test\n\nNew sentence\n"),
562+
},
563+
want: []byte("# Header\nSome code:\n```\n\nSome newlines should be ignored\nhere\n\n```\n\n# Test\n\nNew sentence\n"),
564+
},
565+
}
566+
for _, tt := range tests {
567+
t.Run(tt.name, func(t *testing.T) {
568+
if got := tt.m.filter(); !reflect.DeepEqual(got, tt.want) {
569+
t.Errorf("markdownOutputFilter.filter() = %v, want %v", string(got), string(tt.want))
570+
}
571+
})
572+
}
573+
}
574+
575+
func Test_markdownOutputFilter_split(t *testing.T) {
576+
tests := []struct {
577+
name string
578+
m *markdownOutputFilter
579+
want [][]byte
580+
}{
581+
{
582+
name: "Double new line",
583+
m: &markdownOutputFilter{
584+
content: []byte("Test double\n\ntest"),
585+
},
586+
want: [][]byte{
587+
[]byte("Test double"),
588+
{},
589+
{},
590+
[]byte("test"),
591+
},
592+
},
593+
{
594+
name: "Multiple lines with double new lines",
595+
m: &markdownOutputFilter{
596+
content: []byte("# Header\n## Header 2\n### Header 3\n\nNew sentence\n\nAnother new sentence"),
597+
},
598+
want: [][]byte{
599+
[]byte("# Header"),
600+
{},
601+
[]byte("## Header 2"),
602+
{},
603+
[]byte("### Header 3"),
604+
{},
605+
{},
606+
[]byte("New sentence"),
607+
{},
608+
{},
609+
[]byte("Another new sentence"),
610+
},
611+
},
612+
{
613+
name: "New line in the beginning",
614+
m: &markdownOutputFilter{
615+
content: []byte("\nNew line\n\n"),
616+
},
617+
want: [][]byte{
618+
{},
619+
[]byte("New line"),
620+
{},
621+
{},
622+
},
623+
},
624+
{
625+
name: "Many lines in the end",
626+
m: &markdownOutputFilter{
627+
content: []byte("New test\n\n\n"),
628+
},
629+
want: [][]byte{
630+
[]byte("New test"),
631+
{},
632+
{},
633+
{},
634+
},
635+
},
636+
}
637+
for _, tt := range tests {
638+
t.Run(tt.name, func(t *testing.T) {
639+
if got := tt.m.split(); !reflect.DeepEqual(got, tt.want) {
640+
t.Errorf("markdownOutputFilter.split() = %v, want %v", got, tt.want)
641+
}
642+
})
643+
}
644+
}

formatter/resources/templates/markdown.tmpl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ NMAP Scan Result: {{ .NMAPRun.StartStr }}
1717

1818
{{- if not $skipSummary }}
1919
----
20+
2021
## Scan Summary
2122

2223
| Name | Value |

0 commit comments

Comments
 (0)