Skip to content

Commit 994f9b6

Browse files
authored
chore: wrap CLI flags in backticks in Mintlify doc output (#892)
* chore: wrap CLI flags in backticks in Mintlify doc output Mintlify's smart-typography renderer converts bare `--` to an em dash inside Markdown table cells, garbling flag names in the generated client_reference pages. Wrap every flag mention (--flag, -x) in backticks so the renderer treats them as inline code. * fix: fixtures and add 3 test cases * chore: consolidate function with Regex
1 parent a2ee562 commit 994f9b6

4 files changed

Lines changed: 199 additions & 39 deletions

File tree

cmd/kosli/testdata/output/docs/mintlify/artifact.md

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -33,19 +33,19 @@ registry without needing a local Docker daemon.
3333
## Flags
3434
| Flag | Description |
3535
| :--- | :--- |
36-
| -t, --artifact-type string | The type of the artifact to calculate its SHA256 fingerprint. One of: [oci, docker, file, dir]. Only required if you want Kosli to calculate the fingerprint for you (i.e. when you don't specify '--fingerprint' on commands that allow it). |
37-
| -b, --build-url string | The url of CI pipeline that built the artifact. (defaulted in some CIs: [docs](/integrations/ci_cd) ). |
38-
| -u, --commit-url string | The url for the git commit that created the artifact. (defaulted in some CIs: [docs](/integrations/ci_cd) ). |
39-
| -D, --dry-run | [optional] Run in dry-run mode. When enabled, no data is sent to Kosli and the CLI exits with 0 exit code regardless of any errors. |
40-
| -x, --exclude strings | [optional] The comma separated list of directories and files to exclude from fingerprinting. Can take glob patterns. Only applicable for --artifact-type dir. |
41-
| -F, --fingerprint string | [conditional] The SHA256 fingerprint of the artifact. Only required if you don't specify '--artifact-type'. |
42-
| -f, --flow string | The Kosli flow name. |
43-
| -g, --git-commit string | [defaulted] The git commit from which the artifact was created. (defaulted in some CIs: [docs](/integrations/ci_cd), otherwise defaults to HEAD ). |
44-
| -h, --help | help for artifact |
45-
| -n, --name string | [optional] Artifact display name, if different from file, image or directory name. |
46-
| --registry-password string | [conditional] The container registry password or access token. Only required if you want to read container image SHA256 digest from a remote container registry. |
47-
| --registry-username string | [conditional] The container registry username. Only required if you want to read container image SHA256 digest from a remote container registry. |
48-
| --repo-root string | [defaulted] The directory where the source git repository is available. (default ".") |
36+
| `-t`, `--artifact-type` string | The type of the artifact to calculate its SHA256 fingerprint. One of: [oci, docker, file, dir]. Only required if you want Kosli to calculate the fingerprint for you (i.e. when you don't specify '`--fingerprint`' on commands that allow it). |
37+
| `-b`, `--build-url` string | The url of CI pipeline that built the artifact. (defaulted in some CIs: [docs](/integrations/ci_cd) ). |
38+
| `-u`, `--commit-url` string | The url for the git commit that created the artifact. (defaulted in some CIs: [docs](/integrations/ci_cd) ). |
39+
| `-D`, `--dry-run` | [optional] Run in dry-run mode. When enabled, no data is sent to Kosli and the CLI exits with 0 exit code regardless of any errors. |
40+
| `-x`, `--exclude` strings | [optional] The comma separated list of directories and files to exclude from fingerprinting. Can take glob patterns. Only applicable for `--artifact-type` dir. |
41+
| `-F`, `--fingerprint` string | [conditional] The SHA256 fingerprint of the artifact. Only required if you don't specify '`--artifact-type`'. |
42+
| `-f`, `--flow` string | The Kosli flow name. |
43+
| `-g`, `--git-commit` string | [defaulted] The git commit from which the artifact was created. (defaulted in some CIs: [docs](/integrations/ci_cd), otherwise defaults to HEAD ). |
44+
| `-h`, `--help` | help for artifact |
45+
| `-n`, `--name` string | [optional] Artifact display name, if different from file, image or directory name. |
46+
| `--registry-password` string | [conditional] The container registry password or access token. Only required if you want to read container image SHA256 digest from a remote container registry. |
47+
| `--registry-username` string | [conditional] The container registry username. Only required if you want to read container image SHA256 digest from a remote container registry. |
48+
| `--repo-root` string | [defaulted] The directory where the source git repository is available. (default ".") |
4949

5050

5151
## Examples Use Cases

cmd/kosli/testdata/output/docs/mintlify/snyk.md

Lines changed: 26 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -37,32 +37,32 @@ In other CI systems, set them explicitly to capture repository metadata.
3737
## Flags
3838
| Flag | Description |
3939
| :--- | :--- |
40-
| --annotate stringToString | [optional] Annotate the attestation with data using key=value. |
41-
| -t, --artifact-type string | The type of the artifact to calculate its SHA256 fingerprint. One of: [oci, docker, file, dir]. Only required if you want Kosli to calculate the fingerprint for you (i.e. when you don't specify '--fingerprint' on commands that allow it). |
42-
| --attachments strings | [optional] The comma-separated list of paths of attachments for the reported attestation. Attachments can be files or directories. All attachments are compressed and uploaded to Kosli's evidence vault. |
43-
| -g, --commit string | [conditional] The git commit for which the attestation is associated to. Becomes required when reporting an attestation for an artifact before reporting it to Kosli. (defaulted in some CIs: [docs](/integrations/ci_cd) ). |
44-
| --description string | [optional] attestation description |
45-
| -D, --dry-run | [optional] Run in dry-run mode. When enabled, no data is sent to Kosli and the CLI exits with 0 exit code regardless of any errors. |
46-
| -x, --exclude strings | [optional] The comma separated list of directories and files to exclude from fingerprinting. Can take glob patterns. Only applicable for --artifact-type dir. |
47-
| --external-fingerprint stringToString | [optional] A SHA256 fingerprint of an external attachment represented by --external-url. The format is label=fingerprint (labels cannot contain '.' or '='). This flag can be set multiple times. There must be an external url with a matching label for each external fingerprint. |
48-
| --external-url stringToString | [optional] Add labeled reference URL for an external resource. The format is label=url (labels cannot contain '.' or '='). This flag can be set multiple times. If the resource is a file or dir, you can optionally add its fingerprint via --external-fingerprint |
49-
| -F, --fingerprint string | [conditional] The SHA256 fingerprint of the artifact to attach the attestation to. Only required if the attestation is for an artifact and --artifact-type and artifact name/path are not used. |
50-
| -f, --flow string | The Kosli flow name. |
51-
| -h, --help | help for snyk |
52-
| -n, --name string | The name of the attestation as declared in the flow or trail yaml template. |
53-
| -o, --origin-url string | [optional] The url pointing to where the attestation came from or is related. (defaulted to the CI url in some CIs: [docs](/integrations/ci_cd/#defaulted-kosli-command-flags-from-ci-variables) ). |
54-
| --redact-commit-info strings | [optional] The list of commit info to be redacted before sending to Kosli. Allowed values are one or more of [author, message, branch]. |
55-
| --registry-password string | [conditional] The container registry password or access token. Only required if you want to read container image SHA256 digest from a remote container registry. |
56-
| --registry-username string | [conditional] The container registry username. Only required if you want to read container image SHA256 digest from a remote container registry. |
57-
| --repo-id string | [conditional] The stable, unique identifier for the repository in your VCS provider (e.g. a numeric ID). Do not use the repository name as it can change if the repo is renamed. All three of --repo-id, --repo-url and --repository must be set to record repository information (defaulted in some CIs: [docs](/integrations/ci_cd) ). |
58-
| --repo-provider string | [optional] The source code hosting provider. One of: github, gitlab, bitbucket, azure-devops (defaulted in some CIs: [docs](/integrations/ci_cd) ). |
59-
| --repo-root string | [defaulted] The directory where the source git repository is available. Only used if --commit is used or defaulted in CI, see [docs](/integrations/ci_cd/#defaulted-kosli-command-flags-from-ci-variables) . (default ".") |
60-
| --repo-url string | [conditional] The URL of the repository. Must be a valid URL. All three of --repo-id, --repo-url and --repository must be set to record repository information (defaulted in some CIs: [docs](/integrations/ci_cd) ). |
61-
| --repository string | [conditional] The name of the repository (e.g. owner/repo-name). All three of --repo-id, --repo-url and --repository must be set to record repository information (defaulted in some CIs: [docs](/integrations/ci_cd) ). |
62-
| -R, --scan-results string | The path to Snyk scan SARIF results file from 'snyk test' and 'snyk container test'. By default, the Snyk results will be uploaded to Kosli's evidence vault. |
63-
| -T, --trail string | The Kosli trail name. |
64-
| --upload-results | [defaulted] Whether to upload the provided Snyk results file as an attachment to Kosli or not. (default true) |
65-
| -u, --user-data string | [optional] The path to a JSON file containing additional data you would like to attach to the attestation. |
40+
| `--annotate` stringToString | [optional] Annotate the attestation with data using key=value. |
41+
| `-t`, `--artifact-type` string | The type of the artifact to calculate its SHA256 fingerprint. One of: [oci, docker, file, dir]. Only required if you want Kosli to calculate the fingerprint for you (i.e. when you don't specify '`--fingerprint`' on commands that allow it). |
42+
| `--attachments` strings | [optional] The comma-separated list of paths of attachments for the reported attestation. Attachments can be files or directories. All attachments are compressed and uploaded to Kosli's evidence vault. |
43+
| `-g`, `--commit` string | [conditional] The git commit for which the attestation is associated to. Becomes required when reporting an attestation for an artifact before reporting it to Kosli. (defaulted in some CIs: [docs](/integrations/ci_cd) ). |
44+
| `--description` string | [optional] attestation description |
45+
| `-D`, `--dry-run` | [optional] Run in dry-run mode. When enabled, no data is sent to Kosli and the CLI exits with 0 exit code regardless of any errors. |
46+
| `-x`, `--exclude` strings | [optional] The comma separated list of directories and files to exclude from fingerprinting. Can take glob patterns. Only applicable for `--artifact-type` dir. |
47+
| `--external-fingerprint` stringToString | [optional] A SHA256 fingerprint of an external attachment represented by `--external-url`. The format is label=fingerprint (labels cannot contain '.' or '='). This flag can be set multiple times. There must be an external url with a matching label for each external fingerprint. |
48+
| `--external-url` stringToString | [optional] Add labeled reference URL for an external resource. The format is label=url (labels cannot contain '.' or '='). This flag can be set multiple times. If the resource is a file or dir, you can optionally add its fingerprint via `--external-fingerprint` |
49+
| `-F`, `--fingerprint` string | [conditional] The SHA256 fingerprint of the artifact to attach the attestation to. Only required if the attestation is for an artifact and `--artifact-type` and artifact name/path are not used. |
50+
| `-f`, `--flow` string | The Kosli flow name. |
51+
| `-h`, `--help` | help for snyk |
52+
| `-n`, `--name` string | The name of the attestation as declared in the flow or trail yaml template. |
53+
| `-o`, `--origin-url` string | [optional] The url pointing to where the attestation came from or is related. (defaulted to the CI url in some CIs: [docs](/integrations/ci_cd/#defaulted-kosli-command-flags-from-ci-variables) ). |
54+
| `--redact-commit-info` strings | [optional] The list of commit info to be redacted before sending to Kosli. Allowed values are one or more of [author, message, branch]. |
55+
| `--registry-password` string | [conditional] The container registry password or access token. Only required if you want to read container image SHA256 digest from a remote container registry. |
56+
| `--registry-username` string | [conditional] The container registry username. Only required if you want to read container image SHA256 digest from a remote container registry. |
57+
| `--repo-id` string | [conditional] The stable, unique identifier for the repository in your VCS provider (e.g. a numeric ID). Do not use the repository name as it can change if the repo is renamed. All three of `--repo-id`, `--repo-url` and `--repository` must be set to record repository information (defaulted in some CIs: [docs](/integrations/ci_cd) ). |
58+
| `--repo-provider` string | [optional] The source code hosting provider. One of: github, gitlab, bitbucket, azure-devops (defaulted in some CIs: [docs](/integrations/ci_cd) ). |
59+
| `--repo-root` string | [defaulted] The directory where the source git repository is available. Only used if `--commit` is used or defaulted in CI, see [docs](/integrations/ci_cd/#defaulted-kosli-command-flags-from-ci-variables) . (default ".") |
60+
| `--repo-url` string | [conditional] The URL of the repository. Must be a valid URL. All three of `--repo-id`, `--repo-url` and `--repository` must be set to record repository information (defaulted in some CIs: [docs](/integrations/ci_cd) ). |
61+
| `--repository` string | [conditional] The name of the repository (e.g. owner/repo-name). All three of `--repo-id`, `--repo-url` and `--repository` must be set to record repository information (defaulted in some CIs: [docs](/integrations/ci_cd) ). |
62+
| `-R`, `--scan-results` string | The path to Snyk scan SARIF results file from 'snyk test' and 'snyk container test'. By default, the Snyk results will be uploaded to Kosli's evidence vault. |
63+
| `-T`, `--trail` string | The Kosli trail name. |
64+
| `--upload-results` | [defaulted] Whether to upload the provided Snyk results file as an attachment to Kosli or not. (default true) |
65+
| `-u`, `--user-data` string | [optional] The path to a JSON file containing additional data you would like to attach to the attestation. |
6666

6767

6868
## Examples Use Cases

internal/docgen/mintlify.go

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,8 +58,10 @@ func (MintlifyFormatter) Synopsis(meta CommandMeta) string {
5858
func (MintlifyFormatter) FlagsSection(flags, inherited string) string {
5959
flags = linkifyKosliDocsURLs(flags)
6060
flags = escapeMintlifyProse(flags)
61+
flags = backtickFlags(flags)
6162
inherited = linkifyKosliDocsURLs(inherited)
6263
inherited = escapeMintlifyProse(inherited)
64+
inherited = backtickFlags(inherited)
6365
var b strings.Builder
6466
if flags != "" {
6567
b.WriteString("## Flags\n")
@@ -170,6 +172,65 @@ var htmlTags = map[string]bool{
170172
// single quotes are reserved for example/placeholder URLs.
171173
var singleQuotedURLPattern = regexp.MustCompile(`'(https?://[^\s']+)'`)
172174

175+
// flagPattern matches the body of a CLI flag like --name or -x. The caller
176+
// must check the preceding character to ensure it is a real flag boundary
177+
// (start-of-string or a non-word, non-slash, non-dash character).
178+
var flagPattern = regexp.MustCompile(`^-{1,2}[a-zA-Z][\w-]*`)
179+
180+
// backtickFlags wraps every CLI flag mention (--flag, -x) in backticks so
181+
// Mintlify's smart-typography renderer does not turn `--` into an em dash
182+
// inside Markdown table cells. Idempotent: flags already inside inline-code
183+
// spans or fenced code blocks are left alone.
184+
func backtickFlags(s string) string {
185+
parts := strings.Split(s, "```")
186+
for i := 0; i < len(parts); i += 2 {
187+
parts[i] = backtickFlagsFragment(parts[i])
188+
}
189+
return strings.Join(parts, "```")
190+
}
191+
192+
// nonBoundaryByte matches a single byte that cannot precede a CLI flag:
193+
// a word char (\w), backtick, slash, or dash.
194+
var nonBoundaryByte = regexp.MustCompile("[`/\\w-]")
195+
196+
// isFlagBoundaryChar reports whether c is a valid character before a flag.
197+
func isFlagBoundaryChar(c byte) bool {
198+
return !nonBoundaryByte.Match([]byte{c})
199+
}
200+
201+
func backtickFlagsFragment(s string) string {
202+
var b strings.Builder
203+
i := 0
204+
for i < len(s) {
205+
if s[i] == '`' {
206+
b.WriteByte(s[i])
207+
i++
208+
for i < len(s) && s[i] != '`' {
209+
b.WriteByte(s[i])
210+
i++
211+
}
212+
if i < len(s) {
213+
b.WriteByte(s[i])
214+
i++
215+
}
216+
continue
217+
}
218+
boundary := i == 0 || isFlagBoundaryChar(s[i-1])
219+
if boundary && (s[i] == '-') {
220+
if loc := flagPattern.FindStringIndex(s[i:]); loc != nil {
221+
b.WriteByte('`')
222+
b.WriteString(s[i : i+loc[1]])
223+
b.WriteByte('`')
224+
i += loc[1]
225+
continue
226+
}
227+
}
228+
b.WriteByte(s[i])
229+
i++
230+
}
231+
return b.String()
232+
}
233+
173234
func escapeProseFragment(s string) string {
174235
// Escape curly braces: {expr} -> \{expr\}
175236
s = strings.ReplaceAll(s, "{", "\\{")

internal/docgen/mintlify_test.go

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,105 @@ func TestEscapeMintlifyProse(t *testing.T) {
217217
}
218218
}
219219

220+
func TestBacktickFlags(t *testing.T) {
221+
tests := []struct {
222+
name string
223+
input string
224+
want string
225+
}{
226+
{
227+
name: "double-dash flag at start",
228+
input: "--api-token",
229+
want: "`--api-token`",
230+
},
231+
{
232+
name: "single-dash flag at start",
233+
input: "-x",
234+
want: "`-x`",
235+
},
236+
{
237+
name: "flag in middle of sentence",
238+
input: "use --api-token to authenticate",
239+
want: "use `--api-token` to authenticate",
240+
},
241+
{
242+
name: "flag in table cell",
243+
input: "| --api-token | API token |",
244+
want: "| `--api-token` | API token |",
245+
},
246+
{
247+
name: "two adjacent flags",
248+
input: "--foo --bar",
249+
want: "`--foo` `--bar`",
250+
},
251+
{
252+
name: "comma-separated flags",
253+
input: "--foo,--bar",
254+
want: "`--foo`,`--bar`",
255+
},
256+
{
257+
name: "already-backticked flag is left alone",
258+
input: "use `--api-token` here",
259+
want: "use `--api-token` here",
260+
},
261+
{
262+
name: "hyphenated word is not a flag",
263+
input: "this is built-in behaviour",
264+
want: "this is built-in behaviour",
265+
},
266+
{
267+
name: "code fence content is untouched",
268+
input: "see ```\nkosli foo --bar\n``` for example",
269+
want: "see ```\nkosli foo --bar\n``` for example",
270+
},
271+
{
272+
name: "flag with hyphen in name",
273+
input: "use --jira-base-url here",
274+
want: "use `--jira-base-url` here",
275+
},
276+
{
277+
name: "short flag in middle",
278+
input: "use -h for help",
279+
want: "use `-h` for help",
280+
},
281+
{
282+
name: "bare double-dash is not a flag",
283+
input: "use -- to separate flags from args",
284+
want: "use -- to separate flags from args",
285+
},
286+
{
287+
name: "flag with equals value",
288+
input: "use --output=json here",
289+
want: "use `--output`=json here",
290+
},
291+
{
292+
name: "numeric arg is not a flag",
293+
input: "use -1 for default",
294+
want: "use -1 for default",
295+
},
296+
}
297+
for _, tt := range tests {
298+
t.Run(tt.name, func(t *testing.T) {
299+
got := backtickFlags(tt.input)
300+
if got != tt.want {
301+
t.Errorf("backtickFlags(%q):\ngot: %q\nwant: %q", tt.input, got, tt.want)
302+
}
303+
})
304+
}
305+
}
306+
307+
func TestMintlifyFlagsSectionBackticksFlags(t *testing.T) {
308+
f := MintlifyFormatter{}
309+
flags := "| --api-token string | required (default $KOSLI_API_TOKEN) |\n"
310+
got := f.FlagsSection(flags, "")
311+
if !strings.Contains(got, "`--api-token`") {
312+
t.Errorf("expected --api-token to be backticked, got:\n%s", got)
313+
}
314+
if strings.Contains(got, "| --api-token string |") {
315+
t.Errorf("expected bare --api-token to be replaced, got:\n%s", got)
316+
}
317+
}
318+
220319
func TestSanitizeDescription(t *testing.T) {
221320
tests := []struct {
222321
name string

0 commit comments

Comments
 (0)