github.com/anthonyme00/gomarkdoc@v1.0.0/lang/span.go (about)

     1  package lang
     2  
     3  import (
     4  	"fmt"
     5  	"go/doc/comment"
     6  	"regexp"
     7  	"strings"
     8  )
     9  
    10  type (
    11  	// Span defines a single text span in a block for documentation of a symbol
    12  	// or package.
    13  	Span struct {
    14  		cfg  *Config
    15  		kind SpanKind
    16  		text string
    17  		url  string
    18  	}
    19  
    20  	// SpanKind identifies the type of span element represented by the
    21  	// corresponding Span.
    22  	SpanKind string
    23  )
    24  
    25  const (
    26  	// TextSpan defines a span that represents plain text.
    27  	TextSpan SpanKind = "text"
    28  
    29  	// RawTextSpan defines a span that represents plain text that should be
    30  	// displayed as-is.
    31  	RawTextSpan SpanKind = "rawText"
    32  
    33  	// LinkSpan defines a span that represents a link.
    34  	LinkSpan SpanKind = "link"
    35  
    36  	// AutolinkSpan defines a span that represents text which is itself a link.
    37  	AutolinkSpan SpanKind = "autolink"
    38  )
    39  
    40  // NewSpan creates a new span.
    41  func NewSpan(cfg *Config, kind SpanKind, text string, url string) *Span {
    42  	return &Span{cfg, kind, text, url}
    43  }
    44  
    45  // Kind provides the kind of data that this span represents.
    46  func (s *Span) Kind() SpanKind {
    47  	return s.kind
    48  }
    49  
    50  // Text provides the raw text for the span.
    51  func (s *Span) Text() string {
    52  	return s.text
    53  }
    54  
    55  // URL provides the url associated with the span, if any.
    56  func (s *Span) URL() string {
    57  	return s.url
    58  }
    59  
    60  // ParseSpans turns a set of *comment.Text entries into a slice of spans.
    61  func ParseSpans(cfg *Config, texts []comment.Text) []*Span {
    62  	var s []*Span
    63  	for _, t := range texts {
    64  		switch v := t.(type) {
    65  		case comment.Plain:
    66  			s = append(s, NewSpan(cfg.Inc(0), TextSpan, collapseWhitespace(string(v)), ""))
    67  		case comment.Italic:
    68  			s = append(s, NewSpan(cfg.Inc(0), TextSpan, collapseWhitespace(string(v)), ""))
    69  		case *comment.DocLink:
    70  			var b strings.Builder
    71  			printText(&b, v.Text...)
    72  			str := collapseWhitespace(b.String())
    73  
    74  			// Replace local links as needed
    75  			if v.ImportPath == "" {
    76  				name := symbolName(v.Recv, v.Name)
    77  				if sym, ok := cfg.Symbols[name]; ok {
    78  					s = append(s, NewSpan(cfg.Inc(0), LinkSpan, str, fmt.Sprintf("#%s", sym.Anchor())))
    79  				} else {
    80  					cfg.Log.Warnf("Unable to find symbol %s", name)
    81  					s = append(s, NewSpan(cfg.Inc(0), TextSpan, collapseWhitespace(str), ""))
    82  				}
    83  				break
    84  			}
    85  
    86  			s = append(s, NewSpan(cfg.Inc(0), LinkSpan, str, v.DefaultURL("https://pkg.go.dev/")))
    87  		case *comment.Link:
    88  			var b strings.Builder
    89  			printText(&b, v.Text...)
    90  			str := collapseWhitespace(b.String())
    91  
    92  			if v.Auto {
    93  				s = append(s, NewSpan(cfg.Inc(0), AutolinkSpan, str, str))
    94  				break
    95  			}
    96  
    97  			s = append(s, NewSpan(cfg.Inc(0), LinkSpan, str, v.URL))
    98  		}
    99  	}
   100  
   101  	return s
   102  }
   103  
   104  func printText(b *strings.Builder, text ...comment.Text) {
   105  	for i, t := range text {
   106  		if i > 0 {
   107  			b.WriteRune(' ')
   108  		}
   109  
   110  		switch v := t.(type) {
   111  		case comment.Plain:
   112  			b.WriteString(string(v))
   113  		case comment.Italic:
   114  			b.WriteString(string(v))
   115  		case *comment.DocLink:
   116  			printText(b, v.Text...)
   117  		case *comment.Link:
   118  			// No need to linkify implicit links
   119  			if v.Auto {
   120  				printText(b, v.Text...)
   121  				continue
   122  			}
   123  
   124  			b.WriteRune('[')
   125  			printText(b, v.Text...)
   126  			b.WriteRune(']')
   127  			b.WriteRune('(')
   128  			b.WriteString(v.URL)
   129  			b.WriteRune(')')
   130  		}
   131  	}
   132  }
   133  
   134  var whitespaceRegex = regexp.MustCompile(`\s+`)
   135  
   136  func collapseWhitespace(s string) string {
   137  	return string(whitespaceRegex.ReplaceAll([]byte(s), []byte(" ")))
   138  }