Update to 15.3.0 #1
13
mark.changes
13
mark.changes
@@ -1,3 +1,16 @@
|
||||
-------------------------------------------------------------------
|
||||
Tue Jan 20 16:12:32 UTC 2026 - Elisei Roca <eroca@suse.com>
|
||||
|
||||
- Update to version 15.3.0:
|
||||
* parse "linenumbers" in code block
|
||||
* add view-file template
|
||||
* feat: extend link resolution to support blog posts
|
||||
* feat: enhance Confluence link generation by utilizing base URL from API response
|
||||
* feat: implement GenerateTinyLink function and associated tests for Confluence tiny link generation
|
||||
* fix: resolve link space inheritance and enhance Confluence URL normalization tests
|
||||
* feat: add normalizeConfluenceWebUIPath function and tests for URL rewriting
|
||||
* Bump dependencies
|
||||
|
||||
-------------------------------------------------------------------
|
||||
Thu Dec 11 14:27:10 UTC 2025 - Elisei Roca <eroca@suse.com>
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
# nodebuginfo
|
||||
|
||||
Name: mark
|
||||
Version: 15.2.0
|
||||
Version: 15.3.0
|
||||
Release: 0
|
||||
Summary: A tool for syncing your markdown documentation with Atlassian Confluence pages
|
||||
License: Apache-2.0
|
||||
|
||||
4
mark/.github/workflows/ci.yml
vendored
4
mark/.github/workflows/ci.yml
vendored
@@ -11,7 +11,7 @@ on:
|
||||
- master
|
||||
|
||||
env:
|
||||
GO_VERSION: "~1.24"
|
||||
GO_VERSION: "1.25.6"
|
||||
|
||||
jobs:
|
||||
# Runs Golangci-lint on the source code
|
||||
@@ -39,7 +39,7 @@ jobs:
|
||||
- name: Check out code into the Go module directory
|
||||
uses: actions/checkout@v6
|
||||
- name: markdownlint-cli2-action
|
||||
uses: DavidAnson/markdownlint-cli2-action@v21
|
||||
uses: DavidAnson/markdownlint-cli2-action@v22
|
||||
|
||||
# Executes Unit Tests
|
||||
ci-unit-tests:
|
||||
|
||||
2
mark/.github/workflows/goreleaser.yml
vendored
2
mark/.github/workflows/goreleaser.yml
vendored
@@ -16,7 +16,7 @@ jobs:
|
||||
- name: Set Up Go
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: "1.24"
|
||||
go-version: "1.25.6"
|
||||
- name: Run GoReleaser
|
||||
uses: goreleaser/goreleaser-action@v6
|
||||
with:
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM golang:1.25.5 AS builder
|
||||
FROM golang:1.25.6 AS builder
|
||||
ENV GOPATH="/go"
|
||||
WORKDIR /go/src/github.com/kovetskiy/mark
|
||||
COPY / .
|
||||
|
||||
@@ -237,48 +237,38 @@ Placeholder
|
||||
|
||||
### Code Blocks
|
||||
|
||||
If you have long code blocks, you can make them collapsible with the [Code Block Macro]:
|
||||
|
||||
```bash collapse
|
||||
````text
|
||||
```bash
|
||||
...
|
||||
some long bash code block
|
||||
...
|
||||
```
|
||||
````
|
||||
|
||||
And you can also add a title:
|
||||
| Parameter | Default |
|
||||
| ------------------------------ | ------- |
|
||||
| `collapse` | false |
|
||||
| `title` | none |
|
||||
| `linenumbers` | false |
|
||||
| `1` (any number for firstline) | 1 |
|
||||
|
||||
```bash collapse title Some long long bash function
|
||||
...
|
||||
some long bash code block
|
||||
...
|
||||
```
|
||||
Example:
|
||||
|
||||
Or linenumbers, by giving the first number
|
||||
* `bash collapse`
|
||||
If you have long code blocks, you can make them collapsible.
|
||||
* `bash collapse title Some long long bash function`
|
||||
And you can also add a title.
|
||||
* `bash linenumbers collapse title Some long long bash function`
|
||||
And linenumbers.
|
||||
* `bash 1 collapse title Some long long bash function`
|
||||
Or directly give a number as firstline number.
|
||||
* `bash 1 collapse midnight title Some long long bash function`
|
||||
And even themes.
|
||||
* `- 1 collapse midnight title Some long long code`
|
||||
Please note that, if you want to have a code block without a language
|
||||
use `-` as the first character, if you want to have the other goodies.
|
||||
|
||||
```bash 1 collapse title Some long long bash function
|
||||
...
|
||||
some long bash code block
|
||||
...
|
||||
```
|
||||
|
||||
And even themes
|
||||
|
||||
```bash 1 collapse midnight title Some long long bash function
|
||||
...
|
||||
some long bash code block
|
||||
...
|
||||
```
|
||||
|
||||
Please note that, if you want to have a code block without a language
|
||||
use `-` as the first character, if you want to have the other goodies
|
||||
|
||||
``` - 1 collapse midnight title Some long long code
|
||||
...
|
||||
some long code block
|
||||
...
|
||||
```
|
||||
|
||||
[Code Block Macro]: https://confluence.atlassian.com/doc/code-block-macro-139390.html
|
||||
More details at Confluence [Code Block Macro](https://confluence.atlassian.com/doc/code-block-macro-139390.html) doc.
|
||||
|
||||
### Block Quotes
|
||||
|
||||
@@ -515,6 +505,10 @@ By default, mark provides several built-in templates and macros:
|
||||
* Width: Width of the video (optional)
|
||||
* AutoPlay: Start playing the file on page load (default: false)
|
||||
|
||||
* template `ac:view-file`
|
||||
* Name: Name of the file
|
||||
* Height: height of the view
|
||||
|
||||
* macro `@{...}` to mention user by name specified in the braces.
|
||||
|
||||
## Template & Macros Usecases
|
||||
@@ -815,23 +809,23 @@ USAGE:
|
||||
mark [global options]
|
||||
|
||||
VERSION:
|
||||
14.0.2
|
||||
v15.1.0@b3a6f1efae97dfaa1400a3175cdd3377f8176e88
|
||||
|
||||
DESCRIPTION:
|
||||
Mark is a tool to update Atlassian Confluence pages from markdown. Documentation is available here: https://github.com/kovetskiy/mark
|
||||
|
||||
GLOBAL OPTIONS:
|
||||
--files string, -f string use specified markdown file(s) for converting to html. Supports file globbing patterns (needs to be quoted). [$MARK_FILES]
|
||||
--continue-on-error don't exit if an error occurs while processing a file, continue processing remaining files. (default: false) [$MARK_CONTINUE_ON_ERROR]
|
||||
--compile-only show resulting HTML and don't update Confluence page content. (default: false) [$MARK_COMPILE_ONLY]
|
||||
--dry-run resolve page and ancestry, show resulting HTML and exit. (default: false) [$MARK_DRY_RUN]
|
||||
--edit-lock, -k lock page editing to current user only to prevent accidental manual edits over Confluence Web UI. (default: false) [$MARK_EDIT_LOCK]
|
||||
--drop-h1 don't include the first H1 heading in Confluence output. (default: false) [$MARK_DROP_H1]
|
||||
--strip-linebreaks, -L remove linebreaks inside of tags, to accommodate non-standard Confluence behavior (default: false) [$MARK_STRIP_LINEBREAKS]
|
||||
--title-from-h1 extract page title from a leading H1 heading. If no H1 heading on a page exists, then title must be set in the page metadata. (default: false) [$MARK_TITLE_FROM_H1]
|
||||
--title-append-generated-hash appends a short hash generated from the path of the page (space, parents, and title) to the title (default: false) [$MARK_TITLE_APPEND_GENERATED_HASH]
|
||||
--title-from-filename use the filename (without extension) as the Confluence page title if no explicit page title is set in the metadata. Mutually exclusive with --title-from-h1. (default: false) [$MARK_TITLE_FROM_FILENAME]
|
||||
--minor-edit don't send notifications while updating Confluence page. (default: false) [$MARK_MINOR_EDIT]
|
||||
--continue-on-error don't exit if an error occurs while processing a file, continue processing remaining files. [$MARK_CONTINUE_ON_ERROR]
|
||||
--compile-only show resulting HTML and don't update Confluence page content. [$MARK_COMPILE_ONLY]
|
||||
--dry-run resolve page and ancestry, show resulting HTML and exit. [$MARK_DRY_RUN]
|
||||
--edit-lock, -k lock page editing to current user only to prevent accidental manual edits over Confluence Web UI. [$MARK_EDIT_LOCK]
|
||||
--drop-h1 don't include the first H1 heading in Confluence output. [$MARK_DROP_H1]
|
||||
--strip-linebreaks, -L remove linebreaks inside of tags, to accommodate non-standard Confluence behavior [$MARK_STRIP_LINEBREAKS]
|
||||
--title-from-h1 extract page title from a leading H1 heading. If no H1 heading on a page exists, then title must be set in the page metadata. Mutually exclusive with --title-from-filename. [$MARK_TITLE_FROM_H1]
|
||||
--title-from-filename use the filename (without extension) as the Confluence page title if no explicit page title is set in the metadata. Mutually exclusive with --title-from-h1. [$MARK_TITLE_FROM_FILENAME]
|
||||
--title-append-generated-hash appends a short hash generated from the path of the page (space, parents, and title) to the title [$MARK_TITLE_APPEND_GENERATED_HASH]
|
||||
--minor-edit don't send notifications while updating Confluence page. [$MARK_MINOR_EDIT]
|
||||
--version-message string add a message to the page version, to explain the edit (default: "") [$MARK_VERSION_MESSAGE]
|
||||
--color string display logs in color. Possible values: auto, never. (default: "auto") [$MARK_COLOR]
|
||||
--log-level string set the log level. Possible values: TRACE, DEBUG, INFO, WARNING, ERROR, FATAL. (default: "info") [$MARK_LOG_LEVEL]
|
||||
@@ -839,17 +833,17 @@ GLOBAL OPTIONS:
|
||||
--password string, -p string use specified token for updating Confluence page. Specify - as password to read password from stdin, or your Personal access token. Username is not mandatory if personal access token is provided. For more info please see: https://developer.atlassian.com/server/confluence/confluence-server-rest-api/#authentication. [$MARK_PASSWORD]
|
||||
--target-url string, -l string edit specified Confluence page. If -l is not specified, file should contain metadata (see above). [$MARK_TARGET_URL]
|
||||
--base-url string, -b string base URL for Confluence. Alternative option for base_url config field. [$MARK_BASE_URL]
|
||||
--config string, -c string use the specified configuration file. (default: $HOME/.config/mark.toml") [$MARK_CONFIG]
|
||||
--ci run on CI mode. It won't fail if files are not found. (default: false) [$MARK_CI]
|
||||
--config string, -c string use the specified configuration file. (default: "$HOME/.config/mark.toml") [$MARK_CONFIG]
|
||||
--ci run on CI mode. It won't fail if files are not found. [$MARK_CI]
|
||||
--space string use specified space key. If the space key is not specified, it must be set in the page metadata. [$MARK_SPACE]
|
||||
--parents string A list containing the parents of the document separated by parents-delimiter (default: '/'). These will be prepended to the ones defined in the document itself. [$MARK_PARENTS]
|
||||
--parents-delimiter string The delimiter used for the parents list (default: "/") [$MARK_PARENTS_DELIMITER]
|
||||
--mermaid-scale float defines the scaling factor for mermaid renderings. (default: 1) [$MARK_MERMAID_SCALE]
|
||||
--include-path string Path for shared includes, used as a fallback if the include doesn't exist in the current directory. [$MARK_INCLUDE_PATH]
|
||||
--changes-only Avoids re-uploading pages that haven't changed since the last run. (default: false) [$MARK_CHANGES_ONLY]
|
||||
--changes-only Avoids re-uploading pages that haven't changed since the last run. [$MARK_CHANGES_ONLY]
|
||||
--d2-scale float defines the scaling factor for d2 renderings. (default: 1) [$MARK_D2_SCALE]
|
||||
--features string [ --features string ] Enables optional features. Current features: d2, mermaid (default: "mermaid") [$MARK_FEATURES]
|
||||
--insecure-skip-tls-verify Disables tls verification, useful for instances with self-signed certificates
|
||||
--features string [ --features string ] Enables optional features. Current features: d2, mermaid, mkdocsadmonitions (default: "mermaid") [$MARK_FEATURES]
|
||||
--insecure-skip-tls-verify skip TLS certificate verification (useful for self-signed certificates) [$MARK_INSECURE_SKIP_TLS_VERIFY]
|
||||
--help, -h show help
|
||||
--version, -v print the version
|
||||
```
|
||||
|
||||
@@ -61,6 +61,7 @@ type PageInfo struct {
|
||||
|
||||
Links struct {
|
||||
Full string `json:"webui"`
|
||||
Base string `json:"-"` // Not from JSON; populated from response _links.base
|
||||
} `json:"_links"`
|
||||
}
|
||||
|
||||
@@ -193,6 +194,9 @@ func (api *API) FindPage(
|
||||
) (*PageInfo, error) {
|
||||
result := struct {
|
||||
Results []PageInfo `json:"results"`
|
||||
Links struct {
|
||||
Base string `json:"base"`
|
||||
} `json:"_links"`
|
||||
}{}
|
||||
|
||||
payload := map[string]string{
|
||||
@@ -222,7 +226,13 @@ func (api *API) FindPage(
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return &result.Results[0], nil
|
||||
page := &result.Results[0]
|
||||
// Populate the base URL from the response _links.base
|
||||
if result.Links.Base != "" {
|
||||
page.Links.Base = result.Links.Base
|
||||
}
|
||||
|
||||
return page, nil
|
||||
}
|
||||
|
||||
func (api *API) CreateAttachment(
|
||||
|
||||
@@ -5,7 +5,7 @@ go 1.24.0
|
||||
toolchain go1.24.2
|
||||
|
||||
require (
|
||||
github.com/bmatcuk/doublestar/v4 v4.9.1
|
||||
github.com/bmatcuk/doublestar/v4 v4.9.2
|
||||
github.com/chromedp/cdproto v0.0.0-20250803210736-d308e07a266d
|
||||
github.com/chromedp/chromedp v0.14.2
|
||||
github.com/dreampuf/mermaid.go v0.0.39
|
||||
@@ -17,9 +17,9 @@ require (
|
||||
github.com/stefanfritsch/goldmark-admonitions v1.1.1
|
||||
github.com/stretchr/testify v1.11.1
|
||||
github.com/urfave/cli-altsrc/v3 v3.1.0
|
||||
github.com/urfave/cli/v3 v3.6.1
|
||||
github.com/yuin/goldmark v1.7.13
|
||||
golang.org/x/text v0.32.0
|
||||
github.com/urfave/cli/v3 v3.6.2
|
||||
github.com/yuin/goldmark v1.7.16
|
||||
golang.org/x/text v0.33.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
oss.terrastruct.com/d2 v0.7.1
|
||||
oss.terrastruct.com/util-go v0.0.0-20250213174338-243d8661088a
|
||||
|
||||
16
mark/go.sum
16
mark/go.sum
@@ -16,8 +16,8 @@ github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwTo
|
||||
github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY=
|
||||
github.com/andybalholm/cascadia v1.3.2 h1:3Xi6Dw5lHF15JtdcmAHD3i1+T8plmv7BQ/nsViSLyss=
|
||||
github.com/andybalholm/cascadia v1.3.2/go.mod h1:7gtRlve5FxPPgIgX36uWBX58OdBsSS6lUvCFb+h7KvU=
|
||||
github.com/bmatcuk/doublestar/v4 v4.9.1 h1:X8jg9rRZmJd4yRy7ZeNDRnM+T3ZfHv15JiBJ/avrEXE=
|
||||
github.com/bmatcuk/doublestar/v4 v4.9.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc=
|
||||
github.com/bmatcuk/doublestar/v4 v4.9.2 h1:b0mc6WyRSYLjzofB2v/0cuDUZ+MqoGyH3r0dVij35GI=
|
||||
github.com/bmatcuk/doublestar/v4 v4.9.2/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc=
|
||||
github.com/chromedp/cdproto v0.0.0-20250803210736-d308e07a266d h1:ZtA1sedVbEW7EW80Iz2GR3Ye6PwbJAJXjv7D74xG6HU=
|
||||
github.com/chromedp/cdproto v0.0.0-20250803210736-d308e07a266d/go.mod h1:NItd7aLkcfOA/dcMXvl8p1u+lQqioRMq/SqDp71Pb/k=
|
||||
github.com/chromedp/chromedp v0.14.2 h1:r3b/WtwM50RsBZHMUm9fsNhhzRStTHrKdr2zmwbZSzM=
|
||||
@@ -90,13 +90,13 @@ github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu
|
||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||
github.com/urfave/cli-altsrc/v3 v3.1.0 h1:6E5+kXeAWmRxXlPgdEVf9VqVoTJ2MJci0UMpUi/w/bA=
|
||||
github.com/urfave/cli-altsrc/v3 v3.1.0/go.mod h1:VcWVTGXcL3nrXUDJZagHAeUX702La3PKeWav7KpISqA=
|
||||
github.com/urfave/cli/v3 v3.6.1 h1:j8Qq8NyUawj/7rTYdBGrxcH7A/j7/G8Q5LhWEW4G3Mo=
|
||||
github.com/urfave/cli/v3 v3.6.1/go.mod h1:ysVLtOEmg2tOy6PknnYVhDoouyC/6N42TMeoMzskhso=
|
||||
github.com/urfave/cli/v3 v3.6.2 h1:lQuqiPrZ1cIz8hz+HcrG0TNZFxU70dPZ3Yl+pSrH9A8=
|
||||
github.com/urfave/cli/v3 v3.6.2/go.mod h1:ysVLtOEmg2tOy6PknnYVhDoouyC/6N42TMeoMzskhso=
|
||||
github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
|
||||
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
github.com/yuin/goldmark v1.7.13 h1:GPddIs617DnBLFFVJFgpo1aBfe/4xcvMc3SB5t/D0pA=
|
||||
github.com/yuin/goldmark v1.7.13/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg=
|
||||
github.com/yuin/goldmark v1.7.16 h1:n+CJdUxaFMiDUNnWC3dMWCIQJSkxH4uz3ZwQBkAlVNE=
|
||||
github.com/yuin/goldmark v1.7.16/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg=
|
||||
github.com/zazab/zhash v0.0.0-20221031090444-2b0d50417446 h1:75pcOSsb40+ub185cJI7g5uykl9Uu76rD5ONzK/4s40=
|
||||
github.com/zazab/zhash v0.0.0-20221031090444-2b0d50417446/go.mod h1:NtepZ8TEXErPsmQDMUoN72f8aIy4+xNinSJ3f1giess=
|
||||
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
||||
@@ -138,8 +138,8 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU=
|
||||
golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY=
|
||||
golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE=
|
||||
golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
|
||||
@@ -2,12 +2,14 @@ package page
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/kovetskiy/mark/confluence"
|
||||
@@ -40,6 +42,13 @@ func ResolveRelativeLinks(
|
||||
) ([]LinkSubstitution, error) {
|
||||
matches := parseLinks(string(markdown))
|
||||
|
||||
// If the user didn't provide --space, inherit the current document's space so
|
||||
// relative links can be resolved within the same space.
|
||||
spaceForLinks := spaceFromCli
|
||||
if spaceForLinks == "" && meta != nil {
|
||||
spaceForLinks = meta.Space
|
||||
}
|
||||
|
||||
links := []LinkSubstitution{}
|
||||
for _, match := range matches {
|
||||
log.Tracef(
|
||||
@@ -49,7 +58,7 @@ func ResolveRelativeLinks(
|
||||
match.filename,
|
||||
match.hash,
|
||||
)
|
||||
resolved, err := resolveLink(api, base, match, spaceFromCli, titleFromH1, titleFromFilename, parents, titleAppendGeneratedHash)
|
||||
resolved, err := resolveLink(api, base, match, spaceForLinks, titleFromH1, titleFromFilename, parents, titleAppendGeneratedHash)
|
||||
if err != nil {
|
||||
return nil, karma.Format(err, "resolve link: %q", match.full)
|
||||
}
|
||||
@@ -71,7 +80,7 @@ func resolveLink(
|
||||
api *confluence.API,
|
||||
base string,
|
||||
link markdownLink,
|
||||
spaceFromCli string,
|
||||
spaceForLinks string,
|
||||
titleFromH1 bool,
|
||||
titleFromFilename bool,
|
||||
parents []string,
|
||||
@@ -113,7 +122,7 @@ func resolveLink(
|
||||
|
||||
// This helps to determine if found link points to file that's
|
||||
// not markdown or have mark required metadata
|
||||
linkMeta, _, err := metadata.ExtractMeta(linkContents, spaceFromCli, titleFromH1, titleFromFilename, filepath, parents, titleAppendGeneratedHash)
|
||||
linkMeta, _, err := metadata.ExtractMeta(linkContents, spaceForLinks, titleFromH1, titleFromFilename, filepath, parents, titleAppendGeneratedHash)
|
||||
if err != nil {
|
||||
log.Errorf(
|
||||
err,
|
||||
@@ -193,34 +202,98 @@ func parseLinks(markdown string) []markdownLink {
|
||||
return links
|
||||
}
|
||||
|
||||
// getConfluenceLink build (to be) link for Confluence, and tries to verify from
|
||||
// API if there's real link available
|
||||
// getConfluenceLink builds a stable Confluence tiny link for the given page or blog post.
|
||||
// Tiny links use the format {baseURL}/x/{encodedPageID} and are immune to
|
||||
// Cloud-specific URL variations like /ex/confluence/<cloudId>/wiki/...
|
||||
func getConfluenceLink(
|
||||
api *confluence.API,
|
||||
space, title string,
|
||||
) (string, error) {
|
||||
link := fmt.Sprintf(
|
||||
"%s/display/%s/%s",
|
||||
api.BaseURL,
|
||||
space,
|
||||
url.QueryEscape(title),
|
||||
)
|
||||
|
||||
// Try to find as a page first
|
||||
page, err := api.FindPage(space, title, "page")
|
||||
if err != nil {
|
||||
return "", karma.Format(err, "api: find page")
|
||||
}
|
||||
|
||||
if page != nil {
|
||||
link = api.BaseURL + page.Links.Full
|
||||
// If not found as a page, try to find as a blog post
|
||||
if page == nil {
|
||||
page, err = api.FindPage(space, title, "blogpost")
|
||||
if err != nil {
|
||||
return "", karma.Format(err, "api: find blogpost")
|
||||
}
|
||||
}
|
||||
|
||||
linkUrl, err := url.Parse(link)
|
||||
if err != nil {
|
||||
return "", karma.Format(err, "parse URL: %s", link)
|
||||
if page == nil {
|
||||
return "", nil
|
||||
}
|
||||
// Confluence supports relative links to reference other pages:
|
||||
// https://confluence.atlassian.com/doc/links-776656293.html
|
||||
linkPath := linkUrl.Path
|
||||
return linkPath, nil
|
||||
|
||||
// Prefer the base URL from the API response (_links.base) as it contains
|
||||
// the canonical user-facing wiki URL (e.g., https://tenant.atlassian.net/wiki).
|
||||
// Fall back to api.BaseURL if _links.base is not available.
|
||||
baseURL := page.Links.Base
|
||||
if baseURL == "" {
|
||||
baseURL = api.BaseURL
|
||||
}
|
||||
|
||||
tiny, err := GenerateTinyLink(baseURL, page.ID)
|
||||
if err != nil {
|
||||
return "", karma.Format(err, "generate tiny link for page %s", page.ID)
|
||||
}
|
||||
|
||||
return tiny, nil
|
||||
}
|
||||
|
||||
// GenerateTinyLink generates a Confluence tiny link from a page ID.
|
||||
// The algorithm converts the page ID to a little-endian 32-bit byte array,
|
||||
// base64-encodes it, and applies URL-safe transformations.
|
||||
// Format: {baseURL}/x/{encodedID}
|
||||
//
|
||||
// Reference: https://support.atlassian.com/confluence/kb/how-to-programmatically-generate-the-tiny-link-of-a-confluence-page
|
||||
func GenerateTinyLink(baseURL string, pageID string) (string, error) {
|
||||
id, err := strconv.ParseUint(pageID, 10, 64)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("invalid page ID %q: %w", pageID, err)
|
||||
}
|
||||
|
||||
encoded := encodeTinyLinkID(id)
|
||||
baseURL = strings.TrimSuffix(baseURL, "/")
|
||||
|
||||
return baseURL + "/x/" + encoded, nil
|
||||
}
|
||||
|
||||
// encodeTinyLinkID encodes a page ID into the Confluence tiny link format.
|
||||
// This is the core algorithm extracted for testability.
|
||||
func encodeTinyLinkID(id uint64) string {
|
||||
// Pack as little-endian. Use 8 bytes to support large page IDs,
|
||||
// but the base64 trimming will remove unnecessary trailing zeros.
|
||||
buf := make([]byte, 8)
|
||||
binary.LittleEndian.PutUint64(buf, id)
|
||||
|
||||
// Trim trailing zero bytes (they become 'A' padding in base64)
|
||||
for len(buf) > 1 && buf[len(buf)-1] == 0 {
|
||||
buf = buf[:len(buf)-1]
|
||||
}
|
||||
|
||||
// Base64 encode
|
||||
encoded := base64.StdEncoding.EncodeToString(buf)
|
||||
|
||||
// Transform to URL-safe format:
|
||||
// - Strip '=' padding
|
||||
// - Replace '/' with '-'
|
||||
// - Replace '+' with '_'
|
||||
var result strings.Builder
|
||||
for _, c := range encoded {
|
||||
switch c {
|
||||
case '=':
|
||||
continue
|
||||
case '/':
|
||||
result.WriteByte('-')
|
||||
case '+':
|
||||
result.WriteByte('_')
|
||||
default:
|
||||
result.WriteRune(c)
|
||||
}
|
||||
}
|
||||
|
||||
return result.String()
|
||||
}
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
package page
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/binary"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
@@ -51,3 +54,153 @@ func TestParseLinks(t *testing.T) {
|
||||
assert.Equal(t, "example.md", links[7].full)
|
||||
assert.Equal(t, len(links), 8)
|
||||
}
|
||||
|
||||
func TestEncodeTinyLinkID(t *testing.T) {
|
||||
// Test cases for the tiny link encoding algorithm.
|
||||
// The algorithm: little-endian bytes -> base64 -> URL-safe transform
|
||||
tests := []struct {
|
||||
name string
|
||||
pageID uint64
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
name: "small page ID",
|
||||
pageID: 98319,
|
||||
expected: "D4AB",
|
||||
},
|
||||
{
|
||||
name: "another small page ID",
|
||||
pageID: 98320,
|
||||
expected: "EIAB",
|
||||
},
|
||||
{
|
||||
name: "large page ID (Confluence Cloud)",
|
||||
pageID: 5000000001,
|
||||
expected: "AfIFKgE",
|
||||
},
|
||||
{
|
||||
name: "page ID 1",
|
||||
pageID: 1,
|
||||
expected: "AQ",
|
||||
},
|
||||
{
|
||||
name: "page ID 255",
|
||||
pageID: 255,
|
||||
expected: "-w",
|
||||
},
|
||||
{
|
||||
name: "page ID 256",
|
||||
pageID: 256,
|
||||
expected: "AAE",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result := encodeTinyLinkID(tt.pageID)
|
||||
assert.Equal(t, tt.expected, result)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenerateTinyLink(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
baseURL string
|
||||
pageID string
|
||||
expected string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "cloud URL with trailing slash",
|
||||
baseURL: "https://example.atlassian.net/wiki/",
|
||||
pageID: "5000000001",
|
||||
expected: "https://example.atlassian.net/wiki/x/AfIFKgE",
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "cloud URL without trailing slash",
|
||||
baseURL: "https://example.atlassian.net/wiki",
|
||||
pageID: "5000000001",
|
||||
expected: "https://example.atlassian.net/wiki/x/AfIFKgE",
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "server URL",
|
||||
baseURL: "https://confluence.example.com",
|
||||
pageID: "98319",
|
||||
expected: "https://confluence.example.com/x/D4AB",
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "invalid page ID",
|
||||
baseURL: "https://example.atlassian.net/wiki",
|
||||
pageID: "not-a-number",
|
||||
expected: "",
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result, err := GenerateTinyLink(tt.baseURL, tt.pageID)
|
||||
if tt.wantErr {
|
||||
assert.Error(t, err)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, tt.expected, result)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// encodeTinyLinkIDPerl32 implements the Perl algorithm from Atlassian docs
|
||||
// using pack("L", $pageID) which is 32-bit little-endian.
|
||||
// This is used to validate our implementation matches the documented algorithm.
|
||||
func encodeTinyLinkIDPerl32(id uint32) string {
|
||||
buf := make([]byte, 4)
|
||||
binary.LittleEndian.PutUint32(buf, id)
|
||||
encoded := base64.StdEncoding.EncodeToString(buf)
|
||||
|
||||
var result strings.Builder
|
||||
for _, c := range encoded {
|
||||
switch c {
|
||||
case '=':
|
||||
continue
|
||||
case '/':
|
||||
result.WriteByte('-')
|
||||
case '+':
|
||||
result.WriteByte('_')
|
||||
default:
|
||||
result.WriteRune(c)
|
||||
}
|
||||
}
|
||||
s := result.String()
|
||||
// Perl strips trailing 'A' chars (which are base64 for zero bits)
|
||||
s = strings.TrimRight(s, "A")
|
||||
return s
|
||||
}
|
||||
|
||||
func TestEncodeTinyLinkIDMatchesPerl(t *testing.T) {
|
||||
// Validate that our implementation matches the Perl algorithm from:
|
||||
// https://support.atlassian.com/confluence/kb/how-to-programmatically-generate-the-tiny-link-of-a-confluence-page
|
||||
testIDs := []uint32{1, 255, 256, 65535, 98319, 98320}
|
||||
|
||||
for _, id := range testIDs {
|
||||
goResult := encodeTinyLinkID(uint64(id))
|
||||
perlResult := encodeTinyLinkIDPerl32(id)
|
||||
assert.Equal(t, perlResult, goResult, "ID %d should match Perl implementation", id)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEncodeTinyLinkIDLargeIDs(t *testing.T) {
|
||||
// Test large page IDs (> 32-bit) which are common in Confluence Cloud
|
||||
// These exceed Perl's pack("L") but our implementation handles them
|
||||
largeID := uint64(5000000001)
|
||||
result := encodeTinyLinkID(largeID)
|
||||
assert.NotEmpty(t, result)
|
||||
assert.Equal(t, "AfIFKgE", result)
|
||||
|
||||
// Verify the result is a valid URL-safe base64-like string
|
||||
assert.Regexp(t, `^[A-Za-z0-9_-]+$`, result)
|
||||
}
|
||||
|
||||
@@ -108,6 +108,10 @@ func (r *ConfluenceFencedCodeBlockRenderer) renderFencedCodeBlock(writer util.Bu
|
||||
collapse = false
|
||||
continue
|
||||
}
|
||||
if option == "linenumbers" {
|
||||
linenumbers = true
|
||||
continue
|
||||
}
|
||||
|
||||
var i int
|
||||
if _, err := fmt.Sscanf(option, "%d", &i); err == nil {
|
||||
|
||||
@@ -151,7 +151,7 @@ func templates(api *confluence.API) (*template.Template, error) {
|
||||
`<ac:structured-macro ac:name="jira">`,
|
||||
`<ac:parameter ac:name="key">{{ .Ticket }}</ac:parameter>`,
|
||||
`{{ if .Server }}`,
|
||||
`<ac:parameter ac:name="server">{{ .Server }}</ac:parameter>`,
|
||||
`<ac:parameter ac:name="server">{{ .Server }}</ac:parameter>`,
|
||||
`{{ end }}`,
|
||||
`</ac:structured-macro>`,
|
||||
),
|
||||
@@ -451,6 +451,15 @@ func templates(api *confluence.API) (*template.Template, error) {
|
||||
`<ac:parameter ac:name="autoplay">{{ or .AutoPlay "false"}}</ac:parameter>`,
|
||||
`</ac:structured-macro>`,
|
||||
),
|
||||
/* https://confluence.atlassian.com/conf59/view-file-macro-792499226.html */
|
||||
`ac:view-file`: text(
|
||||
`<ac:structured-macro ac:name="view-file">`,
|
||||
`<ac:parameter ac:name="name">`,
|
||||
`<ri:attachment ri:filename="{{ .Name | convertAttachment }}"/>`,
|
||||
`</ac:parameter>`,
|
||||
`<ac:parameter ac:name="height">{{ or .Height 250 }}</ac:parameter>`,
|
||||
`</ac:structured-macro>`,
|
||||
),
|
||||
|
||||
// TODO(seletskiy): more templates here
|
||||
} {
|
||||
|
||||
32
vendor/github.com/bmatcuk/doublestar/v4/README.md
generated
vendored
32
vendor/github.com/bmatcuk/doublestar/v4/README.md
generated
vendored
@@ -391,6 +391,38 @@ Class | Meaning
|
||||
`[^class]` | matches any single character which does *not* match the class
|
||||
`[!class]` | same as `^`: negates the class
|
||||
|
||||
#### Globs Are Not Regular Expressions
|
||||
|
||||
Occasionally I get bug reports that some regular-expression-style syntax
|
||||
doesn't work, or feature requests to add some regular-expression-inspired
|
||||
syntax. Globs are not regular expressions. However, if globs are not
|
||||
sufficiently expressive for your filtering needs, I recommend a two stage
|
||||
approach using `GlobWalk`. Something like the following will get you started:
|
||||
|
||||
```go
|
||||
var matches []string
|
||||
err := doublestar.GlobWalk(fsys, pattern, func(p string, d fs.DirEntry) error {
|
||||
if (customFilter(p, d)) {
|
||||
matches = append(matches, p)
|
||||
} else if (d.isDir()) {
|
||||
return doublestar.SkipDir
|
||||
}
|
||||
return nil
|
||||
})
|
||||
return matches, err
|
||||
```
|
||||
|
||||
In this example, `pattern` should be a glob that does a first pass at fetching
|
||||
the files you might be interested in; `customFilter` is a function that does a
|
||||
second pass. This second pass could be anything, including regular expressions.
|
||||
Try to fashion a `pattern` that reduces the number of files you need to
|
||||
consider in your second pass `customFilter`.
|
||||
|
||||
One final note: empty alternatives can be used to build some more complicated
|
||||
globs. For example, `some{thing,}` will match both "something" and "some".
|
||||
Alternatives can also be nested, like `some{thing{new,},}`, which would match
|
||||
"somethingnew", "something", and "some".
|
||||
|
||||
## Performance
|
||||
|
||||
```
|
||||
|
||||
2
vendor/github.com/bmatcuk/doublestar/v4/glob.go
generated
vendored
2
vendor/github.com/bmatcuk/doublestar/v4/glob.go
generated
vendored
@@ -158,7 +158,7 @@ func (g *glob) globAlts(fsys fs.FS, pattern string, openingIdx, closingIdx int,
|
||||
nextIdx += patIdx
|
||||
}
|
||||
|
||||
alt := buildAlt(d, pattern, startIdx, openingIdx, patIdx, nextIdx, afterIdx)
|
||||
alt := buildAlt(escapeMeta(d), pattern, startIdx, openingIdx, patIdx, nextIdx, afterIdx)
|
||||
matches, err = g.doGlob(fsys, alt, matches, firstSegment, beforeMeta)
|
||||
if err != nil {
|
||||
return
|
||||
|
||||
2
vendor/github.com/bmatcuk/doublestar/v4/globwalk.go
generated
vendored
2
vendor/github.com/bmatcuk/doublestar/v4/globwalk.go
generated
vendored
@@ -205,7 +205,7 @@ func (g *glob) doGlobAltsWalk(fsys fs.FS, d, pattern string, startIdx, openingId
|
||||
nextIdx += patIdx
|
||||
}
|
||||
|
||||
alt := buildAlt(d, pattern, startIdx, openingIdx, patIdx, nextIdx, afterIdx)
|
||||
alt := buildAlt(escapeMeta(d), pattern, startIdx, openingIdx, patIdx, nextIdx, afterIdx)
|
||||
err = g.doGlobWalk(fsys, alt, firstSegment, beforeMeta, func(p string, d fs.DirEntry) error {
|
||||
// insertion sort, ignoring dups
|
||||
insertIdx := matchesLen
|
||||
|
||||
11
vendor/github.com/bmatcuk/doublestar/v4/utils.go
generated
vendored
11
vendor/github.com/bmatcuk/doublestar/v4/utils.go
generated
vendored
@@ -152,9 +152,16 @@ func indexNextAlt(s string, allowEscaping bool) int {
|
||||
return -1
|
||||
}
|
||||
|
||||
var metaReplacer = strings.NewReplacer("\\*", "*", "\\?", "?", "\\[", "[", "\\]", "]", "\\{", "{", "\\}", "}")
|
||||
var escapeMetaReplacer = strings.NewReplacer("*", "\\*", "?", "\\?", "[", "\\[", "]", "\\]", "{", "\\{", "}", "\\}")
|
||||
|
||||
// Escapes meta characters (*?[]{})
|
||||
func escapeMeta(path string) string {
|
||||
return escapeMetaReplacer.Replace(path)
|
||||
}
|
||||
|
||||
var unescapeMetaReplacer = strings.NewReplacer("\\*", "*", "\\?", "?", "\\[", "[", "\\]", "]", "\\{", "{", "\\}", "}")
|
||||
|
||||
// Unescapes meta characters (*?[]{})
|
||||
func unescapeMeta(pattern string) string {
|
||||
return metaReplacer.Replace(pattern)
|
||||
return unescapeMetaReplacer.Replace(pattern)
|
||||
}
|
||||
|
||||
6
vendor/github.com/urfave/cli/v3/args.go
generated
vendored
6
vendor/github.com/urfave/cli/v3/args.go
generated
vendored
@@ -180,12 +180,10 @@ func (a *ArgumentsBase[T, C, VC]) Usage() string {
|
||||
func (a *ArgumentsBase[T, C, VC]) Parse(s []string) ([]string, error) {
|
||||
tracef("calling arg%[1] parse with args %[2]", &a.Name, s)
|
||||
if a.Max == 0 {
|
||||
fmt.Printf("WARNING args %s has max 0, not parsing argument\n", a.Name)
|
||||
return s, nil
|
||||
return s, fmt.Errorf("args %s has max 0, not parsing argument", a.Name)
|
||||
}
|
||||
if a.Max != -1 && a.Min > a.Max {
|
||||
fmt.Printf("WARNING args %s has min[%d] > max[%d], not parsing argument\n", a.Name, a.Min, a.Max)
|
||||
return s, nil
|
||||
return s, fmt.Errorf("args %s has min[%d] > max[%d], not parsing argument", a.Name, a.Min, a.Max)
|
||||
}
|
||||
|
||||
count := 0
|
||||
|
||||
12
vendor/github.com/urfave/cli/v3/command.go
generated
vendored
12
vendor/github.com/urfave/cli/v3/command.go
generated
vendored
@@ -161,6 +161,8 @@ type Command struct {
|
||||
globaHelpFlagAdded bool
|
||||
// whether global version flag was added
|
||||
globaVersionFlagAdded bool
|
||||
// whether this is a completion command
|
||||
isCompletionCommand bool
|
||||
}
|
||||
|
||||
// FullName returns the full name of the command.
|
||||
@@ -334,14 +336,10 @@ func (cmd *Command) handleExitCoder(ctx context.Context, err error) error {
|
||||
}
|
||||
|
||||
func (cmd *Command) argsWithDefaultCommand(oldArgs Args) Args {
|
||||
if cmd.DefaultCommand != "" {
|
||||
rawArgs := append([]string{cmd.DefaultCommand}, oldArgs.Slice()...)
|
||||
newArgs := &stringSliceArgs{v: rawArgs}
|
||||
rawArgs := append([]string{cmd.DefaultCommand}, oldArgs.Slice()...)
|
||||
newArgs := &stringSliceArgs{v: rawArgs}
|
||||
|
||||
return newArgs
|
||||
}
|
||||
|
||||
return oldArgs
|
||||
return newArgs
|
||||
}
|
||||
|
||||
// Root returns the Command at the root of the graph
|
||||
|
||||
20
vendor/github.com/urfave/cli/v3/command_run.go
generated
vendored
20
vendor/github.com/urfave/cli/v3/command_run.go
generated
vendored
@@ -5,7 +5,6 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"reflect"
|
||||
"slices"
|
||||
"unicode"
|
||||
)
|
||||
@@ -140,13 +139,20 @@ func (cmd *Command) run(ctx context.Context, osArgs []string) (_ context.Context
|
||||
}
|
||||
|
||||
var rargs Args = &stringSliceArgs{v: osArgs}
|
||||
var args Args = &stringSliceArgs{rargs.Tail()}
|
||||
|
||||
if cmd.isCompletionCommand || cmd.Name == helpName {
|
||||
tracef("special command detected, skipping pre-parse (cmd=%[1]q)", cmd.Name)
|
||||
cmd.parsedArgs = args
|
||||
return ctx, cmd.Action(ctx, cmd)
|
||||
}
|
||||
|
||||
for _, f := range cmd.allFlags() {
|
||||
if err := f.PreParse(); err != nil {
|
||||
return ctx, err
|
||||
}
|
||||
}
|
||||
|
||||
var args Args = &stringSliceArgs{rargs.Tail()}
|
||||
var err error
|
||||
|
||||
if cmd.SkipFlagParsing {
|
||||
@@ -252,6 +258,7 @@ func (cmd *Command) run(ctx context.Context, osArgs []string) (_ context.Context
|
||||
|
||||
if cmd.SuggestCommandFunc != nil && name != "--" {
|
||||
name = cmd.SuggestCommandFunc(cmd.Commands, name)
|
||||
tracef("suggested command name=%1[q] (cmd=%[2]q)", name, cmd.Name)
|
||||
}
|
||||
subCmd = cmd.Command(name)
|
||||
if subCmd == nil {
|
||||
@@ -263,14 +270,13 @@ func (cmd *Command) run(ctx context.Context, osArgs []string) (_ context.Context
|
||||
}
|
||||
|
||||
if isFlagName || hasDefault {
|
||||
argsWithDefault := cmd.argsWithDefaultCommand(args)
|
||||
argsWithDefault := cmd.argsWithDefaultCommand(cmd.parsedArgs)
|
||||
tracef("using default command args=%[1]q (cmd=%[2]q)", argsWithDefault, cmd.Name)
|
||||
if !reflect.DeepEqual(args, argsWithDefault) {
|
||||
subCmd = cmd.Command(argsWithDefault.First())
|
||||
}
|
||||
subCmd = cmd.Command(argsWithDefault.First())
|
||||
cmd.parsedArgs = argsWithDefault
|
||||
}
|
||||
}
|
||||
} else if cmd.parent == nil && cmd.DefaultCommand != "" {
|
||||
} else if cmd.DefaultCommand != "" {
|
||||
tracef("no positional args present; checking default command %[1]q (cmd=%[2]q)", cmd.DefaultCommand, cmd.Name)
|
||||
|
||||
if dc := cmd.Command(cmd.DefaultCommand); dc != cmd {
|
||||
|
||||
1
vendor/github.com/urfave/cli/v3/completion.go
generated
vendored
1
vendor/github.com/urfave/cli/v3/completion.go
generated
vendored
@@ -65,6 +65,7 @@ func buildCompletionCommand(appName string) *Command {
|
||||
Action: func(ctx context.Context, cmd *Command) error {
|
||||
return printShellCompletion(ctx, cmd, appName)
|
||||
},
|
||||
isCompletionCommand: true,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
10
vendor/github.com/urfave/cli/v3/errors.go
generated
vendored
10
vendor/github.com/urfave/cli/v3/errors.go
generated
vendored
@@ -150,12 +150,10 @@ func HandleExitCoder(err error) {
|
||||
}
|
||||
|
||||
if exitErr, ok := err.(ExitCoder); ok {
|
||||
if err.Error() != "" {
|
||||
if _, ok := exitErr.(ErrorFormatter); ok {
|
||||
_, _ = fmt.Fprintf(ErrWriter, "%+v\n", err)
|
||||
} else {
|
||||
_, _ = fmt.Fprintln(ErrWriter, err)
|
||||
}
|
||||
if _, ok := exitErr.(ErrorFormatter); ok {
|
||||
_, _ = fmt.Fprintf(ErrWriter, "%+v\n", err)
|
||||
} else {
|
||||
_, _ = fmt.Fprintln(ErrWriter, err)
|
||||
}
|
||||
OsExiter(exitErr.ExitCode())
|
||||
return
|
||||
|
||||
5
vendor/github.com/urfave/cli/v3/flag.go
generated
vendored
5
vendor/github.com/urfave/cli/v3/flag.go
generated
vendored
@@ -77,11 +77,6 @@ func (f FlagsByName) Len() int {
|
||||
}
|
||||
|
||||
func (f FlagsByName) Less(i, j int) bool {
|
||||
if len(f[j].Names()) == 0 {
|
||||
return false
|
||||
} else if len(f[i].Names()) == 0 {
|
||||
return true
|
||||
}
|
||||
return lexicographicLess(f[i].Names()[0], f[j].Names()[0])
|
||||
}
|
||||
|
||||
|
||||
7
vendor/github.com/urfave/cli/v3/flag_bool.go
generated
vendored
7
vendor/github.com/urfave/cli/v3/flag_bool.go
generated
vendored
@@ -50,7 +50,8 @@ func (b boolValue) Create(val bool, p *bool, c BoolConfig) Value {
|
||||
|
||||
// ToString formats the bool value
|
||||
func (b boolValue) ToString(value bool) string {
|
||||
return strconv.FormatBool(value)
|
||||
b.destination = &value
|
||||
return b.String()
|
||||
}
|
||||
|
||||
// Below functions are to satisfy the flag.Value interface
|
||||
@@ -75,7 +76,3 @@ func (b *boolValue) String() string {
|
||||
}
|
||||
|
||||
func (b *boolValue) IsBoolFlag() bool { return true }
|
||||
|
||||
func (b *boolValue) Count() int {
|
||||
return *b.count
|
||||
}
|
||||
|
||||
7
vendor/github.com/urfave/cli/v3/flag_duration.go
generated
vendored
7
vendor/github.com/urfave/cli/v3/flag_duration.go
generated
vendored
@@ -18,7 +18,8 @@ func (d durationValue) Create(val time.Duration, p *time.Duration, c NoConfig) V
|
||||
}
|
||||
|
||||
func (d durationValue) ToString(val time.Duration) string {
|
||||
return fmt.Sprintf("%v", val)
|
||||
d = durationValue(val)
|
||||
return d.String()
|
||||
}
|
||||
|
||||
// Below functions are to satisfy the flag.Value interface
|
||||
@@ -34,7 +35,9 @@ func (d *durationValue) Set(s string) error {
|
||||
|
||||
func (d *durationValue) Get() any { return time.Duration(*d) }
|
||||
|
||||
func (d *durationValue) String() string { return (*time.Duration)(d).String() }
|
||||
func (d *durationValue) String() string {
|
||||
return fmt.Sprintf("%v", time.Duration(*d))
|
||||
}
|
||||
|
||||
func (cmd *Command) Duration(name string) time.Duration {
|
||||
if v, ok := cmd.Value(name).(time.Duration); ok {
|
||||
|
||||
3
vendor/github.com/urfave/cli/v3/flag_float.go
generated
vendored
3
vendor/github.com/urfave/cli/v3/flag_float.go
generated
vendored
@@ -25,7 +25,8 @@ func (f floatValue[T]) Create(val T, p *T, c NoConfig) Value {
|
||||
}
|
||||
|
||||
func (f floatValue[T]) ToString(b T) string {
|
||||
return strconv.FormatFloat(float64(b), 'g', -1, int(unsafe.Sizeof(T(0))*8))
|
||||
f.val = &b
|
||||
return f.String()
|
||||
}
|
||||
|
||||
// Below functions are to satisfy the flag.Value interface
|
||||
|
||||
6
vendor/github.com/urfave/cli/v3/flag_generic.go
generated
vendored
6
vendor/github.com/urfave/cli/v3/flag_generic.go
generated
vendored
@@ -17,10 +17,8 @@ func (f genericValue) Create(val Value, p *Value, c NoConfig) Value {
|
||||
}
|
||||
|
||||
func (f genericValue) ToString(b Value) string {
|
||||
if b != nil {
|
||||
return b.String()
|
||||
}
|
||||
return ""
|
||||
f.val = b
|
||||
return f.String()
|
||||
}
|
||||
|
||||
// Below functions are to satisfy the flag.Value interface
|
||||
|
||||
7
vendor/github.com/urfave/cli/v3/flag_int.go
generated
vendored
7
vendor/github.com/urfave/cli/v3/flag_int.go
generated
vendored
@@ -36,11 +36,8 @@ func (i intValue[T]) Create(val T, p *T, c IntegerConfig) Value {
|
||||
}
|
||||
|
||||
func (i intValue[T]) ToString(b T) string {
|
||||
if i.base == 0 {
|
||||
i.base = 10
|
||||
}
|
||||
|
||||
return strconv.FormatInt(int64(b), i.base)
|
||||
i.val = &b
|
||||
return i.String()
|
||||
}
|
||||
|
||||
// Below functions are to satisfy the flag.Value interface
|
||||
|
||||
18
vendor/github.com/urfave/cli/v3/flag_slice_base.go
generated
vendored
18
vendor/github.com/urfave/cli/v3/flag_slice_base.go
generated
vendored
@@ -82,12 +82,12 @@ func (i *SliceBase[T, C, VC]) Set(value string) error {
|
||||
|
||||
// String returns a readable representation of this value (for usage defaults)
|
||||
func (i *SliceBase[T, C, VC]) String() string {
|
||||
v := i.Value()
|
||||
var t T
|
||||
if reflect.TypeOf(t).Kind() == reflect.String {
|
||||
return fmt.Sprintf("%v", v)
|
||||
var defaultVals []string
|
||||
var v VC
|
||||
for _, s := range *i.slice {
|
||||
defaultVals = append(defaultVals, v.ToString(s))
|
||||
}
|
||||
return fmt.Sprintf("%T{%s}", v, i.ToString(v))
|
||||
return strings.Join(defaultVals, ", ")
|
||||
}
|
||||
|
||||
// Serialize allows SliceBase to fulfill Serializer
|
||||
@@ -110,10 +110,6 @@ func (i *SliceBase[T, C, VC]) Get() interface{} {
|
||||
}
|
||||
|
||||
func (i SliceBase[T, C, VC]) ToString(t []T) string {
|
||||
var defaultVals []string
|
||||
var v VC
|
||||
for _, s := range t {
|
||||
defaultVals = append(defaultVals, v.ToString(s))
|
||||
}
|
||||
return strings.Join(defaultVals, ", ")
|
||||
i.slice = &t
|
||||
return i.String()
|
||||
}
|
||||
|
||||
10
vendor/github.com/urfave/cli/v3/flag_string.go
generated
vendored
10
vendor/github.com/urfave/cli/v3/flag_string.go
generated
vendored
@@ -30,10 +30,8 @@ func (s stringValue) Create(val string, p *string, c StringConfig) Value {
|
||||
}
|
||||
|
||||
func (s stringValue) ToString(val string) string {
|
||||
if val == "" {
|
||||
return val
|
||||
}
|
||||
return fmt.Sprintf("%q", val)
|
||||
s.destination = &val
|
||||
return s.String()
|
||||
}
|
||||
|
||||
// Below functions are to satisfy the flag.Value interface
|
||||
@@ -49,8 +47,8 @@ func (s *stringValue) Set(val string) error {
|
||||
func (s *stringValue) Get() any { return *s.destination }
|
||||
|
||||
func (s *stringValue) String() string {
|
||||
if s.destination != nil {
|
||||
return *s.destination
|
||||
if s.destination != nil && *s.destination != "" {
|
||||
return fmt.Sprintf("%q", *s.destination)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
5
vendor/github.com/urfave/cli/v3/flag_timestamp.go
generated
vendored
5
vendor/github.com/urfave/cli/v3/flag_timestamp.go
generated
vendored
@@ -44,7 +44,8 @@ func (t timestampValue) ToString(b time.Time) string {
|
||||
if b.IsZero() {
|
||||
return ""
|
||||
}
|
||||
return fmt.Sprintf("%v", b)
|
||||
t.timestamp = &b
|
||||
return t.String()
|
||||
}
|
||||
|
||||
// Below functions are to satisfy the Value interface
|
||||
@@ -122,7 +123,7 @@ func (t *timestampValue) Set(value string) error {
|
||||
|
||||
// String returns a readable representation of this value (for usage defaults)
|
||||
func (t *timestampValue) String() string {
|
||||
return fmt.Sprintf("%#v", t.timestamp)
|
||||
return fmt.Sprintf("%v", t.timestamp)
|
||||
}
|
||||
|
||||
// Get returns the flag structure
|
||||
|
||||
8
vendor/github.com/urfave/cli/v3/flag_uint.go
generated
vendored
8
vendor/github.com/urfave/cli/v3/flag_uint.go
generated
vendored
@@ -31,12 +31,8 @@ func (i uintValue[T]) Create(val T, p *T, c IntegerConfig) Value {
|
||||
}
|
||||
|
||||
func (i uintValue[T]) ToString(b T) string {
|
||||
base := i.base
|
||||
if base == 0 {
|
||||
base = 10
|
||||
}
|
||||
|
||||
return strconv.FormatUint(uint64(b), base)
|
||||
i.val = &b
|
||||
return i.String()
|
||||
}
|
||||
|
||||
// Below functions are to satisfy the flag.Value interface
|
||||
|
||||
2
vendor/github.com/urfave/cli/v3/help.go
generated
vendored
2
vendor/github.com/urfave/cli/v3/help.go
generated
vendored
@@ -188,7 +188,7 @@ func printCommandSuggestions(commands []*Command, writer io.Writer) {
|
||||
if command.Hidden {
|
||||
continue
|
||||
}
|
||||
if strings.HasSuffix(os.Getenv("SHELL"), "zsh") {
|
||||
if strings.HasSuffix(os.Getenv("SHELL"), "zsh") && len(command.Usage) > 0 {
|
||||
_, _ = fmt.Fprintf(writer, "%s:%s\n", command.Name, command.Usage)
|
||||
} else {
|
||||
_, _ = fmt.Fprintf(writer, "%s\n", command.Name)
|
||||
|
||||
2
vendor/github.com/urfave/cli/v3/mkdocs-requirements.txt
generated
vendored
2
vendor/github.com/urfave/cli/v3/mkdocs-requirements.txt
generated
vendored
@@ -1,5 +1,5 @@
|
||||
mkdocs-git-revision-date-localized-plugin==1.5.0
|
||||
mkdocs-material==9.6.23
|
||||
mkdocs-material==9.7.1
|
||||
mkdocs==1.6.1
|
||||
mkdocs-redirects==1.2.2
|
||||
pygments==2.19.2
|
||||
|
||||
4
vendor/github.com/yuin/goldmark/README.md
generated
vendored
4
vendor/github.com/yuin/goldmark/README.md
generated
vendored
@@ -497,6 +497,10 @@ Extensions
|
||||
- [goldmark-wiki-table](https://github.com/movsb/goldmark-wiki-table): Adds support for embedding Wiki Tables.
|
||||
- [goldmark-tgmd](https://github.com/Mad-Pixels/goldmark-tgmd): A Telegram markdown renderer that can be passed to `goldmark.WithRenderer()`.
|
||||
- [goldmark-treeblood](https://github.com/Wyatt915/goldmark-treeblood): Renders $\LaTeX$ expressions as MathML (pure Go, no external dependencies).
|
||||
- [goldmark-subtext](https://github.com/zeozeozeo/goldmark-subtext): Support for Discord-style markdown subtexts
|
||||
- [goldmark-customtag](https://github.com/tendstofortytwo/goldmark-customtag): Allows you to define custom block tags.
|
||||
- [goldmark-cjk-friendly](https://github.com/tats-u/goldmark-cjk-friendly): Port of npm package [`remark-cjk-friendly` / `markdown-it-cjk-friendly`](https://github.com/tats-u/markdown-cjk-friendly) to goldmark. Similar to the [CJK extension](#cjk-extension) (`WithEscapedSpace`), but you do not need to explicitly add `\ ` around `*` and `**`. You can combine this with the [CJK extension](#cjk-extension).
|
||||
- [goldmark-chart](https://github.com/TheGreatRambler/goldmark-chart): Generate static ChartJS charts using the simple [Markvis](https://markvis.js.org/#/) format.
|
||||
|
||||
### Loading extensions at runtime
|
||||
[goldmark-dynamic](https://github.com/yuin/goldmark-dynamic) allows you to write a goldmark extension in Lua and load it at runtime without re-compilation.
|
||||
|
||||
6
vendor/github.com/yuin/goldmark/extension/table.go
generated
vendored
6
vendor/github.com/yuin/goldmark/extension/table.go
generated
vendored
@@ -125,12 +125,16 @@ func isTableDelim(bs []byte) bool {
|
||||
if w, _ := util.IndentWidth(bs, 0); w > 3 {
|
||||
return false
|
||||
}
|
||||
allSep := true
|
||||
for _, b := range bs {
|
||||
if b != '-' {
|
||||
allSep = false
|
||||
}
|
||||
if !(util.IsSpace(b) || b == '-' || b == '|' || b == ':') {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
return !allSep
|
||||
}
|
||||
|
||||
var tableDelimLeft = regexp.MustCompile(`^\s*\:\-+\s*$`)
|
||||
|
||||
131
vendor/github.com/yuin/goldmark/parser/atx_heading.go
generated
vendored
131
vendor/github.com/yuin/goldmark/parser/atx_heading.go
generated
vendored
@@ -13,7 +13,7 @@ type HeadingConfig struct {
|
||||
}
|
||||
|
||||
// SetOption implements SetOptioner.
|
||||
func (b *HeadingConfig) SetOption(name OptionName, _ interface{}) {
|
||||
func (b *HeadingConfig) SetOption(name OptionName, _ any) {
|
||||
switch name {
|
||||
case optAutoHeadingID:
|
||||
b.AutoHeadingID = true
|
||||
@@ -98,69 +98,47 @@ func (b *atxHeadingParser) Open(parent ast.Node, reader text.Reader, pc Context)
|
||||
if l == 0 {
|
||||
return nil, NoChildren
|
||||
}
|
||||
start := i + l
|
||||
if start >= len(line) {
|
||||
start = len(line) - 1
|
||||
}
|
||||
origstart := start
|
||||
stop := len(line) - util.TrimRightSpaceLength(line)
|
||||
|
||||
start := min(i+l, len(line)-1)
|
||||
node := ast.NewHeading(level)
|
||||
parsed := false
|
||||
if b.Attribute { // handles special case like ### heading ### {#id}
|
||||
start--
|
||||
closureClose := -1
|
||||
closureOpen := -1
|
||||
for j := start; j < stop; {
|
||||
c := line[j]
|
||||
if util.IsEscapedPunctuation(line, j) {
|
||||
j += 2
|
||||
} else if util.IsSpace(c) && j < stop-1 && line[j+1] == '#' {
|
||||
closureOpen = j + 1
|
||||
k := j + 1
|
||||
for ; k < stop && line[k] == '#'; k++ {
|
||||
}
|
||||
closureClose = k
|
||||
break
|
||||
} else {
|
||||
j++
|
||||
}
|
||||
}
|
||||
if closureClose > 0 {
|
||||
reader.Advance(closureClose)
|
||||
attrs, ok := ParseAttributes(reader)
|
||||
rest, _ := reader.PeekLine()
|
||||
parsed = ok && util.IsBlank(rest)
|
||||
if parsed {
|
||||
for _, attr := range attrs {
|
||||
node.SetAttribute(attr.Name, attr.Value)
|
||||
}
|
||||
node.Lines().Append(text.NewSegment(
|
||||
segment.Start+start+1-segment.Padding,
|
||||
segment.Start+closureOpen-segment.Padding))
|
||||
}
|
||||
}
|
||||
hl := text.NewSegment(
|
||||
segment.Start+start-segment.Padding,
|
||||
segment.Start+len(line)-segment.Padding)
|
||||
hl = hl.TrimRightSpace(reader.Source())
|
||||
if hl.Len() == 0 {
|
||||
reader.AdvanceToEOL()
|
||||
return node, NoChildren
|
||||
}
|
||||
if !parsed {
|
||||
start = origstart
|
||||
stop := len(line) - util.TrimRightSpaceLength(line)
|
||||
if stop <= start { // empty headings like '##[space]'
|
||||
stop = start
|
||||
} else {
|
||||
i = stop - 1
|
||||
for ; line[i] == '#' && i >= start; i-- {
|
||||
}
|
||||
if i != stop-1 && !util.IsSpace(line[i]) {
|
||||
i = stop - 1
|
||||
}
|
||||
i++
|
||||
stop = i
|
||||
}
|
||||
|
||||
if len(util.TrimRight(line[start:stop], []byte{'#'})) != 0 { // empty heading like '### ###'
|
||||
node.Lines().Append(text.NewSegment(segment.Start+start-segment.Padding, segment.Start+stop-segment.Padding))
|
||||
if b.Attribute {
|
||||
node.Lines().Append(hl)
|
||||
parseLastLineAttributes(node, reader, pc)
|
||||
hl = node.Lines().At(0)
|
||||
node.Lines().Clear()
|
||||
}
|
||||
|
||||
// handle closing sequence of '#' characters
|
||||
line = hl.Value(reader.Source())
|
||||
stop := len(line)
|
||||
if stop == 0 { // empty headings like '##[space]'
|
||||
stop = 0
|
||||
} else {
|
||||
i = stop - 1
|
||||
for ; line[i] == '#' && i > 0; i-- {
|
||||
}
|
||||
if i == 0 && line[0] == '#' { // empty headings like '### ###'
|
||||
reader.AdvanceToEOL()
|
||||
return node, NoChildren
|
||||
}
|
||||
if i != stop-1 && util.IsSpace(line[i]) {
|
||||
stop = i
|
||||
stop -= util.TrimRightSpaceLength(line[0:stop])
|
||||
}
|
||||
}
|
||||
hl.Stop = hl.Start + stop
|
||||
node.Lines().Append(hl)
|
||||
reader.AdvanceToEOL()
|
||||
|
||||
return node, NoChildren
|
||||
}
|
||||
|
||||
@@ -169,13 +147,6 @@ func (b *atxHeadingParser) Continue(node ast.Node, reader text.Reader, pc Contex
|
||||
}
|
||||
|
||||
func (b *atxHeadingParser) Close(node ast.Node, reader text.Reader, pc Context) {
|
||||
if b.Attribute {
|
||||
_, ok := node.AttributeString("id")
|
||||
if !ok {
|
||||
parseLastLineAttributes(node, reader, pc)
|
||||
}
|
||||
}
|
||||
|
||||
if b.AutoHeadingID {
|
||||
id, ok := node.AttributeString("id")
|
||||
if !ok {
|
||||
@@ -205,7 +176,7 @@ func generateAutoHeadingID(node *ast.Heading, reader text.Reader, pc Context) {
|
||||
node.SetAttribute(attrNameID, headingID)
|
||||
}
|
||||
|
||||
func parseLastLineAttributes(node ast.Node, reader text.Reader, pc Context) {
|
||||
func parseLastLineAttributes(node ast.Node, reader text.Reader, _ Context) {
|
||||
lastIndex := node.Lines().Len() - 1
|
||||
if lastIndex < 0 { // empty headings
|
||||
return
|
||||
@@ -213,36 +184,36 @@ func parseLastLineAttributes(node ast.Node, reader text.Reader, pc Context) {
|
||||
lastLine := node.Lines().At(lastIndex)
|
||||
line := lastLine.Value(reader.Source())
|
||||
lr := text.NewReader(line)
|
||||
var attrs Attributes
|
||||
var ok bool
|
||||
var start text.Segment
|
||||
var sl int
|
||||
var end text.Segment
|
||||
for {
|
||||
c := lr.Peek()
|
||||
if c == text.EOF {
|
||||
if c == text.EOF || c == '\n' {
|
||||
break
|
||||
}
|
||||
if c == '\\' {
|
||||
lr.Advance(1)
|
||||
if lr.Peek() == '{' {
|
||||
if util.IsPunct(lr.Peek()) {
|
||||
lr.Advance(1)
|
||||
}
|
||||
continue
|
||||
}
|
||||
if c == '{' {
|
||||
sl, start = lr.Position()
|
||||
attrs, ok = ParseAttributes(lr)
|
||||
_, end = lr.Position()
|
||||
attrs, ok := ParseAttributes(lr)
|
||||
if ok {
|
||||
if nl, _ := lr.PeekLine(); nl == nil || util.IsBlank(nl) {
|
||||
for _, attr := range attrs {
|
||||
node.SetAttribute(attr.Name, attr.Value)
|
||||
}
|
||||
lastLine.Stop = lastLine.Start + start.Start
|
||||
lastLine = lastLine.TrimRightSpace(reader.Source())
|
||||
node.Lines().Set(lastIndex, lastLine)
|
||||
return
|
||||
}
|
||||
}
|
||||
lr.SetPosition(sl, start)
|
||||
}
|
||||
lr.Advance(1)
|
||||
}
|
||||
if ok && util.IsBlank(line[end.Start:]) {
|
||||
for _, attr := range attrs {
|
||||
node.SetAttribute(attr.Name, attr.Value)
|
||||
}
|
||||
lastLine.Stop = lastLine.Start + start.Start
|
||||
node.Lines().Set(lastIndex, lastLine)
|
||||
}
|
||||
}
|
||||
|
||||
8
vendor/modules.txt
vendored
8
vendor/modules.txt
vendored
@@ -20,7 +20,7 @@ github.com/andybalholm/brotli/matchfinder
|
||||
# github.com/andybalholm/cascadia v1.3.2
|
||||
## explicit; go 1.16
|
||||
github.com/andybalholm/cascadia
|
||||
# github.com/bmatcuk/doublestar/v4 v4.9.1
|
||||
# github.com/bmatcuk/doublestar/v4 v4.9.2
|
||||
## explicit; go 1.16
|
||||
github.com/bmatcuk/doublestar/v4
|
||||
# github.com/chromedp/cdproto v0.0.0-20250803210736-d308e07a266d
|
||||
@@ -185,10 +185,10 @@ github.com/stretchr/testify/assert/yaml
|
||||
## explicit; go 1.23.2
|
||||
github.com/urfave/cli-altsrc/v3
|
||||
github.com/urfave/cli-altsrc/v3/toml
|
||||
# github.com/urfave/cli/v3 v3.6.1
|
||||
# github.com/urfave/cli/v3 v3.6.2
|
||||
## explicit; go 1.22
|
||||
github.com/urfave/cli/v3
|
||||
# github.com/yuin/goldmark v1.7.13
|
||||
# github.com/yuin/goldmark v1.7.16
|
||||
## explicit; go 1.22
|
||||
github.com/yuin/goldmark
|
||||
github.com/yuin/goldmark/ast
|
||||
@@ -216,7 +216,7 @@ golang.org/x/net/html/atom
|
||||
# golang.org/x/sys v0.36.0
|
||||
## explicit; go 1.24.0
|
||||
golang.org/x/sys/unix
|
||||
# golang.org/x/text v0.32.0
|
||||
# golang.org/x/text v0.33.0
|
||||
## explicit; go 1.24.0
|
||||
golang.org/x/text/cases
|
||||
golang.org/x/text/collate
|
||||
|
||||
Reference in New Issue
Block a user