github.com/haalcala/mattermost-server-change-repo@v0.0.0-20210713015153-16753fbeee5f/utils/markdown/blocks.go (about)

     1  // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
     2  // See LICENSE.txt for license information.
     3  
     4  package markdown
     5  
     6  import (
     7  	"strings"
     8  )
     9  
    10  type continuation struct {
    11  	Indentation int
    12  	Remaining   Range
    13  }
    14  
    15  type Block interface {
    16  	Continuation(indentation int, r Range) *continuation
    17  	AddLine(indentation int, r Range) bool
    18  	Close()
    19  	AllowsBlockStarts() bool
    20  	HasTrailingBlankLine() bool
    21  }
    22  
    23  type blockBase struct{}
    24  
    25  func (*blockBase) AddLine(indentation int, r Range) bool { return false }
    26  func (*blockBase) Close()                                {}
    27  func (*blockBase) AllowsBlockStarts() bool               { return true }
    28  func (*blockBase) HasTrailingBlankLine() bool            { return false }
    29  
    30  type ContainerBlock interface {
    31  	Block
    32  	AddChild(openBlocks []Block) []Block
    33  }
    34  
    35  type Range struct {
    36  	Position int
    37  	End      int
    38  }
    39  
    40  func closeBlocks(blocks []Block, referenceDefinitions []*ReferenceDefinition) []*ReferenceDefinition {
    41  	for _, block := range blocks {
    42  		block.Close()
    43  		if p, ok := block.(*Paragraph); ok && len(p.ReferenceDefinitions) > 0 {
    44  			referenceDefinitions = append(referenceDefinitions, p.ReferenceDefinitions...)
    45  		}
    46  	}
    47  	return referenceDefinitions
    48  }
    49  
    50  func ParseBlocks(markdown string, lines []Line) (*Document, []*ReferenceDefinition) {
    51  	document := &Document{}
    52  	var referenceDefinitions []*ReferenceDefinition
    53  
    54  	openBlocks := []Block{document}
    55  
    56  	for _, line := range lines {
    57  		r := line.Range
    58  		lastMatchIndex := 0
    59  
    60  		indentation, indentationBytes := countIndentation(markdown, r)
    61  		r = Range{r.Position + indentationBytes, r.End}
    62  
    63  		for i, block := range openBlocks {
    64  			if continuation := block.Continuation(indentation, r); continuation != nil {
    65  				indentation = continuation.Indentation
    66  				r = continuation.Remaining
    67  				additionalIndentation, additionalIndentationBytes := countIndentation(markdown, r)
    68  				r = Range{r.Position + additionalIndentationBytes, r.End}
    69  				indentation += additionalIndentation
    70  				lastMatchIndex = i
    71  			} else {
    72  				break
    73  			}
    74  		}
    75  
    76  		if openBlocks[lastMatchIndex].AllowsBlockStarts() {
    77  			if newBlocks := blockStart(markdown, indentation, r, openBlocks[:lastMatchIndex+1], openBlocks[lastMatchIndex+1:]); newBlocks != nil {
    78  				didAdd := false
    79  				for i := lastMatchIndex; i >= 0; i-- {
    80  					if container, ok := openBlocks[i].(ContainerBlock); ok {
    81  						if addedBlocks := container.AddChild(newBlocks); addedBlocks != nil {
    82  							referenceDefinitions = closeBlocks(openBlocks[i+1:], referenceDefinitions)
    83  							openBlocks = openBlocks[:i+1]
    84  							openBlocks = append(openBlocks, addedBlocks...)
    85  							didAdd = true
    86  							break
    87  						}
    88  					}
    89  				}
    90  				if didAdd {
    91  					continue
    92  				}
    93  			}
    94  		}
    95  
    96  		isBlank := strings.TrimSpace(markdown[r.Position:r.End]) == ""
    97  		if paragraph, ok := openBlocks[len(openBlocks)-1].(*Paragraph); ok && !isBlank {
    98  			paragraph.Text = append(paragraph.Text, r)
    99  			continue
   100  		}
   101  
   102  		referenceDefinitions = closeBlocks(openBlocks[lastMatchIndex+1:], referenceDefinitions)
   103  		openBlocks = openBlocks[:lastMatchIndex+1]
   104  
   105  		if openBlocks[lastMatchIndex].AddLine(indentation, r) {
   106  			continue
   107  		}
   108  
   109  		if paragraph := newParagraph(markdown, r); paragraph != nil {
   110  			for i := lastMatchIndex; i >= 0; i-- {
   111  				if container, ok := openBlocks[i].(ContainerBlock); ok {
   112  					if newBlocks := container.AddChild([]Block{paragraph}); newBlocks != nil {
   113  						referenceDefinitions = closeBlocks(openBlocks[i+1:], referenceDefinitions)
   114  						openBlocks = openBlocks[:i+1]
   115  						openBlocks = append(openBlocks, newBlocks...)
   116  						break
   117  					}
   118  				}
   119  			}
   120  		}
   121  	}
   122  
   123  	referenceDefinitions = closeBlocks(openBlocks, referenceDefinitions)
   124  
   125  	return document, referenceDefinitions
   126  }
   127  
   128  func blockStart(markdown string, indentation int, r Range, matchedBlocks, unmatchedBlocks []Block) []Block {
   129  	if r.Position >= r.End {
   130  		return nil
   131  	}
   132  
   133  	if start := blockQuoteStart(markdown, indentation, r); start != nil {
   134  		return start
   135  	} else if start := listStart(markdown, indentation, r, matchedBlocks, unmatchedBlocks); start != nil {
   136  		return start
   137  	} else if start := indentedCodeStart(markdown, indentation, r, matchedBlocks, unmatchedBlocks); start != nil {
   138  		return start
   139  	} else if start := fencedCodeStart(markdown, indentation, r); start != nil {
   140  		return start
   141  	}
   142  
   143  	return nil
   144  }
   145  
   146  func blockStartOrParagraph(markdown string, indentation int, r Range, matchedBlocks, unmatchedBlocks []Block) []Block {
   147  	if start := blockStart(markdown, indentation, r, matchedBlocks, unmatchedBlocks); start != nil {
   148  		return start
   149  	}
   150  	if paragraph := newParagraph(markdown, r); paragraph != nil {
   151  		return []Block{paragraph}
   152  	}
   153  	return nil
   154  }