github.com/rmarku/gomarkdoc@v0.0.0-20230517164305-78688ebe4325/format/devops.go (about)

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