Skip to content

Commit

Permalink
Merge branch 'main' into eval-module
Browse files Browse the repository at this point in the history
  • Loading branch information
rikhuijzer authored Jun 23, 2021
2 parents 726460d + 6471516 commit d15bf8d
Show file tree
Hide file tree
Showing 8 changed files with 176 additions and 179 deletions.
53 changes: 53 additions & 0 deletions docs/contents/demo.md
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,59 @@ This way, you can pass a informative path with plots for which informative capti
To define the labels and/or captions manually, see @sec:labels-captions.
For showing multiple plots, see @sec:plots.

Most things can be done via functions.
However, defining a struct is not possible, because `@sco` cannot locate the struct definition inside the module.
Therefore, it is also possible to pass code and specify that you want to evaluate and show code (sc) without showing the output:

<pre>
```jl
sc("
struct Point
x
y
end
")
```
</pre>

```jl
sc("
struct Point
x
y
end
")
```

and show code and output (sco).
For example,

<pre>
```jl
sco("p = Point(1, 2)")
```
</pre>

shows as

```jl
sco("p = Point(1, 2)")
```

Note that this is starting to look a lot like R Markdown where the syntax would be something like

<pre>
```{r, results='hide'}
x = rnorm(100)
```
</pre>

I guess that there is no perfect way here.
The benefit of evaluating the user input directly, as Books.jl is doing, seems to be that it is more extensible if I'm not mistaken.
Possibly, the reasoning is that R Markdown needs to convert the output directly, whereas Julia's better type system allows for converting in much later stages, but I'm not sure.

> **Tip**: After you run `gen(; M)` with the `Point` struct defined above, the struct will be available in your REPL.
## Labels and captions {#sec:labels-captions}

To set labels and captions, wrap your object in `Options`:
Expand Down
4 changes: 2 additions & 2 deletions src/Books.jl
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ using Requires
using pandoc_jll
using pandoc_crossref_jll

const PROJECT_ROOT = pkgdir(Books)
const PROJECT_ROOT = string(pkgdir(Books))::String
const GENERATED_DIR = "_gen"
const DEFAULTS_DIR = joinpath(PROJECT_ROOT, "defaults")
const BUILD_DIR = "_build"
Expand All @@ -31,7 +31,7 @@ include("generate.jl")
export html, pdf, docx, build_all
export code, ImageOptions, Options
export code_block
export @sc, CodeAndFunction, @sco
export @sc, sc, CodeAndFunction, @sco, sco
export gen
export serve

Expand Down
2 changes: 1 addition & 1 deletion src/build.jl
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ function html(; project="default", extra_head="")
copy_extra_directories(project)
url_prefix = is_ci() ? ci_url_prefix(project) : ""
c = config(project, "contents")
write_html_pages(url_prefix, c, pandoc_html(project), extra_head)
write_html_pages(url_prefix, pandoc_html(project), extra_head)
end

"""
Expand Down
41 changes: 21 additions & 20 deletions src/generate.jl
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ extract_expr_example() = """
```jl
foo(3)
```
```jl
foo(3)
bar
```
ipsum `jl bar()` dolar
"""

Expand All @@ -27,16 +31,17 @@ Here, `s` is the contents of a Markdown file.
julia> s = Books.extract_expr_example();
julia> Books.extract_expr(s)
2-element Vector{String}:
3-element Vector{String}:
"foo(3)"
"foo(3)\\nbar"
"bar()"
```
"""
function extract_expr(s::AbstractString)::Vector
codeblock_pattern = r"```jl\s*([\w\W]*?)```"
matches = eachmatch(codeblock_pattern, s)
function clean(m)
m = m[1]
m = m[1]::SubString{String}
m = strip(m)
m = string(m)::String
end
Expand All @@ -49,7 +54,7 @@ function extract_expr(s::AbstractString)::Vector

