From bc843bd8e9c8d1b6f2970e25c04f2d220b5fb320 Mon Sep 17 00:00:00 2001 From: pedromxavier Date: Thu, 28 Sep 2023 15:11:38 -0400 Subject: [PATCH 1/5] Update `Documenter -> v1` --- docs/Project.toml | 2 +- docs/customdocs.jl | 81 ++++++++++++++++++++++++++++++++++++++++++++++ docs/make.jl | 31 ++---------------- 3 files changed, 84 insertions(+), 30 deletions(-) create mode 100644 docs/customdocs.jl diff --git a/docs/Project.toml b/docs/Project.toml index b550c8d2..c74d338c 100644 --- a/docs/Project.toml +++ b/docs/Project.toml @@ -3,4 +3,4 @@ Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4" PythonCall = "6099a3de-0909-46bc-b1f4-468b9a2dfc0d" [compat] -Documenter = "0.27" +Documenter = "1" diff --git a/docs/customdocs.jl b/docs/customdocs.jl new file mode 100644 index 00000000..c27ad0d6 --- /dev/null +++ b/docs/customdocs.jl @@ -0,0 +1,81 @@ +module CustomDocs + +import Base: Docs +import Documenter +import Documenter: Markdown, MarkdownAST, doccat +import Documenter.Selectors: order, matcher, runner + +# This is a bit of a hack to let us insert documentation blocks with custom content. +# It means we can document Python things directly in the documentation source, and they +# are searchable. +# +# It's a hack because of the `doccat` overload, requiring a special kind of "signature" +# to embed the information we want. +# +# The first line is of the form "name - category", the rest is Markdown documentation. +# For example: +# ```@customdoc +# foo - Function +# Documentation for `foo`. +# ``` +struct CustomCat{cat} end + +# help?> Documenter.doccat +# Returns the category name of the provided Object. +doccat(::Docs.Binding, ::Type{CustomCat{cat}}) where {cat} = string(cat) + +abstract type CustomDocExpander <: Documenter.Expanders.ExpanderPipeline end + +order(::Type{CustomDocExpander}) = 20.0 + +function matcher(::Type{CustomDocExpander}, node, page, doc) + return Documenter.iscode(node, "@customdoc") +end + +function runner(::Type{CustomDocExpander}, node, page, doc) + block = node.element + + header, body = split(block.code, "\n", limit=2) + + docstring = Markdown.parse(body) + + name, cat = split(header, "-", limit=2) + + binding = Docs.Binding(Main, Symbol(strip(name))) + + object = Documenter.Object(binding, CustomCat{Symbol(strip(cat))}) + + # source: + # https://github.com/JuliaDocs/Documenter.jl/blob/7d3dc2ceef39a62edf2de7081e2d3aaf9be8d7c3/src/expander_pipeline.jl#L959 + + # Generate a unique name to be used in anchors and links for the docstring. + slug = Documenter.slugify(object) + anchor = Documenter.anchor_add!(doc.internal.docs, object, slug, page.build) + docsnode = Documenter.DocsNode(anchor, object, page) + + ast = convert(MarkdownAST.Node, docstring) + doc.user.highlightsig && Documenter.highlightsig!(ast) + + # The following 'for' corresponds to the old dropheaders() function + for headingnode in ast.children + headingnode.element isa MarkdownAST.Heading || continue + + boldnode = MarkdownAST.Node(MarkdownAST.Strong()) + + for textnode in collect(headingnode.children) + push!(boldnode.children, textnode) + end + + headingnode.element = MarkdownAST.Paragraph() + + push!(headingnode.children, boldnode) + end + + push!(docsnode.mdasts, ast) + # push!(docsnode.results, result) + push!(docsnode.metas, docstring.meta) + + node.element = docsnode +end + +end # module CustomDocs \ No newline at end of file diff --git a/docs/make.jl b/docs/make.jl index 0fc86ceb..7411919a 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -1,38 +1,11 @@ using Documenter, PythonCall, Markdown -# This is a bit of a hack to let us insert documentation blocks with custom content. -# It means we can document Python things directly in the documentation source, and they -# are searchable. -# -# It's a hack because of the `doccat` overload, requiring a special kind of "signature" -# to embed the information we want. -# -# The first line is of the form "name - category", the rest is Markdown documentation. -# For example: -# ```@customdoc -# foo - Function -# Documentation for `foo`. -# ``` -struct CustomCat{cat} end -Documenter.Utilities.doccat(::Base.Docs.Binding, ::Type{CustomCat{cat}}) where {cat} = string(cat) -struct CustomDocBlocks <: Documenter.Expanders.ExpanderPipeline end -Documenter.Expanders.Selectors.order(::Type{CustomDocBlocks}) = 20.0 -Documenter.Expanders.Selectors.matcher(::Type{CustomDocBlocks}, node, page, doc) = Documenter.Expanders.iscode(node, "@customdoc") -Documenter.Expanders.Selectors.runner(::Type{CustomDocBlocks}, x, page, doc) = begin - header, rest = split(x.code, "\n", limit=2) - docstr = Markdown.parse(rest) - name, cat = split(header, "-", limit=2) - binding = Docs.Binding(Main, Symbol(strip(name))) - object = Documenter.Utilities.Object(binding, CustomCat{Symbol(strip(cat))}) - slug = Documenter.Utilities.slugify(strip(name)) - anchor = Documenter.Anchors.add!(doc.internal.docs, object, slug, page.build) - node = Documenter.Documents.DocsNode(docstr, anchor, object, page) - page.mapping[x] = node -end +include("customdocs.jl") makedocs( sitename = "PythonCall & JuliaCall", modules = [PythonCall], + warnonly = [:missing_docs], # avoid raising error when docs are missing pages = [ "Home" => "index.md", "The Julia module PythonCall" => [ From 0800e7525290c4146f9a517b2e93c2dd819dc85e Mon Sep 17 00:00:00 2001 From: pedromxavier Date: Thu, 28 Sep 2023 21:05:00 -0400 Subject: [PATCH 2/5] Fix docs block dropdown --- docs/customdocs.jl | 54 +++++++++++++++++++++------------------------- docs/make.jl | 2 +- 2 files changed, 25 insertions(+), 31 deletions(-) diff --git a/docs/customdocs.jl b/docs/customdocs.jl index c27ad0d6..7c318054 100644 --- a/docs/customdocs.jl +++ b/docs/customdocs.jl @@ -2,7 +2,7 @@ module CustomDocs import Base: Docs import Documenter -import Documenter: Markdown, MarkdownAST, doccat +import Documenter: DocSystem, Markdown, MarkdownAST, doccat import Documenter.Selectors: order, matcher, runner # This is a bit of a hack to let us insert documentation blocks with custom content. @@ -32,50 +32,44 @@ function matcher(::Type{CustomDocExpander}, node, page, doc) return Documenter.iscode(node, "@customdoc") end +# source: +# https://github.com/JuliaDocs/Documenter.jl/blob/7d3dc2ceef39a62edf2de7081e2d3aaf9be8d7c3/src/expander_pipeline.jl#L353 function runner(::Type{CustomDocExpander}, node, page, doc) - block = node.element + @assert node.element isa MarkdownAST.CodeBlock - header, body = split(block.code, "\n", limit=2) + block = node.element - docstring = Markdown.parse(body) + m = match(r"^(.+?)\s*-\s*(.+?)\s*(\n[\s\S]*)$", strip(block.code)) - name, cat = split(header, "-", limit=2) + @assert !isnothing(m) "Invalid header:\n$(block.code)" - binding = Docs.Binding(Main, Symbol(strip(name))) + name = Symbol(m[1]) + cat = Symbol(m[2]) + body = strip(something(m[3], "")) - object = Documenter.Object(binding, CustomCat{Symbol(strip(cat))}) + binding = DocSystem.binding(Main, name) - # source: - # https://github.com/JuliaDocs/Documenter.jl/blob/7d3dc2ceef39a62edf2de7081e2d3aaf9be8d7c3/src/expander_pipeline.jl#L959 + docsnodes = MarkdownAST.Node[] - # Generate a unique name to be used in anchors and links for the docstring. - slug = Documenter.slugify(object) - anchor = Documenter.anchor_add!(doc.internal.docs, object, slug, page.build) - docsnode = Documenter.DocsNode(anchor, object, page) + object = Documenter.Object(binding, CustomCat{cat}) - ast = convert(MarkdownAST.Node, docstring) - doc.user.highlightsig && Documenter.highlightsig!(ast) + docstr = Markdown.MD[Markdown.parse(body)] + results = Docs.DocStr[Docs.docstr(body, Dict{Symbol,Any}(:module => Main, :path => "", :linenumber => 0))] - # The following 'for' corresponds to the old dropheaders() function - for headingnode in ast.children - headingnode.element isa MarkdownAST.Heading || continue - - boldnode = MarkdownAST.Node(MarkdownAST.Strong()) + docsnode = Documenter.create_docsnode(docstr, results, object, page, doc) - for textnode in collect(headingnode.children) - push!(boldnode.children, textnode) - end + # Track the order of insertion of objects per-binding. + push!(get!(doc.internal.bindings, binding, Documenter.Object[]), object) - headingnode.element = MarkdownAST.Paragraph() + doc.internal.objects[object] = docsnode.element - push!(headingnode.children, boldnode) - end + push!(docsnodes, docsnode) - push!(docsnode.mdasts, ast) - # push!(docsnode.results, result) - push!(docsnode.metas, docstring.meta) + node.element = Documenter.DocsNodesBlock(block) + + push!(node.children, docsnode) - node.element = docsnode + return nothing end end # module CustomDocs \ No newline at end of file diff --git a/docs/make.jl b/docs/make.jl index 7411919a..ccecc4f9 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -1,4 +1,4 @@ -using Documenter, PythonCall, Markdown +using Documenter, PythonCall include("customdocs.jl") From 347ec35a92af759efc9e5452e267fb634f2b060e Mon Sep 17 00:00:00 2001 From: pedromxavier Date: Thu, 28 Sep 2023 23:18:38 -0400 Subject: [PATCH 3/5] Fix slug for `@customdocs` --- docs/customdocs.jl | 37 +++++++++++++++++++++++++++++++++++-- 1 file changed, 35 insertions(+), 2 deletions(-) diff --git a/docs/customdocs.jl b/docs/customdocs.jl index 7c318054..69e44ca4 100644 --- a/docs/customdocs.jl +++ b/docs/customdocs.jl @@ -56,7 +56,8 @@ function runner(::Type{CustomDocExpander}, node, page, doc) docstr = Markdown.MD[Markdown.parse(body)] results = Docs.DocStr[Docs.docstr(body, Dict{Symbol,Any}(:module => Main, :path => "", :linenumber => 0))] - docsnode = Documenter.create_docsnode(docstr, results, object, page, doc) + # NOTE: This was modified because the original Documenter.create_docsnode was generating unreachable links + docsnode = _create_docsnode(docstr, results, object, page, doc) # Track the order of insertion of objects per-binding. push!(get!(doc.internal.bindings, binding, Documenter.Object[]), object) @@ -66,10 +67,42 @@ function runner(::Type{CustomDocExpander}, node, page, doc) push!(docsnodes, docsnode) node.element = Documenter.DocsNodesBlock(block) - + push!(node.children, docsnode) return nothing end +# source: +# https://github.com/JuliaDocs/Documenter.jl/blob/7d3dc2ceef39a62edf2de7081e2d3aaf9be8d7c3/src/expander_pipeline.jl#L959-L960 +function _create_docsnode(docstrings, results, object, page, doc) + # Generate a unique name to be used in anchors and links for the docstring. + # NOTE: The way this is being slugified is causing problems: + # slug = Documenter.slugify(object) + slug = Documenter.slugify(string(object.binding)) + + anchor = Documenter.anchor_add!(doc.internal.docs, object, slug, page.build) + docsnode = Documenter.DocsNode(anchor, object, page) + + # Convert docstring to MarkdownAST, convert Heading elements, and push to DocsNode + for (markdown, result) in zip(docstrings, results) + ast = convert(MarkdownAST.Node, markdown) + doc.user.highlightsig && Documenter.highlightsig!(ast) + # The following 'for' corresponds to the old dropheaders() function + for headingnode in ast.children + headingnode.element isa MarkdownAST.Heading || continue + boldnode = MarkdownAST.Node(MarkdownAST.Strong()) + for textnode in collect(headingnode.children) + push!(boldnode.children, textnode) + end + headingnode.element = MarkdownAST.Paragraph() + push!(headingnode.children, boldnode) + end + push!(docsnode.mdasts, ast) + push!(docsnode.results, result) + push!(docsnode.metas, markdown.meta) + end + return MarkdownAST.Node(docsnode) +end + end # module CustomDocs \ No newline at end of file From b496273791092df8c7f636b191c91b8867f97e92 Mon Sep 17 00:00:00 2001 From: pedromxavier Date: Fri, 29 Sep 2023 11:46:32 -0400 Subject: [PATCH 4/5] Add preview for documentation PR --- .github/workflows/docs.yml | 4 ++++ .github/workflows/docscleanuo.yml | 30 ++++++++++++++++++++++++++++++ docs/make.jl | 3 ++- 3 files changed, 36 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/docscleanuo.yml diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 6eb68a52..8d57d8fa 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -6,9 +6,13 @@ on: - main tags: - '*' + pull_request: + types: [opened, synchronize, reopened] + workflow_dispatch: jobs: docs: + if: ${{ !(github.event_name == 'pull_request') || contains(github.event.pull_request.labels.*.name, 'documentation') }} name: Documentation runs-on: ubuntu-latest steps: diff --git a/.github/workflows/docscleanuo.yml b/.github/workflows/docscleanuo.yml new file mode 100644 index 00000000..daf8cca8 --- /dev/null +++ b/.github/workflows/docscleanuo.yml @@ -0,0 +1,30 @@ +name: Documentation Preview Cleanup + +on: + pull_request: + types: [closed] + +jobs: + doc-preview-cleanup: + runs-on: ubuntu-latest + steps: + - name: Checkout gh-pages branch + uses: actions/checkout@v2 + with: + ref: gh-pages + + - name: Delete preview and history + run: | + git config user.name "Documenter.jl" + git config user.email "documenter@juliadocs.github.io" + git rm -rf "previews/PR$PRNUM" + git commit -m "delete preview" + git branch gh-pages-new $(echo "delete history" | git commit-tree HEAD^{tree}) + env: + PRNUM: ${{ github.event.number }} + + - name: Push changes + run: | + git push --force origin gh-pages-new:gh-pages + +# Workflow copied from https://github.com/CliMA/TimeMachine.jl diff --git a/docs/make.jl b/docs/make.jl index ccecc4f9..7b47eb88 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -28,5 +28,6 @@ makedocs( ) deploydocs( - repo = "github.com/JuliaPy/PythonCall.jl.git", + repo = raw"github.com/JuliaPy/PythonCall.jl.git", + push_preview = true ) From 84558532ec85a65b7d4808fc5eb97657b7a5c732 Mon Sep 17 00:00:00 2001 From: pedromxavier Date: Fri, 29 Sep 2023 16:06:26 -0400 Subject: [PATCH 5/5] Refactor + Add comments + Trigger Preview --- docs/customdocs.jl | 81 +++++++++++++++++++++++++++++++--------------- 1 file changed, 55 insertions(+), 26 deletions(-) diff --git a/docs/customdocs.jl b/docs/customdocs.jl index 69e44ca4..8b02ea6f 100644 --- a/docs/customdocs.jl +++ b/docs/customdocs.jl @@ -39,13 +39,7 @@ function runner(::Type{CustomDocExpander}, node, page, doc) block = node.element - m = match(r"^(.+?)\s*-\s*(.+?)\s*(\n[\s\S]*)$", strip(block.code)) - - @assert !isnothing(m) "Invalid header:\n$(block.code)" - - name = Symbol(m[1]) - cat = Symbol(m[2]) - body = strip(something(m[3], "")) + name, cat, body = _parse_docs(block.code) binding = DocSystem.binding(Main, name) @@ -53,11 +47,20 @@ function runner(::Type{CustomDocExpander}, node, page, doc) object = Documenter.Object(binding, CustomCat{cat}) - docstr = Markdown.MD[Markdown.parse(body)] - results = Docs.DocStr[Docs.docstr(body, Dict{Symbol,Any}(:module => Main, :path => "", :linenumber => 0))] + docstr = Markdown.parse(body)::Markdown.MD + result = Docs.docstr( + body, + Dict{Symbol,Any}( # NOTE: Not sure about what to put here. + :module => Main, # This is supposed to be tracking python code. + :path => "", + :linenumber => 0 + ) + )::Docs.DocStr # NOTE: This was modified because the original Documenter.create_docsnode was generating unreachable links - docsnode = _create_docsnode(docstr, results, object, page, doc) + # Also, the original implementation required docstr, result to be vectors. + + docsnode = _create_docsnode(docstr, result, object, page, doc) # Track the order of insertion of objects per-binding. push!(get!(doc.internal.bindings, binding, Documenter.Object[]), object) @@ -73,9 +76,28 @@ function runner(::Type{CustomDocExpander}, node, page, doc) return nothing end +function _parse_docs(code::AbstractString) + m = match(r"^(.+?)\s*-\s*(.+?)\s*(\n[\s\S]*)$", strip(code)) + + if isnothing(m) + error( + """ + Invalid docstring: + $(code) + """ + ) + end + + name = Symbol(m[1]) + cat = Symbol(m[2]) + body = strip(something(m[3], "")) + + return (name, cat, body) +end + # source: # https://github.com/JuliaDocs/Documenter.jl/blob/7d3dc2ceef39a62edf2de7081e2d3aaf9be8d7c3/src/expander_pipeline.jl#L959-L960 -function _create_docsnode(docstrings, results, object, page, doc) +function _create_docsnode(docstring, result, object, page, doc) # Generate a unique name to be used in anchors and links for the docstring. # NOTE: The way this is being slugified is causing problems: # slug = Documenter.slugify(object) @@ -85,23 +107,30 @@ function _create_docsnode(docstrings, results, object, page, doc) docsnode = Documenter.DocsNode(anchor, object, page) # Convert docstring to MarkdownAST, convert Heading elements, and push to DocsNode - for (markdown, result) in zip(docstrings, results) - ast = convert(MarkdownAST.Node, markdown) - doc.user.highlightsig && Documenter.highlightsig!(ast) - # The following 'for' corresponds to the old dropheaders() function - for headingnode in ast.children - headingnode.element isa MarkdownAST.Heading || continue - boldnode = MarkdownAST.Node(MarkdownAST.Strong()) - for textnode in collect(headingnode.children) - push!(boldnode.children, textnode) - end - headingnode.element = MarkdownAST.Paragraph() - push!(headingnode.children, boldnode) + + ast = convert(MarkdownAST.Node, docstring) + + doc.user.highlightsig && Documenter.highlightsig!(ast) + + # The following 'for' corresponds to the old dropheaders() function + for headingnode in ast.children + headingnode.element isa MarkdownAST.Heading || continue + + boldnode = MarkdownAST.Node(MarkdownAST.Strong()) + + for textnode in collect(headingnode.children) + push!(boldnode.children, textnode) end - push!(docsnode.mdasts, ast) - push!(docsnode.results, result) - push!(docsnode.metas, markdown.meta) + + headingnode.element = MarkdownAST.Paragraph() + + push!(headingnode.children, boldnode) end + + push!(docsnode.mdasts, ast) + push!(docsnode.results, result) + push!(docsnode.metas, docstring.meta) + return MarkdownAST.Node(docsnode) end