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 }