{"id":23342022,"url":"https://github.com/andrewheiss/quarto-wordcount","last_synced_at":"2025-04-10T01:02:13.068Z","repository":{"id":151235136,"uuid":"623797567","full_name":"andrewheiss/quarto-wordcount","owner":"andrewheiss","description":"Quarto extension for calculating accurate word counts","archived":false,"fork":false,"pushed_at":"2024-11-05T16:48:08.000Z","size":117,"stargazers_count":118,"open_issues_count":3,"forks_count":6,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-04-10T01:01:20.589Z","etag":null,"topics":["extension","quarto","quarto-extension","quartopub"],"latest_commit_sha":null,"homepage":"","language":"R","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/andrewheiss.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-04-05T05:32:51.000Z","updated_at":"2025-03-22T10:25:28.000Z","dependencies_parsed_at":"2024-05-30T06:56:03.592Z","dependency_job_id":"9c5a12f9-7b94-4cf3-834f-286a3f652846","html_url":"https://github.com/andrewheiss/quarto-wordcount","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/andrewheiss%2Fquarto-wordcount","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/andrewheiss%2Fquarto-wordcount/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/andrewheiss%2Fquarto-wordcount/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/andrewheiss%2Fquarto-wordcount/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/andrewheiss","download_url":"https://codeload.github.com/andrewheiss/quarto-wordcount/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248137901,"owners_count":21053775,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["extension","quarto","quarto-extension","quartopub"],"created_at":"2024-12-21T05:12:54.684Z","updated_at":"2025-04-10T01:02:13.033Z","avatar_url":"https://github.com/andrewheiss.png","language":"R","funding_links":[],"categories":[],"sub_categories":[],"readme":"\n\n# Quarto word count\n\n\n- [Experimental new feature!](#experimental-new-feature)\n- [Why counting words is hard](#why-counting-words-is-hard)\n- [Using the word count script](#using-the-word-count-script)\n  - [Installing](#installing)\n  - [Usage](#usage)\n  - [Terminal output](#terminal-output)\n  - [Shortcodes](#shortcodes)\n  - [No counting](#no-counting)\n  - [Code blocks](#code-blocks)\n  - [Appendices](#appendices)\n- [Example](#example)\n- [Credits](#credits)\n- [How this all works](#how-this-all-works)\n\n\u003c!-- README.md is generated from README.qmd. Please edit that file --\u003e\n\n## Experimental new feature!\n\n\u003e [!TIP]\n\u003e\n\u003e I’ve added an experimental new feature that lets you add a sticky\n\u003e status bar at the top of HTML documents to show the word count. I’ve\n\u003e found that this is *super helpful* when writing, since I don’t need to\n\u003e constantly check the terminal output.\n\u003e\n\u003e I haven’t figured out the best way to work with Lua filters and Quarto\n\u003e shortcodes (and getting filters/shortcodes to run in the right order),\n\u003e so for now the process is a little convoluted.\n\u003e\n\u003e 1.  Enable `wordcount-banner: true` in the YAML front matter\n\u003e\n\u003e 2.  Specify the content of the banner using the different shortcodes\n\u003e     in `params: wordcount:`\n\u003e\n\u003e 3.  Include an HTML file that uses Javascript to inject the status\n\u003e     banner into the document.\n\u003e\n\u003e Here’s a minimal example:\n\u003e\n\u003e ``` qmd\n\u003e ---\n\u003e title: Something\n\u003e format:\n\u003e   wordcount-html: \n\u003e     wordcount-banner: true\n\u003e params:\n\u003e   wordcount: |\n\u003e     \u003cstrong\u003e{{\u003c words-sum body-note-ref \u003e}} total words\u003c/strong\u003e: {{\u003c words-body \u003e}} in the body • {{\u003c words-ref \u003e}} in the references • {{\u003c words-note \u003e}} in the notes\n\u003e ---\n\u003e\n\u003e ::: {.content-visible when-meta=\"wordcount-banner\"}\n\u003e {{\u003c include _extensions/andrewheiss/wordcount/banner.html \u003e}}\n\u003e :::\n\u003e\n\u003e Actual text stuff goes here.\n\u003e ```\n\n## Why counting words is hard\n\nIn academic writing and publishing, word counts are important, since\nmany journals specify word limits for submitted articles. Counting how\nmany words you have in a Quarto Markdown file is tricky, though, for a\nbunch of reasons:\n\n1.  **Compatibility with Word**: Academic publishing portals tend to\n    care about Microsoft Word-like counts, but lots of R and Python\n    functions for counting words in a document treat word boundaries\n    differently.\n\n    For instance, Word considers hyphenated words to be one word (e.g.,\n    “A super-neat kick-in-the-pants example” is 4 words in Word), while\n    `stringi::stri_count_words()` counts them as multiple words (e.g. “A\n    super-neat kick-in-the-pants example” is 8 words with {stringi}).\n    Making matters worse, {stringi} counts “/” as a word boundary, so\n    URLs can severely inflate your actual word count.\n\n2.  **Extra text elements**: Academic writing typically doesn’t count\n    the title, abstract, table text, table and figure captions, or\n    equations as words in the manuscript.\n\n    In computational documents like Quarto Markdown, these often don’t\n    appear until the document is rendered, so simply running a\n    word-counting function on a `.qmd` file will count the code\n    generating tables and figures, again inflating the word count.\n\n3.  **Citations and bibliography**: Academic writing typically counts\n    references as part of the word count (even though IT SHOULDN’T).\n    However, in Quarto Markdown (and all other flavors of pandoc-based\n    markdown), citations don’t get counted until the bibliography is\n    generated, which only happens when the document is rendered.\n\n    Simply running a word-counting function on a `.qmd` file (or\n    something like the super neat\n    [{wordcountaddin}](https://github.com/benmarwick/wordcountaddin))\n    will see citekeys in the document like `@Lovelace1842`, but it will\n    only count them as individual words (e.g. not “(Lovelace 1842)” in\n    in-text styles or ‘Ada Augusta Lovelace, “Sketch of the Analytical\n    Engine…,” *Taylor’s Scientific Memoirs* 3 (1842): 666–731.’ in\n    footnote styles), and more importantly, it will not count any of the\n    automatically generated references in the final bibliography list.\n\nThis extension fixes all three of these issues by relying on a [Lua\nfilter](_extensions/wordcount/wordcount.lua) to count the words after\nthe document has been rendered and before it has been converted to its\nfinal output format. [Frederik Aust (@crsh)](https://github.com/crsh)\nuses the same Lua filter for counting words in R Markdown documents with\nthe [{rmdfiltr}](https://github.com/crsh/rmdfiltr) package (I actually\njust copied and slightly expanded [that package’s\n`inst/wordcount.lua`](https://github.com/crsh/rmdfiltr/blob/master/inst/wordcount.lua)).\nThe filter works really well and [is generally comparable to Word’s word\ncount](https://cran.r-project.org/web/packages/rmdfiltr/vignettes/wordcount.html).\nYou should definitely glance through the [“How this all works”\nsection](#how-this-all-works) to understand… um… how it works.\n\n## Using the word count script\n\n### Installing\n\n``` bash\nquarto add andrewheiss/quarto-wordcount\n```\n\n{quarto-wordcount} requires Quarto version \\\u003e= 1.4.551\n\nThis will install the extension under the `_extensions` subdirectory. If\nyou’re using version control, you will want to check in this directory.\n\n### Usage\n\nThere are two ways to enable the extension: (1) as an output format and\n(2) as a filter.\n\n#### Output format\n\nYou can specify one of four different output formats in your YAML\nsettings: `wordcount-html`, `wordcount-pdf`, `wordcount-docx`:\n\n``` yaml\ntitle: Something\nformat:\n  wordcount-html: default\n```\n\nThe `wordcount-FORMAT` format type is really just a wrapper for each\nbase format (HTML, PDF, Word, and Markdown), so all other HTML-, PDF-,\nWord-, and Markdown-specific options work like normal:\n\n``` yaml\ntitle: Something\nformat:\n  wordcount-html:\n    toc: true\n    fig-align: center\n    cap-location: margin\n```\n\n#### Filter\n\nIf you’re using a [custom output\nformat](https://quarto.org/docs/extensions/listing-formats.html) like\n[{hikmah-academic-quarto}](https://github.com/andrewheiss/hikmah-academic-quarto)\nor a [journal article\nformat](https://quarto.org/docs/extensions/listing-journals.html) like\n[{jss}](https://github.com/quarto-journals/jss), you can’t use the\n`wordcount-html` format, since you can’t combine output formats.\n\nTo enable word counting for *any* format, including custom formats, you\ncan add the extension Lua scripts as filters. You need to specify three\nsettings:\n\n1.  `citeproc: false` must be set so that Quarto doesn’t try to process\n    citations\n2.  The path to `citeproc.lua` so that citations are processed before\n    counting words—[this must come *before*\n    `wordcount.lua`](#how-this-all-works)\n3.  The path to `wordcount.lua` so that words are counted\n\n``` yaml\ntitle: Something\nformat:\n  html:  # Regular built-in format\n    citeproc: false\n    filters:\n      - at: pre-quarto\n        path: _extensions/andrewheiss/wordcount/citeproc.lua\n      - at: pre-quarto\n        path: _extensions/andrewheiss/wordcount/wordcount.lua\n  jss-pdf:  # Custom third-party format\n    citeproc: false\n    filters:\n      - at: pre-quarto\n        path: _extensions/andrewheiss/wordcount/citeproc.lua\n      - at: pre-quarto\n        path: _extensions/andrewheiss/wordcount/wordcount.lua\n```\n\n### Terminal output\n\nThe word count will appear in the terminal output when rendering the\ndocument. It shows multiple values:\n\n- **Overall totals**: (1) the total count of everything, including the\n  body, notes, references, and appendix sections, and (2) the count for\n  just the body and notes.\n\n  The journals I typically work with count the body + notes + references\n  towards the total word count. When shrinking manuscripts to fit word\n  limits, I find it helpful to split the references count off from the\n  body + notes so I can more easily see where edits might be most\n  efficient (e.g. re-word sentences vs. remove references)\n\n- **Individual section totals**: counts for the (1) text body, (2)\n  notes, (3) references, and (4) appendix\n\n``` text\nOverall totals:\n--------------------------------\n- 451 total words\n- 378 words in body and notes\n\nSection totals:\n--------------------------------\n- 315 words in text body\n- 63 words in notes\n- 53 words in reference section\n- 20 words in appendix section\n```\n\n### Shortcodes\n\nThere are also multiple shortcodes you can use to include different word\ncounts directly in the document:\n\n- Use `{{\u003c words-total \u003e}}` to include a count of all words\n\n- Use `{{\u003c words-body \u003e}}` to include a count of the words in the text\n  body only, omitting the references, notes, and appendix\n\n- Use `{{\u003c words-ref \u003e}}` to include a count of the words in the\n  reference section\n\n- Use `{{\u003c words-append \u003e}}` to include a count of the words in the\n  appendix, which must be wrapped in a div with the `#appendix-count` id\n  ([see below for more details](#appendices))\n\n- Use `{{\u003c words-note \u003e}}` to include a count of the words in the notes\n\n- Use `{{\u003c words-sum ARG \u003e}}` where `ARG` is some concatenation of the\n  four countable areas: `body`, `ref`, `append`, and `note`\n\n  For example, `{{\u003c words-sum body-note \u003e}}` includes a count of the\n  words in the body and notes; `{{\u003c words-sum ref-append \u003e}}` includes a\n  count of the words in the references and appendix\n\nYou can use shortcodes in your YAML metadata too:\n\n``` yaml\ntitle: Something\nsubtitle: \"{{\u003c words-sum body-note-ref \u003e}} words\"\n```\n\n### No counting\n\nIf you want to omit text from the word count, you can include it in a\n[fenced\ndiv](https://quarto.org/docs/authoring/markdown-basics.html#sec-divs-and-spans)\nwith the `{.no-count}` class:\n\n``` markdown\n::: {.no-count}\n\nThese words don't count.\n\n:::\n```\n\n### Code blocks\n\nBy default, text inside code blocks ***is*** counted. For example, this:\n\n```` markdown\n---\ntitle: \"Code counting\"\nformat: wordcount-html\n---\n\nThis sentence has seven words in it.\n\n```{r}\n# Here is some code\n\nnumbers \u003c- 1:10\nmean(numbers)\n```\n````\n\n…will result in these counts:\n\n``` text\nOverall totals:\n-----------------------------\n- 16 total words\n- 16 words in body and notes\n\nSection totals:\n-----------------------------\n- 16 words in text body\n```\n\n…with 7 words from the sentence and 9 from the code.\n\nCode block counting can be disabled with the YAML option\n`count-code-blocks`:\n\n```` markdown\n---\ntitle: \"Code counting\"\nformat: \n  wordcount-html:\n    count-code-blocks: false\n---\n\nThis sentence has seven words in it.\n\n```{r}\n# Here is some code\n\nnumbers \u003c- 1:10\nmean(numbers)\n```\n````\n\n…which makes these counts:\n\n``` text\nOverall totals:\n----------------------------\n- 7 total words\n- 7 words in body and notes\n\nSection totals:\n----------------------------\n- 7 words in text body\n```\n\n### Appendices\n\nIn academic writing, it’s often helpful to have a separate word count\nfor content in the appendices, since things there don’t typically count\nagainst journal word limits. [Quarto has a neat feature for\nautomatically creating an appendix\nsection](https://quarto.org/docs/authoring/appendices.html) and moving\ncontent there automatically as needed. It does this (I think) with a\nfancy Lua filter.\n\nHowever, Quarto’s appendix-generating process comes *after* any custom\nLua filters, so even though the final rendered document creates a div\nwith the id “appendix”, that div isn’t accessible when counting words\n(since it doesn’t exist yet), so there’s no easy way to extract the\nappendix words from the rest of the text.\n\nSo, as a (temporary?) workaround (until I can figure out how to make\nthis Lua filter run after the creation of the appendix div?), you can\nget a separate word count for the appendix by creating your own fenced\ndiv with the id `appendix-count`:\n\n``` markdown\n# Introduction\n\nRegular text goes here.\n\n::: {#appendix-count}\n\n# Appendix {.appendix}\n\nMore words here\n\n:::\n```\n\n## Example\n\nYou can see a minimal sample document at [`template.qmd`](template.qmd).\n\n## Credits\n\nThe original [`wordcount.lua`](_extensions/wordcount/wordcount.lua)\nfilter came from [Frederik Aust’s (@crsh)](https://github.com/crsh)\n[{rmdfiltr}](https://github.com/crsh/rmdfiltr) package.\n\n## How this all works\n\nBehind the scenes, pandoc typically converts a Markdown document to an\nabstract syntax tree (AST), or an output-agnostic representation of all\nthe document elements. In AST form, it’s easy to use the [Lua\nlanguage](https://pandoc.org/lua-filters.html) to extract or exclude\nspecific elements of the document (i.e. exclude captions or only look at\nthe references).\n\nQuarto was designed to be language-agnostic, so {rmdfiltr}’s approach of\nusing R to dynamically set the path to its Lua filters in YAML front\nmatter does not work with Quarto files. ([See this comment from the\nQuarto team stating that you cannot use R output in the Quarto YAML\nheader](https://github.com/quarto-dev/quarto-cli/issues/1391#issuecomment-1185348644).)\n\nBut it’s still possible to use the fancy {rmdfiltr} Lua filter with\nQuarto with a little trickery!\n\nIn order to include citations in the word count, we have to feed the\nword count filter a version of the document that has been processed with\nthe [`--citeproc`\noption](https://pandoc.org/MANUAL.html#citation-rendering) enabled.\nHowever, in both R Markdown/knitr and in Quarto, the `--citeproc` flag\nis designed to be the last possible option, resulting in pandoc commands\nthat look something like this:\n\n``` sh\npandoc whatever.md --output whatever.html --lua-filter wordcount.lua --citeproc\n```\n\nThe order of these arguments matters, so having\n`--lua-filter wordcount.lua` come before `--citeproc` makes it so the\nwords will be counted before the bibliography is generated, which isn’t\ngreat.\n\n{rmdfiltr} gets around this ordering issue by editing the YAML front\nmatter to (1) disable citeproc in general and (2) specify the\n`--citeproc` flag before running the filter:\n\n``` yaml\noutput:\n  html_document:\n    citeproc: false\n    pandoc_args:\n      - '--citeproc'\n      - '--lua-filter'\n      - '/path/to/rmdfiltr/wordcount.lua'\n```\n\nThat generates a pandoc command like this, with `--citeproc` first, so\nthe generated references get counted:\n\n``` sh\npandoc whatever.md --output whatever.html --citeproc --lua-filter wordcount.lua\n```\n\nQuarto doesn’t have a `pandoc_args` option though. Instead, it has a\n`filters` YAML key that lets you specify a list of Lua filters to apply\nto the document at specific steps in the rendering process:\n\n``` yaml\nformat:\n  html:\n    citeproc: false\n    filters: \n      - \"/path/to/wordcount.lua\"\n```\n\nHowever, there’s no obvious way to reposition the `--citeproc` argument\nand it will automatically appear at the end, making it so generated\nreferences aren’t counted.\n\nFortunately, [this GitHub\ncomment](https://github.com/quarto-dev/quarto-cli/issues/2294#issuecomment-1238954661)\nshows that it’s possible to make a Lua filter that basically behaves\nlike `--citeproc` by feeding the whole document to\n`pandoc.utils.citeproc()`. That means we can create a little Lua script\nlike `citeproc.lua`:\n\n``` lua\n-- Lua filter that behaves like `--citeproc`\nfunction Pandoc (doc)\n  return pandoc.utils.citeproc(doc)\nend\n```\n\n…and then include *that* as a filter:\n\n``` yaml\nformat:\n  html:\n    citeproc: false\n    filters:\n      - at: pre-quarto\n        path: \"path/to/citeproc.lua\"\n      - at: pre-quarto\n        path: \"path/to/wordcount.lua\"\n```\n\nThis creates a pandoc command that looks something like this, feeding\nthe document to the citeproc “filter” first, then feeding that to the\nword count script:\n\n``` sh\npandoc whatever.md --output whatever.html  --lua-filter citeproc.lua --lua-filter wordcount.lua\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fandrewheiss%2Fquarto-wordcount","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fandrewheiss%2Fquarto-wordcount","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fandrewheiss%2Fquarto-wordcount/lists"}