├── .gitattributes ├── .github └── workflows │ ├── docs.yml │ ├── netlify_build_docs.yml │ ├── netlify_deploy_preview.yml │ ├── netlify_pr_task.yml │ └── tests.yml ├── .gitignore ├── LICENSE ├── README.md ├── book ├── .gitignore ├── cli.nim ├── config.nims ├── configuration.nim ├── content.nim ├── index.nim ├── licenses.nim ├── toc.nim └── tocexample │ ├── back_to_parent.md │ ├── index.md │ ├── nested.md │ └── nested_entry.md ├── changelog.md ├── config.nims ├── examplebook ├── examplebook.nim └── nimib.toml ├── nbook.nim ├── nimib.toml ├── nimibook.nimble ├── src ├── nimibook.nim ├── nimibook │ ├── assets.nim │ ├── assets │ │ ├── FontAwesome │ │ │ ├── css │ │ │ │ └── font-awesome.min.css │ │ │ └── fonts │ │ │ │ ├── FontAwesome.otf │ │ │ │ ├── fontawesome-webfont.eot │ │ │ │ ├── fontawesome-webfont.svg │ │ │ │ ├── fontawesome-webfont.ttf │ │ │ │ ├── fontawesome-webfont.woff │ │ │ │ └── fontawesome-webfont.woff2 │ │ ├── css │ │ │ ├── ayu-highlight.css │ │ │ ├── chrome.css │ │ │ ├── general.css │ │ │ ├── highlight.css │ │ │ ├── print.css │ │ │ ├── tomorrow-night.css │ │ │ └── variables.css │ │ ├── fonts │ │ │ ├── OPEN-SANS-LICENSE.txt │ │ │ ├── SOURCE-CODE-PRO-LICENSE.txt │ │ │ ├── fonts.css │ │ │ ├── open-sans-v17-all-charsets-300.woff2 │ │ │ ├── open-sans-v17-all-charsets-300italic.woff2 │ │ │ ├── open-sans-v17-all-charsets-600.woff2 │ │ │ ├── open-sans-v17-all-charsets-600italic.woff2 │ │ │ ├── open-sans-v17-all-charsets-700.woff2 │ │ │ ├── open-sans-v17-all-charsets-700italic.woff2 │ │ │ ├── open-sans-v17-all-charsets-800.woff2 │ │ │ ├── open-sans-v17-all-charsets-800italic.woff2 │ │ │ ├── open-sans-v17-all-charsets-italic.woff2 │ │ │ ├── open-sans-v17-all-charsets-regular.woff2 │ │ │ └── source-code-pro-v11-all-charsets-500.woff2 │ │ └── js │ │ │ ├── book.js │ │ │ └── clipboard.min.js │ ├── builds.nim │ ├── commands.nim │ ├── configs.nim │ ├── defaults.nim │ ├── entries.nim │ ├── paths.nim │ ├── sort.nim │ ├── themes.nim │ ├── toc_dsl.nim │ ├── toc_render.nim │ └── types.nim └── readme.md └── tests ├── config.nims └── ttocs.nim /.gitattributes: -------------------------------------------------------------------------------- 1 | src/nimibook/assets/** linguist-vendored 2 | -------------------------------------------------------------------------------- /.github/workflows/docs.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: 4 | - main 5 | 6 | jobs: 7 | gh-docs: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v2 11 | - name: "install_nim" 12 | id: install_nim 13 | uses: iffy/install-nim@v3 14 | - name: install dependencies 15 | run: nimble install -y 16 | - name: Gen Book 17 | run: nimble --verbose genbook 18 | - name: Deploy 19 | uses: peaceiris/actions-gh-pages@v3 20 | with: 21 | github_token: ${{ secrets.GITHUB_TOKEN }} 22 | publish_dir: docs 23 | -------------------------------------------------------------------------------- /.github/workflows/netlify_build_docs.yml: -------------------------------------------------------------------------------- 1 | # build docs and uploads saves output as artifact for netlify_deploy_preview pipeline to be used 2 | name: netlify_build_docs 3 | on: 4 | pull_request: 5 | branches: 6 | - main 7 | jobs: 8 | tests: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v2 12 | - name: install nim 13 | id: install_nim 14 | uses: iffy/install-nim@v3 15 | # this part might change depending from your project 16 | - name: install dependencies 17 | run: nimble install -y 18 | - name: Gen Book 19 | run: nimble --verbose genbook 20 | # the rest is standard 21 | - run: echo Commit hash = ${{ github.event.pull_request.head.sha }} 22 | - uses: actions/upload-artifact@v4 23 | with: 24 | name: build-${{ github.event.pull_request.head.sha }} 25 | path: docs/ 26 | retention-days: 1 27 | if-no-files-found: error 28 | -------------------------------------------------------------------------------- /.github/workflows/netlify_deploy_preview.yml: -------------------------------------------------------------------------------- 1 | # runs when netlify_build_docs is completed 2 | # reason for the split: we do not want to run user code in an environment that has access to netlify secrets 3 | name: netlify_deploy_preview 4 | on: 5 | workflow_run: 6 | workflows: 7 | - netlify_build_docs 8 | types: 9 | - completed 10 | jobs: 11 | deploy: 12 | name: Deploy Preview 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v2 16 | - uses: potiuk/get-workflow-origin@v1_1 17 | id: source-run-info 18 | with: 19 | token: ${{ secrets.GITHUB_TOKEN }} 20 | sourceRunId: ${{ github.event.workflow_run.id }} 21 | - run: echo sourceHeadSha = ${{ steps.source-run-info.outputs.sourceHeadSha }} 22 | - name: Set env 23 | run: echo "GITHUB_SHA_SHORT=$(echo ${{ steps.source-run-info.outputs.sourceHeadSha }} | cut -c 1-20)" >> $GITHUB_ENV 24 | - run: echo Short hash = ${{ env.GITHUB_SHA_SHORT }} 25 | - name: Show pending status check 26 | uses: Sibz/github-status-action@v1.1.5 27 | with: 28 | authToken: ${{ secrets.GITHUB_TOKEN }} 29 | context: Netlify preview 30 | sha: ${{ steps.source-run-info.outputs.sourceHeadSha }} 31 | description: Deploying site to Netlify. Please wait... 32 | state: pending 33 | - run: rm -rf docs 34 | - name: 'Download artifacts' 35 | uses: dawidd6/action-download-artifact@v2 36 | with: 37 | github_token: ${{secrets.GITHUB_TOKEN}} 38 | workflow: netlify_build_docs.yml # Name of the workflow that created the artifact 39 | name: "build-${{ steps.source-run-info.outputs.sourceHeadSha }}" # Name of the artifact 40 | path: docs/ 41 | - run: echo Deploy Alias = ${{ env.GITHUB_SHA_SHORT }} 42 | - uses: jsmrcaga/action-netlify-deploy@master 43 | with: 44 | NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }} 45 | NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE_ID }} 46 | deploy_alias: ${{ env.GITHUB_SHA_SHORT }} 47 | build_directory: docs/ 48 | install_command: ls 49 | build_command: ls 50 | - name: Status check 51 | uses: Sibz/github-status-action@v1.1.5 52 | with: 53 | authToken: ${{ secrets.GITHUB_TOKEN }} 54 | context: Netlify preview 55 | description: Click link to preview ⇒ 56 | sha: ${{ steps.source-run-info.outputs.sourceHeadSha }} 57 | state: success 58 | # customize with netlify site name 59 | target_url: https://${{ env.GITHUB_SHA_SHORT }}--nimibook.netlify.app 60 | -------------------------------------------------------------------------------- /.github/workflows/netlify_pr_task.yml: -------------------------------------------------------------------------------- 1 | # creates a task saying that it is waiting for the build to finish, does nothing else 2 | name: netlify PR task 3 | on: 4 | pull_request_target: # runs for activity on pr but does not use code from pr and only from pr target 5 | branches: 6 | - main 7 | jobs: 8 | tests: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v2 12 | - run: echo Commit hash = ${{ github.event.pull_request.head.sha }} 13 | - name: Show pending status check 14 | uses: Sibz/github-status-action@v1.1.5 15 | with: 16 | authToken: ${{ secrets.GITHUB_TOKEN }} 17 | context: Netlify preview 18 | sha: ${{ github.event.pull_request.head.sha }} 19 | description: Waiting for build to finish... 20 | state: pending 21 | -------------------------------------------------------------------------------- /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | name: Run tests 2 | on: 3 | push: 4 | branches: 5 | - main 6 | pull_request: 7 | jobs: 8 | tests: 9 | runs-on: ubuntu-latest 10 | strategy: 11 | matrix: 12 | nim: 13 | - '1.6.x' 14 | - 'stable' 15 | - 'devel' 16 | fail-fast: false 17 | name: Nim ${{ matrix.nim }} 18 | steps: 19 | - uses: actions/checkout@v3 20 | - uses: jiro4989/setup-nim-action@v1.4.3 21 | with: 22 | nim-version: ${{ matrix.nim }} 23 | repo-token: ${{ secrets.GITHUB_TOKEN }} 24 | - run: nimble -y install 25 | - run: nimble test 26 | - name: Test generation of nimibook documentation 27 | run: nimble genbook 28 | - name: Test example book 29 | run: nimble test_example 30 | - name: Clean example book 31 | run: nimble clean_example 32 | 33 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # ignore all executables (both windows and linux): 2 | * 3 | !/**/ 4 | !*.* 5 | *.exe 6 | 7 | # ignore build outputs 8 | docs/* 9 | examplebook/my* 10 | *.log 11 | 12 | # useful to create invisible stuff and play around (for x_devnotes.md for x_issue73.nim, ...) 13 | x_* -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Pietro Peterlongo 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # nimibook 2 | 3 | **nimibook** is a port of [mdbook] to [Nim], powered by [nimib]. 4 | Nimibook allows to create a nice looking book from nim code and markdown, 5 | making sure that nim code is running correctly and being able to incorporate code outputs in the final book. 6 | An example book is [nimibook] documentation. 7 | 8 | > [mdBook] is a command line tool and Rust crate to create books 9 | > using Markdown (as by the CommonMark specification) files. 10 | > It's very similar to [Gitbook], which is a command line tool (and Node.js library) 11 | > for building beautiful books using GitHub/Git and Markdown (or AsciiDoc). 12 | 13 | [nimib] is a Nim library to convert your Nim code and its outputs to html documents. 14 | The html output can be easily customized thanks to [nim-mustache], 15 | and this is what allows it to use it to build nimibook. 16 | _nimib documents_ are normal nim files which use nimib library to produce an output document. 17 | 18 | One particular advantage of nimib is that it can incorporate interactive content 19 | taking advantage of nim's js backend. 20 | For the basic examples see 21 | [nimib interactivity doc](https://pietroppeter.github.io/nimib/interactivity.html). 22 | 23 | The Markdown dialect supported by both nimib and nimibook is the subset of [Github Flavored Markdown][GFM] 24 | provided by [nim-markdown]. For a quick reference of supported syntax see this [cheatsheet] (created with nimib). 25 | 26 | 27 | [mdbook]: https://rust-lang.github.io/mdBook/index.html 28 | [Nim]: https://nim-lang.org/ 29 | [nimib]: https://pietroppeter.github.io/nimib/ 30 | [Gitbook]: https://github.com/GitbookIO/gitbook 31 | [nim-mustache]: https://github.com/soasme/nim-mustache 32 | [nimibook]: https://pietroppeter.github.io/nimibook/ 33 | [GFM]: https://github.github.com/gfm/ 34 | [nim-markdown]: https://github.com/soasme/nim-markdown 35 | [cheatsheet]: https://pietroppeter.github.io/nimib/cheatsheet.html 36 | 37 | ## Status 38 | 39 | Nimibook as it is provides the basic functionality 40 | needed to create a book with markdown and nimib sources. 41 | It still has some features missing from mdbook 42 | (see [this issue](https://github.com/pietroppeter/nimibook/issues/9#issuecomment-851989939)). 43 | Contributions are welcome and we will provide guidance and code reviews. 44 | You can join a [Nimib Speaking Hours](https://github.com/pietroppeter/nimib/discussions/categories/nimib-speaking-hours) 45 | session if you want some live discussions on whether or not a feature fits or you want pointers on how to help out. 46 | 47 | To follow up on recent changes check the [changelog.md](https://github.com/pietroppeter/nimibook/blob/main/changelog.md). 48 | 49 | ## Example sites using nimibook 50 | 51 | - [scinim/getting-started](https://scinim.github.io/getting-started/): Getting started with Nim for Scientific Computing 52 | - [moigagoo/norm](https://norm.nim.town): A Nim ORM for SQLite and Postgres 53 | - [moigagoo/karkas](https://karkas.nim.town/): Layout helpers and syntactic sugar for Karax 54 | - [PhilippMDoerner/Snorlogue](https://philippmdoerner.github.io/Snorlogue/bookCompiled/): A plugin for the prologue web-framework that provides a set of simple CRUD routes to administrate your database 55 | - [PhilippMDoerner/mapster](https://philippmdoerner.github.io/mapster/index.html): A simple way to generate mapping functions at compile-time without having to write them yourself 56 | - [dsrw/enu](https://getenu.com/docs/intro.html): A Logo-like 3D environment, implemented in Nim 57 | - [can-lehmann/owlkettle](https://can-lehmann.github.io/owlkettle/README.html): A declarative user interface framework based on GTK 4 58 | 59 | You are welcome to open a PR and add your site using nimibook here. 60 | 61 | ## Features 62 | 63 | - table of contents in a collapsible sidebar 64 | - five themes (Light, Rust, Coal, Navy, Ayu) 65 | - buttons to next/previous pages 66 | - build multiple files in parallel 67 | - (optional) latex content with katex 68 | - (optional) link to github repo 69 | - (optional) link to track analytics with plausible analytics 70 | 71 | ## Installation 72 | 73 | To install Nimibook: `nimble install nimibook` 74 | 75 | ## How to setup your book with nimibook 76 | 77 | Nimibook does not ([yet](https://github.com/pietroppeter/nimibook/issues/63)) provides an executable to manage your book, but it provides the basic building blocks to write your own. 78 | 79 | **1. example nbook.nim**: in a folder of your choice create a `nbook.nim` file with the following content: 80 | 81 | ```nim 82 | import nimibook 83 | 84 | var book = initBookWithToc: 85 | entry("Preface", "preface.md", numbered = false) 86 | entry("Introduction", "intro.md") 87 | section("Chapter 1", "chapter1/index.nim"): 88 | entry("Content", "content.nim") 89 | draft("Nothing yet") 90 | section("Sub chapter", "no_ext"): 91 | entry("and some more content", "more.md") 92 | 93 | nimibookCli(book) 94 | ``` 95 | 96 | **2. write a TOC**: modify `nbook.nim` to specify 97 | the planned Table of Content (TOC) for your book. 98 | 99 | **3. nbook init**: running `nim r nbook init` (or compile `nbook` and run `nbook init`) will set up the book with: 100 | - a `nimib.toml` that contains the default configuration for the book 101 | - a `book` folder that contains sources for all chapters mentioned in the TOC. Note that `.nim` files already contain default `nimib` content to be used in nimibook. 102 | - a `docs` folder that contains static assets for the book 103 | and that will contain the built book 104 | 105 | **4. nbook build**: run `nim r nbook build` to build the book. Open any `.html` file in `docs` folder to navigate your book. 106 | 107 | **5. create your content and enjoy!**: now you are ready to start creating content in your sources and publish your book. 108 | 109 | 112 | 113 | See [nimibook] documentation for more details. 114 | 115 | ## Contribute 116 | 117 | You are more than welcome to contribute! 118 | 119 | - We usually have some open issues of stuff we need to fix or we would like to do. 120 | - You have an overview of the code base in [src/readme.md](src/readme.md) 121 | - The CI is setup to run tests and publish a document PR preview (click on details on the Netlify preview task once it's green), so that we can all check the changes directly from the PR. 122 | - You should also test and build the book locally, there are nimble tasks to help with that (run `nimble tasks` for the list). 123 | - If the feature can be tested with a unit test, make sure to add one. 124 | - Once you make a change, remember to document your changes in the appropriate place in the docs. 125 | - If you add a module, remember to update the code guide. 126 | - bump the version (usually a patch increment) so that we can immediately tag and release your contribution. 127 | - Make sure that the title of your PR is clear and edit the initial message with a few short sentences that will be added to the changelog once we release. 128 | 129 | ## Analytics 130 | 131 | This website is tracking analytics with [plausible.io](https://plausible.io/index.html), a lightweight and open-source website analytics tool with no cookies and fully compliant with GDPR, CCPA and PECR. 132 | Analytics for this website are publicly available [here](https://plausible.io/pietroppeter.github.io%2Fnimibook). You can opt out from analytics tracking with [standard ad-blocking](https://plausible.io/docs/excluding) or typing [`localStorage.plausible_ignore=true`](https://plausible.io/docs/excluding-localstorage) in browser console. 133 | -------------------------------------------------------------------------------- /book/.gitignore: -------------------------------------------------------------------------------- 1 | *.html 2 | *.mustache 3 | #toc.mustache 4 | -------------------------------------------------------------------------------- /book/cli.nim: -------------------------------------------------------------------------------- 1 | import nimib, nimibook 2 | import std / strformat 3 | 4 | nbInit(theme = useNimibook) 5 | nbText: fmt"""# Commands 6 | 7 | With the `nimibookCli(book)` command at the end of `nbook.nim` file, 8 | the executable is provided with a number of commands. 9 | The help on the executable can be obtained running `nbook` without arguments: 10 | 11 | ``` 12 | {cliHelp} 13 | ``` 14 | 15 | ## Customize nim compile time parameters 16 | 17 | During build nimibook compiles and runs a `example.nim` file 18 | with `nim r /example.nim`. 19 | Any additional argument after `build` is used as-is in ``. 20 | 21 | For example to compile the whole book in release mode with no hints 22 | or other messages from the compiler you can run: 23 | 24 | ``` 25 | nbook build -d:release --hints:off --verbosity:0 26 | ``` 27 | 28 | ## Single nimib document build 29 | 30 | When you are working on a single nimib document you are not interested in 31 | building the complete book. In order to build a single file 32 | you need first to dump the `book.json` with `nbook dump`. 33 | After this is done you can build the single `example.nim` file 34 | as you would with standard nimib. For example to show it in the browser after 35 | build is completed you can run: 36 | 37 | ``` 38 | nim r book/example.nim --nbShow 39 | ``` 40 | 41 | ## Parallel build and error logs 42 | 43 | By default nimibook builds source files in parallel. 44 | If an error is produced when building a source file a `.log` file 45 | with the output of build command is created next to source file. 46 | 47 | If you need to disable the parallel builds you can set 48 | `-d:nimibParallelBuild=false` when compiling `nbook`. 49 | 50 | By default it builds up to a maximum of 10 source files in parallel. 51 | To change the default maximum to `n` you can set 52 | `-d:nimibMaxProcesses=n` when compiling `nbook`. 53 | 54 | To avoid file collisions of C/object files in nimcache for files with the same name, 55 | typically you have multiple `index.nim` files in separate folders for example, 56 | each file will get its own nimcache folder. For example the files `index.nim` and `tutorials/index.nim` 57 | will be assigned the nimcache folders `$nimcache/index/` and `$nimcache/tutorials/index/` where `$nimcache` is the 58 | nimcache folder of the file were you run `nimibookCli`. 59 | Without this they would both try to use the same `index_r`/`index_d` folder in nimcache and mutate the same files. 60 | All of this is handled by nimiBook, so you don't have to worry about this. 61 | """ 62 | nbSave 63 | -------------------------------------------------------------------------------- /book/config.nims: -------------------------------------------------------------------------------- 1 | --path:"." 2 | --path:"../src" -------------------------------------------------------------------------------- /book/configuration.nim: -------------------------------------------------------------------------------- 1 | import nimib, nimibook 2 | import std / [os, strutils, strformat] 3 | import nimibook / [defaults, configs] 4 | 5 | func skipUntil*(text: string, keyword: string): string = 6 | var untilReached = false 7 | var lines: seq[string] 8 | for line in text.splitLines: 9 | if line.startsWith(keyword): 10 | untilReached = true 11 | if untilReached: 12 | lines.add line 13 | result = lines.join("\n") 14 | 15 | nbInit(theme = useNimibook) 16 | 17 | template debug(message: string) = 18 | when defined(nimibookDebugConfigurationDoc): 19 | debugEcho message 20 | 21 | # the following is very dependent on how source is written 22 | proc readBookConfigFields: seq[string] = 23 | # current folder is docs 24 | let src = "../src/nimibook/types.nim" 25 | var process = false 26 | var field, typ, description: string 27 | for line in src.lines: 28 | debug line 29 | if line.strip.startsWith("BookConfig"): 30 | debug ">start processing" 31 | process = true 32 | continue 33 | if line.strip.startsWith("#"): 34 | debug ">skip" 35 | continue 36 | if line.strip.startsWith("Book"): 37 | debug ">stop processing" 38 | break 39 | if process and line.contains("##"): 40 | debug ">match" 41 | description = line.split("##")[1].strip 42 | field = line.split("##")[0].split("*:")[0].strip 43 | typ = line.split("##")[0].split("*:")[1].strip 44 | result.add fmt"* **{field}** (`{typ}`): {description}" 45 | else: 46 | debug ">noMatch" 47 | 48 | let fieldList = readBookConfigFields().join("\n") 49 | 50 | nbText: fmt""" 51 | # Configuration 52 | 53 | Book configuration is done inside the `[nimibook]` section of `nimib.toml` configuration file. 54 | 55 | Here are the available fields: 56 | 57 | {fieldList} 58 | 59 | As an example here is nimibook configuration:""" 60 | nbCode: # highlight as nim since it is better than no highlighting... 61 | discard 62 | nb.blk.code = "../nimib.toml".readFile 63 | nbText: "This is the default configuration created by the `init command`:" 64 | nbCode: # highlight as nim since it is better than no highlighting... 65 | discard 66 | nb.blk.code = block: 67 | var book = Book() 68 | book.setDefaults 69 | book.renderConfig.skipUntil("[nimibook]") 70 | 71 | 72 | nbText: """ 73 | ## Folder structure 74 | By default the nimibook folder structure is to put all sources in a `book` folder 75 | and to put the book built output in a `docs` folder (so that it is straightforward to publish the book with github pages). 76 | 77 | These folders can be customized since they are taken from [nimib] section of `nimib.toml` 78 | as `srcDir` and `homeDir`. 79 | 80 | ### where does nimibook puts the output html? 81 | 82 | The logic of nimibook is that for every source, the **relative path** with respect to `srcDir` is applied as relative path to `homeDir` and this will give the html output path. 83 | For example, if we have default values `srcDir="book";homeDir="docs"`: 84 | - a source in `book\example.md` will generate `docs\example.html` 85 | - a source in `book\folder\subfolder\example.nim` will generate `docs\folder\subfolder\example.html` 86 | 87 | Note in particular that if you have this structure in root folder: 88 | ``` 89 | README.md 90 | img.png 91 | book/ 92 | example.nim 93 | docs/ 94 | ... 95 | ``` 96 | and you add `..\README.md` as source, the generated `README.html` will NOT be in `docs` folder and it will be next to `README.md` (since `docs` is a folder inside root folder, at the same level as `book`). 97 | You can override this behaviour in `nbook.nim` but if the README.md references an image (say `img.png`) you will also need to make a copy of `img.png` in the docs folder. 98 | 99 | ## Additional remarks 100 | * for consistency with template values, we use snake case for fields of this object. 101 | * the book object replicates functionalities available in mdbook 102 | * relevant documentation for mdbook is in this two pages: 103 | - 104 | - 105 | * if not indicated otherwise, these fields are present in `document.mustache` and they are directly adapted from `index.hbs` 106 | * documentation comments above come directly from mdbook documentation (unless otherwise stated) 107 | """ 108 | nbSave -------------------------------------------------------------------------------- /book/content.nim: -------------------------------------------------------------------------------- 1 | import std / strformat 2 | import nimib, nimibook 3 | import nimib / themes 4 | from nimibook / commands import emptySrcFile 5 | nbInit(theme = useNimibook) 6 | 7 | nbText: """ 8 | # Content 9 | 10 | The content of a chapter of a book can be a standard Markdown file (`.md`) or a `.nim` file. 11 | 12 | The nim file must be a nimib document and the default content (created by `init` command) is: 13 | """ 14 | nbCode: 15 | discard 16 | nb.blk.code = emptySrcFile("Default Content", ".nim") 17 | 18 | 19 | nbText: """## Latex 20 | 21 | Latex is available through [Katex](https://www.latex-project.org) as in Nimib. 22 | It is enabled on a specific document with the command 23 | """ 24 | nbCode: 25 | nb.useLatex 26 | 27 | nbText: &""" 28 | which adds the following content in the head of the document: 29 | 30 | ```html 31 | {themes.latex} 32 | ``` 33 | """ 34 | 35 | let inline_paragraph = """ 36 | > Euler's identity is the equality 37 | > $e^{i\pi} + 1 = 0$, where $e$ is the base of natural logarithms, 38 | > $i$ is the imaginary unit, and $\pi$ is the ratio of circumference of a circle 39 | > to its diameter. 40 | """ 41 | let block_equation = """ 42 | $$ 43 | f(x) = \frac{1}{\sigma\sqrt{2\pi}} 44 | \exp\left( -\frac{1}{2}\left(\frac{x-\mu}{\sigma}\right)^{\!2}\,\right) 45 | $$ 46 | """ 47 | 48 | nbText: &"""## Inline equations 49 | 50 | **Inline equations** are obtained with delimiters `$ ... $`. The following inline paragraph: 51 | 52 | {inline_paragraph} 53 | 54 | ...is created with the following source: 55 | 56 | ```md 57 | {inline_paragraph} 58 | ``` 59 | 60 | ## Block equations 61 | 62 | **Block equations** are obtained with delimiters `$$ ... $$`. The follwing block equation: 63 | 64 | {block_equation} 65 | 66 | ...is created with the following source: 67 | 68 | ```md 69 | {block_equation} 70 | ``` 71 | 72 | ### MathJax support 73 | 74 | As in the original mdbook, you can opt instead to activate [MathJax support](https://rust-lang.github.io/mdBook/format/mathjax.html) 75 | with: 76 | 77 | ```nim 78 | nb.context["mathjax_support"] = true 79 | ``` 80 | """ 81 | 82 | nbSave -------------------------------------------------------------------------------- /book/index.nim: -------------------------------------------------------------------------------- 1 | import nimib, nimibook 2 | import strutils 3 | nbInit(theme = useNimibook) 4 | 5 | proc readFileUntil(filename: string, text: string): string = 6 | for line in filename.lines: 7 | if line.startsWith(text): 8 | return result 9 | result &= line & '\n' 10 | 11 | nbText: "../README.md".readFileUntil(" 10 | 11 | {{ title }} 12 | {{#is_print }} 13 | 14 | {{/is_print}} 15 | {{#base_url}} 16 | 17 | {{/base_url}} 18 | 19 | 20 | {{> head}} 21 | 22 | 23 | 24 | 25 | 26 | 27 | {{&favicon_escaped}} 28 | {{#favicon_svg}} 29 | 30 | {{/favicon_svg}} 31 | {{#favicon_png}} 32 | 33 | {{/favicon_png}} 34 | 35 | 36 | 37 | {{#print_enable}} 38 | 39 | {{/print_enable}} 40 | 41 | 42 | 43 | {{#copy_fonts}} 44 | 45 | {{/copy_fonts}} 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | {{#additional_css}} 54 | 55 | {{/additional_css}} 56 | 57 | {{#mathjax_support}} 58 | 59 | 60 | {{/mathjax_support}} 61 | 62 | {{&latex}} 63 | 64 | {{^disableHighlightJs}} 65 | {{{highlightJs}}} 66 | {{/disableHighlightJs}} 67 | 68 | 69 | {{#plausible_analytics_url}} 70 | 71 | {{/plausible_analytics_url}} 72 | 73 | 74 | 75 | 79 | 80 | 81 | 95 | 96 | 97 | 107 | 108 | 109 | 119 | 120 | 126 | 127 |
128 | 129 |
130 | {{> header}} 131 | 132 | 175 | 176 | {{#search_enabled}} 177 | 187 | {{/search_enabled}} 188 | 189 | 190 | 197 | 198 |
199 |
200 | {{#blocks}} 201 | {{&.}} 202 | {{/blocks}} 203 |
204 | 205 | 221 |
222 |
223 | 224 | 237 | 238 |
239 | 240 | {{#livereload}} 241 | 242 | 255 | {{/livereload}} 256 | 257 | {{#google_analytics}} 258 | 259 | 274 | {{/google_analytics}} 275 | 276 | {{#playground_line_numbers}} 277 | 280 | {{/playground_line_numbers}} 281 | 282 | {{#playground_copyable}} 283 | 286 | {{/playground_copyable}} 287 | 288 | {{#playground_js}} 289 | 290 | 291 | 292 | 293 | 294 | {{/playground_js}} 295 | 296 | {{#search_js}} 297 | 298 | 299 | 300 | {{/search_js}} 301 | 302 | 303 | 304 | 305 | 306 | {{#additional_js}} 307 | 308 | {{/additional_js}} 309 | 310 | {{#is_print}} 311 | {{#mathjax_support}} 312 | 319 | {{/mathjax_support}} 320 | {{^mathjax_support}} 321 | 326 | {{/mathjax_support}} 327 | {{/is_print}} 328 | 329 | 330 | 331 | """ 332 | 333 | proc useNimibook*(doc: var NbDoc) = 334 | doc.context["path_to_root"] = doc.srcDirRel.string & "/" # I probably should make sure to have / at the end 335 | 336 | # templates are in memory 337 | doc.partials["document"] = document 338 | # if they need to be overriden a specific template folder should be created in nbSrcDir 339 | doc.templateDirs = @[doc.srcDir.string / "templates"] 340 | 341 | # book.json is publicly accessible (sort of a public static api) 342 | let bookPath = doc.homeDir.string / "book.json" 343 | # load book object 344 | var book = load(bookPath) 345 | 346 | # book configuration 347 | doc.context["language"] = book.language 348 | doc.context["default_theme"] = book.default_theme 349 | doc.context["description"] = book.description 350 | doc.context["favicon_escaped"] = book.favicon_escaped 351 | doc.context["preferred_dark_theme"] = book.preferred_dark_theme 352 | doc.context["theme_option"] = book.theme_option 353 | doc.context["book_title"] = book.title 354 | doc.context["git_repository_url"] = book.git_repository_url 355 | doc.context["git_repository_icon"] = book.git_repository_icon 356 | doc.context["plausible_analytics_url"] = book.plausible_analytics_url 357 | doc.context["highlightJs"] = highlightJsTags 358 | 359 | var thisEntry: Entry 360 | # process toc 361 | for i, entry in enumerate(book.toc.entries.mitems): 362 | if normalizePath(entry.url) == normalizePath(doc.filename.replace('\\', '/')): # replace needed for windows 363 | thisEntry = entry 364 | entry.isActive = true 365 | let 366 | prevUrl = book.prevEntryUrl i 367 | nextUrl = book.nextEntryUrl i 368 | if prevUrl.len > 0: 369 | doc.context["previous"] = prevUrl 370 | if nextUrl.len > 0: 371 | doc.context["next"] = nextUrl 372 | break 373 | doc.partials["toc"] = render book.toc 374 | 375 | # html.head.title (what appears in the tab) 376 | doc.context["title"] = thisEntry.title & " - " & book.title 377 | -------------------------------------------------------------------------------- /src/nimibook/toc_dsl.nim: -------------------------------------------------------------------------------- 1 | import std / [os, macros] 2 | import nimibook / [types, paths, entries] 3 | export os.splitFile, os.normalizedPath, paths.formatFileName, paths.joinPath 4 | 5 | proc inc(levels: var seq[int]) = 6 | levels[levels.high] = levels[levels.high] + 1 7 | 8 | template initToc*(body: untyped): Toc = 9 | var toc {.inject.}: Toc 10 | var levels {.inject.}: seq[int] = @[1] 11 | var folders {.inject.}: seq[string] = @[] 12 | 13 | body 14 | toc 15 | 16 | template entry*(label, rfile: string, numbered = true) = 17 | let inputs = rfile.splitFile 18 | let file = inputs.dir / formatFileName(inputs) 19 | toc.entries.add Entry(title: label, path: joinPath(folders, file).normalizedPath(), levels: levels, isNumbered: numbered) 20 | if numbered: 21 | inc levels 22 | 23 | template draft*(label: string, numbered = true) = 24 | toc.entries.add Entry(title: label, path: "", levels: levels, isNumbered: numbered, isDraft: true) 25 | if numbered: 26 | inc levels 27 | 28 | template section*(label, rfile: string, sectionBody: untyped) = 29 | let inputs = rfile.splitFile 30 | let curfolder = inputs.dir 31 | let file = formatFileName(inputs) 32 | folders.add curfolder 33 | toc.entries.add Entry(title: label, path: joinPath(folders, file).normalizedPath(), levels: levels, isNumbered: true) 34 | levels.add 1 35 | sectionBody 36 | discard pop levels 37 | discard pop folders 38 | inc levels 39 | 40 | proc showToc*(book: Book): string = 41 | result.add "S O == Table Of Contents == (S: Source, O: Output)\n" 42 | for e in book.toc.entries: 43 | result.add book.renderLine(e) & "\n" 44 | result.add " == " & $(book.toc.entries.len) & " entries ==" -------------------------------------------------------------------------------- /src/nimibook/toc_render.nim: -------------------------------------------------------------------------------- 1 | import std / strformat 2 | import nimibook / [types, entries] 3 | 4 | const path_to_root = "{{path_to_root}}" 5 | 6 | proc closeSection(): string = 7 | result.add """ 8 | 9 | 10 | """ 11 | 12 | proc openSection(): string = 13 | result.add """ 14 |
  • 15 |
      16 | """ 17 | 18 | proc addEntryImpl(e: Entry): string = 19 | let active = if e.isActive: " class=\"active\"" else: "" 20 | result.add "
    1. \n" 21 | if e.isDraft: 22 | result.add " \n" 31 | else: 32 | result.add " " 33 | result.add "
    2. \n" 34 | 35 | proc openGenToc(): string = 36 | result.add """ 37 |
        38 | """ 39 | 40 | proc closeGenToc(): string = 41 | # Close last opened section that will not be closed anywhere 42 | # result.add closeSection() 43 | result.add """ 44 |
      45 | """ 46 | 47 | proc render*(toc: Toc): string = 48 | ## renders toc as a mustache partial 49 | # assume entries are sorted 50 | result.add openGenToc() 51 | var previousLevel = 1 52 | for e in toc.entries: 53 | if len(e.levels) == previousLevel: 54 | discard 55 | elif len(e.levels) > previousLevel: 56 | result.add openSection() 57 | else: 58 | for _ in 1 .. (previousLevel - len(e.levels)): 59 | result.add closeSection() 60 | 61 | result.add addEntryImpl(e) 62 | previousLevel = len(e.levels) 63 | result.add closeGenToc() 64 | -------------------------------------------------------------------------------- /src/nimibook/types.nim: -------------------------------------------------------------------------------- 1 | import nimib / types 2 | import std / tables 3 | export tables 4 | import macros 5 | 6 | type 7 | Entry* = object 8 | title*: string 9 | path*: string 10 | levels*: seq[int] 11 | isNumbered*: bool 12 | isDraft*: bool 13 | isActive*: bool 14 | Toc* = object 15 | entries*: seq[Entry] 16 | BookConfig* = object ## All the fields in this object can be set from Toml configuration in a [nimibook] section 17 | title*: string ## Title of the book 18 | language*: string ## The main language of the book, which is used as a language attribute `` for example (defaults to en) 19 | description*: string ## A description for the book, which is added as meta information in the html of each page 20 | default_theme*: string ## The theme color scheme to select by default in the 'Change Theme' dropdown. Defaults to light. 21 | preferred_dark_theme*: string ## The default dark theme. This theme will be used if the browser requests the dark version of the site via the ['prefers-color-scheme'](https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-color-scheme) CSS media query. Defaults to navy. 22 | git_repository_url*: string ## A url to the git repository for the book. If provided an icon link will be output in the menu bar of the book. 23 | git_repository_icon*: string ## The FontAwesome icon class to use for the git repository link. Defaults to `fa-github`. 24 | plausible_analytics_url*: string ## (new in nimibook) if non empty it will include plausible analytics script in every page. 25 | favicon_escaped*: string ## (new in nimibook) provide your fully custom ``. defaults to whale emoji as in nimib. 26 | Book* = object 27 | initDir*: AbsoluteDir 28 | cfgDir*: AbsoluteDir 29 | rawCfg*: string 30 | nbCfg*: NbConfig 31 | cfg*: BookConfig 32 | theme_option*: Table[string, string] # cannot find it mentioned in mdbook docs. by default is a Table with available themes and their names. 33 | toc*: Toc 34 | keep*: seq[string] # used in commands.shouldDelete but unclear if it is really used or not 35 | 36 | template expose(ObjType, cfg, field, FieldType: untyped) = 37 | template `field`*(o: ObjType): FieldType = 38 | o.`cfg`.`field` 39 | 40 | template `field =`*(o: var ObjType, v: FieldType) = 41 | o.`cfg`.`field` = v 42 | 43 | macro expose(ObjType, myCfg, body: untyped) = 44 | #echo body.treerepr 45 | result = newStmtList() 46 | for arg in body: 47 | doAssert arg.kind == nnkCall 48 | let f = arg[0] 49 | let typ = arg[1][0] # [1] is nnkStmtList w/ 1 element 50 | result.add quote do: 51 | expose `ObjType`, `myCfg`, `f`, `typ` 52 | #echo arg.treerepr 53 | #echo result.repr 54 | 55 | # thanks to vindaar, see https://stackoverflow.com/q/71459423/4178189 56 | expose(Book, cfg): 57 | title: string 58 | language: string 59 | description: string 60 | default_theme: string 61 | preferred_dark_theme: string 62 | git_repository_url: string 63 | git_repository_icon: string 64 | plausible_analytics_url: string 65 | favicon_escaped: string 66 | 67 | # srcDir and homeDir should be given as relative to cfgDir 68 | proc srcDir*(book: Book): string = book.nbCfg.srcDir 69 | proc homeDir*(book: Book): string = book.nbCfg.homeDir 70 | 71 | #[ 72 | documentation for mdbook is in this two pages: 73 | - https://rust-lang.github.io/mdBook/format/theme/index-hbs.html 74 | - https://rust-lang.github.io/mdBook/format/config.html 75 | 76 | here is the list index.hbs adapted from mdbook to mustache 77 | current list is what I have not yet put in Book object: 78 | - is_print 79 | - base_url 80 | - favicon_svg/favicon_png 81 | - print_enable 82 | - additional_css 83 | - mathjax_support 84 | - search_enable 85 | - git_repository_edit_url 86 | - previous (link) 87 | - next (link) 88 | - livereload 89 | - google_analytics 90 | - playground_line_numbers 91 | - playground_copyable 92 | - playground_js 93 | - search_js 94 | - additional_js 95 | 96 | these are partials that were referred in original index.hbs: 97 | - head (also in document.mustache) 98 | - header (also in document.mustache) 99 | - toc (it was handlebar helper and it is handled differently in nimibook) 100 | 101 | list of assets required as mentioned directly in document.mustache (other assets might be mentioned elsewhere - e.g. in css): 102 | - css/variables.css 103 | - css/general.css 104 | - css/chrome.css 105 | - FontAwesome/css/font-awesome.css 106 | - highlight.css 107 | - tomorrow-night.css 108 | - ayu-highlight.css 109 | - clipboard.min.js 110 | - highlight.js 111 | - book.js 112 | 113 | ]# 114 | -------------------------------------------------------------------------------- /src/readme.md: -------------------------------------------------------------------------------- 1 | # Code guide 2 | 3 | Code in nimibook serves two purposes: 4 | 5 | 1. code to implement a CLI to manage the book (init, build, ...) 6 | 2. code for the theme to be used in every chapter (page) of the book 7 | 8 | A special role is played by the Table of Contents (TOC), which 9 | is needed both by the CLI and by each page. 10 | 11 | Currently the CLI has to be generated in a custom file 12 | where the TOC is also specified (default name: `nbook.nim`). 13 | In the future nimibook will come with its own 14 | prebuilt `nbook` binary and Toc will be defined elsewhere 15 | (e.g. with a SUMMARY.md as in mdbook). 16 | 17 | To make the TOC available for every page, the `Book` object 18 | (created by the CLI) is serialized as `book.json`. 19 | 20 | Summary content of the various files: 21 | 22 | - `src\nimibook.nim`: 23 | - imports and exports for public api 24 | - CLI parser 25 | - `src\nimibook\types.nim`: the types (`Book`, `Toc`, `Entry`) 26 | - `src\nimibook\toc_dsl.nim`: template DSL to specify a TOC (and generate a `Book` object) 27 | - `src\nimibook\entries.nim`: utility functions for `Entry` object (an entry is a element of the Toc, aka chapter) 28 | - `src\nimibook\toc_render.nim`: function to render `Toc` object as a mustache partial (to be used in every page) 29 | - `src\nimibook\theme.nim`: 30 | - implements `useNimibook` theme function to be used in every page 31 | - contains `document` partial (the mustache template for every page) 32 | - `src\nimibook\assets\`: a folder where all the assets (css, js, fonts) reside 33 | - `src\nimibook\assets.nim`: function to initialize and update assets 34 | - `src\nimibook\defaults.nim`: default values for `Book` object 35 | - `src\nimibook\commands.nim`: implementations for all commands used by the CLI except build 36 | - `src\nimibook\builds.nim`: implementation for build command 37 | - `src\nimibook\paths.nim`: utilities for path handling (used only in `tocs.nim`) 38 | - `src\nimibook\sort.nim`: utilities for sorting entries. Currently not used, could be used in the future to sort entries generated automatically from a folder. 39 | -------------------------------------------------------------------------------- /tests/config.nims: -------------------------------------------------------------------------------- 1 | switch("path", "$projectDir/../src") -------------------------------------------------------------------------------- /tests/ttocs.nim: -------------------------------------------------------------------------------- 1 | import unittest 2 | import std / strutils 3 | import nimibook / [toc_dsl, toc_render, entries, types] 4 | 5 | test "toc dsl": 6 | var book: Book 7 | book.toc = initToc: 8 | entry("Introduction", "index.nim", numbered = false) 9 | section("Part 1", "part1/index.nim"): 10 | entry("This is important", "important.nim") 11 | entry("Also this", "also.nim") 12 | entry("this a little less", "less.nim") 13 | section("subsection", "sub.nim"): 14 | entry("and this may be skipped", "skip.nim") 15 | section("Part 2", "part2/index.md"): 16 | entry("this might be interesting", "mmh.md") 17 | draft("and I have not written this yet") 18 | entry("Appendix", "appendix.md", numbered = false) 19 | 20 | echo book.showToc 21 | check len(book.toc.entries) == 11 22 | check book.toc.entries[0].url == "index.html" 23 | check book.toc.entries[1].url == "part1/index.html" 24 | check book.toc.entries[2].url == "part1/important.html" 25 | 26 | test "toc render": # issue 73 27 | let myToc = initToc: 28 | entry("Should Be 1.", "book/bla.nim") 29 | section("Should be 2", "book/internals.nim"): 30 | section("Should be 2.1", "internals/adders.nim"): 31 | entry("Should be 2.1.1", "adders/one_adder.nim") 32 | 33 | entry("Should be 3.", "CONTRIBUTING.md") 34 | let renderedToc = myToc.render 35 | check renderedToc.count("") 36 | check renderedToc.count("") --------------------------------------------------------------------------------