13 changed files with 517 additions and 11 deletions
@ -0,0 +1,32 @@ |
|||||||
|
using Revise |
||||||
|
using Documenter |
||||||
|
using Examples |
||||||
|
|
||||||
|
# generate examples |
||||||
|
EXAMPLE = joinpath(@__DIR__, "..", "examples", "example.jl") |
||||||
|
OUTPUT = joinpath(@__DIR__, "src/generated") |
||||||
|
|
||||||
|
Examples.markdown(EXAMPLE, OUTPUT) |
||||||
|
Examples.notebook(EXAMPLE, OUTPUT) |
||||||
|
Examples.script(EXAMPLE, OUTPUT) |
||||||
|
|
||||||
|
makedocs( |
||||||
|
modules = [Examples], |
||||||
|
format = :html, |
||||||
|
sitename = "Examples.jl", |
||||||
|
pages = Any[ |
||||||
|
"index.md", |
||||||
|
"fileformat.md", |
||||||
|
"pipeline.md", |
||||||
|
"outputformats.md", |
||||||
|
"customprocessing.md", |
||||||
|
"documenter.md", |
||||||
|
"generated/example.md"] |
||||||
|
) |
||||||
|
|
||||||
|
deploydocs( |
||||||
|
repo = "github.com/fredrikekre/Examples.jl.git", |
||||||
|
target = "build", |
||||||
|
deps = nothing, |
||||||
|
make = nothing |
||||||
|
) |
||||||
@ -0,0 +1,47 @@ |
|||||||
|
# [**5.** Custom pre- and post-processing](@id Custom-pre-and-post-processing) |
||||||
|
|
||||||
|
Since all packages are different, and may have different demands on how |
||||||
|
to create a nice example for the documentation it is important that |
||||||
|
the package maintainer does not feel limited by the by default provided syntax |
||||||
|
that this package offers. While you can generally come a long way by utilizing |
||||||
|
[line filtering](@ref Filtering-lines) there might be situations where you need |
||||||
|
to manually hook into the generation and change things. In `Examples.jl` this |
||||||
|
is done by letting the user supply custom pre- and post-processing functions |
||||||
|
that may do transformation of the content. |
||||||
|
|
||||||
|
All of the generators ([`Examples.markdown`](@ref), [`Examples.notebook`](@ref) |
||||||
|
and [`Examples.script`](@ref)) accepts `preprocess` and `postprocess` keyword |
||||||
|
arguments. The default "transformation" is the `identity` function. The input |
||||||
|
to the transformation functions is a `String`, and the output should be the |
||||||
|
transformed `String`. |
||||||
|
|
||||||
|
`preprocess` is sent the raw input that is read from the source file ([modulo the |
||||||
|
default line ending transformation](@ref Pre-processing)). `postprocess` is given |
||||||
|
different things depending on the output: For markdown and script output `postprocess` |
||||||
|
is given the content `String` just before writing it to the output file, but for |
||||||
|
notebook output `postprocess` is given the dictionary representing the notebook, |
||||||
|
since, in general, this is more useful. |
||||||
|
|
||||||
|
As an example, lets say we want to splice the date of generation into the output. |
||||||
|
We could of course update our source file before generating the docs, but we could |
||||||
|
instead use a `preprocess` function that splices the date into the source for us. |
||||||
|
Consider the following source file: |
||||||
|
```julia |
||||||
|
#' # Example |
||||||
|
#' This example was generated DATEOFTODAY |
||||||
|
|
||||||
|
x = 1 // 3 |
||||||
|
``` |
||||||
|
where `DATEOFTODAY` is a placeholder, to make it easier for our `preprocess` function |
||||||
|
to find the location. Now, lets define the `preprocess` function, for example |
||||||
|
```julia |
||||||
|
function update_date(content) |
||||||
|
content = replace(content, "DATEOFTODAY" => Date(now())) |
||||||
|
return content |
||||||
|
end |
||||||
|
``` |
||||||
|
which would replace every occurrence of `"DATEOFTODAY"` with the current date. We would |
||||||
|
now simply give this function to the generator, for example: |
||||||
|
```julia |
||||||
|
Examples.markdown("input.jl", "outputdir"; preprocess = update_date) |
||||||
|
``` |
||||||
@ -0,0 +1,3 @@ |
|||||||
|
# [**6.** Interaction with Documenter.jl](@id Interaction-with-Documenter) |
||||||
|
|
||||||
|
TBW |
||||||
@ -0,0 +1,111 @@ |
|||||||
|
# **2.** File Format |
||||||
|
|
||||||
|
The source file format for `Examples.jl` is a regular, commented, julia (`.jl`) scripts. |
||||||
|
The idea is that the scripts also serve as documentation on their own and it is also |
||||||
|
simple to include them in the test-suite, with e.g. `include`, to make sure the examples |
||||||
|
stay up do date with other changes in your package. |
||||||
|
|
||||||
|
## [**2.1.** Syntax](@id Syntax) |
||||||
|
|
||||||
|
The basic syntax is simple: |
||||||
|
- lines starting with `#'` is treated as markdown, |
||||||
|
- all other lines are treated as julia code. |
||||||
|
|
||||||
|
The reason for using `#'` instead of `#` is that we want to be able to use `#` as comments, |
||||||
|
just as in a regular script. Lets look at a simple example: |
||||||
|
```julia |
||||||
|
#' # Rational numbers |
||||||
|
#' |
||||||
|
#' In julia rational numbers can be constructed with the `//` operator. |
||||||
|
#' Lets define two rational numbers, `x` and `y`: |
||||||
|
|
||||||
|
x = 1//3 |
||||||
|
y = 2//5 |
||||||
|
|
||||||
|
#' When adding `x` and `y` together we obtain a new rational number: |
||||||
|
|
||||||
|
z = x + y |
||||||
|
``` |
||||||
|
In the lines `#'` we can use regular markdown syntax, for example the `#` |
||||||
|
used for the heading and the backticks for formatting code. The other lines are regular |
||||||
|
julia code. We note a couple of things: |
||||||
|
- The script is valid julia, which means that we can `include` it and the example will run |
||||||
|
- The script is "self-explanatory", i.e. the markdown lines works as comments and |
||||||
|
thus serve as good documentation on its own. |
||||||
|
|
||||||
|
For simple use this is all you need to know, the script above is valid. Let's take a look |
||||||
|
at what the above snippet would generate, with default settings: |
||||||
|
|
||||||
|
- [`Examples.markdown`](@ref): leading `#'` are removed, and code lines are wrapped in |
||||||
|
`@example`-blocks: |
||||||
|
````markdown |
||||||
|
# Rational numbers |
||||||
|
|
||||||
|
In julia rational numbers can be constructed with the `//` operator. |
||||||
|
Lets define two rational numbers, `x` and `y`: |
||||||
|
|
||||||
|
```@example filename |
||||||
|
x = 1//3 |
||||||
|
y = 2//5 |
||||||
|
``` |
||||||
|
|
||||||
|
When adding `x` and `y` together we obtain a new rational number: |
||||||
|
|
||||||
|
```@example filename |
||||||
|
z = x + y |
||||||
|
``` |
||||||
|
```` |
||||||
|
|
||||||
|
- [`Examples.notebook`](@ref): leading `#'` are removed, markdown lines are placed in |
||||||
|
`"markdown"` cells, and code lines in `"code"` cells: |
||||||
|
``` |
||||||
|
│ # Rational numbers |
||||||
|
│ |
||||||
|
│ In julia rational numbers can be constructed with the `//` operator. |
||||||
|
│ Lets define two rational numbers, `x` and `y`: |
||||||
|
|
||||||
|
In [1]: │ x = 1//3 |
||||||
|
│ y = 2//5 |
||||||
|
|
||||||
|
Out [1]: │ 2//5 |
||||||
|
|
||||||
|
│ When adding `x` and `y` together we obtain a new rational number: |
||||||
|
|
||||||
|
In [2]: │ z = x + y |
||||||
|
|
||||||
|
Out [2]: │ 11//15 |
||||||
|
``` |
||||||
|
|
||||||
|
- [`Examples.script`](@ref): all lines starting with `#'` are removed: |
||||||
|
```julia |
||||||
|
x = 1//3 |
||||||
|
y = 2//5 |
||||||
|
|
||||||
|
z = x + y |
||||||
|
``` |
||||||
|
|
||||||
|
## [**2.2.** Filtering Lines](@id Filtering-lines) |
||||||
|
|
||||||
|
It is possible to filter out lines depending on the output format. For this purpose, |
||||||
|
there are three different "tokens" that can be placed on the start of the line: |
||||||
|
- `#md`: markdown output only, |
||||||
|
- `#nb`: notebook output only, |
||||||
|
- `#jl`: script output only. |
||||||
|
|
||||||
|
Lines starting with one of these tokens are filtered out in the |
||||||
|
[preprocessing step](@ref Pre-processing). |
||||||
|
|
||||||
|
Suppose, for example, that we want to include a docstring within a `@docs` block |
||||||
|
using Documenter. Obviously we don't want to include this in the notebook, |
||||||
|
since `@docs` is Documenter syntax that the notebook will not understand. This |
||||||
|
is a case where we can prepend `#md` to those lines: |
||||||
|
````julia |
||||||
|
#md #' ```@docs |
||||||
|
#md #' Examples.markdown |
||||||
|
#md #' Examples.notebook |
||||||
|
#md #' Examples.markdown |
||||||
|
#md #' ``` |
||||||
|
```` |
||||||
|
The lines in the example above would be filtered out in the preprocessing step, unless we are |
||||||
|
generating a markdown file. When generating a markdown file we would simple remove |
||||||
|
the leading `#md ` from the lines. Beware that the space after the tag is also removed. |
||||||
@ -0,0 +1,51 @@ |
|||||||
|
# **1.** Introduction |
||||||
|
|
||||||
|
Welcome to the documentation for `Examples.jl`. A simplistic package |
||||||
|
to help you organize examples for you package documentation. |
||||||
|
|
||||||
|
### What? |
||||||
|
|
||||||
|
`Examples.jl` is a package that, based on a single source file, generates markdown, |
||||||
|
for e.g. [Documenter.jl](https://github.com/JuliaDocs/Documenter.jl), |
||||||
|
[Jupyter notebooks](http://jupyter.org/) and uncommented scripts for documentation |
||||||
|
of your package. |
||||||
|
|
||||||
|
The main design goal is simplicity. It should be simple to use, and the syntax should |
||||||
|
be simple. In short all you have to do is to write a commented julia script! |
||||||
|
|
||||||
|
The package consists mainly of three functions, which all takes the same script file |
||||||
|
as input, but generates different output: |
||||||
|
- [`Examples.markdown`](@ref): generates a markdown file |
||||||
|
- [`Examples.notebook`](@ref): generates an (optionally executed) notebook |
||||||
|
- [`Examples.script`](@ref): generates a plain script file, removing everything |
||||||
|
that is not code |
||||||
|
|
||||||
|
### Why? |
||||||
|
|
||||||
|
Examples are (probably) the best way to showcase your awesome package, and examples |
||||||
|
are often the best way for a new user to learn how to use it. It is therefore important |
||||||
|
that the documentation of your package contains examples for users to read and study. |
||||||
|
However, people are different, and we all prefer different ways of trying out a new |
||||||
|
package. Some people wants to RTFM, others want to explore the package interactively in, |
||||||
|
for example, a notebook, and some people wants to study the source code. The aim of |
||||||
|
`Examples.jl` is to make it easy to give the user all of these options, while still |
||||||
|
keeping maintenance to a minimum. |
||||||
|
|
||||||
|
It is quite common that packages have "example notebooks" to showcase the package. |
||||||
|
Notebooks are great for this, but they are not so great with version control, like git. |
||||||
|
The reason is that a notebook is a very "rich" format since it contains output and other |
||||||
|
metadata. Changes to the notebook thus result in large diffs, which makes it harder to |
||||||
|
review the actual changes. |
||||||
|
|
||||||
|
It is also common that packages include examples in the documentation, for example |
||||||
|
by using [Documenter.jl](https://github.com/JuliaDocs/Documenter.jl) `@example`-blocks. |
||||||
|
This is also great, but it is not quite as interactive as a notebook, for the users |
||||||
|
who prefer that. |
||||||
|
|
||||||
|
`Examples.jl` tries to solve the problems above by creating the output as a part of the doc |
||||||
|
build. `Examples.jl` generates the output from a single source file which makes it easier to |
||||||
|
maintain, test, and keep the manual and your example notebooks in sync. |
||||||
|
|
||||||
|
### How? |
||||||
|
|
||||||
|
TBD |
||||||
@ -0,0 +1,50 @@ |
|||||||
|
# [**4.** Output formats](@id Output-formats) |
||||||
|
|
||||||
|
|
||||||
|
## [**4.1.** Markdown output](@id Markdown-output) |
||||||
|
|
||||||
|
```` |
||||||
|
#' # Markdown ┐ |
||||||
|
#' │ |
||||||
|
#' This line is treated as markdown, since it starts with #' │ |
||||||
|
#' The leading #' (including the space) is removed ┘ |
||||||
|
|
||||||
|
#' Here is an example with some code ] |
||||||
|
|
||||||
|
x = sin.(cos.([1, 2, 3])) ┐ |
||||||
|
y = x.^2 - x ┘ |
||||||
|
```` |
||||||
|
|
||||||
|
By default, `CodeChunks` written to Documenter `@example` blocks. For example, |
||||||
|
the code above would result in the following markdown: |
||||||
|
|
||||||
|
````markdown |
||||||
|
# Markdown |
||||||
|
|
||||||
|
This line is treated as markdown, since it starts with #' |
||||||
|
The leading #' (including the space) is removed |
||||||
|
|
||||||
|
Here is an example with some code |
||||||
|
|
||||||
|
```@example |
||||||
|
x = sin.(cos.([1, 2, 3])) |
||||||
|
y = x.^2 - x |
||||||
|
``` |
||||||
|
```` |
||||||
|
|
||||||
|
```@docs |
||||||
|
Examples.markdown |
||||||
|
``` |
||||||
|
|
||||||
|
## [**4.2.** Notebook output](@id Notebook-output) |
||||||
|
|
||||||
|
```@docs |
||||||
|
Examples.notebook |
||||||
|
``` |
||||||
|
|
||||||
|
|
||||||
|
## [**4.3.** Script output](@id Script-output) |
||||||
|
|
||||||
|
```@docs |
||||||
|
Examples.script |
||||||
|
``` |
||||||
@ -0,0 +1,136 @@ |
|||||||
|
# **3.** Processing pipeline |
||||||
|
|
||||||
|
The generation of output follows the same pipeline for all output formats: |
||||||
|
1. [Pre-processing](@ref) |
||||||
|
2. [Parsing](@ref) |
||||||
|
3. [Document generation](@ref) |
||||||
|
4. [Post-processing](@ref) |
||||||
|
5. [Writing to file](@ref) |
||||||
|
|
||||||
|
|
||||||
|
## [**3.1.** Pre-processing](@id Pre-processing) |
||||||
|
|
||||||
|
The first step is pre-processing of the input file. The file is read to a `String` |
||||||
|
and CRLF style line endings (`"\r\n"`) are replaced with LF line endings (`"\n"`) to simplify |
||||||
|
internal processing. The next step is to apply the user specified pre-processing function. |
||||||
|
See [Custom pre- and post-processing](@ref Custom-pre-and-post-processing). |
||||||
|
|
||||||
|
Next the line filtering is performed, see [Filtering lines](@ref), meaning that lines |
||||||
|
starting with `#md `, `#nb ` or `#jl ` are handled (either just the token itself is removed, |
||||||
|
or the full line, depending on the output target). |
||||||
|
|
||||||
|
|
||||||
|
## [**3.2.** Parsing](@id Parsing) |
||||||
|
|
||||||
|
After the preprocessing the file is parsed. The first step is to categorize each line |
||||||
|
and mark them as either markdown or code according to the rules described in the |
||||||
|
[Syntax](@ref Syntax) section. Lets consider the example from the previous section |
||||||
|
with each line categorized: |
||||||
|
``` |
||||||
|
#' # Rational numbers <- markdown |
||||||
|
#' <- markdown |
||||||
|
#' In julia rational numbers can be constructed with the `//` operator. <- markdown |
||||||
|
#' Lets define two rational numbers, `x` and `y`: <- markdown |
||||||
|
<- code |
||||||
|
x = 1 // 3 <- code |
||||||
|
y = 2 // 5 <- code |
||||||
|
<- code |
||||||
|
#' When adding `x` and `y` together we obtain a new rational number: <- markdown |
||||||
|
<- code |
||||||
|
z = x + y <- code |
||||||
|
``` |
||||||
|
|
||||||
|
In the next step the lines are grouped into "chunks" of markdown and code. |
||||||
|
This is done by simply collecting adjacent lines of the same "type" into |
||||||
|
chunks: |
||||||
|
``` |
||||||
|
#' # Rational numbers ┐ |
||||||
|
#' │ |
||||||
|
#' In julia rational numbers can be constructed with the `//` operator. │ markdown |
||||||
|
#' Lets define two rational numbers, `x` and `y`: ┘ |
||||||
|
┐ |
||||||
|
x = 1 // 3 │ |
||||||
|
y = 2 // 5 │ code |
||||||
|
┘ |
||||||
|
#' When adding `x` and `y` together we obtain a new rational number: ] markdown |
||||||
|
┐ |
||||||
|
z = x + y ┘ code |
||||||
|
``` |
||||||
|
|
||||||
|
In the last parsing step all empty leading and trailing lines for each chunk |
||||||
|
are removed, but empty lines *within the same* block are kept. The leading `#' ` |
||||||
|
tokens are also removed from the markdown chunks. Finally we would |
||||||
|
end up with the following 4 chunks: |
||||||
|
|
||||||
|
Chunks #1: |
||||||
|
```markdown |
||||||
|
# Rational numbers |
||||||
|
|
||||||
|
In julia rational numbers can be constructed with the `//` operator. |
||||||
|
Lets define two rational numbers, `x` and `y`: |
||||||
|
``` |
||||||
|
Chunk #2: |
||||||
|
```julia |
||||||
|
x = 1 // 3 |
||||||
|
y = 2 // 5 |
||||||
|
``` |
||||||
|
Chunk #3: |
||||||
|
```markdown |
||||||
|
When adding `x` and `y` together we obtain a new rational number: |
||||||
|
``` |
||||||
|
Chunk #4: |
||||||
|
```julia |
||||||
|
z = x + y |
||||||
|
``` |
||||||
|
|
||||||
|
It is then up to the [Document generation](@ref) step to decide how these chunks should be treated. |
||||||
|
|
||||||
|
### Custom control over chunk splits |
||||||
|
|
||||||
|
Sometimes it is convenient to be able to manually control how the chunks are split. |
||||||
|
For example, if you want to split a block of code into two, such that they end up in |
||||||
|
two different `@example` blocks or notebook cells. The `#-` token can be used for this |
||||||
|
purpose. All lines starting with `#-` are used as "chunk-splitters": |
||||||
|
```julia |
||||||
|
x = 1 // 3 |
||||||
|
y = 2 // 5 |
||||||
|
#- |
||||||
|
z = x + y |
||||||
|
``` |
||||||
|
The example above would result in two consecutive code-chunks. |
||||||
|
|
||||||
|
!!! tip |
||||||
|
The rest of the line, after `#-`, is discarded, so it is possible to use e.g. |
||||||
|
`#-------------` as a chunk splitter, which may make the source code more readable. |
||||||
|
|
||||||
|
|
||||||
|
## [**3.3.** Document generation](@id Document-generation) |
||||||
|
|
||||||
|
After the parsing it is time to generate the output. What is done in this step is |
||||||
|
very different depending on the output target, and it is describe in more detail in |
||||||
|
the Output format sections: [Markdown output](@ref), [Notebook output](@ref) and |
||||||
|
[Script output](@ref). In short, the following is happening: |
||||||
|
|
||||||
|
* Markdown output: markdown chunks are printed as-is, code chunks are put inside |
||||||
|
a code fence (defaults to `@example`-blocks), |
||||||
|
* Notebook output: markdown chunks are printed in markdown cells, code chunks are |
||||||
|
put in code cells, |
||||||
|
* Script output: markdown chunks are discarded, code chunks are printed as-is. |
||||||
|
|
||||||
|
|
||||||
|
## [**3.4.** Post-processing](@id Post-processing) |
||||||
|
|
||||||
|
When the document is generated the user, again, has the option to hook-into the generation |
||||||
|
with a custom post-processing function. The reason is that one might want to change |
||||||
|
things that are only visible in the rendered document. |
||||||
|
See [Custom pre- and post-processing](@ref Custom-pre-and-post-processing). |
||||||
|
|
||||||
|
|
||||||
|
## [**3.5.** Writing to file](@id Writing-to-file) |
||||||
|
|
||||||
|
The last step of the generation is writing to file. The result is written to |
||||||
|
`$(outputdir)/$(name)(.md|.ipynb|.jl)` where `outputdir` is the output directory supplied |
||||||
|
by the user (for example `docs/generated`), and `name` is a user supplied filename. |
||||||
|
It is recommended to add the output directory to `.gitignore` since the idea is that |
||||||
|
the generated documents will be generated as part of the build process rather than |
||||||
|
beeing files in the repo. |
||||||
@ -0,0 +1,36 @@ |
|||||||
|
#' # **7.** Example |
||||||
|
#' |
||||||
|
#' *Output generated with Examples.jl based on |
||||||
|
#' [this](../../../examples/example.jl) source file.* |
||||||
|
#' |
||||||
|
#' This is an example source file for input to Examples.jl. |
||||||
|
#' |
||||||
|
#md #' If you are reading this you are seeing the markdown output |
||||||
|
#md #' generated from the source file, here you can see the corresponding |
||||||
|
#md #' notebook output: [example.ipynb](./example.ipynb) |
||||||
|
#nb #' If you are reading this you are seeing the notebook output |
||||||
|
#nb #' generated from the source file, here you can see the corresponding |
||||||
|
#nb #' markdown output: [example.md](./example.md) |
||||||
|
|
||||||
|
#' ## Rational numbers in Julia |
||||||
|
#' Rational number in julia can be constructed with the `//` operator: |
||||||
|
|
||||||
|
x = 1//3 |
||||||
|
y = 2//5 |
||||||
|
|
||||||
|
#' Operations with rational number returns a new rational number |
||||||
|
|
||||||
|
x + y |
||||||
|
|
||||||
|
#- |
||||||
|
|
||||||
|
x * y |
||||||
|
|
||||||
|
#' Everytime a rational number is constructed, it will be simplified |
||||||
|
#' using the `gcd` function, for example `2//4` simplifies to `1//2`: |
||||||
|
|
||||||
|
2//4 |
||||||
|
|
||||||
|
#' and `2//4 + 2//4` simplifies to `1//1`: |
||||||
|
|
||||||
|
2//4 + 2//4 |
||||||
Loading…
Reference in new issue