Skip to content
Merged
8 changes: 8 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
labels:
- "Type: Dependencies :arrow_up:"
30 changes: 26 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,21 +18,43 @@ This Quarto extension automatically shortens and converts GitHub references into

To activate the filter, add the following to your YAML front matter:

```yaml
```yml
filters:
- github
- path: github
at: post-quarto
```

Some references require a default repository to be set via the `repository-name` YAML key.
> [!IMPORTANT]
> The extension must be run after Quarto's processing (*i.e.*, `at: post-quarto`) to ensure that references (*e.g.*, `@fig-my-beautiful-figure`) are processed first by Quarto, then by the GitHub filter to avoid conflicts.

Some references require a default repository to be set. Use the extension configuration structure:

```yml
repository-name: jlord/sheetsee.js
extensions:
github:
repository-name: jlord/sheetsee.js
```

> [!WARNING]
>
> The old top-level `repository-name` syntax is deprecated but still supported:
>
> ```yml
> repository-name: jlord/sheetsee.js # deprecated
> ```

## References

Source: [Autolinked references and URLs - GitHub Docs](https://docs.github.com/en/get-started/writing-on-github/working-with-advanced-formatting/autolinked-references-and-urls).

### Mentioning users

| User/Organisation | Raw reference | Short link |
|----------------------|----------------|---------------|
| User mention | `@mcanouil` | `@mcanouil` |
|----------------------|----------------|---------------|
| Organisation mention | `@quarto-dev` | `@quarto-dev` |

### Issues and pull requests

| Reference type | Raw reference | Short link |
Expand Down
167 changes: 139 additions & 28 deletions _extensions/github/github.lua
Original file line number Diff line number Diff line change
Expand Up @@ -22,27 +22,133 @@
# SOFTWARE.
]]

--- Flag to track if deprecation warning has been shown
--- @type boolean
local deprecation_warning_shown = false

--- @type string|nil The GitHub repository name (e.g., "owner/repo")
local github_repository = nil

--- @type table<string, boolean> Set of reference IDs from the document
local references_ids_set = {}

--- Check if a string is empty or nil
--- @param s string|nil The string to check
--- @return boolean true if the string is nil or empty
local function is_empty(s)
return s == nil or s == ''
end

--- Create a GitHub URI link element
--- @param text string|nil The link text
--- @param uri string|nil The URI to link to
--- @return pandoc.Link|nil A Pandoc Link element or nil if text or uri is empty
local function github_uri(text, uri)
if not is_empty(uri) and not is_empty(text) then
return pandoc.Link(text, uri)
return pandoc.Link({pandoc.Str(text --[[@as string]])}, uri --[[@as string]])
end
return nil
end

local github_repository = nil
--- Extract metadata value from document meta, supporting both new nested structure and deprecated top-level keys
--- @param meta table The document metadata table
--- @param key string The metadata key to retrieve
--- @return string|nil The metadata value as a string, or nil if not found
local function get_metadata_value(meta, key)
-- Check for the new nested structure first: extensions.github.key
if meta['extensions'] and meta['extensions']['github'] and meta['extensions']['github'][key] then
return pandoc.utils.stringify(meta['extensions']['github'][key])
end

-- Check for deprecated top-level key and warn
if meta[key] then
if not deprecation_warning_shown then
quarto.log.warning(
"Using '" .. key .. "' directly in metadata is deprecated. " ..
"Please use the following structure instead:\n" ..
"extensions:\n" ..
" github:\n" ..
" " .. key .. ": value"
)
deprecation_warning_shown = true
end
return pandoc.utils.stringify(meta[key])
end

return nil
end

--- Get repository name from metadata or git remote
--- This function extracts the GitHub repository name either from document metadata
--- or by querying the git remote origin URL
--- @param meta table The document metadata table
--- @return table The metadata table (unchanged)
function get_repository(meta)
local meta_github_repository = nil
if not is_empty(meta['repository-name']) then
meta_github_repository = pandoc.utils.stringify(meta['repository-name'])
local meta_github_repository = get_metadata_value(meta, 'repository-name')

if is_empty(meta_github_repository) then
local is_windows = package.config:sub(1, 1) == "\\"
if is_windows then
remote_repository_command = "(git remote get-url origin) -replace '.*[:/](.+?)(\\.git)?$', '$1'"
else
remote_repository_command =
"git remote get-url origin 2>/dev/null | sed -E 's|.*[:/]([^/]+/[^/.]+)(\\.git)?$|\\1|'"
end

local handle = io.popen(remote_repository_command)

if handle then
local git_repo = handle:read("*a"):gsub("%s+$", "")
handle:close()
if not is_empty(git_repo) then
meta_github_repository = git_repo
end
end
end

github_repository = meta_github_repository
return meta
end

--- Extract and store reference IDs from the document
--- This function collects all reference IDs from the document to distinguish
--- between actual citations and GitHub mentions
--- @param doc pandoc.Pandoc The Pandoc document
--- @return pandoc.Pandoc The document (unchanged)
function get_references(doc)
local references = pandoc.utils.references(doc)

for _, reference in ipairs(references) do
if reference.id then
references_ids_set[reference.id] = true
end
end
return doc
end

--- Process GitHub mentions in citations
--- Distinguishes between actual bibliography citations and GitHub @mentions
--- @param cite pandoc.Cite The citation element
--- @return pandoc.Cite|pandoc.Link The original citation or a GitHub mention link
function mentions(cite)
if references_ids_set[cite.citations[1].id] then
return cite
else
local mention_text = pandoc.utils.stringify(cite.content)
local github_link = github_uri(mention_text, "https://github.com/" .. mention_text:sub(2))
return github_link or cite
end
end

--- Process GitHub issues, pull requests, and discussions
--- Converts various GitHub reference formats into clickable links
--- Supported formats:
--- - #123 (issue in current repo)
--- - owner/repo#123 (issue in specific repo)
--- - GH-123 (issue in current repo)
--- - https://github.com/owner/repo/issues/123 (full URL)
--- @param elem pandoc.Str The string element to process
--- @return pandoc.Link|nil A GitHub link or nil if no valid pattern found
function issues(elem)
local user_repo = nil
local issue_number = nil
Expand Down Expand Up @@ -84,6 +190,15 @@ function issues(elem)
return github_uri(text, uri)
end

--- Process GitHub commit references
--- Converts various commit reference formats into clickable links
--- Supported formats:
--- - 40-character SHA (commit in current repo)
--- - owner/repo@sha (commit in specific repo)
--- - username@40-character-sha (commit by user)
--- - https://github.com/owner/repo/commit/sha (full URL)
--- @param elem pandoc.Str The string element to process
--- @return pandoc.Link|nil A GitHub commit link or nil if no valid pattern found
function commits(elem)
local user_repo = nil
local commit_sha = nil
Expand Down Expand Up @@ -117,7 +232,7 @@ function commits(elem)
local uri = nil
local text = nil
if not is_empty(short_link) and not is_empty(commit_sha) and not is_empty(user_repo) and not is_empty(type) then
if type == "commit" and commit_sha:len() == 40 then
if type == "commit" and commit_sha and commit_sha:len() == 40 then
uri = "https://github.com/" .. user_repo .. '/' .. type .. '/' .. commit_sha
text = pandoc.utils.stringify(short_link)
end
Expand All @@ -126,40 +241,36 @@ function commits(elem)
return github_uri(text, uri)
end

function mentions(elem)
local uri = nil
local text = nil
if elem.text:match("^@(%w+)$") then
local mention = elem.text:match("^@(%w+)$")
uri = "https://github.com/" .. mention
text = pandoc.utils.stringify(elem.text)
end

return github_uri(text, uri)
end

--- Main GitHub processing function
--- Attempts to convert string elements into GitHub links by trying different patterns
--- @param elem pandoc.Str The string element to process
--- @return pandoc.Str|pandoc.Link The original element or a GitHub link
function github(elem)
local link = nil
if is_empty(link) then
if link == nil then
link = issues(elem)
end
if is_empty(link) then

if link == nil then
link = commits(elem)
end

-- if is_empty(link) then
-- link = mentions(elem)
-- end

if is_empty(link) then
if link == nil then
return elem
else
return link
end
end

--- Pandoc filter configuration
--- Defines the order of filter execution:
--- 1. Extract references from the document
--- 2. Get repository information from metadata
--- 3. Process string elements for GitHub patterns
--- 4. Process citations for GitHub mentions
return {
{Meta = get_repository},
{Str = github}
{ Pandoc = get_references },
{ Meta = get_repository },
{ Str = github },
{ Cite = mentions }
}
Loading