github.com/phsym/gomarkdoc@v0.5.4/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/phsym/gomarkdoc/format/formatcore" 11 "github.com/phsym/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 // Header converts the provided text into a header of the provided level. The 32 // level is expected to be at least 1. 33 func (f *AzureDevOpsMarkdown) Header(level int, text string) (string, error) { 34 return formatcore.Header(level, formatcore.Escape(text)) 35 } 36 37 // RawHeader converts the provided text into a header of the provided level 38 // without escaping the header text. The level is expected to be at least 1. 39 func (f *AzureDevOpsMarkdown) RawHeader(level int, text string) (string, error) { 40 return formatcore.Header(level, text) 41 } 42 43 var devOpsWhitespaceRegex = regexp.MustCompile(`\s`) 44 45 // LocalHref generates an href for navigating to a header with the given 46 // headerText located within the same document as the href itself. Link 47 // generation follows the guidelines here: 48 // https://docs.microsoft.com/en-us/azure/devops/project/wiki/markdown-guidance?view=azure-devops#anchor-links 49 func (f *AzureDevOpsMarkdown) LocalHref(headerText string) (string, error) { 50 result := strings.ToLower(headerText) 51 result = strings.TrimSpace(result) 52 result = devOpsWhitespaceRegex.ReplaceAllString(result, "-") 53 result = url.PathEscape(result) 54 // We also have to escape the `:` character if present 55 result = strings.ReplaceAll(result, ":", "%3A") 56 57 return fmt.Sprintf("#%s", result), nil 58 } 59 60 // CodeHref generates an href to the provided code entry. 61 func (f *AzureDevOpsMarkdown) CodeHref(loc lang.Location) (string, error) { 62 // If there's no repo, we can't compute an href 63 if loc.Repo == nil { 64 return "", nil 65 } 66 67 var ( 68 relative string 69 err error 70 ) 71 if filepath.IsAbs(loc.Filepath) { 72 relative, err = filepath.Rel(loc.WorkDir, loc.Filepath) 73 if err != nil { 74 return "", err 75 } 76 } else { 77 relative = loc.Filepath 78 } 79 80 full := filepath.Join(loc.Repo.PathFromRoot, relative) 81 p, err := filepath.Rel(string(filepath.Separator), full) 82 if err != nil { 83 return "", err 84 } 85 86 return fmt.Sprintf( 87 "%s?path=%s&version=GB%s&lineStyle=plain&line=%d&lineEnd=%d&lineStartColumn=%d&lineEndColumn=%d", 88 loc.Repo.Remote, 89 url.PathEscape(filepath.ToSlash(p)), 90 loc.Repo.DefaultBranch, 91 loc.Start.Line, 92 loc.End.Line, 93 loc.Start.Col, 94 loc.End.Col, 95 ), nil 96 } 97 98 // Link generates a link with the given text and href values. 99 func (f *AzureDevOpsMarkdown) Link(text, href string) (string, error) { 100 return formatcore.Link(text, href), nil 101 } 102 103 // ListEntry generates an unordered list entry with the provided text at the 104 // provided zero-indexed depth. A depth of 0 is considered the topmost level of 105 // list. 106 func (f *AzureDevOpsMarkdown) ListEntry(depth int, text string) (string, error) { 107 return formatcore.ListEntry(depth, text), nil 108 } 109 110 // Accordion generates a collapsible content. The accordion's visible title 111 // while collapsed is the provided title and the expanded content is the body. 112 func (f *AzureDevOpsMarkdown) Accordion(title, body string) (string, error) { 113 return formatcore.GFMAccordion(title, body), nil 114 } 115 116 // AccordionHeader generates the header visible when an accordion is collapsed. 117 // 118 // The AccordionHeader is expected to be used in conjunction with 119 // AccordionTerminator() when the demands of the body's rendering requires it to 120 // be generated independently. The result looks conceptually like the following: 121 // 122 // accordion := format.AccordionHeader("Accordion Title") + "Accordion Body" + format.AccordionTerminator() 123 func (f *AzureDevOpsMarkdown) AccordionHeader(title string) (string, error) { 124 return formatcore.GFMAccordionHeader(title), nil 125 } 126 127 // AccordionTerminator generates the code necessary to terminate an accordion 128 // after the body. It is expected to be used in conjunction with 129 // AccordionHeader(). See AccordionHeader for a full description. 130 func (f *AzureDevOpsMarkdown) AccordionTerminator() (string, error) { 131 return formatcore.GFMAccordionTerminator(), nil 132 } 133 134 // Paragraph formats a paragraph with the provided text as the contents. 135 func (f *AzureDevOpsMarkdown) Paragraph(text string) (string, error) { 136 return formatcore.Paragraph(text), nil 137 } 138 139 // Escape escapes special markdown characters from the provided text. 140 func (f *AzureDevOpsMarkdown) Escape(text string) string { 141 return formatcore.Escape(text) 142 }