Skip to content

Commit

Permalink
refactor geomakiedocumenterblocks to be really generic
Browse files Browse the repository at this point in the history
  • Loading branch information
asinghvi17 committed Jul 13, 2024
1 parent 1d27b68 commit 139324c
Show file tree
Hide file tree
Showing 11 changed files with 151 additions and 56 deletions.
File renamed without changes.
21 changes: 21 additions & 0 deletions GeoMakieDocumenterBlocks/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# GeoMakieDocumenterBlocks

This package implements two Documenter blocks, `@cardmeta` and `@overviewgallery`, which are used to generate DemoCards-like cards but in memory and without file manipulation or external json files, nor any requirement for directory structure.

The package is integrated with Makie but can also be used standalone, with Strings encoding either base64 images or urls.

## Usage

See the GeoMakie docs for usage examples, the idea is that you put a cardmeta block in each example file (it's automagically moved to the end) and put an overviewgallery block in your gallery page if you want one.

You also have to pass the `ExampleFormat` plugin to your `makedocs` function to use these blocks. This can be a no-arg constructor if you just want defaults.

## How it works

1. The Documenter build step moves all cardmeta blocks to the bottom of the page, and adds a "copy pastable code block" to the top just below the title.
2. The example blocks run and do magical things.
3. The cardmeta block runs and:
1. Pushes a dict of card metadata
2. Places the cover image directly below the title, displacing the copy pastable code block
3. Nothing else yet but coming soon
4. The overviewgallery block parses this metadata and emits HTML for the gallery blocks and links.
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,9 @@ using ImageTransformations, ImageIO, Base64, FileIO # for resize
import Documenter: MarkdownAST
using Makie

const GALLERY_DICT = Dict{String, Any}()

export GALLERY_DICT

include("types.jl")
include("ast_utils.jl")
include("build_step.jl")
include("cardmeta.jl")
include("overview.jl")

Expand Down
10 changes: 10 additions & 0 deletions GeoMakieDocumenterBlocks/src/ast_utils.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@

function _stitch_all_example_blocks(page)
# Extract all example blocks (this is naturally in order)
all_example_blocks_in_order = filter(page.mdast.children) do x
x.element isa MarkdownAST.CodeBlock && Base.occursin("@example", x.element.info)
end
# Join all the code blocks into a single string
result = join(getproperty.(getproperty.(all_example_blocks_in_order, :element), :code), "\n")
return result
end
32 changes: 32 additions & 0 deletions GeoMakieDocumenterBlocks/src/build_step.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
"""
ExampleProcessing <: Documenter.Builder.DocumenterPipeline
What does this do?
- Moves Cardmeta blocks
- Adds a quick example block (in Vitepress syntax) if requested in the pipeline and a cardmeta block is found
- Adds badges to the page if necessary
"""
abstract type ExampleProcessing <: Documenter.Builder.DocumentPipeline end

Documenter.Selectors.order(::Type{ExampleProcessing}) = 1.2 # after doctest, before expand templates.

function _is_cardmeta_block(x)
return x.element isa MarkdownAST.CodeBlock && Base.occursin("@cardmeta", x.element.info)
end

function Documenter.Selectors.runner(::Type{ExampleProcessing}, doc::Documenter.Document)
# Main.@infiltrate
for (filename, page) in doc.blueprint.pages
cardmeta_blocks = filter(_is_cardmeta_block, collect(page.mdast.children))
is_examples_page = Base.occursin("examples", splitdir(page.build)[1]) || !isempty(cardmeta_blocks)
if !isempty(cardmeta_blocks) # some cardmeta block was detected
# move the cardmeta block from wherever it is to the end of the page.
MarkdownAST.insert_after!(last(page.mdast.children), first(cardmeta_blocks))
elseif Base.occursin("examples", splitdir(page.build)[1]) # only inject cardmeta if in examples dir
# do nothing for now - potentially inject an extra cardmeta block at the end
# of every page.
MarkdownAST.insert_after!(last(page.mdast.children), MarkdownAST.@ast MarkdownAST.CodeBlock("@cardmeta", ""))
end
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,6 @@ abstract type CardMetaBlocks <: Documenter.Expanders.ExpanderPipeline end
Documenter.Selectors.order(::Type{CardMetaBlocks}) = 12.0
Documenter.Selectors.matcher(::Type{CardMetaBlocks}, node, page, doc) = Documenter.iscode(node, r"^@cardmeta")

GALLERY_DICT = Dict{String, Any}()

function Documenter.Selectors.runner(::Type{CardMetaBlocks}, node, page, doc)
# Bail early if in draft mode
if Documenter.is_draft(doc, page)
Expand All @@ -37,9 +35,9 @@ function Documenter.Selectors.runner(::Type{CardMetaBlocks}, node, page, doc)
page_name = first(splitext(last(splitdir(page.source))))
page_link_path = first(splitext(relpath(page.build, doc.user.build)))
@info "Running Cardmeta for $page_name"
gallery_dict = doc.plugins[findfirst(x -> x isa ExampleConfig, doc.plugins)].gallery_dict

global GALLERY_DICT
meta = get!(GALLERY_DICT, page_name, Dict{Symbol, Any}())
meta = get!(gallery_dict, page_name, Dict{Symbol, Any}())
meta[:Path] = page_link_path
# The sandboxed module -- either a new one or a cached one from this page.
current_mod = Documenter.get_sandbox_module!(page.globals.meta, "atexample", page_name)
Expand Down Expand Up @@ -98,7 +96,6 @@ function Documenter.Selectors.runner(::Type{CardMetaBlocks}, node, page, doc)
end
get!(meta, :Title, title)


# Cover - check for e.g. `fig`, `f`, `figure`
if !haskey(meta, :Cover) # no default was assigned
for potential_name in (:fig, :f, :figure)
Expand All @@ -122,11 +119,11 @@ function Documenter.Selectors.runner(::Type{CardMetaBlocks}, node, page, doc)
end

if haskey(meta, :Cover)
set_cover_to_png!(meta, page, doc)
# insert the cover image into the page
if !isnothing(idx)
MarkdownAST.insert_after!(elements[idx], MarkdownAST.@ast Documenter.RawNode(:html, "<img src=\"$(meta[:Cover])\"/>"))
MarkdownAST.insert_after!(elements[idx], MarkdownAST.@ast Documenter.RawNode(:html, "<img src=\"$(get_image_url(page, doc, meta[:Cover]))\"/>"))
end
set_cover_to_image!(meta, page, doc)
# insert the cover image into the page
end


Expand All @@ -137,47 +134,43 @@ function Documenter.Selectors.runner(::Type{CardMetaBlocks}, node, page, doc)

end

function set_cover_to_png!(meta, page, doc)
if meta[:Cover] isa Makie.FigureLike
# convert figure to image
original_cover_image = Makie.colorbuffer(meta[:Cover])
ratio = 600 / size(original_cover_image, 1) # get 300px height
resized_cover_image = ImageTransformations.imresize(original_cover_image; ratio)

# Below is the "inline pipeline"
iob = IOBuffer()
ImageIO.save(FileIO.Stream{FileIO.format"PNG"}(iob), resized_cover_image)
# We could include this inline, but that seems to be causing issues.
# meta[:Cover] = "data:image/png;base64, " * Base64.base64encode(String(take!(iob)))
# Instead, we will save to a file and include that.
bytes = take!(iob)
filename = string(hash(bytes), base = 62) * ".png"
write(joinpath(page.workdir, filename), bytes)
meta[:Cover] = "/" * joinpath(relpath(page.workdir, doc.user.build), filename)
end
function get_image_url(page, doc, fig::Makie.FigureLike)
image = Makie.colorbuffer(fig)
iob = IOBuffer()
ImageIO.save(FileIO.Stream{FileIO.format"PNG"}(iob), image)
# We could include this inline, but that seems to be causing issues.
# meta[:Cover] = "data:image/png;base64, " * Base64.base64encode(String(take!(iob)))
# Instead, we will save to a file and include that.
bytes = take!(iob)
filename = string(hash(bytes), base = 62) * ".png"
path = joinpath(page.workdir, filename)
write(path, bytes)
return "/" * joinpath(relpath(page.workdir, doc.user.build), filename)
end

get_image_url(page, doc, s::String) = s

function set_cover_to_image!(meta, page, doc)
return set_cover_to_image!(meta, page, doc, meta[:Cover])
end

abstract type MoveCardMeta <: Documenter.Builder.DocumentPipeline end

Documenter.Selectors.order(::Type{MoveCardMeta}) = 1.2 # after doctest, before expand templates.

function _is_cardmeta_block(x)
return x.element isa MarkdownAST.CodeBlock && Base.occursin("@cardmeta", x.element.info)
set_cover_to_image!(meta, page, doc, cover::String) = cover

function set_cover_to_image!(meta, page, doc, cover::Makie.FigureLike)
# convert figure to image
original_cover_image = Makie.colorbuffer(meta[:Cover])
ratio = 600 / size(original_cover_image, 1) # get 300px height
resized_cover_image = ImageTransformations.imresize(original_cover_image; ratio)

# Below is the "inline pipeline"
iob = IOBuffer()
ImageIO.save(FileIO.Stream{FileIO.format"PNG"}(iob), resized_cover_image)
# We could include this inline, but that seems to be causing issues.
# meta[:Cover] = "data:image/png;base64, " * Base64.base64encode(String(take!(iob)))
# Instead, we will save to a file and include that.
bytes = take!(iob)
filename = string(hash(bytes), base = 62) * ".png"
write(joinpath(page.workdir, filename), bytes)
meta[:Cover] = "/" * joinpath(relpath(page.workdir, doc.user.build), filename)
end

function Documenter.Selectors.runner(::Type{MoveCardMeta}, doc::Documenter.Document)
# Main.@infiltrate
for (filename, page) in doc.blueprint.pages
cardmeta_blocks = filter(_is_cardmeta_block, collect(page.mdast.children))
if !isempty(cardmeta_blocks) # some cardmeta block was detected
# move the cardmeta block from wherever it is to the end of the page.
MarkdownAST.insert_after!(last(page.mdast.children), first(cardmeta_blocks))
elseif Base.occursin("examples", splitdir(page.build)[1]) # only inject cardmeta if in examples dir
# do nothing for now - potentially inject an extra cardmeta block at the end
# of every page.
MarkdownAST.insert_after!(last(page.mdast.children), MarkdownAST.@ast MarkdownAST.CodeBlock("@cardmeta", ""))
end
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -21,17 +21,18 @@ function Documenter.Selectors.runner(::Type{OverviewGalleryBlocks}, node, page,
Documenter.create_draft_result!(node; blocktype="@example")
return
end
gallery_dict = doc.plugins[findfirst(x -> x isa ExampleConfig, doc.plugins)].gallery_dict

not_found = String[]
entries = String[]
# find the blocks and use them as strings
for pagename in split(x.code, '\n')
if !haskey(Main.GALLERY_DICT, pagename)
if !haskey(gallery_dict, pagename)
push!(not_found, pagename)
continue
end
# obtain the element
element = Main.GALLERY_DICT[pagename]
element = gallery_dict[pagename]
# obtain properties from the element, with defaults if not found from the cardmeta blocks
href = element[:Path] # this is must have!!!
src = get(element, :Cover, "data:image/svg+xml;charset=utf-8,<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"1\" height=\"1\"/>")
Expand Down
19 changes: 19 additions & 0 deletions GeoMakieDocumenterBlocks/src/types.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
abstract type Feature end

struct CopyPastableExample <: Feature end

abstract type Badge <: Feature end
struct JuliaFileBadge <: Badge end
struct DateBadge <: Badge end
struct AuthorBadge <: Badge end
struct LicenseBadge <: Badge
name::String
link::String
end


@kwdef struct ExampleConfig <: Documenter.Plugin
features::Vector{Feature} = Feature[CopyPastableExample()]
known_examples::Vector = []
gallery_dict::Dict = Dict{String, Any}()
end
7 changes: 3 additions & 4 deletions docs/make.jl
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ using GeoMakie, CairoMakie, Makie, GeoInterfaceMakie
# some strategic imports to avoid world age issues
using FHist

include(joinpath(@__DIR__, "GeoMakieDocumenterBlocks", "src", "GeoMakieDocumenterBlocks.jl"))
import .GeoMakieDocumenterBlocks: GALLERY_DICT
include(joinpath(dirname(@__DIR__), "GeoMakieDocumenterBlocks", "src", "GeoMakieDocumenterBlocks.jl"))

# Set some global settings
# Good quality CairoMakie with PNG
CairoMakie.activate!(px_per_unit = 2, type = :png)
Expand Down Expand Up @@ -60,7 +60,6 @@ for filename in examples
Literate.markdown(file, joinpath(@__DIR__, "src", "examples", first(splitdir(filename))); documenter = true)
end

empty!(GALLERY_DICT)
Documenter.makedocs(;
modules=[GeoMakie],
doctest=false,
Expand All @@ -81,13 +80,13 @@ Documenter.makedocs(;
],
"Examples" => joinpath.(("examples",), replace.(examples, (".jl" => ".md",))),
],
plugins = Documenter.Plugin[GeoMakieDocumenterBlocks.ExampleConfig(),],
sitename="GeoMakie.jl",
authors="Anshul Singhvi and the Makie.jl contributors",
warnonly = true,
draft = false,
expandfirst = joinpath.(("examples",),first.(splitext.(examples)) .* ".md"),
)
empty!(GALLERY_DICT)

deploydocs(;
repo="github.com/MakieOrg/GeoMakie.jl",
Expand Down
21 changes: 21 additions & 0 deletions examples/specialized/earth3d.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using GLMakie, GeoMakie, Geodesy

transf = Geodesy.ECEFfromLLA(Geodesy.WGS84())

transf2 = Makie.PointTrans{3}() do p
ϕ, θ, r = p
sθ, cθ = sincos(deg2rad(θ))
sϕ, cϕ = sincos(deg2rad(ϕ))
Point3(r ** cϕ, r ** cϕ, r * sϕ)
end

f, a, p = meshimage(-180..180, -90..90, GeoMakie.earth(); npoints = 100, z_level = 0, axis = (; type = LScene));
lp = lines!(a, Point3f.(1:10, 1:10, 110); color = :red, linewidth = 2)
cc = cameracontrols(a.scene)
cc.settings.mouse_translationspeed[] = 0.0
cc.settings.zoom_shift_lookat[] = false
Makie.update_cam!(a.scene, cc)
p.transformation.transform_func[] = transf
lp.transformation.transform_func[] = transf
f

0 comments on commit 139324c

Please sign in to comment.