function check_parse_errors(expr)
try
Meta.parse(expr)
Meta.parse("begin $expr end")
catch e
error("Exception occured when trying to parse `$expr`")
end
Expand Down Expand Up @@ -100,7 +105,6 @@ julia> Books.method_name("Options(foo(); caption='b')")
function method_name(expr::String)
remove_macros(expr) = replace(expr, r"@[\w\_]*" => "")
expr = remove_macros(expr)
# These rewrites are not reversible, because they do not have to be.
expr = replace(expr, '(' => '_')
expr = replace(expr, ')' => "")
expr = replace(expr, ';' => "_")
Expand All @@ -119,23 +123,17 @@ Escape an expression to the corresponding path.
The logic in this method should match the logic in the Lua filter.
"""
function escape_expr(expr::String)
replace_map = [
'(' => "-ob-",
')' => "-cb-",
'"' => "-dq-",
':' => "-fc-",
';' => "-sc-",
'@' => "-ax-"
]
escaped = reduce(replace, replace_map; init=expr)
escaped = 60 < length(expr) ? expr[1:60] : expr
escaped = replace(escaped, r"([^a-zA-Z0-9]+)" => "_")
joinpath(GENERATED_DIR, "$escaped.md")
end

function evaluate_and_write(M::Module, expr::String)
path = escape_expr(expr)
println("Writing output of `$expr` to $path")
expr_info = replace(expr, '\n' => "\\n")
println("Writing output of `$expr_info` to $path")

ex = Meta.parse(expr)
ex = Meta.parse("begin $expr end")
out = Core.eval(M, ex)
out = convert_output(expr, path, out)
out = string(out)::String
Expand All @@ -148,7 +146,8 @@ function evaluate_and_write(f::Function)
function_name = Base.nameof(f)
expr = "$(function_name)()"
path = escape_expr(expr)
println("Writing output of `$expr` to $path")
expr_info = replace(expr, '\n' => "\\n")
println("Writing output of `$expr_info` to $path")
out = f()
out = convert_output(expr, path, out)
out = string(out)::String
Expand All @@ -158,11 +157,11 @@ function evaluate_and_write(f::Function)
end

"""
evaluate_include(expr::String, M::Module, fail_on_error::Bool)
evaluate_include(expr::String, M, fail_on_error::Bool)
For a `path` included in a Markdown file, run the corresponding function and write the output to `path`.
"""
function evaluate_include(expr::String, M::Module, fail_on_error::Bool)
function evaluate_include(expr::String, M, fail_on_error::Bool)
if isnothing(M)
# This code isn't really working.
M = caller_module()
Expand Down Expand Up @@ -193,7 +192,7 @@ function expand_path(p)
end

"""
gen(paths::Vector{String}; M=Main, fail_on_error=false, project="default")
gen(paths::Vector; M=Main, fail_on_error=false, project="default")
Populate the files in `$(Books.GENERATED_DIR)/` by calling the required methods.
These methods are specified by the filename and will output to that filename.
Expand All @@ -209,8 +208,10 @@ After calling the methods, this method will also call `html()` to update the sit
just load them inside your REPL (module `Main`) and call `gen()`.
For example, you can define `M = YourModule` to shorten calls to methods in your module.
"""

function gen(paths::Vector{String};
M=Main, fail_on_error=false, project="default", call_html=true)

mkpath(GENERATED_DIR)
paths = [contains(dirname(p), "contents") ? p : expand_path(p) for p in paths]
included_expr = vcat([extract_expr(read(p, String)) for p in paths]...)
Expand All @@ -225,7 +226,7 @@ end
"""
gen(path::AbstractString; kwargs...)
Convenience method for passing `path::AbstractString` instead of `paths::Vector{AbstractString}`.
Convenience method for passing `path::AbstractString` instead of `paths::Vector`.
"""
function gen(path::AbstractString; kwargs...)
path = string(path)::String
Expand Down
24 changes: 12 additions & 12 deletions src/html.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@ import YAML
import URIs

"""
split_keepdelim(str::AbstractString, dlm::Regex)
split_keepdelim(str::AbstractString, delim::Regex)
Split on regex while keeping the matches.
Based on https://github.com/JuliaLang/julia/issues/20625#issuecomment-581585151.
"""
function split_keepdelim(str::AbstractString, dlm::Regex)
dlm = string(dlm)[3:end-1]
rx = Regex("(?=$dlm)")
function split_keepdelim(str::AbstractString, delim::Regex)
delim = string(delim)[3:end-1]
rx = Regex("(?=$delim)")
split(str, rx)
end

Expand Down Expand Up @@ -79,7 +79,7 @@ function section_infos(text)
for line in lines
m = match(numbered_rx, line)
if !isnothing(m)
number, id = m.captures
number, id = m.captures
line_end = split(line, '>')[end-1]
text = line_end[nextind(line_end, 0, 2):prevind(line_end, end, 4)]
tuple = (num = number, id = id, text = lstrip(text))
Expand Down Expand Up @@ -116,7 +116,7 @@ end
function html_href(text, link, level)
threshold = 33
if threshold < length(text)
shortened = text[1:threshold]
shortened = text[1:threshold]::String
text = shortened * ".."
end
"""<a class="menu-level-$level" href="$link">$text</a>"""
Expand All @@ -139,7 +139,7 @@ end
Menu including numbered sections.
"""
function add_menu(splitted=split_html())
function add_menu(splitted)
head, bodies, foot = splitted
data = pandoc_metadata()
title = data["title"]
Expand Down Expand Up @@ -224,7 +224,7 @@ function add_extra_head(head, extra_head::AbstractString)
replace(head, before => after)
end

function html_pages(chs=chapters(), h=pandoc_html(), extra_head="")
function html_pages(h, extra_head="")
head, menu, bodies, foot = add_menu(split_html(h))
head = add_extra_head(head, extra_head)
ids_texts = html_page_name.(bodies)
Expand All @@ -247,7 +247,7 @@ function map_ids(names, pages)
html = page
matches = eachmatch(rx, html)
for m in matches
capture = first(m.captures)
capture = first(m.captures)::SubString{String}
if startswith(capture, "sec:")
key = '#' * capture
mapping[key] = name
Expand All @@ -269,7 +269,7 @@ function fix_links(names, pages, url_prefix)
updated_pages = []
function fix_page(name, page)
function replace_match(s)
capture = first(match(rx, s).captures)
capture = first(match(rx, s).captures)::SubString{String}
if startswith(capture, "#sec:")
page_link = mapping[capture]
return uncapture("$url_prefix/$page_link.html$capture")
Expand All @@ -290,9 +290,9 @@ function fix_links(names, pages, url_prefix)
(names, fixed_pages)
end

function write_html_pages(url_prefix, chs=chapters(), h=pandoc_html(), extra_head="")
function write_html_pages(url_prefix, h::AbstractString, extra_head="")
h = fix_image_urls(h, url_prefix)
names, pages = html_pages(chs, h, extra_head)
names, pages = html_pages(h, extra_head)
names, pages = fix_links(names, pages, url_prefix)
for (i, (name, page)) in enumerate(zip(names, pages))
name = i == 1 ? "index" : name
Expand Down
Loading

0 comments on commit d15bf8d

Please sign in to comment.