├── .gitignore ├── citation-backlinks.lua ├── _extensions └── citation-backlinks │ ├── _extension.yml │ └── citation-backlinks.lua ├── test ├── test.yaml ├── input.md ├── example.bib └── expected.native ├── .editorconfig ├── .github └── workflows │ ├── ci.yaml │ └── website.yml ├── README.md ├── LICENSE ├── .tools └── docs.lua └── Makefile /.gitignore: -------------------------------------------------------------------------------- 1 | /_site/ 2 | -------------------------------------------------------------------------------- /citation-backlinks.lua: -------------------------------------------------------------------------------- 1 | _extensions/citation-backlinks/citation-backlinks.lua -------------------------------------------------------------------------------- /_extensions/citation-backlinks/_extension.yml: -------------------------------------------------------------------------------- 1 | name: citation-backlinks 2 | author: Albert Krewinkel 3 | version: 0.0.1 4 | contributes: 5 | filters: 6 | - citation-backlinks.lua 7 | -------------------------------------------------------------------------------- /test/test.yaml: -------------------------------------------------------------------------------- 1 | input-files: 2 | - test/input.md 3 | 4 | to: native 5 | 6 | filters: 7 | - type: citeproc 8 | - type: lua 9 | path: citation-backlinks.lua 10 | 11 | standalone: true 12 | -------------------------------------------------------------------------------- /test/input.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Lorem ipsum 3 | author: Nullus 4 | nocite: '@*' 5 | bibliography: test/example.bib 6 | --- 7 | 8 | Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Donec 9 | hendrerit tempor tellus. Donec pretium posuere tellus. Proin quam 10 | nisl @krewinkel2017, tincidunt et, mattis eget, convallis nec, 11 | purus. 12 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # editorconfig.org 4 | 5 | root = true 6 | 7 | [*] 8 | end_of_line = lf 9 | charset = utf-8 10 | trim_trailing_whitespace = true 11 | insert_final_newline = true 12 | 13 | [Makefile] 14 | indent_style = tab 15 | 16 | [*.lua] 17 | indent_style = space 18 | indent_size = 2 19 | # Code should stay below 80 characters per line. 20 | max_line_length = 80 21 | 22 | [*.md] 23 | # Text with 60 to 66 characters per line is said to be the easiest 24 | # to read. 25 | max_line_length = 66 26 | -------------------------------------------------------------------------------- /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | # Run on all pull requests that change code. 5 | pull_request: 6 | paths-ignore: 7 | - 'README.md' 8 | - LICENSE 9 | - .editorconfig 10 | # Run every time a code change is pushed. 11 | push: 12 | paths-ignore: 13 | - 'README.md' 14 | - LICENSE 15 | - .editorconfig 16 | # Test if things still work each Tuesday morning at 5:39 UTC. 17 | # This way we will catch incompatible pandoc changes in a timely 18 | # manner. 19 | schedule: 20 | # At 5:39am each Tuesday 21 | - cron: '39 5 * * 2' 22 | 23 | jobs: 24 | test: 25 | runs-on: ubuntu-latest 26 | 27 | steps: 28 | - name: Checkout 29 | uses: actions/checkout@v2 30 | 31 | - name: Test 32 | run: | 33 | # Use docker image with current release. 34 | make test \ 35 | PANDOC="docker run --rm --volume $(pwd):/data \ 36 | --user $(id -u):$(id -g) pandoc/core:latest" 37 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Citation Backlinks Filter 2 | ================================================================== 3 | 4 | [![GitHub build status][CI badge]][CI workflow] 5 | 6 | This filter modifies the bibliography, adding backlinks to the 7 | locations in the text where an item is cited. 8 | 9 | [Lua filters]: https://pandoc.org/lua-filters.html 10 | [CI badge]: https://img.shields.io/github/workflow/status/tarleb/citation-backlinks/CI?logo=github 11 | [CI workflow]: https://github.com/tarleb/citation-backlinks/actions/workflows/ci.yaml 12 | 13 | Usage 14 | ------------------------------------------------------------------ 15 | 16 | This filter should be invoked after `citeproc`, e.g. 17 | 18 | pandoc --citeproc --lua-filter citation-backlinks.lua ... 19 | 20 | The filter doesn't work yet as a Quarto extension. 21 | 22 | License 23 | ------------------------------------------------------------------ 24 | 25 | This pandoc Lua filter is published under the MIT license, see 26 | file `LICENSE` for details. 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright © 2021–2022 John MacFarlane, Albert Krewinkel 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 | -------------------------------------------------------------------------------- /.github/workflows/website.yml: -------------------------------------------------------------------------------- 1 | name: Publish Website 2 | 3 | # Allow one concurrent deployment 4 | concurrency: 5 | group: "pages" 6 | cancel-in-progress: true 7 | 8 | # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages 9 | permissions: 10 | contents: read 11 | pages: write 12 | id-token: write 13 | 14 | on: 15 | push: 16 | branches: ['main'] 17 | 18 | jobs: 19 | website: 20 | runs-on: ubuntu-latest 21 | environment: 22 | name: github-pages 23 | url: ${{ steps.deployment.outputs.page_url }} 24 | steps: 25 | - name: Checkout repository 26 | uses: actions/checkout@v3 27 | - name: Setup Pages 28 | uses: actions/configure-pages@v1 29 | - name: Render Website 30 | run: | 31 | make -B website \ 32 | PANDOC="docker run --rm --volume $(pwd):/data \ 33 | --user $(id -u):$(id -g) pandoc/core:latest" 34 | - name: Upload artifact 35 | uses: actions/upload-pages-artifact@v1 36 | with: 37 | path: '_site' 38 | - name: Deploy to GitHub Pages 39 | id: deployment 40 | uses: actions/deploy-pages@main 41 | -------------------------------------------------------------------------------- /test/example.bib: -------------------------------------------------------------------------------- 1 | @article{smith2018, 2 | title = {Journal of Open Source Software (JOSS): design and first-year review}, 3 | author = {Smith, Arfon M. and Niemeyer, Kyle E. and Katz, Daniel S. and Barba, 4 | Lorena A. and Githinji, George and Gymrek, Melissa and Huff, 5 | Kathryn D. and Madan, Christopher R. and Cabunoc Mayes, 6 | Abigail and Moerman, Kevin M. and Prins, Pjotr and Ram, 7 | Karthik and Rokem, Ariel and Teal, Tracy K. and Valls Guimera, 8 | Roman and Vanderplas, Jacob T.}, 9 | year = 2018, 10 | month = feb, 11 | volume = 4, 12 | pages = {e147}, 13 | journal = {PeerJ Computer Science}, 14 | issn = {2376-5992}, 15 | url = {https://doi.org/10.7717/peerj-cs.147}, 16 | doi = {10.7717/peerj-cs.147} 17 | } 18 | 19 | @article{krewinkel2017, 20 | title = {Formatting Open Science: agilely creating multiple document formats for academic manuscripts with Pandoc Scholar}, 21 | author = {Krewinkel, Albert and Winkler, Robert}, 22 | year = 2017, 23 | month = may, 24 | keywords = {Open science, Markdown, Latex, Publishing, Typesetting, Document formats}, 25 | volume = 3, 26 | pages = {e112}, 27 | journal = {PeerJ Computer Science}, 28 | issn = {2376-5992}, 29 | url = {https://doi.org/10.7717/peerj-cs.112}, 30 | doi = {10.7717/peerj-cs.112} 31 | } 32 | -------------------------------------------------------------------------------- /_extensions/citation-backlinks/citation-backlinks.lua: -------------------------------------------------------------------------------- 1 | --- citation-backlinks.lua – adds citation backlinks to the bibliography 2 | --- 3 | --- Copyright: © 2022 John MacFarlane and Albert Krewinkel 4 | --- License: MIT – see LICENSE for details 5 | 6 | -- Makes sure users know if their pandoc version is too old for this 7 | -- filter. 8 | PANDOC_VERSION:must_be_at_least '2.17' 9 | 10 | -- cites is a table mapping citation item identifiers 11 | -- to an array of cite identifiers 12 | local cites = {} 13 | 14 | -- counter for cite identifiers 15 | local cite_number = 1 16 | 17 | local function with_latex_label(s, el) 18 | if FORMAT == "latex" then 19 | return {pandoc.RawInline("latex", "\\label{" .. s .. "}"), el} 20 | else 21 | return {el} 22 | end 23 | end 24 | 25 | function Cite(el) 26 | local cite_id = "cite_" .. cite_number 27 | cite_number = cite_number + 1 28 | for _,citation in ipairs(el.citations) do 29 | if cites[citation.id] then 30 | table.insert(cites[citation.id], cite_id) 31 | else 32 | cites[citation.id] = {cite_id} 33 | end 34 | end 35 | return pandoc.Span(with_latex_label(cite_id, el), pandoc.Attr(cite_id)) 36 | end 37 | 38 | function append_inline(blocks, inlines) 39 | local last = blocks[#blocks] 40 | if last.t == 'Para' or last.t == 'Plain' then 41 | -- append to last block 42 | last.content:extend(inlines) 43 | else 44 | -- append as additional block 45 | blocks[#blocks + 1] = pandoc.Plain(inlines) 46 | end 47 | return blocks 48 | end 49 | 50 | function Div(el) 51 | local citation_id = el.identifier:match("ref%-(.+)") 52 | if citation_id then 53 | local backlinks = pandoc.Inlines{pandoc.Str("Cited:"),pandoc.Space()} 54 | for i,cite_id in ipairs(cites[citation_id] or {}) do 55 | local marker = pandoc.Str(i) 56 | if FORMAT == "latex" then 57 | marker = pandoc.RawInline("latex", "\\pageref{" .. cite_id .. "}") 58 | end 59 | if #backlinks > 2 then 60 | table.insert(backlinks, pandoc.Str(",")) 61 | table.insert(backlinks, pandoc.Space()) 62 | end 63 | table.insert(backlinks, pandoc.Link(marker, "#"..cite_id)) 64 | end 65 | if #backlinks > 2 then 66 | append_inline(el.content, {pandoc.Space()} .. backlinks) 67 | end 68 | return el 69 | end 70 | end 71 | -------------------------------------------------------------------------------- /.tools/docs.lua: -------------------------------------------------------------------------------- 1 | local path = require 'pandoc.path' 2 | local utils = require 'pandoc.utils' 3 | local stringify = utils.stringify 4 | 5 | local function read_file (filename) 6 | local fh = io.open(filename) 7 | local content = fh:read('*a') 8 | fh:close() 9 | return content 10 | end 11 | 12 | local formats_by_extension = { 13 | md = 'markdown', 14 | latex = 'latex', 15 | native = 'haskell', 16 | tex = 'latex', 17 | } 18 | 19 | local function sample_blocks (sample_file) 20 | local sample_content = read_file(sample_file) 21 | local extension = select(2, path.split_extension(sample_file)):sub(2) 22 | local format = formats_by_extension[extension] or extension 23 | local filename = path.filename(sample_file) 24 | 25 | local sample_attr = pandoc.Attr('', {format, 'sample'}) 26 | return { 27 | pandoc.Header(3, pandoc.Str(filename), {filename}), 28 | pandoc.CodeBlock(sample_content, sample_attr) 29 | } 30 | end 31 | 32 | local function result_blocks (result_file) 33 | local result_content = read_file(result_file) 34 | local extension = select(2, path.split_extension(result_file)):sub(2) 35 | local format = formats_by_extension[extension] or extension 36 | local filename = path.filename(result_file) 37 | 38 | local result_attr = pandoc.Attr('', {format, 'sample'}) 39 | return { 40 | pandoc.Header(3, pandoc.Str(filename), {filename}), 41 | pandoc.CodeBlock(result_content, result_attr) 42 | } 43 | end 44 | 45 | local function code_blocks (code_file) 46 | local code_content = read_file(code_file) 47 | local code_attr = pandoc.Attr(code_file, {'lua'}) 48 | return { 49 | pandoc.CodeBlock(code_content, code_attr) 50 | } 51 | end 52 | 53 | function Pandoc (doc) 54 | local meta = doc.meta 55 | local blocks = doc.blocks 56 | 57 | -- Set document title from README title. There should usually be just 58 | -- a single level 1 heading. 59 | blocks = blocks:walk{ 60 | Header = function (h) 61 | if h.level == 1 then 62 | meta.title = h.content 63 | return {} 64 | end 65 | end 66 | } 67 | 68 | -- Add the sample file as an example. 69 | blocks:extend{pandoc.Header(2, 'Example', pandoc.Attr('Example'))} 70 | blocks:extend(sample_blocks(stringify(meta['sample-file']))) 71 | blocks:extend(result_blocks(stringify(meta['result-file']))) 72 | 73 | -- Add the filter code. 74 | local code_file = stringify(meta['code-file']) 75 | blocks:extend{pandoc.Header(2, 'Code', pandoc.Attr('Code'))} 76 | blocks:extend{pandoc.Para{pandoc.Link(pandoc.Str(code_file), code_file)}} 77 | blocks:extend(code_blocks(code_file)) 78 | 79 | return pandoc.Pandoc(blocks, meta) 80 | end 81 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Name of the filter file, *with* `.lua` file extension. 2 | FILTER_FILE := $(wildcard *.lua) 3 | # Name of the filter, *without* `.lua` file extension 4 | FILTER_NAME = $(patsubst %.lua,%,$(FILTER_FILE)) 5 | 6 | # Allow to use a different pandoc binary, e.g. when testing. 7 | PANDOC ?= pandoc 8 | # Allow to adjust the diff command if necessary 9 | DIFF = diff 10 | 11 | # Directory containing the Quarto extension 12 | QUARTO_EXT_DIR = _extensions/$(FILTER_NAME) 13 | # The extension's name. Used in the Quarto extension metadata 14 | EXT_NAME = $(FILTER_NAME) 15 | # Current version, i.e., the latest tag. Used to version the quarto 16 | # extension. 17 | VERSION = $(shell git tag --sort=-version:refname --merged | head -n1 | \ 18 | sed -e 's/^v//' | tr -d "\n") 19 | ifeq "$(VERSION)" "" 20 | VERSION = 0.0.0 21 | endif 22 | 23 | # Test that running the filter on the sample input document yields 24 | # the expected output. 25 | # 26 | # The automatic variable `$<` refers to the first dependency 27 | # (i.e., the filter file). 28 | test: $(FILTER_FILE) test/input.md 29 | $(PANDOC) --defaults test/test.yaml | \ 30 | $(DIFF) test/expected.native - 31 | 32 | # Ensure that the `test` target is run each time it's called. 33 | .PHONY: test 34 | 35 | # Re-generate the expected output. This file **must not** be a 36 | # dependency of the `test` target, as that would cause it to be 37 | # regenerated on each run, making the test pointless. 38 | test/expected.native: $(FILTER_FILE) test/input.md test/example.bib 39 | $(PANDOC) --defaults test/test.yaml --output=$@ 40 | 41 | # 42 | # Website 43 | # 44 | .PHONY: website 45 | website: _site/index.html _site/$(FILTER_FILE) 46 | 47 | _site/index.html: README.md test/input.md $(FILTER_FILE) .tools/docs.lua \ 48 | _site/output.md _site/style.css 49 | @mkdir -p _site 50 | $(PANDOC) \ 51 | --standalone \ 52 | --lua-filter=.tools/docs.lua \ 53 | --metadata=sample-file:test/input.md \ 54 | --metadata=result-file:_site/output.md \ 55 | --metadata=code-file:$(FILTER_FILE) \ 56 | --css=style.css \ 57 | --toc \ 58 | --output=$@ $< 59 | 60 | _site/style.css: 61 | @mkdir -p _site 62 | curl \ 63 | --output $@ \ 64 | 'https://cdn.jsdelivr.net/gh/kognise/water.css@latest/dist/light.css' 65 | 66 | _site/output.md: $(FILTER_FILE) test/input.md test/example.bib 67 | @mkdir -p _site 68 | $(PANDOC) \ 69 | --defaults=test/test.yaml \ 70 | --to=markdown-citations \ 71 | --output=$@ 72 | 73 | _site/$(FILTER_FILE): $(FILTER_FILE) 74 | @mkdir -p _site 75 | (cd _site && ln -sf ../$< $<) 76 | 77 | # 78 | # Quarto extension 79 | # 80 | 81 | # Creates or updates the quarto extension 82 | .PHONY: quarto-extension 83 | quarto-extension: $(QUARTO_EXT_DIR)/_extension.yml \ 84 | $(QUARTO_EXT_DIR)/$(FILTER_FILE) 85 | 86 | $(QUARTO_EXT_DIR): 87 | mkdir -p $@ 88 | 89 | ## This may change, so re-create the file every time 90 | .PHONY: $(QUARTO_EXT_DIR)/_extension.yml 91 | $(QUARTO_EXT_DIR)/_extension.yml: _extensions/$(FILTER_NAME) 92 | @printf 'Creating %s\n' $@ 93 | @printf 'name: %s\n' "$(EXT_NAME)" > $@ 94 | @printf 'author: %s\n' "$(shell git config user.name)" >> $@ 95 | @printf 'version: %s\n' "$(VERSION)" >> $@ 96 | @printf 'contributes:\n filters:\n - %s\n' $(FILTER_FILE) >> $@ 97 | 98 | ## The filter file must be below the quarto _extensions folder: a 99 | ## symlink in the extension would not work due to the way in which 100 | ## quarto installs extensions. 101 | $(QUARTO_EXT_DIR)/$(FILTER_FILE): $(FILTER_FILE) $(QUARTO_EXT_DIR) 102 | if [ ! -L $(FILTER_FILE) ]; then \ 103 | mv $(FILTER_FILE) $(QUARTO_EXT_DIR)/$(FILTER_FILE) && \ 104 | ln -s $(QUARTO_EXT_DIR)/$(FILTER_FILE) $(FILTER_FILE); \ 105 | fi 106 | 107 | # 108 | # Release 109 | # 110 | .PHONY: release 111 | release: quarto-extension 112 | git commit -am "Release $(FILTER_NAME) $(VERSION)" 113 | git tag v$(VERSION) -m "$(FILTER_NAME) $(VERSION)" 114 | 115 | # 116 | # Clean 117 | # 118 | .PHONY: clean 119 | clean: 120 | rm -f _site/output.md _site/index.html _site/style.css 121 | -------------------------------------------------------------------------------- /test/expected.native: -------------------------------------------------------------------------------- 1 | Pandoc 2 | Meta 3 | { unMeta = 4 | fromList 5 | [ ( "author" , MetaInlines [ Str "Nullus" ] ) 6 | , ( "bibliography" 7 | , MetaInlines [ Str "test/example.bib" ] 8 | ) 9 | , ( "nocite" 10 | , MetaInlines 11 | [ Span 12 | ( "cite_1" , [] , [] ) 13 | [ Cite 14 | [ Citation 15 | { citationId = "*" 16 | , citationPrefix = [] 17 | , citationSuffix = [] 18 | , citationMode = AuthorInText 19 | , citationNoteNum = 1 20 | , citationHash = 0 21 | } 22 | ] 23 | [ Str "@*" ] 24 | ] 25 | ] 26 | ) 27 | , ( "title" 28 | , MetaInlines [ Str "Lorem" , Space , Str "ipsum" ] 29 | ) 30 | ] 31 | } 32 | [ Para 33 | [ Str "Lorem" 34 | , Space 35 | , Str "ipsum" 36 | , Space 37 | , Str "dolor" 38 | , Space 39 | , Str "sit" 40 | , Space 41 | , Str "amet," 42 | , Space 43 | , Str "consectetuer" 44 | , Space 45 | , Str "adipiscing" 46 | , Space 47 | , Str "elit." 48 | , Space 49 | , Str "Donec" 50 | , SoftBreak 51 | , Str "hendrerit" 52 | , Space 53 | , Str "tempor" 54 | , Space 55 | , Str "tellus." 56 | , Space 57 | , Str "Donec" 58 | , Space 59 | , Str "pretium" 60 | , Space 61 | , Str "posuere" 62 | , Space 63 | , Str "tellus." 64 | , Space 65 | , Str "Proin" 66 | , Space 67 | , Str "quam" 68 | , SoftBreak 69 | , Str "nisl" 70 | , Space 71 | , Span 72 | ( "cite_2" , [] , [] ) 73 | [ Cite 74 | [ Citation 75 | { citationId = "krewinkel2017" 76 | , citationPrefix = [] 77 | , citationSuffix = [] 78 | , citationMode = AuthorInText 79 | , citationNoteNum = 2 80 | , citationHash = 0 81 | } 82 | ] 83 | [ Str "Krewinkel" 84 | , Space 85 | , Str "and" 86 | , Space 87 | , Str "Winkler" 88 | , Space 89 | , Str "(2017)" 90 | ] 91 | ] 92 | , Str "," 93 | , Space 94 | , Str "tincidunt" 95 | , Space 96 | , Str "et," 97 | , Space 98 | , Str "mattis" 99 | , Space 100 | , Str "eget," 101 | , Space 102 | , Str "convallis" 103 | , Space 104 | , Str "nec," 105 | , SoftBreak 106 | , Str "purus." 107 | ] 108 | , Div 109 | ( "refs" 110 | , [ "references" , "csl-bib-body" , "hanging-indent" ] 111 | , [] 112 | ) 113 | [ Div 114 | ( "ref-krewinkel2017" , [ "csl-entry" ] , [] ) 115 | [ Para 116 | [ Str "Krewinkel," 117 | , Space 118 | , Str "Albert," 119 | , Space 120 | , Str "and" 121 | , Space 122 | , Str "Robert" 123 | , Space 124 | , Str "Winkler." 125 | , Space 126 | , Str "2017." 127 | , Space 128 | , Span 129 | ( "" , [] , [] ) 130 | [ Str "\8220" 131 | , Str "Formatting" 132 | , Space 133 | , Str "Open" 134 | , Space 135 | , Str "Science:" 136 | , Space 137 | , Str "Agilely" 138 | , Space 139 | , Str "Creating" 140 | , Space 141 | , Str "Multiple" 142 | , Space 143 | , Str "Document" 144 | , Space 145 | , Str "Formats" 146 | , Space 147 | , Str "for" 148 | , Space 149 | , Str "Academic" 150 | , Space 151 | , Str "Manuscripts" 152 | , Space 153 | , Str "with" 154 | , Space 155 | , Str "Pandoc" 156 | , Space 157 | , Str "Scholar" 158 | , Str "." 159 | , Str "\8221" 160 | ] 161 | , Space 162 | , Emph 163 | [ Str "PeerJ" 164 | , Space 165 | , Str "Computer" 166 | , Space 167 | , Str "Science" 168 | ] 169 | , Space 170 | , Str "3" 171 | , Space 172 | , Str "(May):" 173 | , Space 174 | , Str "e112." 175 | , Space 176 | , Link 177 | ( "" , [] , [] ) 178 | [ Str "https://doi.org/10.7717/peerj-cs.112" ] 179 | ( "https://doi.org/10.7717/peerj-cs.112" , "" ) 180 | , Str "." 181 | , Space 182 | , Str "Cited:" 183 | , Space 184 | , Link ( "" , [] , [] ) [ Str "1" ] ( "#cite_2" , "" ) 185 | ] 186 | ] 187 | , Div 188 | ( "ref-smith2018" , [ "csl-entry" ] , [] ) 189 | [ Para 190 | [ Str "Smith," 191 | , Space 192 | , Str "Arfon" 193 | , Space 194 | , Str "M.," 195 | , Space 196 | , Str "Kyle" 197 | , Space 198 | , Str "E." 199 | , Space 200 | , Str "Niemeyer," 201 | , Space 202 | , Str "Daniel" 203 | , Space 204 | , Str "S." 205 | , Space 206 | , Str "Katz," 207 | , Space 208 | , Str "Lorena" 209 | , Space 210 | , Str "A." 211 | , Space 212 | , Str "Barba," 213 | , Space 214 | , Str "George" 215 | , Space 216 | , Str "Githinji," 217 | , Space 218 | , Str "Melissa" 219 | , Space 220 | , Str "Gymrek," 221 | , Space 222 | , Str "Kathryn" 223 | , Space 224 | , Str "D." 225 | , Space 226 | , Str "Huff," 227 | , Space 228 | , Str "et" 229 | , Space 230 | , Str "al." 231 | , Space 232 | , Str "2018." 233 | , Space 234 | , Span 235 | ( "" , [] , [] ) 236 | [ Str "\8220" 237 | , Str "Journal" 238 | , Space 239 | , Str "of" 240 | , Space 241 | , Str "Open" 242 | , Space 243 | , Str "Source" 244 | , Space 245 | , Str "Software" 246 | , Space 247 | , Str "(JOSS):" 248 | , Space 249 | , Str "Design" 250 | , Space 251 | , Str "and" 252 | , Space 253 | , Str "First-Year" 254 | , Space 255 | , Str "Review" 256 | , Str "." 257 | , Str "\8221" 258 | ] 259 | , Space 260 | , Emph 261 | [ Str "PeerJ" 262 | , Space 263 | , Str "Computer" 264 | , Space 265 | , Str "Science" 266 | ] 267 | , Space 268 | , Str "4" 269 | , Space 270 | , Str "(February):" 271 | , Space 272 | , Str "e147." 273 | , Space 274 | , Link 275 | ( "" , [] , [] ) 276 | [ Str "https://doi.org/10.7717/peerj-cs.147" ] 277 | ( "https://doi.org/10.7717/peerj-cs.147" , "" ) 278 | , Str "." 279 | ] 280 | ] 281 | ] 282 | ] 283 | --------------------------------------------------------------------------------