diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..a780c73 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,8 @@ +version: 2 +updates: + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" + labels: + - "Type: Dependencies :arrow_up:" diff --git a/README.md b/README.md index 91b4bfb..4b1d0ea 100644 --- a/README.md +++ b/README.md @@ -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 | diff --git a/_extensions/github/github.lua b/_extensions/github/github.lua index a9c1a4b..5636f71 100644 --- a/_extensions/github/github.lua +++ b/_extensions/github/github.lua @@ -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 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 @@ -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 @@ -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 @@ -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 } } diff --git a/example.qmd b/example.qmd index 6c78e1f..9fc3036 100644 --- a/example.qmd +++ b/example.qmd @@ -4,8 +4,8 @@ description: | References to GitHub issues, pull requests, and commits are automatically shortened and converted into links. page-layout: full filters: - - github -repository-name: jlord/sheetsee.js + - path: github + at: post-quarto format: html: output-file: index @@ -20,59 +20,90 @@ This Quarto extension automatically shortens and converts GitHub references into ```yml filters: - - github + - path: github + at: post-quarto ``` -Some references require to define the default repository via `repository-name` YAML key. +:::: {.callout-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 to define the default repository via the extension configuration or have an `origin` remote set in your Git configuration. + +```yml +extensions: + github: + repository-name: mcanouil/quarto-github +``` + +:::: {.callout-warning} +## Deprecated + +The old top-level `repository-name` syntax is deprecated but still supported: ```yml -repository-name: jlord/sheetsee.js +repository-name: mcanouil/quarto-github ``` +::: + +## Mentioning users + ++----------------------+----------------+-------------+ +| User/Organisation | Raw reference | Short link | ++======================+================+=============+ +| | ```txt | | +| User mention | @mcanouil | @mcanouil | +| | ``` | | ++----------------------+----------------+-------------+ +| | ```txt | | +| Organisation mention | @quarto-dev | @quarto-dev | +| | ``` | | ++----------------------+----------------+-------------+ ## Issues and pull requests -+-------------------------------------------------------------------------------+------------------------------------------------+------------------------------------------------+ -| Reference type | Raw reference | Short link | -+===============================================================================+================================================+================================================+ -| | ```txt | | -| Issue, discussion, or pull request URL | https://github.com/jlord/sheetsee.js/issues/26 | https://github.com/jlord/sheetsee.js/issues/26 | -| ***`repository-name` is optional!*** | ``` | | -+-------------------------------------------------------------------------------+------------------------------------------------+------------------------------------------------+ -| | ```txt | | -| `#` and issue, discussion, or pull request number \ | #26 | #26 | -| ***`repository-name` is required!*** | ``` | | -+-------------------------------------------------------------------------------+------------------------------------------------+------------------------------------------------+ -| | ```txt | | -| `GH-` and issue, discussion, or pull request number \ | GH-26 | GH-26 | -| ***`repository-name` is required!*** | ``` | | -+-------------------------------------------------------------------------------+------------------------------------------------+------------------------------------------------+ -| | ```txt | | -| `Username/Repository#` and issue, discussion, or pull request number | jlord/sheetsee.js#26 | jlord/sheetsee.js#26 | -| | ``` | | -+-------------------------------------------------------------------------------+------------------------------------------------+------------------------------------------------+ -| | ```txt | | -| `Organization_name/Repository#` and issue, discussion, or pull request number | github-linguist/linguist#4039 | github-linguist/linguist#4039 | -| | ``` | | -+-------------------------------------------------------------------------------+------------------------------------------------+------------------------------------------------+ ++-------------------------------------------------------------------------------+----------------------------------------------------+----------------------------------------------------+ +| Reference type | Raw reference | Short link | ++===============================================================================+====================================================+====================================================+ +| | ```txt | | +| Issue, discussion, or pull request URL | https://github.com/mcanouil/quarto-github/issues/3 | https://github.com/mcanouil/quarto-github/issues/3 | +| ***`repository-name` is optional!*** | ``` | | ++-------------------------------------------------------------------------------+----------------------------------------------------+----------------------------------------------------+ +| | ```txt | | +| `#` and issue, discussion, or pull request number \ | #3 | #3 | +| ***`repository-name` is required!*** | ``` | | ++-------------------------------------------------------------------------------+----------------------------------------------------+----------------------------------------------------+ +| | ```txt | | +| `GH-` and issue, discussion, or pull request number \ | GH-3 | GH-3 | +| ***`repository-name` is required!*** | ``` | | ++-------------------------------------------------------------------------------+----------------------------------------------------+----------------------------------------------------+ +| | ```txt | | +| `Username/Repository#` and issue, discussion, or pull request number | mcanouil/quarto-github#3 | mcanouil/quarto-github#3 | +| | ``` | | ++-------------------------------------------------------------------------------+----------------------------------------------------+----------------------------------------------------+ +| | ```txt | | +| `Organization_name/Repository#` and issue, discussion, or pull request number | github-linguist/linguist#4039 | github-linguist/linguist#4039 | +| | ``` | | ++-------------------------------------------------------------------------------+----------------------------------------------------+----------------------------------------------------+ ## Commit SHAs -+--------------------------------------+--------------------------------------------------------------------------------------+--------------------------------------------------------------------------------------+ -| Reference type | Raw reference | Short link | -+======================================+======================================================================================+======================================================================================+ -| | ```txt | | -| Commit URL | https://github.com/jlord/sheetsee.js/commit/a5c3785ed8d6a35868bc169f07e40e889087fd2e | https://github.com/jlord/sheetsee.js/commit/a5c3785ed8d6a35868bc169f07e40e889087fd2e | -| ***`repository-name` is optional!*** | ``` | | -+--------------------------------------+--------------------------------------------------------------------------------------+--------------------------------------------------------------------------------------+ -| | ```txt | | -| SHA \ | a5c3785ed8d6a35868bc169f07e40e889087fd2e | a5c3785ed8d6a35868bc169f07e40e889087fd2e | -| ***`repository-name` is required!*** | ``` | | -+--------------------------------------+--------------------------------------------------------------------------------------+--------------------------------------------------------------------------------------+ -| | ```txt | | -| `User@SHA` \ | jlord@a5c3785ed8d6a35868bc169f07e40e889087fd2e | ***Not supported!*** | -| ***Not supported!*** | ``` | | -+--------------------------------------+--------------------------------------------------------------------------------------+--------------------------------------------------------------------------------------+ -| | ```txt | | -| `Username/Repository@SHA` | jlord/sheetsee.js@a5c3785ed8d6a35868bc169f07e40e889087fd2e | jlord/sheetsee.js@a5c3785ed8d6a35868bc169f07e40e889087fd2e | -| | ``` | | -+--------------------------------------+--------------------------------------------------------------------------------------+--------------------------------------------------------------------------------------+ ++--------------------------------------+-------------------------------------------------------------------------------------------+-------------------------------------------------------------------------------------------+ +| Reference type | Raw reference | Short link | ++======================================+===========================================================================================+===========================================================================================+ +| | ```txt | | +| Commit URL | https://github.com/mcanouil/quarto-github/commit/0b2eb553779f0a7cd56d59c2ec102b8bb643e9c3 | https://github.com/mcanouil/quarto-github/commit/0b2eb553779f0a7cd56d59c2ec102b8bb643e9c3 | +| | ``` | | ++--------------------------------------+-------------------------------------------------------------------------------------------+-------------------------------------------------------------------------------------------+ +| | ```txt | | +| SHA \ | 0b2eb553779f0a7cd56d59c2ec102b8bb643e9c3 | 0b2eb553779f0a7cd56d59c2ec102b8bb643e9c3 | +| ***`repository-name` is required!*** | ``` | | ++--------------------------------------+-------------------------------------------------------------------------------------------+-------------------------------------------------------------------------------------------+ +| | ```txt | | +| `User@SHA` \ | mcanouil@0b2eb553779f0a7cd56d59c2ec102b8bb643e9c3 | ***Not supported!*** | +| ***Not supported!*** | ``` | | ++--------------------------------------+-------------------------------------------------------------------------------------------+-------------------------------------------------------------------------------------------+ +| | ```txt | | +| `Username/Repository@SHA` | mcanouil/quarto-github@0b2eb553779f0a7cd56d59c2ec102b8bb643e9c3 | mcanouil/quarto-github@0b2eb553779f0a7cd56d59c2ec102b8bb643e9c3 | +| | ``` | | ++--------------------------------------+-------------------------------------------------------------------------------------------+-------------------------------------------------------------------------------------------+