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

     1  package format
     2  
     3  import (
     4  	"fmt"
     5  	"path/filepath"
     6  	"regexp"
     7  	"strings"
     8  
     9  	"github.com/anthonyme00/gomarkdoc/format/formatcore"
    10  	"github.com/anthonyme00/gomarkdoc/lang"
    11  )
    12  
    13  // GitHubFlavoredMarkdown provides a Format which is compatible with GitHub
    14  // Flavored Markdown's syntax and semantics. See GitHub's documentation for
    15  // more details about their markdown format:
    16  // https://guides.github.com/features/mastering-markdown/
    17  type GitHubFlavoredMarkdown struct{}
    18  
    19  // Bold converts the provided text to bold
    20  func (f *GitHubFlavoredMarkdown) Bold(text string) (string, error) {
    21  	return formatcore.Bold(text), nil
    22  }
    23  
    24  // CodeBlock wraps the provided code as a code block and tags it with the
    25  // provided language (or no language if the empty string is provided).
    26  func (f *GitHubFlavoredMarkdown) CodeBlock(language, code string) (string, error) {
    27  	return formatcore.GFMCodeBlock(language, code), nil
    28  }
    29  
    30  // Anchor produces an anchor for the provided link.
    31  func (f *GitHubFlavoredMarkdown) Anchor(anchor string) string {
    32  	return formatcore.Anchor(anchor)
    33  }
    34  
    35  // AnchorHeader converts the provided text and custom anchor link into a header
    36  // of the provided level. The level is expected to be at least 1.
    37  func (f *GitHubFlavoredMarkdown) AnchorHeader(level int, text, anchor string) (string, error) {
    38  	return formatcore.AnchorHeader(level, formatcore.Escape(text), anchor)
    39  }
    40  
    41  // Header converts the provided text into a header of the provided level. The
    42  // level is expected to be at least 1.
    43  func (f *GitHubFlavoredMarkdown) Header(level int, text string) (string, error) {
    44  	return formatcore.Header(level, formatcore.Escape(text))
    45  }
    46  
    47  // RawAnchorHeader converts the provided text and custom anchor link into a
    48  // header of the provided level without escaping the header text. The level is
    49  // expected to be at least 1.
    50  func (f *GitHubFlavoredMarkdown) RawAnchorHeader(level int, text, anchor string) (string, error) {
    51  	return formatcore.AnchorHeader(level, text, anchor)
    52  }
    53  
    54  // RawHeader converts the provided text into a header of the provided level
    55  // without escaping the header text. The level is expected to be at least 1.
    56  func (f *GitHubFlavoredMarkdown) RawHeader(level int, text string) (string, error) {
    57  	return formatcore.Header(level, text)
    58  }
    59  
    60  var (
    61  	gfmWhitespaceRegex = regexp.MustCompile(`\s`)
    62  	gfmRemoveRegex     = regexp.MustCompile(`[^\pL-_\d]+`)
    63  )
    64  
    65  // LocalHref generates an href for navigating to a header with the given
    66  // headerText located within the same document as the href itself.
    67  func (f *GitHubFlavoredMarkdown) LocalHref(headerText string) (string, error) {
    68  	result := formatcore.PlainText(headerText)
    69  	result = strings.ToLower(result)
    70  	result = strings.TrimSpace(result)
    71  	result = gfmWhitespaceRegex.ReplaceAllString(result, "-")
    72  	result = gfmRemoveRegex.ReplaceAllString(result, "")
    73  
    74  	return fmt.Sprintf("#%s", result), nil
    75  }
    76  
    77  // RawLocalHref generates an href within the same document but with a direct
    78  // link provided instead of text to slugify.
    79  func (f *GitHubFlavoredMarkdown) RawLocalHref(anchor string) string {
    80  	return fmt.Sprintf("#%s", anchor)
    81  }
    82  
    83  // Link generates a link with the given text and href values.
    84  func (f *GitHubFlavoredMarkdown) Link(text, href string) (string, error) {
    85  	return formatcore.Link(text, href), nil
    86  }
    87  
    88  // CodeHref generates an href to the provided code entry.
    89  func (f *GitHubFlavoredMarkdown) CodeHref(loc lang.Location) (string, error) {
    90  	// If there's no repo, we can't compute an href
    91  	if loc.Repo == nil {
    92  		return "", nil
    93  	}
    94  
    95  	var (
    96  		relative string
    97  		err      error
    98  	)
    99  	if filepath.IsAbs(loc.Filepath) {
   100  		relative, err = filepath.Rel(loc.WorkDir, loc.Filepath)
   101  		if err != nil {
   102  			return "", err
   103  		}
   104  	} else {
   105  		relative = loc.Filepath
   106  	}
   107  
   108  	full := filepath.Join(loc.Repo.PathFromRoot, relative)
   109  	p, err := filepath.Rel(string(filepath.Separator), full)
   110  	if err != nil {
   111  		return "", err
   112  	}
   113  
   114  	var locStr string
   115  	if loc.Start.Line == loc.End.Line {
   116  		locStr = fmt.Sprintf("L%d", loc.Start.Line)
   117  	} else {
   118  		locStr = fmt.Sprintf("L%d-L%d", loc.Start.Line, loc.End.Line)
   119  	}
   120  
   121  	return fmt.Sprintf(
   122  		"%s/blob/%s/%s#%s",
   123  		loc.Repo.Remote,
   124  		loc.Repo.DefaultBranch,
   125  		filepath.ToSlash(p),
   126  		locStr,
   127  	), nil
   128  }
   129  
   130  // ListEntry generates an unordered list entry with the provided text at the
   131  // provided zero-indexed depth. A depth of 0 is considered the topmost level of
   132  // list.
   133  func (f *GitHubFlavoredMarkdown) ListEntry(depth int, text string) (string, error) {
   134  	return formatcore.ListEntry(depth, text), nil
   135  }
   136  
   137  // Accordion generates a collapsible content. The accordion's visible title
   138  // while collapsed is the provided title and the expanded content is the body.
   139  func (f *GitHubFlavoredMarkdown) Accordion(title, body string) (string, error) {
   140  	return formatcore.GFMAccordion(title, body), nil
   141  }
   142  
   143  // AccordionHeader generates the header visible when an accordion is collapsed.
   144  //
   145  // The AccordionHeader is expected to be used in conjunction with
   146  // AccordionTerminator() when the demands of the body's rendering requires it to
   147  // be generated independently. The result looks conceptually like the following:
   148  //
   149  //	accordion := format.AccordionHeader("Accordion Title") + "Accordion Body" + format.AccordionTerminator()
   150  func (f *GitHubFlavoredMarkdown) AccordionHeader(title string) (string, error) {
   151  	return formatcore.GFMAccordionHeader(title), nil
   152  }
   153  
   154  // AccordionTerminator generates the code necessary to terminate an accordion
   155  // after the body. It is expected to be used in conjunction with
   156  // AccordionHeader(). See AccordionHeader for a full description.
   157  func (f *GitHubFlavoredMarkdown) AccordionTerminator() (string, error) {
   158  	return formatcore.GFMAccordionTerminator(), nil
   159  }
   160  
   161  // Escape escapes special markdown characters from the provided text.
   162  func (f *GitHubFlavoredMarkdown) Escape(text string) string {
   163  	return formatcore.Escape(text)
   164  }